Use a Custom Command
How to use N|Solid Custom Commands to remotely change the log level of your process during runtime using the pino Logger.
This guide will walk through an example of setting up and calling a custom command. Custom commands allow you to define code to be triggered via the N|Solid Agent's communication channel. This allows you to set up hooks that let you collect custom metrics, perform maintenance, behavior changes, or send signals to any N|Solid process connected to your architecture.
In this example we'll change the log level remotely.
Our Demo App
Let's start off with a fake application to use for our guide--we'll make a demo app that will occasionally log messages using the pino
logger.
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)
It's not much, but it'll do!
Let's run it:
node logleveldemo.js
{"level":30,"time":1717019325981,"pid":81854,"hostname":"anoxia","msg":"INFO log"}
{"level":40,"time":1717019327979,"pid":81854,"hostname":"anoxia","msg":"WARN log"}
{"level":50,"time":1717019330979,"pid":81854,"hostname":"anoxia","msg":"ERROR log"}
{"level":30,"time":1717019330980,"pid":81854,"hostname":"anoxia","msg":"INFO log"}
{"level":40,"time":1717019334981,"pid":81854,"hostname":"anoxia","msg":"WARN log"}
{"level":30,"time":1717019335981,"pid":81854,"hostname":"anoxia","msg":"INFO log"}
{"level":50,"time":1717019340981,"pid":81854,"hostname":"anoxia","msg":"ERROR log"}
{"level":30,"time":1717019340981,"pid":81854,"hostname":"anoxia","msg":"INFO log"}
{"level":40,"time":1717019341982,"pid":81854,"hostname":"anoxia","msg":"WARN log"}
Great! Notice we don't see any logger.debug
at the default log level.
Creating a Custom Command handler in your application
The first step will be to create a function to handle our custom command and register it with the N|Solid agent.
The custom command function should be defined to take a single parameter, request
.
The request
object:
Property | Description |
---|---|
request.value | Optional data sent in the custom command request. |
request.return(value) | Continuation function for the custom command chain, returns value to the caller. |
request.throw(error) | Continuation function for the custom command chain error state, returns error to the caller. |
Your function you must call either request.return()
or request.throw()
to signal completion of the command.
For now, let's just put some debug messages in so that we can see what happens in the application and from the caller's perspective.
function loglevelHandler(request) {
console.log("Hello world!", request.value)
request.return({hello: "world"})
}
Next, register the handler with nsolid.on
--we'll use the name "loglevel" for our command:
nsolid.on("loglevel", loglevelHandler)
For reference, here's where we're at with our application:
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)
My NSOLID_SAAS
value from NodeSource Accounts is: xxxxxxZZZaaaaa%Z0000a0a0-00000000-00a0-00ac0f00af0.prod.proxy.saas.nodesource.io:9001
Let's start our application using simple environment variables to configure N|Solid:
$ NSOLID_SAAS='xxxxxxZZZaaaaa%Z0000a0a0-00000000-00a0-00ac0f00af0.prod.proxy.saas.nodesource.io:9001' NSOLID_APP='demoapp' node demo.js
{"level":30,"time":1717020371696,"pid":86194,"hostname":"anoxia","msg":"INFO log"}
{"level":40,"time":1717020373695,"pid":86194,"hostname":"anoxia","msg":"WARN log"}
{"level":50,"time":1717020376696,"pid":86194,"hostname":"anoxia","msg":"ERROR log"}
{"level":30,"time":1717020376698,"pid":86194,"hostname":"anoxia","msg":"INFO log"}
Calling our custom command with nsolid-cli
Now that we've registered our command and connected to our N|Solid Console and then call our handler via nsolid-cli
. In my case, I'm using N|Solid SaaS and I've contacted NodeSource Support and received a security token that I can use with the nsolid-cli
command. If you use N|Solid Enterprise with your own on-premise N|Solid Console Server, contact support for instructions on enabling this feature on your system.
- My console server address is
https://0000a0a0-00000000-00a0-00ac0f00af0.saas.nodesource.io
- And the token provided by support is
30badbc9-31af-4d00-970e-eacd6ecd55ba
Putting these together to call my custom command let's run this command to test the connection and confirm that the demo app is running.
nsolid-cli --remote=https://0000a0a0-00000000-00a0-00ac0f00af0.saas.nodesource.io --auth="30badbc9-31af-4d00-970e-eacd6ecd55ba" list
{
"time": "2024-05-29T22:06:55.820Z",
"info": {
"time": "2024-05-29T22:06:55.820Z",
"id": "001d392042891200705f20b538c21600078023c1",
"app": "demoapp",
"hostname": "anoxia",
"tags": [],
"appVersion": "0.0.0",
"arch": "x64",
"cpuCores": 12,
"cpuModel": "AMD Ryzen 5 2600 Six-Core Processor",
"execPath": "/usr/bin/nsolid",
"main": "/home/bryce/tmp/demo.js",
"nodeEnv": "prod",
"pid": 86194,
"platform": "linux",
"processStart": 1717020366615,
"totalMem": 8295538688,
"versions": { ... }
},
"metrics": { ... }
}
Awesome! We've achieved two important things with that step. First, we confirmed our demo app is connected. Second, we can grab the id
for our N|Solid process so that we can send our custom command to it.
Custom commands must be sent to specific N|Solid Agent IDs. This is to prevent accidentally broadcasting configuration changes.
Let's give our custom command a try:
nsolid-cli --remote=https://0000a0a0-00000000-00a0-00ac0f00af0.saas.nodesource.io --auth="30badbc9-31af-4d00-970e-eacd6ecd55ba" --id="001d392"
{"result":{"hello":"world"},"id":"001d392042891200705f20b538c21600078023c1","app":"demoapp","hostname":"anoxia","tags":[],"time":1717020885165}
The first 7 characters of the agentId
can be used as a short id.
And now let's check our application:
{"level":40,"time":1717020877779,"pid":86194,"hostname":"anoxia","msg":"WARN log"}
{"level":30,"time":1717020881777,"pid":86194,"hostname":"anoxia","msg":"INFO log"}
{"level":40,"time":1717020884781,"pid":86194,"hostname":"anoxia","msg":"WARN log"}
Hello world! debug
{"level":50,"time":1717020886765,"pid":86194,"hostname":"anoxia","msg":"ERROR log"}
{"level":30,"time":1717020886777,"pid":86194,"hostname":"anoxia","msg":"INFO log"}
Great! Now let's stop our application and finish our handler. Here's a possibility for a handler:
function loglevelHandler(request) {
if (!['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})
}
Every time the process restarts it generates a new N|Solid Agent id, so every time we will need to get the new id for nsolid-cli
.
Putting it all together
Changing the log level remotely:
# not shown: fetching the new agentId `e1c3b9a`
$ nsolid-cli --remote=https://0000a0a0-00000000-00a0-00ac0f00af0.saas.nodesource.io --auth="30badbc9-31af-4d00-970e-eacd6ecd55ba" custom --name=loglevel --data=debug --id=e1c3b9a
{"result":{"ok":true,"from":"info","to":"debug"},"id":"e1c3b9a5edec6a008344293e69f2e10097bd79fb","app":"demoapp","hostname":"anoxia","tags":[],"time":1717021427127}
$ nsolid-cli --remote=https://0000a0a0-00000000-00a0-00ac0f00af0.saas.nodesource.io --auth="30badbc9-31af-4d00-970e-eacd6ecd55ba" custom --name=loglevel --data=warn --id=e1c3b9a
{"result":{"ok":true,"from":"debug","to":"warn"},"id":"e1c3b9a5edec6a008344293e69f2e10097bd79fb","app":"demoapp","hostname":"anoxia","tags":[],"time":1717021443756}
Our logs:
{"level":30,"time":1717021401750,"pid":89925,"hostname":"anoxia","msg":"INFO log"}
{"level":40,"time":1717021403750,"pid":89925,"hostname":"anoxia","msg":"WARN log"}
{"level":50,"time":1717021406749,"pid":89925,"hostname":"anoxia","msg":"ERROR log"}
{"level":30,"time":1717021406750,"pid":89925,"hostname":"anoxia","msg":"INFO log"}
{"level":40,"time":1717021410751,"pid":89925,"hostname":"anoxia","msg":"WARN log"}
{"level":30,"time":1717021411751,"pid":89925,"hostname":"anoxia","msg":"INFO log"}
{"level":50,"time":1717021416751,"pid":89925,"hostname":"anoxia","msg":"ERROR log"}
{"level":30,"time":1717021416751,"pid":89925,"hostname":"anoxia","msg":"INFO log"}
{"level":40,"time":1717021417753,"pid":89925,"hostname":"anoxia","msg":"WARN log"}
{"level":30,"time":1717021421751,"pid":89925,"hostname":"anoxia","msg":"INFO log"}
{"level":40,"time":1717021424753,"pid":89925,"hostname":"anoxia","msg":"WARN log"}
{"level":50,"time":1717021426753,"pid":89925,"hostname":"anoxia","msg":"ERROR log"}
{"level":30,"time":1717021426754,"pid":89925,"hostname":"anoxia","msg":"INFO log"}
{"level":40,"time":1717021427126,"pid":89925,"hostname":"anoxia","msg":"N|Solid invoked loglevel change from info to debug"}
{"level":20,"time":1717021429765,"pid":89925,"hostname":"anoxia","msg":"DEBUG log"}
{"level":40,"time":1717021431755,"pid":89925,"hostname":"anoxia","msg":"WARN log"}
{"level":30,"time":1717021431755,"pid":89925,"hostname":"anoxia","msg":"INFO log"}
{"level":20,"time":1717021432765,"pid":89925,"hostname":"anoxia","msg":"DEBUG log"}
{"level":20,"time":1717021435767,"pid":89925,"hostname":"anoxia","msg":"DEBUG log"}
{"level":50,"time":1717021436754,"pid":89925,"hostname":"anoxia","msg":"ERROR log"}
{"level":30,"time":1717021436755,"pid":89925,"hostname":"anoxia","msg":"INFO log"}
{"level":40,"time":1717021438757,"pid":89925,"hostname":"anoxia","msg":"WARN log"}
{"level":20,"time":1717021438767,"pid":89925,"hostname":"anoxia","msg":"DEBUG log"}
{"level":30,"time":1717021441758,"pid":89925,"hostname":"anoxia","msg":"INFO log"}
{"level":20,"time":1717021441767,"pid":89925,"hostname":"anoxia","msg":"DEBUG log"}
{"level":40,"time":1717021443755,"pid":89925,"hostname":"anoxia","msg":"N|Solid invoked loglevel change from debug to warn"}
{"level":40,"time":1717021445758,"pid":89925,"hostname":"anoxia","msg":"WARN log"}
{"level":50,"time":1717021446755,"pid":89925,"hostname":"anoxia","msg":"ERROR log"}
And there we have it, remotely changing the loglevel of a running Node.js process via N|Solid.