---
title: "evlog vs other loggers"
description: "Choose a logger by context model, extensibility, output, and migration cost, then follow the detailed comparison for your current stack."
canonical_url: "https://www.evlog.dev/reference/vs-other-loggers"
---
# evlog vs other loggers

> Choose a logger by context model, extensibility, output, and migration cost, then follow the detailed comparison for your current stack.

Choose a logger around the code and delivery pipeline you want to maintain. evlog combines individual structured logs with accumulated request events, structured errors, plugins, and drain adapters. Other libraries provide different configuration and extension models that may already fit your application.

## Pino vs Winston

Both write a record per call, and both produce JSON. They differ in how that record is shaped and where it goes.

<table>
<thead>
  <tr>
    <th>
      
    </th>
    
    <th>
      <a href="https://github.com/pinojs/pino/blob/main/docs/api.md" rel="nofollow">
        Pino
      </a>
    </th>
    
    <th>
      <a href="https://github.com/winstonjs/winston" rel="nofollow">
        Winston
      </a>
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Levels
    </td>
    
    <td>
      Numeric, <code>
        trace
      </code>
      
       10 to <code>
        fatal
      </code>
      
       60, written as a number unless a formatter changes it
    </td>
    
    <td>
      npm levels by default, custom level sets supported
    </td>
  </tr>
  
  <tr>
    <td>
      Shaping output
    </td>
    
    <td>
      Serializers per key, such as <code>
        pino.stdSerializers.err
      </code>
    </td>
    
    <td>
      Formats composed through <code>
        logform
      </code>
      
      : <code>
        combine
      </code>
      
      , <code>
        timestamp
      </code>
      
      , <code>
        json
      </code>
      
      , <code>
        printf
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Context
    </td>
    
    <td>
      <code>
        logger.child(bindings)
      </code>
      
      , plus <code>
        setBindings()
      </code>
      
       later
    </td>
    
    <td>
      <code>
        logger.child()
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      Destinations
    </td>
    
    <td>
      <a href="https://github.com/pinojs/pino/blob/main/docs/transports.md" rel="nofollow">
        Transports
      </a>
      
       run in a worker thread, so the write leaves the main thread
    </td>
    
    <td>
      Several transports on one logger, each with its own level
    </td>
  </tr>
  
  <tr>
    <td>
      Built in
    </td>
    
    <td>
      Redaction paths. Request logs come from the separate <a href="https://github.com/pinojs/pino-http" rel="nofollow">
        <code>
          pino-http
        </code>
      </a>
      
       package
    </td>
    
    <td>
      Profiling, exception and rejection handlers
    </td>
  </tr>
  
  <tr>
    <td>
      Overhead
    </td>
    
    <td>
      Low overhead is the stated design goal. Its <a href="https://github.com/pinojs/pino/blob/main/docs/benchmarks.md" rel="nofollow">
        benchmarks
      </a>
      
       include Winston and name their setup
    </td>
    
    <td>
      The README publishes no benchmark of its own
    </td>
  </tr>
</tbody>
</table>

Rerun those benchmarks on your own versions and payload before relying on the gap. Pino suits a service that wants lean JSON lines and offloaded writes. Winston suits a codebase that already composes formats and fans out to several transports with different levels. evlog takes a third route: it accumulates fields over a request and emits one event at the end. [evlog vs Pino](https://www.evlog.dev/compare/pino) and [evlog vs Winston](https://www.evlog.dev/compare/winston) cover each migration in detail.

## Choose the comparison for your stack

<table>
<thead>
  <tr>
    <th>
      You need to decide about
    </th>
    
    <th>
      Start here
    </th>
    
    <th>
      Evaluate before migrating
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Pino JSON logging
    </td>
    
    <td>
      <a href="https://www.evlog.dev/compare/pino">
        evlog vs Pino
      </a>
    </td>
    
    <td>
      Child bindings, serializers, custom levels, worker transports, existing HTTP logging
    </td>
  </tr>
  
  <tr>
    <td>
      Winston formats and transports
    </td>
    
    <td>
      <a href="https://www.evlog.dev/compare/winston">
        evlog vs Winston
      </a>
    </td>
    
    <td>
      Format composition, per-transport levels, custom transports, exception handling
    </td>
  </tr>
  
  <tr>
    <td>
      Consola in Nuxt, Nitro, or a CLI
    </td>
    
    <td>
      <a href="https://www.evlog.dev/compare/consola">
        evlog vs Consola
      </a>
    </td>
    
    <td>
      Reporters, terminal interaction, output formatting, request context
    </td>
  </tr>
  
  <tr>
    <td>
      LogTape in a library or application
    </td>
    
    <td>
      <a href="https://www.evlog.dev/compare/logtape">
        evlog vs LogTape
      </a>
    </td>
    
    <td>
      Consumer-owned configuration, categories, explicit and implicit context, testing
    </td>
  </tr>
  
  <tr>
    <td>
      LogLayer in front of an existing logger
    </td>
    
    <td>
      <a href="https://www.evlog.dev/compare/loglayer">
        evlog vs LogLayer
      </a>
    </td>
    
    <td>
      Transports to the logger you keep, groups, the wide-events mixin and the middleware it needs
    </td>
  </tr>
  
  <tr>
    <td>
      An existing OpenTelemetry Collector
    </td>
    
    <td>
      <a href="https://www.evlog.dev/compare/opentelemetry">
        evlog and OpenTelemetry
      </a>
    </td>
    
    <td>
      OTLP logs, active span correlation, separate sampling policies
    </td>
  </tr>
</tbody>
</table>

## What adopting evlog changes

For an accumulated event, application code calls `set()` as work progresses and emits at the end. [Wide Events](https://www.evlog.dev/learn/wide-events) explains that lifecycle. Other loggers can also receive one completed object, so the difference is which accumulation and lifecycle code the library supplies.

The global API handles individual messages. Its signatures do not make evlog a drop-in replacement for Pino, Winston, or Consola. Review call sites and output envelopes using [Simple Logging](https://www.evlog.dev/learn/simple-logging).

## Check the constraints

<table>
<thead>
  <tr>
    <th>
      Concern
    </th>
    
    <th>
      evlog contract
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Severity
    </td>
    
    <td>
      Six levels: <code>
        trace
      </code>
      
      , <code>
        debug
      </code>
      
      , <code>
        info
      </code>
      
      , <code>
        warn
      </code>
      
      , <code>
        error
      </code>
      
      , <code>
        fatal
      </code>
      
      . <code>
        trace
      </code>
      
       is dropped unless <code>
        sampling.rates.trace
      </code>
      
       is set
    </td>
  </tr>
  
  <tr>
    <td>
      Filtering
    </td>
    
    <td>
      Global <code>
        minLevel
      </code>
      
       for individual logs, sampling for wide events
    </td>
  </tr>
  
  <tr>
    <td>
      Extensibility
    </td>
    
    <td>
      Plugins, enrichers, and drains. Pino serializers and Winston formats need an explicit port.
    </td>
  </tr>
  
  <tr>
    <td>
      Redaction
    </td>
    
    <td>
      Configured paths and recognized patterns, enabled by default in production once <code>
        initLogger()
      </code>
      
       has run. Not a guarantee that all personal data is identified.
    </td>
  </tr>
  
  <tr>
    <td>
      Delivery
    </td>
    
    <td>
      Choose an adapter and its pipeline configuration, then handle pending delivery in the runtime you deploy.
    </td>
  </tr>
  
  <tr>
    <td>
      Testing
    </td>
    
    <td>
      Capture emitted events with the memory drain and assert required fields.
    </td>
  </tr>
</tbody>
</table>

Keeping an existing logger is reasonable when its integrations already satisfy your requirements. If you migrate, first preserve the fields used by dashboards and alerts, then verify success, failure, and shutdown behavior.

## Compare costs on the same workload

The [Performance reference](https://www.evlog.dev/reference/performance) describes the repository's unequal output configurations and the limits of its historical comparisons. Do not turn those measurements into a general ranking of loggers.

For deployment, choose a [drain adapter](https://www.evlog.dev/integrate/adapters/overview). For a script or library without framework lifecycle management, follow [Standalone TypeScript](https://www.evlog.dev/integrate/frameworks/standalone).

---

- [Performance Benchmarks](https://www.evlog.dev/reference/performance)
- [Simple Logging](https://www.evlog.dev/learn/simple-logging)


## Sitemap

See the full [sitemap](https://www.evlog.dev/sitemap.md) for all pages.
