npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@zemd/nestjs-pino-logger

v1.0.7

Published

Nestjs logger based on pino

Downloads

5

Readme

Nestjs-Pino-Logger

Motivation

The motivation of this package is to provide a pino logger for nestjs framework with near identical behaviour of the standard nestjs logger and to provide a good defaults for the logger. This also means that pino logger can be customized as needed without need to configure the Logger service, that makes the package flexible and lightweight.

Installation

at first make sure you have access to the github, and update your .npmrc file:

//npm.pkg.github.com/:_authToken=${GITHUB_AUTH_TOKEN}
@zemd:registry=https://npm.pkg.github.com
always-auth=true
npm install --save @zemd/nestjs-pino-logger

you also might need:

npm install --save @nestjs/common @nestjs/config pino pino-http
npm install --save-dev pino-pretty

Usage

let's say you are defining AppModule:

// app.module.ts
import { LoggerModule } from '@zemd/nestjs-pino-logger';
import {ConfigModule} from "@nestjs/config";
import pinoConfig from "./config/pino.config";
import pinoHttpConfig from "./config/pino-http.config";

@Module({
    imports: [
        ConfigModule.forRoot({
            isGlobal: true,
            load: [pinoConfig, pinoHttpConfig],
            // ... any other configuration options you want to use
        }),
        LoggerModule.forRootAsync({
            imports: [ConfigModule],
            useFactory: (configService: ConfigService) => configService.get<LoggerOptions>('pino'),
            inject: [ConfigService],
        }),
    ]
})
class AppModule {}

then you should inject Logger into your main app:

// main.ts
import {Logger, PINO_LOGGER_INSTANCE} from '@zemd/nestjs-pino-logger';
import pinoHttp from "pino-http";
import type {Options} from 'pino-http';

const app = await NestFactory.create(AppModule, {bufferLogs: true});
app.useLogger(app.get(Logger));

// if you want to use pino-http ↓
app.use(pinoHttp({
  ...configService.get<Options>('pino-http'),
  logger: app.get(PINO_LOGGER_INSTANCE),
}));

now let's look closer at the pino.config.ts and pino-http.config.ts files:

// pino.config.ts
import {registerAs} from "@nestjs/config";
import pino from "pino";
import {customLevels} from "@zemd/nestjs-pino-logger";

export default registerAs('pino', (): Partial<LoggerOptions> => {
    const targets: pino.TransportTargetOptions<Record<string, any>>[] = [];
    
    if (process.env.NODE_ENV !== "production") {
        // you don't need to use this transport in production, usually you would want to send logs as json object to the observability service
        targets.push({
            target: "@zemd/nestjs-pino-logger/dist/pino-pretty-transport.js", 
            level: process.env.NODE_ENV === "development" ? "verbose" : "error",
            options: {
                colorize: false,
                translateTime: true,
                include: '',
                singleLine: false,
                hideObject: true,
            },
        });
    }

    const transport = targets.length ? {targets} : undefined;

    return {
        customLevels,
        useOnlyCustomLevels: true, // this is required! and you should use this config explicitly to avoid any unexpected behaviour
        transport,
    };
});

that's basically it, if you want to use pino logger in your nestjs application, you can use any kind on configuration, but at the same time you can use default nestjs logger.

// pino-http.config.ts
import {registerAs} from "@nestjs/config";
import {Options} from "pino-http";
import * as crypto from "node:crypto";

export default registerAs('pino-http', (): Partial<Options> => {
    return {
        level: process.env.LOG_LEVEL ?? "verbose",
        genReqId: (req, res) => {
            // this is not required 
            if (req.id) {
                return req.id;
            }
            return crypto.randomUUID();
        },
        customProps: () => ({
            // by default there is no "context" provided inside the middleware, so we need to add it manually
            context: 'http',
        }),
        // pino-http types don't accept custom log levels, so we need to use ts-ignore here,
        // if you want to use one log level for all requests, just use `useLevel` option.
        // @ts-ignore 
        customLogLevel: function (req, res, err) {
            if (res.statusCode >= 400 && res.statusCode < 500) {
                return 'warn'
            } else if (res.statusCode >= 500 || err) {
                return 'error'
            } else if (res.statusCode >= 300 && res.statusCode < 400) {
                return 'verbose'
            }
            return 'log'
        },
    };
});

Advanced usage

Extending message object

In case if you want extend pino log object by adding more fields, you can use buildPinoMessage helper function, that adds hidden Symbol to the object and which allows to distinguish between regular log message and custom.

It looks like:

const message = buildPinoMessage({
  message: 'Hello World %o',
  mergingObject: { foo: 'bar' },
  interpolationValues: [{ data: 'the data object will be used to format the message' }]
});
this.logger.log(message, 'here you can also pass something that will be added to the msg string');

Using request id

nestjs-pino package has exceptional feature that allows to leverage the request-id that you might noticed previously when we added pino-http middleware. Essentially this is great feature, but at the moment I don't think that it should be implemented within this package anytime in the future. It can be achieved by storing this info using AsyncLocalStorage inside the middleware function in main.ts file, and then retrieved in pino mixin. But in more complex scenario you should be using open telemetry, which can handle global trace-id and pass it with every log message. Also having request object in each log message would increase it's size and make it more expensive to transfer and store.

Example of using open telemetry:

// pino.config.ts
import { context, isSpanContextValid, trace } from '@opentelemetry/api';

//...registerAs...
return {
    // ... transfer
    // ... other pino options
    mixin: () => {
        const record = {};
        const span = trace.getSpan(context.active());
        if (span) {
            const spanContext = span.spanContext();

            if (isSpanContextValid(spanContext)) {
                Object.assign(record, {
                    trace_id: spanContext.traceId,
                    span_id: spanContext.spanId,
                    trace_flags: `0${spanContext.traceFlags.toString(16)}`,
                });
            }
        }

        return record;
    }
}
//...

@opentelemetry/api package also uses AsyncLocalStorage under the hood, so as you can see having control over the pino configuration can allow to achieve any kind of task.

Example

logging example

License

@zemd/nestjs-pino-logger released under the LGPL-3.0 License.

Donate