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.
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:
| Property | Description |
|---|---|
request.value | The --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.
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}
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 })
}
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.