Skip to main content
Version: 6.0.0

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:

PropertyDescription
request.valueOptional 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.

important

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}
tip

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})
}
note

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.