file-stream-rotator icon indicating copy to clipboard operation
file-stream-rotator copied to clipboard

Usage with Pino as a Transport

Open thehobbit85 opened this issue 2 years ago • 1 comments

I wanted to use this library as a Pino Transport. The problem is that Pino can only accept streams using the streams API which then doesn't allow the usage of other transports. So if for example I wanted to save the logs to a file and also pretty-print them, I would have to setup both the FileStreamRotator and the pretty-print as streams using the stream API and I can't just setup the pretty-print as a normal transport.

And in my app I have 7 different transports and I really didn't want to have to change the code to use the stream API.

So here is what I came up with:

Create a file named 'logger.ts' and add this code to it:

import pino from 'pino'
import * as FileStreamRotator from 'file-stream-rotator'

const transport = pino.transport({
  targets: [
    {
      level: 'info',
      target: './logger.ts',
      options: {
        filename: '/tmp/test-%DATE%',
        frequency: 'daily',
        date_format: 'YYYY-MM-DD',
        size: '100M',
        max_logs: '10',
        audit_file: '/tmp/audit.json',
        extension: '.log',
        create_symlink: true,
        symlink_name: 'tail-current.log'
      }
    }
  ]
})

export const logger = pino(transport)
export default (options: any): any => FileStreamRotator.getStream(options)

Since Pino forces us to pass the target prop as string that points to a transport file, instead of creating a new file just for this, I export the stream as the default of this file and then point the target to myself.

It's super hacky but that's the only way I found that wouldn't require creating a new file and then having to deal with windows/mac/linux file paths nonsense.

If anyone has a better, cleaner solution, I would love to hear it . Thanks.

thehobbit85 avatar Feb 14 '23 20:02 thehobbit85

I wanted to use this library as a Pino Transport. The problem is that Pino can only accept streams using the streams API which then doesn't allow the usage of other transports. So if for example I wanted to save the logs to a file and also pretty-print them, I would have to setup both the FileStreamRotator and the pretty-print as streams using the stream API and I can't just setup the pretty-print as a normal transport.

And in my app I have 7 different transports and I really didn't want to have to change the code to use the stream API.

So here is what I came up with:

Create a file named 'logger.ts' and add this code to it:

import pino from 'pino'
import * as FileStreamRotator from 'file-stream-rotator'

const transport = pino.transport({
  targets: [
    {
      level: 'info',
      target: './logger.ts',
      options: {
        filename: '/tmp/test-%DATE%',
        frequency: 'daily',
        date_format: 'YYYY-MM-DD',
        size: '100M',
        max_logs: '10',
        audit_file: '/tmp/audit.json',
        extension: '.log',
        create_symlink: true,
        symlink_name: 'tail-current.log'
      }
    }
  ]
})

export const logger = pino(transport)
export default (options: any): any => FileStreamRotator.getStream(options)

Since Pino forces us to pass the target prop as string that points to a transport file, instead of creating a new file just for this, I export the stream as the default of this file and then point the target to myself.

It's super hacky but that's the only way I found that wouldn't require creating a new file and then having to deal with windows/mac/linux file paths nonsense.

If anyone has a better, cleaner solution, I would love to hear it . Thanks.

Please check out adzejs as an alternative logger to Pino. It just works with everything. Here's an example of setting up this library with Adze:

import adze, { createShed, Configuration, Constraints } from 'adze';
import { Shed } from 'adze/dist/shed';
import * as FileStreamRotator from 'file-stream-rotator';

/*
We are applying a constraints type to our logger in order to force developers
to add their namespaces to a centralized list. This makes it easy to know what
namespaces are being used throughout the application for filtering purposes.
*/
interface AppConstraints extends Constraints {
  allowedNamespaces: 'optin' | 'optin-controller';
}

/**
 * Configure our Adze logger for the development environment.
 */
function setupDevLogger(shed: Shed) {
  // This Adze configuration is used in the development environment
  const devConfig: Configuration = {
    useEmoji: true,
    logLevel: 8,
  };

  // Create our listener for writing to our log file
  shed.addListener('*', async (data, render) => {
    const logString = render[1][0];
    logStream.write(`${logString}\n`);
  });

  // Set up our base logger
  return adze<AppConstraints>(devConfig).timestamp.seal();
}

/**
 * Configure our Adze logger for the production environment.
 */
function setupProdLogger(shed: Shed) {
  // This Adze configuration is used in the production environment
  const prodConfig: Configuration = {
    unstyled: true,
    machineReadable: true,
    logLevel: 5,
  };

  // Create our listener for writing to our log file
  shed.addListener('*', async (data, render) => {
    const logString = render[1][0];
    logStream.write(`${logString}\n`);
  });

  // Set up our base logger
  return adze<AppConstraints>(prodConfig).timestamp.seal();
}

// Create a rotating file stream for writing our logs to a file.
const logStream = FileStreamRotator.getStream({
  filename: './logs/%DATE%',
  frequency: 'daily',
  date_format: 'MM-DD-YYYY',
  size: '2M',
  max_logs: '14',
  audit_file: './logs/audit.json',
  extension: '.log',
  create_symlink: true,
  symlink_name: 'current.log',
});

// Create an instance of shed for controlling our loggers and adding listeners
const shed = createShed({ cacheLimit: 0 });

// Create our logger factory for use throughout our application
const logger = process.env.ENV === 'development' ? setupDevLogger(shed) : setupProdLogger(shed);

export default logger;

AJStacy avatar Apr 12 '23 18:04 AJStacy