Is there an equivalent of log.IsDebugEnabled in Winston? - node.js

Is there an equivalent of log.IsDebugEnabled in Winston?
I want to use this to skip expensive logging code in a production environment but have it execute in development.
For example:
if(winston.isDebugEnabled){
// Call to expensive dump routine here
dump();
}
Checking winston.debug just checks whether the method is defined, not whether it is enabled.
Many thanks!
Edit: Added code example.

I've added a method to my logger to achieve just that:
logger.isLevelEnabled = function(level) {
return _.any(this.transports, function(transport) {
return (transport.level && this.levels[transport.level] <= this.levels[level])
|| (!transport.level && this.levels[this.level] <= this.levels[level]);
}, this);
};
This goes through each of your logger's transports and checks whether it 'wants' to log the specified level.
Note _.any is lodash, you can replace with for loop.

I'm sure you'd be able to get that directly from winston, but if you want to have different logging levels for different environments, you should pass these in when you're creating the winston.logger.
For example:
// default log file level is info (which is the lowest by default)
var logFileLevel = 'info';
if (process.env.NODE_ENV == 'production') {
// only write logs with a level of 'error' or above when in production
logFileLevel = 'error';
}
var logger = new (winston.Logger)({
transports: [
new (winston.transports.File)({
filename: '/var/log/node-logger.log',
level: logFileLevel
})
]
});
It's also useful to switch out the transports. You might want to use the Console transport whilst in development, and the File transport when in production for example.
More documentation on all this on the winston readme.

Try
if ( logger.levels[logger.level] >= logger.levels['debug'] ) {
// expensive calculation here
logger.debug(...)
}

Since Winston 3.1.0 (PR), you can use the Logger functions isLevelEnabled(string) & isXXXEnabled() for this.

Related

Structured Logs in Google Cloud Run not being parsed (using Winston for logging)

I'm attempting to format my logs in such a way that Google Cloud will correctly extract the log level. This is running on Cloud Run, with typescript. Cloud Run is grabbing the logs from the container output.
If I do the following, google correctly parses the log line:
console.log(JSON.stringify({
severity: 'ERROR',
message: 'This is testing a structured log error for GCP'
}));
And the log output looks like this:
I've tried a number of different ways to format with winston, ended up with the following:
useFormat = format.combine(
format((info, opts) => {
info['severity'] = info.level;
delete info.level;
return info;
})(),
format.json());
this.winston = winston.createLogger({
level: logLevel,
format: useFormat,
transports: [new winston.transports.Console()]
});
Which looks like it will work (it correctly outputs the json line), I get this in the GCP logs:
Any help appreciated.
Turns out I was close, just needed to .upperCase() the log level (and I'm mapping Verbose -> Debug, I don't really understand why GCP decided to do a totally different log leveling system than everyone else). New code:
useFormat =
format.combine(
format((info, opts) => {
let level = info.level.toUpperCase();
if(level === 'VERBOSE') {
level = 'DEBUG';
}
info['severity'] = level;
delete info.level;
return info;
})(),
format.json());
The last bit of the question is confusing. The problem OP is pointing to is that the json is printed out and the severity is default. The json should not be printed out, only the message, and the severity should be debug. The answer that OP provides does what is wanted.
For others that may be confused in the same way I was.

Log all errors and warnings

I am using a third party nodejs application which is quite large and it is using many different things for logging. For instance console.log() and console.error(). I would like to be able to trap all output and log to a specific file. I was thinking about using winston and do something like this:
const winston = require('winston')
const logger = winston.createLogger({
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' })
]
})
const ce = console.error
console.error = function(...args) {
logger.log.apply('error', args)
ce.apply(args)
}
Is there a better way to handle this kind of situation?
I might also add that some parts of the application is already using winston for logging purposes.
My aim is the create a single file with all errors and warnings generated from within the application.

How do I override config values at runtime with node-config?

I'd like to override some values at test-time, specifically setting my retries for an http service to 1 (immediate failure, no retries). Our project uses node-config. According to the docs I can override with NODE_CONFIG env variable:
node myapp.js --NODE_CONFIG='{"Customer":{"dbConfig":{"host":"customerdb.prod"}}}'
Well I would prefer to do this in my test, but not for all tests. The code says that you can allow config mutations by setting ALLOW_CONFIG_MUTATIONS.
process.env.ALLOW_CONFIG_MUTATIONS = "true";
const importFresh = require('import-fresh');
importFresh("config");
process.env.NODE_CONFIG = JSON.stringify({httpServices:{integration:{enrich: {retryInterval: 1, retries: 1}}}});
expect(process.env.NODE_CONFIG, 'NODE_CONFIG not set').to.exist();
expect(process.env.NODE_CONFIG, 'NODE_CONFIG not set').to.match(/retryInterval/);
expect(process.env.ALLOW_CONFIG_MUTATIONS, 'ALLOW_CONFIG_MUTATIONS not set').to.equal("true");
const testConfig = require("config");
console.dir(testConfig.get("httpServices.integration.enrich"));
expect(testConfig.get("httpServices.integration.enrich.retryInterval"), 'config value not set to 1').to.equal(1);
Result:
{ url: 'https://internal-**********',
retryInterval: 5000,
retries: 5 }
`Error: config value not set to 1: Expected 5000 to equal specified value: 1`
How do I get this override to work?
(expect is from Hapi.js Code library)
I'm one of the maintainers of node-config. Your bug is that you used require the second time when you should have used importFresh again.
Your first use of "importFresh()" does nothing different than require() would, because it is the first use of require().
After setting some variables, you call require(), which will return the copy of config already generated and cached, ignoring the effects of the environment variables set.
You only needed to use importFresh() once, where you currently use require(). This will cause a "fresh" copy of the config object to be returned, as you expected.
Simply changing config's property worked for me.
For example:
const config = require( 'config' );
config.httpServices.integration.enrich.retryInterval = 1;
// Do your tests...
UPD: Make sure that overrides are done before anyone calls the first config.get(), because the config object is made immutable as soon as any client uses the values via get().
Joining late, but other answers did not fit with the testing standard in my project, so here is what I came up with
TL;DR
Use mocks..
Detailed Answer
node-config uses a function get to get the configuration values.
By mocking the function get you can easily modify any configuration you see fit..
My personal favorite library is sinon
Here is an implementation of a mock with sinon
const config = require('config');
const sinon = require('sinon');
class MockConfig {
constructor () {
this.params = {};
this.sandbox = sinon.sandbox.create();
}
withConfValue (confKey, confValue) {
this.params.confValues[confKey] = confValue;
return this;
}
reset () {
this.params.confValues: {};
return this;
}
restore() {
this.sandbox.restore();
}
apply () {
this.restore(); // avoid duplicate wrapping
this.sandbox.stub(config, 'get').callsFake((configKey) => {
if (this.params.confValues.hasOwnProperty(configKey)) {
return this.params.confValues[configKey];
}
// not ideal.. however `wrappedMethod` approach did not work for me
// https://stackoverflow.com/a/57017971/1068746
return configKey
.split('.')
.reduce((result, item) => result[item], config)
});
}
}
const instance = new MockConfig();
MockConfig.instance = () => instance;
module.exports = MockConfig;
Usage would be
const mockConfig = require('./mock_config').instance();
...
beforeEach(function () {
mockConfig.reset().apply();
})
afterEach(function () {
mockConfig.reset().clear();
})
it('should do something') {
mockConfig.withConfValue('some_topic.some_field.property', someValue);
... rest of the test ...
}
Assumptions
The only assumption this approach makes is that you adhere to node-config way of reading the configuration (using the get function) and not bypass it by accessing fields directly.
It's better to create a development.json, production.json et test.json in your config folder node-config will use it your app configuration.
you just net to set your NODE_ENV to use the specific file.
Hope it helps :)

Restify - Best practice for accessing logger from a module

I have looked for the answer to this for a while and just have not come up with a solution. I understand that I have access to the builtin logger from req.log.xxxxx(...), but what about a module that I have required into my controller? For example:
in my controller file, someController.js
var myModule = require('myModule');
SomeController.listUsers = function listUsers(req, res, next){
req.log.info('Some log message'); // <---- this works just fine
//...
}
In myModule.js:
module.exports = {
getUsers: function () {
// ...
// I would like to be able to log from here, but don't have access to the req object.
}
}
I don't really like the idea of passing the log object to the module's method, as that seems sloppy to me. If that's the only solution, then I'll live with it.
Restify uses bunyan to provide logging.
Looking through the documentation, the logger that's used for req.log is the one that's created at server startup (or at least a child of that logger). Since you can also create your own logger instance, I think that this should work:
// logger.js
var bunyan = require('bunyan');
module.exports = bunyan.createLogger(...);
// app.js
var server = restify.createServer({
log : require('./logger')
});
// someController.js
var logger = require('./logger');
...
This shares the same logger between the Restify server and other parts of your application. If you don't necessarily require that the logger is the same, you can also just create a new Bunyan instance in someController.js.
There aren't many options here. Typically I pass the "req" object. Another way around it is utilizing the "this" argument as a context. Something like this:
function listUsers(req, res, next) {
req.log.info('Some log message');
myModule.getUsers.call({log: req.log});
}
and...
module.exports = {
getUsers: function () {
(this.log || logger.log)() // ...
}
}
Usually, loggers get required into modules, implemented in the middleware, or inside a custom error object.
Another option you have in Events. Node.js is an Event driven language. You can just create a logger module that listens for log events. That's called neat programming.
However, I'd choose to require my logger into files, so that when we go into prod I have it deactivated/replaced with another more specific logger object with the exact interface.

How to set log level in Winston/Node.js

I am using Winston logging with my Node.js app and have defined a file transport. Throughout my code, I log using either logger.error, logger.warn, or logger.info.
My question is, how do I specify the log level? Is there a config file and value that I can set so that only the appropriate log messages are logged? For example, I'd like the log level to be "info" in my development environment but "error" in production.
If you are using the default logger, you can adjust the log levels like this:
const winston = require('winston');
// ...
winston.level = 'debug';
will set the log level to 'debug'. (Tested with winston 0.7.3, default logger is still around in 3.2.1).
However, the documentation recommends creating a new logger with the appropriate log levels and then using that logger:
const myLogger = winston.createLogger({
level: 'debug'
});
myLogger.debug('hello world');
If you are already using the default logger in your code base this may require you to replace all usages with this new logger that you are using:
const winston = require('winston');
// default logger
winston.log('debug', 'default logger being used');
// custom logger
myLogger.log('debug', 'custom logger being used');
Looks like there is a level option in the options passed covered here
From that doc:
var logger = new (winston.Logger)({
transports: [
new (winston.transports.Console)({ level: 'error' }),
new (winston.transports.File)({ filename: 'somefile.log' })
]
});
Now, those examples show passing level in the option object to the console transport. When you use a file transport, I believe you would pass an options object that not only contains the filepath but also the level.
That should lead to something like:
var logger = new (winston.Logger)({
transports: [
new (winston.transports.File)({ filename: 'somefile.log', level: 'error' })
]
});
Per that doc, note also that as of 2.0, it exposes a setLevel method to change at runtime. Look in the Using Log Levels section of that doc.
There are 6 default levels in winston: silly=0(lowest), debug=1, verbose=2, info=3, warn=4, error=5(highest)
While creating the logger transports, you can specify the log level like:
new (winston.transports.File)({ filename: 'somefile.log', level: 'warn' })
Above code will set log level to warn, which means silly, verbose and info will not be output to somefile.log, while warn, debug and error will.
You can also define your own levels:
var myCustomLevels = {
levels: {
foo: 0,
bar: 1,
baz: 2,
foobar: 3
}
};
var customLevelLogger = new (winston.Logger)({ levels: myCustomLevels.levels });
customLevelLogger.foobar('some foobar level-ed message');
Note that it's better to always include the 6 predefined levels in your own custom levels, in case somewhere used the predefined levels.
You can change the logging level in runtime by modifying the level property of the appropriate transport:
var log = new (winston.Logger)({
transports: [
new (winston.transports.Console)({ level : 'silly' })
]
});
...
// Only messages with level 'info' or higher will be logged after this.
log.transports.Console.level = 'info';
I guess, it works similarly for file but I haven't tried that.
If you want to change the log level on the fly. Like for when you need to trace production issue for short amount of time; then revert to error log level. You can use a dynamic logger provided you can expose a service on the web https://github.com/yannvr/Winston-dynamic-loglevel
apart from this you can cleanly achieve this by imlplementing runtime-node-refresh follow this link for more.

Resources