How to sent log to Jaeger in NestJs? - node.js

In my nestjs project, I am using Winston as a logger. here is the example of my log.info:
logger.log(
'Error: test',
inout_data,
);
here is the logger:
var winston = require('winston');
const logger = winston.createLogger({
level: 'verbose',
format: winston.format.json(),
transports: [
new winston.transports.Console({
format: winston.format.json(),
}),
],
});
export default logger;
this is how I setup the Jaeger:
import { NodeSDK, tracing } from '#opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '#opentelemetry/auto-instrumentations-node';
import { JaegerExporter, ExporterConfig } from '#opentelemetry/exporter-jaeger';
import { BatchSpanProcessor } from '#opentelemetry/sdk-trace-base';
import { Resource } from '#opentelemetry/resources';
import { SemanticResourceAttributes } from '#opentelemetry/semantic-conventions';
interface Tag {
key: string;
value: TagValue;
}
declare type TagValue = string | number | boolean;
const options = {
tags: [], // optional
host: 'localhost',
port: 6832, // optional
maxPacketSize: 65000,
};
const exporter = new JaegerExporter(options);
export const sdk = new NodeSDK({
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: 'test,
[SemanticResourceAttributes.DEPLOYMENT_ENVIRONMENT]: '2',
}),
traceExporter: exporter,
instrumentations: [getNodeAutoInstrumentations()],
});
sdk.start();
I want to send the log to Jaeger, but I can not find a way to connect them. Am I missing anything here?

Related

Writing every log.info content to a text file

I am using Pino and Pino pretty packages for displaying loggers. I would like to write all log.info contents (called from multiple js files in the same project) into a common text file
logger.ts
import pinoCaller from 'pino-caller'
import pino from 'pino'
const job_name="job123"
const pinoPretty = pino(
{
prettyPrint: {
messageFormat: `{"job_name":${job_name}, "message":{msg}}`,
},
})
export log=pinoCaller(pinoPretty)
Is there anyway I can write all log.info content from multiple files to a common text file.
lets say I have following files:
file1.ts
import {log} from 'logger'
const calculatesum = (a:any,b:any)=>{
log.info('**********')
log.info('sum begins')
const sum=a+b;
log.info('sum is '+sum)
log.info('sum ends')
}
file2.ts
import {log} from 'logger'
const calculateproduct = (a:any,b:any)=>{
log.info('product begins')
const product=a*b;
log.info('product is '+product)
log.info('product ends')
log.info('**********')
}
output of text file should look like below:
***************
sum begins
sum is x
sum ends
product begins
product is y
product ends
***************
If you're using Pino v7.x or later, you can make use of Pino transports.
const pinoPretty = pino({
prettyPrint: {
messageFormat: `{"job_name":${job_name}, "message":{msg}}`,
},
transport: {
targets: [
{ level: 'info', target: 'pino/file', options: { destination: '/path/to/store/logs', mkdir: true } },
{ target: 'pino-pretty', options: { destination: '/dev/null' }
]
}
})
Alternatively you can use pino-tee. An example of how to do it:
const pino = require('pino')
const pinoTee = pino.transport({
target: 'pino-tee',
options: {
filters: {
info: 'info.log'
}
}
})

Winston with AWS Cloudwatch on Nestjs

All the articles and documentation I have read so far talk about the integration of Cloudwatch and Winston on a vanilla Node app, but nothing on Nestjs
So far I have on my app.module.ts:
imports: [
ConfigModule.forRoot({ isGlobal: true }),
MongooseModule.forRoot(
`mongodb://${environment.MONGO_INITDB_ROOT_USERNAME}:${environment.MONGO_INITDB_ROOT_PASSWORD}#${environment.MONGODB_HOST}/${environment.MONGO_INITDB_DATABASE}`,
),
VoucherModule,
ApiKeyModule,
WinstonModule.forRoot(loggerConfig),
],
where loggerConfig are the basic Winston configs depending on the env.
Using winston-cloudwatch package I need to create a new Transporter and add it add it to winston, but can't seem to find a way to do this.
I recently implemented aws-cloudwatch in nestjs and faced similar issue but after some browsing and reading about winston and cloudwatch, came up with this solution.
//main.ts
import {
utilities as nestWinstonModuleUtilities,
WinstonModule,
} from 'nest-winston';
import * as winston from 'winston';
import CloudWatchTransport from 'winston-cloudwatch';
const app = await NestFactory.create(AppModule, {
logger: WinstonModule.createLogger({
format: winston.format.uncolorize(), //Uncolorize logs as weird character encoding appears when logs are colorized in cloudwatch.
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.ms(),
nestWinstonModuleUtilities.format.nestLike()
),
}),
new CloudWatchTransport({
name: "Cloudwatch Logs",
logGroupName: process.env.CLOUDWATCH_GROUP_NAME,
logStreamName: process.env.CLOUDWATCH_STREAM_NAME,
awsAccessKeyId: process.env.AWS_ACCESS_KEY,
awsSecretKey: process.env.AWS_KEY_SECRET,
awsRegion: process.env.CLOUDWATCH_AWS_REGION,
messageFormatter: function (item) {
return (
item.level + ": " + item.message + " " + JSON.stringify(item.meta)
);
},
}),
],
}),
});
Here, we have defined two transports one is transports.Console() which is the default winston transport and another one is the cloudwatch transport.
What this basically does is, replaces the default nestjs Logger(imported from #nestjs/common). So, we don't have to import winston in any module not even in 'app.module.ts'. Instead just import Logger in whichever module you need, and whenever we log anything it will be shown in the terminal and will upload it to the cloudwatch.
EDIT:
With configService..
//main.js
import {
utilities as nestWinstonModuleUtilities,
WinstonModule,
} from 'nest-winston';
import * as winston from 'winston';
import CloudWatchTransport from 'winston-cloudwatch';
import { ConfigService } from '#nestjs/config';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const configService = app.get(ConfigService);
app.useLogger(
WinstonModule.createLogger({
format: winston.format.uncolorize(),
transports: [
new winston.transports.Console({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.ms(),
nestWinstonModuleUtilities.format.nestLike(),
),
}),
new CloudWatchTransport({
name: 'Cloudwatch Logs',
logGroupName: configService.get('CLOUDWATCH_GROUP_NAME'),
logStreamName: configService.get('CLOUDWATCH_STREAM_NAME'),
awsAccessKeyId: configService.get('AWS_ACCESS_KEY'),
awsSecretKey: configService.get('AWS_KEY_SECRET'),
awsRegion: configService.get('CLOUDWATCH_AWS_REGION'),
messageFormatter: function (item) {
return (
item.level + ': ' + item.message + ' ' + JSON.stringify(item.meta)
);
},
}),
],
}),
);
await app.listen(configService.get('PORT') || 3000);
}

Unable to inject winston's logger instance with NestJS

I'm using NestJS 7.0.7 and Winston 3.2.1 (with nest-winston 1.3.3).
I'm trying to integrate Winston into NestJS, but so far, I'm unable to inject a logger instance (to actually log anything) into any controller/service.
Since I would like to use Winston across the application AND during bootstrapping, I'm using the approach as the main Nest logger:
// main.ts
import { NestFactory } from "#nestjs/core";
import { WinstonModule } from "nest-winston";
import { format, transports } from "winston";
import { AppModule } from "./app.module";
async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AppModule, {
logger: WinstonModule.createLogger({
exitOnError: false,
format: format.combine(format.colorize(), format.timestamp(), format.printf(msg => {
return `${msg.timestamp} [${msg.level}] - ${msg.message}`;
})),
transports: [new transports.Console({ level: "debug" })], // alert > error > warning > notice > info > debug
}),
});
app.use(helmet());
await app.listen(process.env.PORT || 3_000);
}
bootstrap().then(() => {
// ...
});
I'm not doing anything in regard to the logging in app.module.ts:
// app.module.ts
import { SomeController } from "#controller/some.controller";
import { Module } from "#nestjs/common";
import { SomeService } from "#service/some.service";
#Module({
controllers: [SomeController],
imports: [],
providers: [SomeService],
})
export class AppModule {
// ...
}
// some.controller.ts
import { Controller, Get, Inject, Param, ParseUUIDPipe, Post } from "#nestjs/common";
import { SomeService } from "#service/some.service";
import { WINSTON_MODULE_PROVIDER } from "nest-winston";
import { Logger } from "winston";
#Controller("/api/some-path")
export class SomeController {
constructor(#Inject(WINSTON_MODULE_PROVIDER) private readonly logger: Logger, private readonly service: SomeService) {
// ...
}
...
}
The application tries to start but fails at some point:
2020-04-06T18:51:08.779Z [info] - Starting Nest application...
2020-04-06T18:51:08.787Z [error] - Nest can't resolve dependencies of the SomeController (?, SomeService). Please make sure that the argument winston at index [0] is available in the AppModule context.
Potential solutions:
- If winston is a provider, is it part of the current AppModule?
- If winston is exported from a separate #Module, is that module imported within AppModule?
#Module({
imports: [ /* the Module containing winston */ ]
})
Try importing the WinstonModule in the root AppModule, as explained in the official docs: https://github.com/gremo/nest-winston:
import { Module } from '#nestjs/common';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
const logger: LoggerConfig = new LoggerConfig();
#Module({
imports: [WinstonModule.forRoot(logger.console())],
})
export class AppModule {}
It's probably a good idea to create some kind of factory/Logging-Config in order not to have to duplicate the logger options.
import winston, { format, transports } from "winston";
export class LoggerConfig {
private readonly options: winston.LoggerOptions;
constructor() {
this.options = {
exitOnError: false,
format: format.combine(format.colorize(), format.timestamp(), format.printf(msg => {
return `${msg.timestamp} [${msg.level}] - ${msg.message}`;
})),
transports: [new transports.Console({ level: "debug" })], // alert > error > warning > notice > info > debug
};
}
public console(): object {
return this.options;
}
}
Implement Winston custom logger in NestJs project
Prerequisit:
npm install --save nest-winston winston winston-daily-rotate-file
import { NestFactory } from '#nestjs/core';
import { WinstonModule } from 'nest-winston';
import * as winston from 'winston';
import * as winstonDailyRotateFile from 'winston-daily-rotate-file';
import { AppModule } from './app.module';
const transports = {
console: new winston.transports.Console({
level: 'silly',
format: winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss',
}),
winston.format.colorize({
colors: {
info: 'blue',
debug: 'yellow',
error: 'red',
},
}),
winston.format.printf((info) => {
return `${info.timestamp} [${info.level}] [${
info.context ? info.context : info.stack
}] ${info.message}`;
}),
// winston.format.align(),
),
}),
combinedFile: new winstonDailyRotateFile({
dirname: 'logs',
filename: 'combined',
extension: '.log',
level: 'info',
}),
errorFile: new winstonDailyRotateFile({
dirname: 'logs',
filename: 'error',
extension: '.log',
level: 'error',
}),
};
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useLogger(
WinstonModule.createLogger({
format: winston.format.combine(
winston.format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss',
}),
winston.format.errors({ stack: true }),
winston.format.splat(),
winston.format.json(),
),
transports: [
transports.console,
transports.combinedFile,
transports.errorFile,
],
}),
);
await app.listen(4000);
}
bootstrap();
NestJs Custom Logger
NestJs Winston NPM Documentation
Note Log Levels, file names, dateformat you may edit as per your requirement. Follow officials documentations with more option.

How to optionally set Winston transports when logging to Stackdriver from GKE

I have a Node.js app which runs in a Docker container in Google Kubernetes Engine. I have set up a logging class which uses Winston (v3.2.1) with two transports defined; one to log to the console and one to log to Stackdriver (using #google-cloud/logging-winston (v3.0.0)).
With both transports defined, all is good and I can see the logs in Stackdriver. The console logs go to projects/[project-id]/logs/stdout and the Stackdriver logs go to projects/[project-id]/logs/winston_log.
However, I want to configure the logger so that when debugging locally, logs are only sent to the console and when running in GKE, logs are only sent to Stackdriver, as follows:
// Configure console logger
private readonly consoleLogger = new winston.transports.Console({
format: combine(
colorize(),
simple(),
printf(context => {
return `[${context.level}]${context.message}`;
}),
),
});
// Configure Stackdriver logger
private readonly stackdriverLogger = new LoggingWinston({
serviceContext: {
service: this.serviceName,
},
});
// Create Winston logger
private readonly logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: json(),
defaultMeta: {
service: this.serviceName,
},
// This line does not work:
transports: [process.env.NODE_ENV === 'development' ? this.consoleLogger : this.stackdriverLogger],
});
The aim here is that if the NODE_ENV is development, use the console logger, otherwise use the Stackdriver logger. However, when I deploy this to GKE, I see the following errors in the Stackdriver console log (and nothing in projects/[project-id]/logs/winston_log):
[winston] Attempt to write logs with no transports { // Logged message }
When I run this code locally on my dev machine with NODE_ENV=development, I see the logs in my local console and if I set NODE_ENV=production I see the logs in Stackdriver.
If I remove the ternary operator and have both transports defined and deploy to GKE, I do not see the above error and logging works correctly to both transports:
transports: [this.consoleLogger, this.stackdriverLogger],
Can anyone help me to configure this correctly?
EDIT
Added the full Logger.ts file for context:
import { LoggerService } from '#nestjs/common';
import * as winston from 'winston';
const { colorize, combine, json, printf, simple } = winston.format;
import { LoggingWinston } from '#google-cloud/logging-winston';
import cls from 'cls-hooked';
import { ConfigManager } from '../config';
import { TraceId } from '../middleware/traceId/constants';
export class Logger implements LoggerService {
private readonly serviceName: string = process.env.SERVICE_NAME;
private readonly consoleLogger = new winston.transports.Console({
format: combine(
colorise(),
simple(),
printf(context => {
return `[${context.level}]${context.message}`;
}),
),
});
private stackdriverLogger = new LoggingWinston({
serviceContext: {
service: this.serviceName,
},
});
private readonly logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: json(),
defaultMeta: {
service: this.serviceName,
},
transports: [process.env.NODE_ENV === 'development' ? this.consoleLogger : this.stackdriverLogger]
});
constructor(private readonly context?: string) {}
public verbose(message: string, context?: string) {
const log = this.buildLog(message, context);
this.logger.verbose(log.message, log.metadata);
}
public debug(message: string, context?: string) {
const log = this.buildLog(message, context);
this.logger.debug(log.message, log.metadata);
}
public log(message: string, context?: string) {
const log = this.buildLog(message, context);
this.logger.info(log.message, log.metadata);
}
public warn(message: string, context?: string) {
const log = this.buildLog(message, context);
this.logger.warn(log.message, log.metadata);
}
public error(message: string, trace?: string, context?: string) {
const log = this.buildLog(message, context, trace);
this.logger.error(log.message, log.metadata);
}
private buildLog(message: string, context?: string, trace?: string) {
const ctx = context || this.context;
const traceId = this.getTraceId();
return {
message: `[${ctx}] ${message}`,
metadata: {
traceId,
source: ctx,
stackTrace: trace,
},
};
}
private getTraceId(): string {
const clsNamespace = cls.getNamespace(TraceId.Namespace);
if (!clsNamespace) {
return null;
}
return clsNamespace.get(TraceId.Key);
}
}
So it turns out the problem was the #google-cloud/logging-winston package had a bug in it which caused it to throw this error:
UnhandledPromiseRejectionWarning: FetchError: request to http://169.254.169.254/computeMetadata/v1/instance failed, reason: connect ECONNREFUSED 169.254.169.254:80
This has now been fixed in version 3.0.6 - see https://github.com/googleapis/nodejs-logging-winston/issues/389#issuecomment-593727968.
After updating #google-cloud/logging-winston, the Winston logging is working correctly for me in Stackdriver.

How to add session id to each log in winston logger

In my node application I'm using winston module to store my application logs. I am getting the log as:
2017-11-22T07:16:38.632Z - info: asset type is attached successfully
Now I want to add sessionID after timestamp. I want my log as:
2017-11-22T07:16:38.632Z -**sessionId here**- info: asset type is attached successfully.
My code which I used for winston logging is:
var winston = require('winston');
require('winston-daily-rotate-file');
const levels = {
error: 0,
warn: 1,
info: 2,
http: 3,
verbose: 4,
debug: 5,
silly: 6,
trace: 7
};
var transport = new (winston.transports.DailyRotateFile)({
filename: 'logs/./log',
datePattern: 'yyyy-MM-dd.',
prepend: true,
json: false,
level: process.env.ENV === 'development' ? 'debug' : 'info'
});
var logger = new (winston.Logger)({
levels: levels,
transports: [
transport
]
});
module.exports = logger;
You have to custom the log format https://github.com/winstonjs/winston/tree/2.x#custom-log-format
First update your transport :
var transport = new (winston...
...
level: process.env.ENV === 'development' ? 'debug' : 'info',
timestamp: () => {
let today = new Date();
return today.toISOString();
},
formatter: options => `${options.timestamp()} -${options.meta.sessionId}- ${options.level}: ${options.message}`
});
Then, just pass the session ID to your logger meta feature :
logger.info('asset type is attached successfully', {sessionId: 'mySessionID'});
and you get
2017-11-22T14:13:17.697Z -mySessionID- info: asset type is attached successfully
Edit : instead of exporting only the winston.logger object, we export an object which requires a sessionId as a parameter, and contains the winston.logger. We also update the transport, so we customize its formatter property in the new Logger object. In the formatter property, we replace the meta declaration with the new this.sessionId variable, so we don't use the meta property anymore.
logger.js :
var transport = new (winston...
...
level: process.env.ENV === 'development' ? 'debug' : 'info',
timestamp: () => {
let today = new Date();
return today.toISOString();
}
});
class Logger {
constructor(session) {
this.sessionId = session;
this.transport = transport;
this.transport.formatter = options => `${options.timestamp()} -${this.sessionId}- ${options.level}: ${options.message}`;
this.logger = new (winston.Logger)({
levels: levels,
transports: [this.transport]
});
}
}
module.exports = Logger;
server.js :
const Logger = require('./logger.js');
let logman = new Logger('my_session_id');
let logger = logman.logger;
logger.info('asset type is attached successfully');
2017-11-23T13:13:08.769Z -my_session_id- info: asset type is attached successfully
I had the same request and wasn't wild about the solution... I liked the way the standard logger was formatting logs and didn't want to reinvent that wheel. Also, I was hoping for something more compact and I think I found it. I'm not a javascript programmer, so it might not be good coding practice, but it seems to work well for me...
server.js
//Initiate winston logging please
const logger = require('winston');
const common = require('winston/lib/winston/common');
function myFormat(options) {
options.formatter = null
options.label = helpers.getSessionId(logger.req)
return common.log(options);
}
var consoleLoggingConfig = {
timestamp: true,
level: process.env.LOG_LEVEL ? process.env.LOG_LEVEL : "info",
handleExceptions: true,
humanReadableUnhandledException: true,
formatter: myFormat
}
logger.remove(logger.transports.Console);
logger.add(logger.transports.Console, consoleLoggingConfig)
logger.info('Winston logging initiated', consoleLoggingConfig)
//Helper to ensure that logger has access to the request object to obtain the session id
app.use(helpers.attachReqToLogger)
module.exports=logger
helpers.js
// Helper to ensure that the winston logger has the request object on it to obtain the sessionId
helpers.attachReqToLogger = function(req, res, next) {
logger.req = req
next();
}
import winston from "winston";
import { v4 as uuidv4 } from "uuid";
const { format } = winston;
const commonFormat = [format.timestamp(), format.json()];
const withId = [
format.printf(({ message, id, ...rest }) => {
return JSON.stringify(
{
// if frontend privide id, use it, else create one
id: id ?? uuidv4(),
message,
...rest,
},
null,
// prettyPrint seems will overload printf's output
// so i mannually do this
4,
);
}),
];
export const logger = winston.createLogger({
level: "debug",
format: format.combine(...commonFormat, ...withId),
// ...

Resources