Adze Logging Library

repository·master·Indexed 43 days ago

https://github.com/adzejs/adze

A universal, modern, and extendable logging library for JavaScript and TypeScript version 2.0.0. Designed to work across Node.js, Bun, Deno, and web browsers with zero configuration, Adze features a chainable API, support for namespaces and labels, and multiple output formats including Pretty, JSON, Standard, and Common. It provides advanced capabilities such as middleware for log transport, log listeners, and scoped loggers via the .seal() method.

Tokens
38.7K
Snippets
140
Records
190
Agent score
54%

What's inside Adze

  1. Core features of Adze

    master

    Adze is a universal logger with the following key capabilities:

    • Universalism: Runs in Node, Bun, Deno, and Browser with zero config.
    • Standard API Extension: Wraps and extends the standard console API.
    • Chainable API: Fluent interface for building log statements.
    • Log Listeners: For capturing log data.
    • Middleware: Support for plugins and log transport.
    • Annotations: Support for namespaces, labels, and metadata.
    • Output Formats: Supports Pretty, JSON, Standard, and Common (Common Log Format) out of the box.
    • Advanced Tooling: Support for log threads, child loggers, caching, and filtering.
  2. What is the Adze Global Store?

    master

    The Global Store acts as a global cache and control hub for all Adze logs within a single runtime environment. It is designed to enable centralized control over logging across multiple applications or micro-frontends that share the same execution context.

    Key capabilities include:

    • Configuration Overrides: Centrally override logging settings (like log levels or formats) for all instances of Adze in the environment.
    • Log Listeners: Attach listeners to the global stream of logs.
    • Filtering and Recall: Use tools to filter and retrieve logs from the global cache.

    In a micro-frontend architecture, the Global Store prevents log pollution by allowing you to manage the configuration of all running applications from one central point, even if they are developed independently.

  3. Automatic and Custom Serialization in JSON logs

    master

    Automatic Serialization

    In version >= 2.3.0, JSON formatted logs automatically attempt to serialize the following types if they are passed as arguments:

    • BigInt, Error, Date, Map, Set, ArrayBuffer (and all TypedArrays like Uint8Array, Float64Array, etc.), and DataView.

    Custom Serialization

    If a value is not handled automatically, you can provide a customReplacer function in the setup configuration or via the seal terminator. The replacer follows the standard JSON replacer pattern.

    Example: Replacing null with 0

    import adze, { setup } from 'adze';
    
    setup({
      customReplacer: (key: string, value: unknown) => {
        if (value === null) {
          return 0;
        }
      },
      format: 'json',
    });
    
    adze.log('No null values allowed', null); // Logs with 0 instead of null
  4. Manage logs centrally with the Adze global store

    master

    Adze includes a built-in global store that acts as a central configuration point for all logs generated within the application. This is particularly useful in microfrontend architectures where multiple independent frontend applications are running simultaneously. By applying configuration to the global store, you can control all logs regardless of their source.

    Use the global store to:

    • Change the global log level.
    • Filter out irrelevant logs across all applications.
    • Change the log format for all logs centrally.
  5. Use LevelSelector to select log levels

    master

    The LevelSelector type is used throughout the library (in Configuration.activeLevel, Configuration.filters, GlobalStore.addListener, and GlobalStore.tools.filterByLevel) to allow flexible selection of one or more log levels.

    Supported formats for LevelSelector include:

    • *: Selects all log levels.
    • string: A single level name (e.g., 'info').
    • number: A single level value.
    • string[] or number[]: An array of specific levels.
    • [string, '-', string] or [number, '-', number]: A range of levels (e.g., ['alert', '-', 'warn']).
    import adze, { setup } from 'adze';
    
    const store = setup();
    
    // Add a listener that selects ALL log levels.
    store.addListener('*', (log: Log) => {
      /* Do something */
    });
    
    // Add a listener that selects a single log level by name.
    store.addListener('info', (log: Log) => {
      /* Do something with info logs */
    });
    
    // Add a listener that selects both alert and error log levels by name.
    store.addListener(['alert', 'error'], (log: Log) => {
      /* Do something with the alert and error logs */
    });
    
    // Or select a range of values by their name.
    store.addListener(['alert', '-', 'warn'], (log: Log) => {
      /* Do something with the alert, error, and warn logs */
    });
    
    // Alternatively, select alert and error logs by their level values.
    store.addListener([0, 1], (log: Log) => {
      /* Do something with the alert and error logs */
    });
    
    // Or select a range of logs by their level values.
    store.addListener([0, '-', 3], (log: Log) => {
      /* Do something with the alert, error, warn, and info logs */
    });
  6. Use Adze in universal (isomorphic) environments

    master
    Adze is designed to be universal, meaning it can be executed on both the server side and the browser side without extra configuration. This makes it compatible with modern full-stack JavaScript frameworks that combine back-end and front-end code in the same location, such as NextJS, NuxtJS, and SvelteKit.
  7. Configure log filters for levels, labels, and namespaces

    master

    Filters allow you to control which logs are rendered to the console or server based on their level, label, or namespace.

    Each filter uses a FilterConfig which requires a type and a set of values:

    • type: 'include': Only logs matching the values will be rendered.
    • type: 'exclude': Logs matching the values will be suppressed.

    If both include and exclude are specified, include takes precedence.

    Note: When filtering by level, you can use the LevelSelector type to specify single levels, arrays of levels, or ranges (e.g., ['alert', '-', 'warn']).

    import adze, { setup } from 'adze';
    
    setup({
      filters: {
        namespaces: {
          type: 'include',
          values: ['foo'],
        },
      },
    });
    
    adze.ns('foo').success('I should print.');
    adze.ns('foo', 'bar').success('I should print.');
    adze.ns('bar').fail('I should not print.');
    adze.fail('I should not print because I do not have a namespace.');
  8. How the Global Store is instantiated

    master

    Adze automatically manages the lifecycle of the Global Store. If you call an Adze logging method (like adze.log()) and no Global Store exists, Adze will automatically instantiate one and attach it to the environment's global object.

    Depending on your runtime, the store is attached to:

    • Web Browser / Deno: window.$adzeGlobal
    • Node.js / Bun: global.$adzeGlobal

    Logs generated within the runtime context will automatically merge their local configuration with any overrides defined on this Global Store.

  9. How the Adze log lifecycle works

    master

    An Adze log follows a specific lifecycle of construction, termination, and storage. Understanding this flow is essential for working with middleware, log listeners, and the global store.

    1. Construction & Modification: You start a log using the Adze class. You can chain an indefinite number of modifier methods together. Each time a static modifier method is called, a new instance of Adze is returned, allowing for continuous chaining.
    2. Termination: To finalize a log, you call a terminator method. Unlike modifiers, a terminator method does not return a new Adze instance; it ends the chain. At this point, the log properties are evaluated, and the log message is generated and printed.
    3. Middleware Execution: During the termination process, Adze triggers lifecycle hooks on any registered middleware instances. This allows plugins to intercept, manipulate, or capture log data before it is finalized.
    4. Storage: Once terminated, the log instance is stored in the Global Store (provided that caching is enabled in your configuration). This allows you to later filter and recall historical logs using global store tools.
  10. Create a custom formatter by extending the Formatter class

    master

    If built-in formatters do not meet your needs, you can create a third-party formatter by extending the Formatter class.

    The Formatter class handles environment detection (browser vs. server) and timestamp formatting logic. To implement a complete formatter, you must override both formatBrowser and formatServer.

    Implementation Details

    • formatBrowser(data: ModifierData, timestamp: string, args: unknown[]): unknown[]: Called in browser environments. It should return an array of formatted arguments in the same order they were provided.
    • formatServer(data: ModifierData, timestamp: string, args: unknown[]): unknown[]: Called in backend environments. It should return an array of formatted arguments in the same order they were provided.
    • timestampFormatter getter: Returns the most relevant timestamp formatter based on a hierarchy:
      1. The Formatter class definition (highest priority).
      2. The UserConfiguration override.
      3. The default ISO-8601 formatter (lowest priority).
    import { Configuration, Formatter, type LevelConfiguration, type ModifierData } from 'adze';
    
    class HelloFormatter extends Formatter {
      constructor(cfg: Configuration, level: LevelConfiguration) {
        super(cfg, level);
      }
    
      protected formatBrowser(data: ModifierData, timestamp: string, args: unknown[]): unknown[] {
        // Example: prepend 'Hello' to browser logs
        return ['Hello', ...args];
      }
    
      protected formatServer(data: ModifierData, timestamp: string, args: unknown[]): unknown[] {
        // Example: prepend 'Hello' to server logs
        return ['Hello', ...args];
      }
    }
  11. Use Modifiers to enrich log data

    master

    Modifiers are methods that change the behavior of a Log instance without ending the chain. They are used to add extra context or metadata to a log entry. For example, using the timeNow modifier adds a performance timestamp representing the time since the page loaded.

    After calling a modifier, the method returns the Log instance, allowing you to either add more modifiers or call a terminator.

    // Using the timeNow modifier
    adze.timeNow.log('This is the time from pageload.');