Skip to main content
Version: 6.x (latest)

Use a custom command

Define a custom command in your application and call it remotely with nsolid-cli. This guide uses a custom command to change the log level of a running process that uses the pino logger.

Custom commands let you define code that runs when a command arrives over the N|Solid Agent's connection to the N|Solid Console. Use them to collect custom metrics, perform maintenance, change behavior, or send signals to any N|Solid process connected to the Console.

warning

Custom commands are only delivered over the ZeroMQ connection to a self-hosted N|Solid Console (NSOLID_COMMAND). They are not available when a process connects with NSOLID_SAAS or NSOLID_GRPC.

Create the demo application​

Start with a demo application, demo.js, that logs messages with pino:

const logger = require('pino')()

setInterval(() => {
logger.info('INFO log')
}, 5000)
setInterval(() => {
logger.error('ERROR log')
}, 10000)
setInterval(() => {
logger.debug('DEBUG log')
}, 3000)
setInterval(() => {
logger.warn('WARN log')
}, 7000)

Run it:

node demo.js

The output looks like this:

{"level":30,"time":1717019325981,"pid":81854,"hostname":"my-host","msg":"INFO log"}
{"level":40,"time":1717019327979,"pid":81854,"hostname":"my-host","msg":"WARN log"}
{"level":50,"time":1717019330979,"pid":81854,"hostname":"my-host","msg":"ERROR log"}
{"level":30,"time":1717019330980,"pid":81854,"hostname":"my-host","msg":"INFO log"}
{"level":40,"time":1717019334981,"pid":81854,"hostname":"my-host","msg":"WARN log"}

At the default log level (info), the logger.debug messages don't appear.

Create a custom command handler​

Next, write a function that handles the custom command and register it with the N|Solid Agent.

The handler takes a single parameter, request:

PropertyDescription
request.valueThe --data value sent with the command, as a string (null if no data was sent). To send structured data, pass a JSON string and call JSON.parse(request.value) in your handler.
request.return(value)Completes the command and returns value to the caller.
request.throw(error)Completes the command with an error and returns error to the caller.

Your handler must call either request.return() or request.throw() to signal that the command is complete.

Start with a handler that logs a message, so you can see what happens in the application and what the caller receives:

function loglevelHandler (request) {
console.log('Hello world!', request.value)
request.return({ hello: 'world' })
}

Register the handler with nsolid.on(), using the command name loglevel:

nsolid.on('loglevel', loglevelHandler)

You can register only one handler per command name. If you register another handler with the same name, it replaces the first.

Here's the application so far:

const logger = require('pino')()
const nsolid = require('nsolid')

function loglevelHandler (request) {
console.log('Hello world!', request.value)
request.return({ hello: 'world' })
}
nsolid.on('loglevel', loglevelHandler)

setInterval(() => {
logger.info('INFO log')
}, 5000)
setInterval(() => {
logger.error('ERROR log')
}, 10000)
setInterval(() => {
logger.debug('DEBUG log')
}, 3000)
setInterval(() => {
logger.warn('WARN log')
}, 7000)

Connect the application to the N|Solid Console​

Start the application and connect it to your self-hosted N|Solid Console over ZeroMQ. Replace console.example.com with your Console's address; 9001 is the default command port:

NSOLID_COMMAND='console.example.com:9001' NSOLID_APPNAME='demoapp' node demo.js

The application starts logging as before:

{"level":30,"time":1717020371696,"pid":86194,"hostname":"my-host","msg":"INFO log"}
{"level":40,"time":1717020373695,"pid":86194,"hostname":"my-host","msg":"WARN log"}
{"level":50,"time":1717020376696,"pid":86194,"hostname":"my-host","msg":"ERROR log"}

Call the custom command with nsolid-cli​

Now that the command is registered and the application is connected, call the handler with nsolid-cli. Point --remote at your Console's API address (the default is http://localhost:6753). If your Console has authentication enabled, pass its admin token (the value of NSOLID_CONSOLE_AUTH_ADMIN_TOKEN) with --auth. See Using the N|Solid CLI.

First, list the connected processes to confirm that the demo application is running:

nsolid-cli --remote=http://console.example.com:6753 --auth='<your-console-admin-token>' list
{
"time": "2024-05-29T22:06:55.820Z",
"info": {
"time": "2024-05-29T22:06:55.820Z",
"id": "0a1b2c3d4e5f60718293a4b5c6d7e8f901234567",
"app": "demoapp",
"hostname": "my-host",
"tags": [],
"appVersion": "0.0.0",
"arch": "x64",
"cpuCores": 12,
"cpuModel": "AMD Ryzen 5 2600 Six-Core Processor",
"execPath": "/usr/bin/nsolid",
"main": "/home/user/app/demo.js",
"nodeEnv": "prod",
"pid": 86194,
"platform": "linux",
"processStart": 1717020366615,
"totalMem": 8295538688,
"versions": { ... }
},
"metrics": { ... }
}

This confirms that the demo application is connected, and gives you its agent id, which you need to send the custom command.

info

Custom commands must be sent to a specific N|Solid Agent ID. This prevents you from broadcasting configuration changes by accident.

Send the loglevel command to the process with the custom command:

nsolid-cli --remote=http://console.example.com:6753 --auth='<your-console-admin-token>' custom --name=loglevel --data=debug --id=0a1b2c3
{"result":{"hello":"world"},"id":"0a1b2c3d4e5f60718293a4b5c6d7e8f901234567","app":"demoapp","hostname":"my-host","tags":[],"time":1717020885165}
tip

You can use the first 7 characters of the agent ID as a short ID.

The application output shows that the handler ran and received debug as request.value:

{"level":40,"time":1717020877779,"pid":86194,"hostname":"my-host","msg":"WARN log"}
{"level":30,"time":1717020881777,"pid":86194,"hostname":"my-host","msg":"INFO log"}
Hello world! debug
{"level":50,"time":1717020886765,"pid":86194,"hostname":"my-host","msg":"ERROR log"}

Finish the handler​

Stop the application and replace the test handler with one that changes the log level:

function loglevelHandler (request) {
if (!['trace', 'debug', 'info', 'warn', 'error', 'fatal'].includes(request.value)) {
return request.throw(new Error('Invalid log level'))
}
const oldLevel = logger.level
logger.warn(`N|Solid invoked loglevel change from ${oldLevel} to ${request.value}`)
logger.level = request.value
request.return({ ok: true, from: oldLevel, to: request.value })
}
note

Each time the process restarts, it gets a new N|Solid Agent ID. Look up the new ID with nsolid-cli list before you send commands.

Put it all together​

Restart the application, look up its new agent ID (9f8e7d6 in this example), and change the log level remotely:

nsolid-cli --remote=http://console.example.com:6753 --auth='<your-console-admin-token>' custom --name=loglevel --data=debug --id=9f8e7d6
nsolid-cli --remote=http://console.example.com:6753 --auth='<your-console-admin-token>' custom --name=loglevel --data=warn --id=9f8e7d6

The two commands return:

{"result":{"ok":true,"from":"info","to":"debug"},"id":"9f8e7d6c5b4a39281706f5e4d3c2b1a098765432","app":"demoapp","hostname":"my-host","tags":[],"time":1717021427127}
{"result":{"ok":true,"from":"debug","to":"warn"},"id":"9f8e7d6c5b4a39281706f5e4d3c2b1a098765432","app":"demoapp","hostname":"my-host","tags":[],"time":1717021443756}

The application logs show DEBUG log messages after the first command, and only WARN and ERROR messages after the second:

{"level":30,"time":1717021421751,"pid":89925,"hostname":"my-host","msg":"INFO log"}
{"level":40,"time":1717021424753,"pid":89925,"hostname":"my-host","msg":"WARN log"}
{"level":50,"time":1717021426753,"pid":89925,"hostname":"my-host","msg":"ERROR log"}
{"level":40,"time":1717021427126,"pid":89925,"hostname":"my-host","msg":"N|Solid invoked loglevel change from info to debug"}
{"level":20,"time":1717021429765,"pid":89925,"hostname":"my-host","msg":"DEBUG log"}
{"level":40,"time":1717021431755,"pid":89925,"hostname":"my-host","msg":"WARN log"}
{"level":30,"time":1717021431755,"pid":89925,"hostname":"my-host","msg":"INFO log"}
{"level":20,"time":1717021432765,"pid":89925,"hostname":"my-host","msg":"DEBUG log"}
{"level":20,"time":1717021435767,"pid":89925,"hostname":"my-host","msg":"DEBUG log"}
{"level":50,"time":1717021436754,"pid":89925,"hostname":"my-host","msg":"ERROR log"}
{"level":30,"time":1717021436755,"pid":89925,"hostname":"my-host","msg":"INFO log"}
{"level":40,"time":1717021438757,"pid":89925,"hostname":"my-host","msg":"WARN log"}
{"level":20,"time":1717021438767,"pid":89925,"hostname":"my-host","msg":"DEBUG log"}
{"level":40,"time":1717021443755,"pid":89925,"hostname":"my-host","msg":"N|Solid invoked loglevel change from debug to warn"}
{"level":40,"time":1717021445758,"pid":89925,"hostname":"my-host","msg":"WARN log"}
{"level":50,"time":1717021446755,"pid":89925,"hostname":"my-host","msg":"ERROR log"}

You have now changed the log level of a running Node.js process remotely with N|Solid.

NodeSource Support