pino-loki

repository·dev·Indexed 41 days ago

https://github.com/julien-r44/pino-loki

A high-performance transport for the Pino logging library that forwards log messages directly to a Loki instance. Version 3.0.0 supports usage within Pino worker threads, a CLI tool for piping logs, and integration with AdonisJS. It features configurable batching, basic authentication, custom log formatting, and label mapping to optimize log ingestion in cloud-deployed SaaS applications.

Tokens
5.3K
Snippets
15
Records
23
Agent score
50%

What's inside pino-loki

  1. Integrate pino-loki with AdonisJS

    dev

    Since AdonisJS uses Pino as its default logger, you can integrate pino-loki by adding it as a transport in your config/logger.ts file.

    import type { LokiOptions } from 'pino-loki'
    import app from '@adonisjs/core/services/app'
    import { defineConfig, targets } from '@adonisjs/core/logger'
    
    import env from '#start/env'
    
    const loggerConfig = defineConfig({
      default: 'app',
    
      loggers: {
        app: {
          enabled: true,
          name: env.get('APP_NAME'),
          level: env.get('LOG_LEVEL'),
          transport: {
            targets: targets()
              .push({
                target: 'pino-loki',
                options: {
                  labels: { application: 'MY-APP' },
                  host: env.get('LOKI_HOST'),
                  basicAuth: {
                    username: env.get('LOKI_USERNAME'),
                    password: env.get('LOKI_PASSWORD'),
                  },
                } satisfies LokiOptions,
              })
              .toArray(),
          },
        },
      },
    })
  2. How to use logFormat as a function with worker threads

    dev

    Because Pino transports run in worker threads, non-serializable options like a logFormat function cannot be passed directly via pino.transport. To use a function for logFormat, you must create a custom local module that wraps pino-loki and then point your transport to that local file.

    // main.ts
    import pino from 'pino'
    
    const logger = pino({
      transport: {
        target: './my-custom-pino-loki.js',
        options: { labels: { application: 'MY-APP' } }
      },
    })
    
    // my-custom-pino-loki.js
    import { pinoLoki } from 'pino-loki'
    
    export default function customPinoLoki(options) {
      return pinoLoki({
        ...options,
        logFormat: (log) => {
          return `hello ${log.msg} ${log.lokilevel} ${log.req.id} ${log.level}`
        },
      })
    }
  3. Install and use pino-loki in a worker thread

    dev

    The recommended way to use pino-loki is within a Pino worker thread. This is compatible with Pino v7.0.0 and above. You configure the transport using pino.transport and provide a LokiOptions object containing the Loki host and optional basicAuth credentials.

    import pino from 'pino'
    import type { LokiOptions } from 'pino-loki'
    
    const transport = pino.transport<LokiOptions>({
      target: "pino-loki",
      options: {
        host: 'https://my-loki-instance:3100',
        basicAuth: {
          username: "username",
          password: "password",
        },
      },
    });
    
    const logger = pino(transport);
    logger.error({ foo: 'bar' })
  4. Configure batching for pino-loki

    dev

    Batching is enabled by default. It accumulates logs in a buffer and sends them at regular intervals to reduce HTTP requests.

    Options:

    • batching: Set to false to disable batching and send logs immediately.
    • batching.interval: Interval in seconds to send logs. Defaults to 5.
    • batching.maxBufferSize: Maximum number of logs to keep in the buffer. When full, the oldest logs are dropped (FIFO). Defaults to 10000. Set to 0 for an unlimited buffer (not recommended due to OOM risk).
    // Batching enabled with default options (interval: 5s, maxBufferSize: 10000)
    pinoLoki({ host: '...' })
    
    // Batching with custom options
    pinoLoki({
      host: '...',
      batching: {
        interval: 2,        // Send logs every 2 seconds
        maxBufferSize: 5000 // Keep max 5000 logs in buffer
      }
    })
    
    // Batching disabled - logs sent immediately
    pinoLoki({ host: '...', batching: false })
  5. Configure Loki transport options

    dev

    When initializing pino-loki, you can pass several options to customize how logs are sent to Loki:

    • host (Required): The URL for your Loki instance.
    • endpoint: The path to the Loki push API. Defaults to /loki/api/v1/push.
    • basicAuth: Object containing username and password.
    • headers: An object of headers to send (e.g., X-Scope-OrgID for Grafana Cloud).
    • labels: Additional labels applied to all logs (e.g., { application: 'my-app' }).
    • propsToLabels: A list of properties in the log object to be converted into Loki labels.
    • levelMap: A mapping of Pino log levels to Loki log levels.
    • timeout: Max timeout in milliseconds for sending logs. Defaults to 30000.
    • silenceErrors: If false, errors during transmission are logged to the console. Defaults to false.
    • replaceTimestamp: If true, replaces the Pino timestamp with Date.now(). Note: when using batching, this will use the batch time, not the individual log time.
    • structuredMetaKey: The key used for structured metadata. Defaults to 'meta'. Set to false to disable.
    • convertArrays: If true, converts arrays to objects with index keys (e.g., ['a', 'b'] becomes { "0": "a", "1": "b" ext{\}}). This helps avoid issues with Loki's JSON parser.
    • logFormat: Converts the JSON log into a custom string. Can be a string template (using dot notation like {msg}) or a function.
  6. Run Loki via Docker Compose

    dev

    You can use the grafana/otel-lgtm:latest image via Docker Compose to set up a local environment containing Grafana and Loki. This setup is useful for testing pino-loki locally.

    By default, the service exposes:

    • Port 3000: Grafana UI
    • Port 3100: Loki HTTP API (the endpoint pino-loki will send logs to)

    The configuration below enables anonymous Admin access to Grafana for easier local development.

    services:
      lgtm:
        image: grafana/otel-lgtm:latest
        ports:
          - '3000:3000' # Grafana
          - '3100:3100' # Loki HTTP API
        environment:
          - GF_AUTH_ANONYMOUS_ENABLED=true
          - GF_AUTH_ANONYMOUS_ORG_ROLE=Admin
  7. Troubleshooting: Out-of-order errors and Dropped logs

    dev

    Out-of-order errors

    Due to the asynchronous nature of Pino, you may encounter out-of-order errors in Loki. To resolve this, configure your Loki instance to allow out-of-order logs.

    Dropped logs

    Logs can be lost in two scenarios:

    1. Network issues: If Loki is unreachable, the current batch of logs will be lost.
    2. Buffer overflow: If batching is enabled and the buffer reaches maxBufferSize (default: 10000), the oldest logs are dropped (FIFO) to prevent memory exhaustion (OOM).
  8. Configure LokiOptions for pino-loki

    dev

    When calling pinoLoki(options), you can provide the following configuration keys:

    KeyTypeDefaultDescription
    endpointstring'loki/api/v1/push'The URL path for the Loki push API.
    timeoutnumber30000Request timeout in milliseconds.
    silenceErrorsbooleanfalseIf true, errors during log pushing are suppressed.
    batchingBatchingOptions | false{ enabled: true, interval: 5, maxBufferSize: 10000 }Configuration for log batching. Set to false to disable batching.
    replaceTimestampbooleanfalseWhether to replace the log timestamp with the current time.
    propsToLabelsstring[][]Array of property names to be extracted as Loki labels.
    convertArraysbooleanfalseWhether to convert arrays in the log object.
    structuredMetaKeystring | false'meta'The key used for structured metadata. Set to false to disable.
    logFormatanyundefinedCustom log format configuration.
    basicAuthobjectundefinedCredentials for Basic Authentication (not logged).
  9. Configure batching behavior in pino-loki

    dev

    Batching improves performance by sending multiple logs in a single request. You can enable or disable it using the batching key in LokiOptions.

    • To disable batching: Set batching: false.
    • To enable/configure batching: Provide a BatchingOptions object.

    BatchingOptions properties:

    • interval (number): The interval at which batched logs are sent in seconds. Default: 5.
    • maxBufferSize (number): Maximum number of logs to buffer before dropping the oldest ones. Set to 0 for an unlimited buffer (not recommended). Default: 10_000.
    // Example: Custom batching configuration
    batching: {
      interval: 10,
      maxBufferSize: 5000
    }
  10. Use structured metadata in Loki

    dev

    Loki supports structured metadata. You can configure pino-loki to move extra log properties into a structured metadata block instead of just the log line.

    • structuredMetaKey: Specify the key name to be used for the structured metadata. Default: 'meta'. Set to false to disable structured metadata.
    • convertArrays: If set to true, arrays in log messages are converted to objects where the index is the key, which can help with metadata compatibility. Default: false.
  11. Configure BatchingOptions

    dev

    Batching improves performance by grouping logs together before sending them to Loki. You can configure batching via the batching property in LokiOptions.

    If you set batching: false, batching is disabled and logs are pushed immediately.

    If you provide an object, the following keys apply:

    • interval: The time interval (in seconds) between batches. Default is 5.
    • maxBufferSize: The maximum number of logs to hold in the buffer before triggering a push or dropping logs. Default is 10000. If set to 0, the buffer size limit is ignored (though the transport still uses the interval).
    const transport = pinoLoki({
      batching: {
        interval: 10,
        maxBufferSize: 500
      }
    });
  12. Configure the Pino-Loki transport with LokiOptions

    dev

    When setting up the pino-loki transport, you provide a LokiOptions object. This object controls how logs are sent to Loki, including connection details, batching behavior, and how Pino log properties are mapped to Loki labels or structured metadata.

    const options: LokiOptions = {
      host: 'http://loki:3100',
      endpoint: 'loki/api/v1/push',
      labels: {
        env: 'production',
        service: 'my-app'
      },
      batching: {
        interval: 10,
        maxBufferSize: 5000
      },
      propsToLabels: ['service_name', 'version'],
      structuredMetaKey: 'meta'
    };