Using express-pino-logger and pino-pretty together - node.js

I'm have existing code that uses express-pino-logger. This words great with our ELK stack setup, but is pretty unfortunate (logs minified JSON) when running locally.
I'd like to use pino-pretty to make local use not a pain.
There is an alternative in pino-pretty-express that solves the problem, but uses its own pretty formatter. I'd like to use the standard packages from pinojs if I could.
Here's what I have so far:
// with just pino-pretty installed, pino works out of the box
const pino = require('pino')
const logger = pino({
prettyPrint: true
})
logger.info('hi') // prints pretty
And:
// adding this option to express-pino-logger, doesn't work
const pino = require('express-pino-logger')
const logger = pino({
prettyPrint: true
})
logger.info('hi') // does NOT print pretty

I've solved my own problem I guess.
The key lies in the very last example on the express-pino-logger page:
'use strict'
const pino = require('pino')()
const expressPino = require('express-pino-logger')({
logger: pino
})
Here's my solution:
// use pino-pretty and express-pino-logger together
const basicPino = require('pino')
const basicPinoLogger = basicPino({ prettyPrint: true })
const expressPino = require('express-pino-logger')({
logger: basicPinoLogger
})
const logger = expressPino.logger
logger.info('hi') // prints pretty

const pino = require('pino');
const expressPino = require('express-pino-logger');
const logger = pino({ level: process.env.LOG_LEVEL || 'info', prettyPrint: true });
const expressLogger = expressPino({ logger });

I suppose the express pino logger is explicitly being used to log unique request ids. There is an alternative approach using the http-context and node-uuid (although I am unsure about the benchmark results)
You can create a unique uuid before the call of each request like this
const uuid = require('node-uuid');
const httpContext = require('express-http-context');
....
app.use(httpContext.middleware);
app.use((req, res, next) => {
httpContext.set('reqId', uuid.v4());
next();
});
.. you can obtain the requestId anywhere from the httpContext. No need of relying on the req object to be passed
Example usage in a custom implementation of PinoLogger service
public infoLogService (fileName): pino.Logger {
return pino({
level: 'info',
name: this.appService.getApp_name(),
messageKey: 'feedback-Logs',
base: {pid: process.pid, hostname: os.hostname,
timestamp: this.getTimeStamp(),
appName: this.appService.getApp_name(),
fileName: fileName,
request_id: isNullOrUndefined(httpContext.get('reqId')) ? 'Not an actual request ' : httpContext.get('reqId')
},
enabled: true,
useLevelLabels: true,
});
}
For every http calls, we get a UUID. for other type of loggings like logger invocation before the app starts to find if the connection to database happened successfully, there will be no uuid and hence passed a default message

Related

pino-pretty, how to add file name to log line

i need to add file name to pino-pretty line output,
now i'm using:
const pino = require('pino');
const logger = pino({
prettyPrint: {
colorize: true,
translateTime: 'yyyy-mm-dd HH:MM:ss',
ignore: 'pid,hostname'
}
})
and have this output:
[2020-05-14 16:25:45] INFO : Network is private
but i want something like this:
[2020-05-14 16:25:45] INFO myFile.js: Network is private
i.e. i want see filename in line witch was launch, i try play with customPrettifiers option but can't get hoped result,
for example i try this:
const pino = require('pino');
const path = require('path');
const logger = pino({
prettyPrint: {
colorize: true,
translateTime: 'yyyy-mm-dd HH:MM:ss',
ignore: 'pid,hostname',
customPrettifiers: {
filename: path.basename(__filename)
}
}
})
I think the closest you can get is as follows:
const path = require('path');
const pino = require('pino');
const logger = pino({
prettyPrint: {
// Adds the filename property to the message
messageFormat: '{filename}: {msg}',
// need to ignore 'filename' otherwise it appears beneath each log
ignore: 'pid,hostname,filename',
},
}).child({ filename: path.basename(__filename) });
Note that you can't style the filename differently to the message, but hopefully that's good enough.
It's probably also better to have a separate logger.js file where the default pino options are passed e.g.:
// logger.js
const logger = require('pino')({
prettyPrint: {
messageFormat: '{filename}: {msg}',
ignore: 'pid,hostname,filename',
},
});
module.exports = logger;
// file_with_logging.js
const parentLogger = require('./logger.js');
const logger = parentLogger.child({ filename: path.basename(__filename) });

winston - TypeError: winston.createLogger is not a constructor

winston.createLogger(); is apparently not a constructor. Why is this so?
I have seen some people try and roll back to winston#3.0.0, but that doesn't work for me. I am on the latest version of winston. Here is some of logger.js:
const winston = require('winston');
const level = process.env.LOG_LEVEL || 'debug';
let logger = new winston.createLogger({
transports: [
new winston.transports.Console({
level: level,
timestamp: function() {
return (new Date()).toISOString();
}
})
]
});
module.exports = logger;
I expect it to create the logger, but it throws a TypeError telling me that createLogger isn't a constructor!
Do not use new winston.Logger(opts) – it has been removed for improved performance. Use winston.createLogger(opts) instead.
Check this for reference
Its simply winston.createLogger and not new winston.createLogger. new keyword is not needed.

Custom service in feathersjs

I try to write a custom service, but it doesn't work at all. I try to post a request and make two update queries in the collections, but i will not work at all
this is my code
// Initializes the `bedrijven` service on path `/bedrijven`
const createService = require('feathers-mongoose');
const createModel = require('../../models/bedrijven.model');
const hooks = require('./bedrijven.hooks');
const filters = require('./bedrijven.filters');
module.exports = function() {
const app = this;
const Model = createModel(app);
const paginate = app.get('paginate');
const options = {
name: 'bedrijven',
Model,
paginate
};
// Initialize our service with any options it requires
app.post('/bedrijven/setfavo', function(req, res) {
Promise.all([
app.service('bedrijven').update({
owner: req.body.userid
}, {
favo: false
}),
app.service('bedrijven').update(req.body._id, {
favo: true
})
]);
});
app.use('/bedrijven', createService(options));
// Get our initialized service so that we can register hooks and filters
const service = app.service('bedrijven');
service.hooks(hooks);
if (service.filter) {
service.filter(filters);
}
};
Make sure this file is included in your main app.js file.
Something like:
const bedrijven = require('./bedrijven/bedrijven.service.js');
app.configure(bedrijven);
Is there a reason you don't want to use feathers generate service? It would take care of these questions for you.

Firebase functions: logging with winston in stackdriver console

I cannot make winston logger to write logs to stackdriver console. I deploy my functions as google firebase functions (using firebase deploy). console logging works fine, but we don't use such tool in the project.
What I tried:
output to stderr using https://github.com/greglearns/winston-stderr
using https://www.npmjs.com/package/#google-cloud/logging-winston (both winston.add(require('#google-cloud/logging-winston')); winston.log('error', 'Winston error!'); and also adding with parameters such as project ID projectId / service account JSON credentials file keyFilename);
using https://github.com/findanyemail/winston-transport-stackdriver-error-reporting . Also no luck. I still cannot see logs in stackdriver.
Please suggest... I'm tired of experiments (each re-deploy takes time)
Finally what I did - implemented custom transport which actually calls console.log under the hood. This helped.
const winston = require('winston');
const util = require('util');
const ClassicConsoleLoggerTransport = winston.transports.CustomLogger = function (options) {
options = options || {};
this.name = 'ClassicConsoleLoggerTransport';
this.level = options.level || 'info';
// Configure your storage backing as you see fit
};
util.inherits(ClassicConsoleLoggerTransport, winston.Transport);
ClassicConsoleLoggerTransport.prototype.log = function (level, msg, meta, callback) {
let args = [msg, '---', meta];
switch (level) {
case 'verbose':
case 'debug':
console.log.apply(null, args);
break;
case 'notice':
case 'info':
console.info.apply(null, args);
break;
case 'warn':
case 'warning':
console.warn.apply(null, args);
break;
case 'error':
case 'crit':
case 'alert':
case 'emerg':
console.error.apply(null, args);
break;
default:
console.log.apply(null, args);
}
callback(null, true);
};
Winston's default Console transport fails because it uses console._stdout.write when it's available, which is not accepted by Firebase Functions.
There's now a Google Cloud transport package for Stackdriver you can try. Haven't used it and it requires node ^8.11.2 if you're using Winston 3.
Docs for node.js winston setup are here and here
I've added my full logger.js setup below.
The important bit is:
const format = winston.format.combine(winston.format.colorize({ all: true }))
const console = new winston.transports.Console({ format: winston.format.combine(format) })
const options = this.#explicitSetup ? { projectId: appConfig.firebase.options.projectId, keyFilename: `${rootDirname}/service-account-file.json` } : {}
const loggingWinston = new LoggingWinston(options)
const transports = emulators ? [console] : [console, loggingWinston]
this.#logger = winston.createLogger({
level: this.#defaultLevel,
transports
})
Basically, if the emulators are running then use the console logger else use the console logger and the stack driver transports. You can check if the emulators are running by pinging a functions endpoint (e.g. a /ping endpoint you have created) on localhost. If it does not exist then the emulators are not running or this is a production environment. Notice also the ability to use an explicit setup whereby the projectId and keyFilename are passed in.
The JSON file for keyFilename can be created here:
https://cloud.google.com/docs/authentication/getting-started
My full logger.js code, in case it helps, follows:
import winston from 'winston'
import { LoggingWinston } from '#google-cloud/logging-winston'
import { appConfig } from '../app-config.js'
import { rootDirname } from './root-dirname.js'
import { isObjectLike } from 'lodash-es'
// https://cloud.google.com/logging/docs/setup/nodejs
export class Logger {
#logger
#defaultLevel = 'debug'
#explicitSetup = false
constructor() {
this.error = this.error.bind(this)
this.warn = this.warn.bind(this)
this.info = this.info.bind(this)
this.debug = this.debug.bind(this)
this.log = this.log.bind(this)
}
init(emulators) {
// https://stackoverflow.com/a/64173978/1205871
winston.addColors({
error: 'red',
warn: 'yellow',
info: 'bold cyan',
debug: 'bold green'
})
const format = winston.format.combine(winston.format.colorize({ all: true }))
const console = new winston.transports.Console({ format: winston.format.combine(format) })
const options = this.#explicitSetup ? { projectId: appConfig.firebase.options.projectId, keyFilename: `${rootDirname}/service-account-file.json` } : {}
const loggingWinston = new LoggingWinston(options)
const transports = emulators ? [console] : [console, loggingWinston]
this.#logger = winston.createLogger({
level: this.#defaultLevel,
transports
})
}
error(...args) {
this.#logger.error(this.#argsToString(args))
}
warn(...args) {
this.#logger.warn(this.#argsToString(args))
}
info(...args) {
this.#logger.info(this.#argsToString(args))
}
debug(...args) {
this.#logger.debug(this.#argsToString(args))
}
log(...args) {
this.#logger[this.#defaultLevel](this.#argsToString(args))
}
#argsToString(args) {
return args.map(arg => {
const str = isObjectLike(arg) ? JSON.stringify(arg) : arg.toString()
return str.trim()
}).join(' \u2022\u2022 ')
}
}
const blogger = new Logger()
export const logger = blogger

Nodejs winston session id logging

I'm using winston with node & express & can't find a suitable way to simply augment all log entries with express's session.id (via an extensive google & SO search) - any suggestions?
For Express request logs you could do:
var morgan = require('morgan'),
winston = require('winston');
// Define a new format token
morgan.token('id', function(req, res) {
return req.session ? req.session.id : 'N/A';
});
// Create a stream using Winston for logging
var winstonStream = {
write: function(msg, encoding) {
winston.info(msg);
}
};
// Create a logger middleware that uses the new token in format
// and writes to Winston logging stream
var expressLogger = morgan(morgan.combined + ' - :id', { stream: winstonStream });
app.use(expressLogger);
Don't know how you could achieve the same for every winston[level]() call as you do need to have the request object at hand (apart from passing the object around to every bit of code).
Thanks for your answer vesse, I need it slightly more general than that though - ie. wherever I call winston, not just morgan request logging. I've overridden winston.log so I can call log.info(req, message), or log.warn etc and the session id is passed to the meta info from the req. I'm also using cookie-session which doesnt create the session id for me, so my solution is:
var CustomLogger = function(options){
CustomLogger.super_.apply(this, arguments);
}
util.inherits(CustomLogger, winston.Logger);
CustomLogger.prototype.log = function () {
var args = Array.prototype.slice.call(arguments);
var req = args[1];
if (req.session) {
req.session.id = req.session.id || uuid.v4();
var meta = _.merge((args[3] || {}), {sessionId: req.session.id});
CustomLogger.super_.prototype.log.call(this, args[0], args[2] + ' | ', meta);
}
else {
// no req.session, assume req hasnt been passed in as 1st param
CustomLogger.super_.prototype.log.apply(this, arguments);
}
}
var logger = new CustomLogger({
transports: [ ... ],
exitOnError: false
});

Resources