JSPM

  • Created
  • Published
  • Downloads 134
  • Score
    100M100P100Q98582F
  • License MIT

๐ŸŒŒ A consistent logging system for all JavaScript runtimes.

Package Exports

  • logry
  • logry/plugins

Readme

Logry

Your go-to logger thatโ€™s small but mighty, universal across JavaScript runtimes.

NPM version Bundle size Coverage Status TypeScript License

๐Ÿณ Logry 2.0 has landed, but the docs are still cooking, please read gently.

๐Ÿ“š Read the Docs

Lightweight. Customizable. Ready-to-go.
Console-first. The rest? You decide.


Books Outline


Glowing Star Output Preview

Hereโ€™s how logs look in Node.js vs. the browser:

Logry node
Console output in Node.js
Logry browser
Console output in Browser

Kissing Cat Not your style? No worries! Itโ€™s fully customizable.

Upwards Button


Rocket Quick Start

Triangular Flag Installation

npm install logry

or use yarn

yarn add logry

Label Use in plain HTML with this script:

<!-- Use logry in any static HTML page via jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/logry@1.1.2/dist/browser-lite/index.global.min.js"></script>
<script>
  logry.info("Hey! I'm here!");
</script>

Sparkles Using Static Logger Methods

They work instantly, ignore level restrictions, and use the default โ€œprettyโ€ preset for clean output.

import { trace, debug, info, warn, error, fatal } from "logry";

info("๐Ÿ‘‹๐Ÿผ Hi there!");

// second argument is metadata (meta)
warn("Who are you?", { name: "Logry" });

// you can also pass an Error
error("Uh-oh!", new Error("Lost in the code"));

Sparkles Creating a Custom Logger Instance

Call logry() to create a logger.
The default level is warn, so only warn, error, and fatal logs are shown.

import { logry } from "logry";

// Create a custom logger instance (defaults to id: 'default' and level: 'warn')
const logger = logry();

// โŒ This won't be shown, 'info' is lower than the default 'warn' level
logger.info("User logged in");

// โœ… This will be shown
logger.warn("User login warning");

A quick and easy setup to start logging right away:

import { logry } from "logry";

const logger = logry({
  id: "MyLogger",
  level: "debug", // Will show: debug, info, warn, error, fatal (trace will be hidden)
});
๐Ÿ’ญ Want a more customized setup?

A fully customizable logger setup when you need more control and personality:

import { logry } from "logry";

const logger = logry({
  id: "๐ŸŒ My Logger",
  level: "info",
  scope: ["auth", "api"],
  context: { env: "production", appVersion: "2.5.1" },
  preset: "verbose", // "pretty" | "pretty-expanded" | "minimal" | "verbose"
  normalizerConfig: {
    node: {
      timestamp: { style: "iso" },
      // ...
    },
    browser: {
      timestamp: { style: "pretty" },
      // ...
    },
  },
  formatterConfig: {
    node: {
      id: { ansiStyle: "\x1b[35m" },
      message: {
        customFormatter: ({ fieldValue }) => ({
          fieldValue: "\n" + fieldValue.toUpperCase(),
        }),
      },
      // ...
    },
    browser: {
      id: { cssStyle: "color: purple;" },
      context: { format: "compact" },
      // ...
    },
  },
  handlerManagerConfig: {
    // ...
  },
});

Joystick Connect with the Outside World

Want to send your logs to files, Slack, or other services?
Check out the โœจ Handlers & Integrations to see how Logry integrates with real-world outputs.

Upwards Button


Smiling Face with Sunglasses Features at a Glance

  • ๐ŸŒ Environment Agnostic โ€” Works across universal JavaScript runtimes including server, API, browser, Edge, and beyond

  • โšก๏ธ Zero Dependency & Lightning Fast โ€” Zero dependencies, fully typed, minimal runtime overhead

  • ๐ŸŽจ Flexible Formatter Pipeline โ€” Customize every step from normalization to formatting with full control

  • ๐Ÿ“ฆ Plugin-Ready Core โ€” Easily extendable with custom handlers and lifecycle hooks to build your own logging workflows

  • ๐Ÿ” Contextual Logging โ€” Supports separate meta and context data for flexible and clear log enrichment

  • ๐ŸŒณ Scoped Loggers โ€” Organize and filter logs with nested, hierarchical scopes

Upwards Button


Shooting Star Presets

Logry offers several built-in logger presets.
Each preset is a set of normalizer and formatter configs for different log styles.

Preset Description
pretty Formatted, easy to read
pretty-expanded expanded output with line breaks
minimal Simple output with essential info only
verbose Full detail with context and depth

To use a preset, pass it when creating the logger:

const logger = logry({ preset: "pretty" });

Presets are fixed for now.
๐ŸŽฏ Custom presets may come in future versions.

Upwards Button


Comet Core Concepts

Logry is built with modularity, precision, and developer experience in mind.
Here are the key concepts that define how it works:

Memo Log Level

Logry supports seven log levels, ordered from most critical to most verbose:

Level Description
fatal โ— Logs critical system failures. The application may crash or exit immediately
error โŒ Logs runtime errors that should be investigated and typically require action
warn โš ๏ธ Logs recoverable issues or unexpected behaviors that don't prevent operation
info โ„น๏ธ Logs general operational messages, such as successful startups or actions
debug ๐Ÿ› ๏ธ Logs detailed internal information helpful for debugging
trace ๐Ÿ” Logs the most granular details, every step, useful for profiling or deep debugging
silent ๐Ÿค Disables all logging output

The logger only outputs messages at or above the current level.
For example, if the level is set to warn, only warn, error, and fatal logs will be printed.

You can specify the desired log level when creating a logger instance

Core-level configs like level are only applied when creating a new core.
If a core with the same ID exists, those configs will be ignored, and a warning will be logged.

// Initialize a logger with a preferred level
const logger = logry({ id: "my-app", level: "debug" });

Bypass the log level filter with force

Normally, the logger will only output messages at or above the configured level.
However, you can force a log to be emitted regardless of the current level:

logger.force.error("Something went wrong!");
logger.force.info("This will show even if level is set to 'warn'");

โš ๏ธ Use this with careโ€”force is designed for exceptional situations where logs must be guaranteed to appear.

Upwards Button

Teddy Bear Child Loggers

In Logry, every logger instance is lightweight and modular.
You can freely create child loggers that inherit settings from their parent โ€” while overriding only what you need.

Creating a Child Logger

You can use the .child() method to create a scoped or customized logger:

const logger = logry({ id: "main-app", level: "info" });

const authLogger = logger.child({
  level: "debug", // override log level
  scope: "auth", // add a scope
  context: { userType: "admin" }, // inject default context
});

Child Logger Inheritance

Child loggers inherit settings by merging properties differently depending on their type:

  • scope: Appended
    ["main"] + "auth" โ†’ ["main", "auth"]

  • context: Merged, child overrides
    { app: "main", user: "guest" } + { user: "admin" } โ†’ { app: "main", user: "admin" }

  • formatterConfig / normalizerConfig:
    Shallow merged per platform (node, browser), with child taking precedence

This keeps child loggers flexible and contextual, without needing to re-specify everything.

Upwards Button

Moai Logger Core

The core engine responsible for managing log levels, shared identity (id), and optional configurations for formatting, normalization, and handlers.

  • Multiple logger instances can share a single core by specifying the same id, enabling centralized and synchronized log level management across instances.

  • It supports dynamic runtime control of log verbosity:

    • setLevel(level): updates the active log level
    • resetLevel(): restore to the initial log level
  • Factory When calling logry(), the system checks the Logger Core map by id.
    If found, it returns a Logger linked to that core; otherwise, it creates a new core and returns a Logger.

โš ๏ธ Note: Core configurations are fixed per LoggerCore identified by id.
Creating a logger with an existing id ignores new core-level options and logs a warning.

โ„น๏ธ Note: In Edge runtime environments, the Logger Core concept is intentionally not available.
This design aligns with the stateless and ephemeral nature of Edge environments.

Upwards Button

Control Knobs Handler Manager

Every Logger instance is paired with a dedicated HandlerManager,
an internal module inherited from its LoggerCore.

Where the Logger emits the log, the HandlerManager ensures your logs travel further,
writing to files, sending to remote servers, or reporting to dashboards, wherever needed ๐ŸŒ

It orchestrates all registered log handlers, manages asynchronous tasks,
and provides robust strategies for flushing, cleanup, and error recovery.

  • โ™ป๏ธ Handler lifecycle
    • Initializes handlers on registration
    • Optionally handles errors via a configurable onError callback
    • Disposes each handler safely when no longer needed
  • ๐Ÿ”Ž Async task tracking
    • Tracks all pending asynchronous log operations
    • Ensures that every delivery completes or fails safely
  • โฑ๏ธ Flush support
    • Call flush(timeout?) to wait for all pending handler tasks
    • Supports flushStrategy for time-based or event-driven flushing
  • ๐Ÿ›‘ Error recovery
    • Catches errors during log handling
    • Reports errors with handler ID and payload context via onError
  • ๐Ÿงผ Resource cleanup
    • dispose() cancels flush strategies, removes all handlers, and clears internal states

Upwards Button


Classical Building Architecture

Logry is designed with a focus on console output as the primary logging target.
The Platform Transporter is tailored for each environment to efficiently handle core console output.

Beyond console output, tasks like file writing, remote logging, or service integration are handled by flexible, user-defined Handlers.
This plugin-like architecture allows you to easily extend or customize logging behavior without affecting the main console pipeline.

Party Popper Think of the Platform Transporter as the reliable frontman, while Handlers are the creative backstage crew adding all the magic.

This architecture ensures that:

  • The console remains the central output, optimized for each platformโ€™s specifics.
  • Other output destinations can be easily added or modified by introducing new Handlers.
  • The system maintains consistency and reliability, while providing maximum flexibility to adapt to various use cases and environments.

Water Wave Log Pipeline

Building on the architecture described above,

When you call any logging method on your logger instance (e.g. info(), error()),
it triggers two parallel paths internally:

  • Airplane Departure Platform Transporter: Normalizes, formats, and outputs logs to the console, optimized for the running environment.
  • Control Knobs HandlerManager: Runs additional handlers for side effects like writing files, sending logs remotely, or custom integrations.
                Logger.log()
                    โ†“
     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
     โ”‚                               โ”‚
     โ–ผ                               โ–ผ
  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”   โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  โ”‚ Platform Transporter โ”‚   โ”‚  handlerManager  โ”‚
  โ”‚ (normalize, format,  โ”‚   โ”‚  .runHandlers()  โ”‚
  โ”‚  output to console)  โ”‚   โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜             โ”‚
                                       โ–ผ
     โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
     โ”‚ FileHandler โ”‚ SendRemoteHandler โ”‚ CustomHandler โ”‚ ...
     โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Upwards Button

Airplane Departure Transporter

When a log is passed to a Transporter, it flows through three clear stages:

  1. ๐Ÿ”€ Normalization โ€” Ensures a consistent, structured shape.
  2. ๐ŸŽจ Formatting โ€” Transforms the data into a readable format.
  3. ๐Ÿ–จ Output โ€” Sends the final log to the destination console.

Note:
In Node.js, NodeConsoleTransporter lazily appends fields like pid and hostname.
This is done asynchronously to ensure that Logger.log() remains synchronous and returns immediately.

Built-in Transporters

Logry ships with platform-aware console transporters,
so your logs always show up in the right placeโ€”without any extra setup ๐Ÿ› ๏ธ

Platform Transporter Output target
Node.js NodeConsoleTransporter Terminal console
Browser BrowserConsoleTransporter Browser developer console
Edge EdgeConsoleTransporter Platform console (plain text)

Each transporter activates only in its matching runtime, and does nothing otherwise.

Universal by Default

Importing from "logry" gives you a universal logger with both Node and browser transporters attached:

import { logry } from "logry"; // Includes both NodeConsoleTransporter and BrowserConsoleTransporter

logry.info("Hello from anywhere");

In Node.js, logs go to the terminal. In the browser, they appear in the browser console.
โšก๏ธ No extra configuration required.

Platform-Specific Variants

To reduce bundle size or fine-tune behavior, you can import from a platform-specific entry point:

Import Path Platform Bound Transporter
"logry/node" Node.js NodeConsoleTransporter
"logry/browser" Browser BrowserConsoleTransporter
"logry/edge" Edge EdgeConsoleTransporter

Each variant includes only the relevant transporter for its environment.

import { logry } from "logry/node"; // Logs only to terminal (no browser logic)

Edge Runtime Support

The "logry/edge" export is optimized for environments like Cloudflare Workers and other serverless platforms.
It uses EdgeConsoleTransporter, a minimal transporter that prints plain-text logs to the platformโ€™s console.

โš ๏ธ Always use logry/edge in Edge runtimes.
Other versions rely on Node.js APIs and may fail to run.

import { logry } from "logry/edge";

logry.info("Hello from the Edge");

Design Principle: Console Only

๐Ÿ”ฎ Unlike traditional loggers that mix console output with side-effects,
Logry keeps things clean and focused.
Transporters handle console output only; for other log deliveries, use Handlers.

Upwards Button

Shuffle Tracks Button Normalizer

Before any log is formatted, Logry first passes it through a platform-aware normalizer.
This process ensures a consistent structure, reliable data types, and full flexibility for customization.

What it does

The Normalizer transforms a raw log payload into a normalized shape, handling core fields like:

  • timestamp
  • id
  • level
  • scope
  • message
  • meta
  • context
  • pid (Node.js only)
  • hostname (Node.js only)

Each field has a dedicated normalizer, all of which can be overridden via custom logic.

Customization

Every normalizer supports a customNormalizer function, letting you override default behavior:

id: {
  customNormalizer: ({ fieldValue, raw }) => `node-${fieldValue}`, // e.g., "default" โ†’ "node-default"
  // fieldValue: the original value for the "id" field
  // raw: a snapshot of the full raw payload before any normalization
}

You can also fine-tune behavior using extra options per field.

Field Extra Options Available
timestamp style, useUTC, showTimeOnly
level style
scope separator
meta errorStackLines

Platform Awareness

Normalization logic in Logry adapts based on the runtime environment,
allowing logs to be tailored specifically for Node.js or Browser contexts.

For example:

  • Timestamps appear as full ISO 8601 UTC strings in Node, but as simplified human-readable strings in the browser.

You can define environment-specific behavior using the normalizerConfig structure.
It can be set globally in logry(...), scoped to a logger.child(...), or overridden per log method:

const logger = logry({
  // ...
  normalizerConfig: {
    node: {
      timestamp: {
        style: "iso", // Use full ISO format for timestamp in Node.js
        useUTC: true, // Display timestamp in UTC timezone
      },
      level: {
        style: "upper", // Show log level in uppercase (e.g., "ERROR")
      },
      meta: {
        errorStackLines: 10, // Limit error stack trace to 10 lines
      },
    },
    browser: {
      timestamp: {
        style: "pretty", // Use a more human-friendly timestamp format in browsers
        useUTC: false, // Display timestamp in local timezone
      },
      level: {
        style: "lower", // Show log level in lowercase (e.g., "error")
      },
    },
  },
  // ...
});

Upwards Button

Artist Palette Formatter

The Formatter takes normalized log data and turns it into readable, styled output.
It supports optional color coding to make logs clearer and easier to scan.

What it does

The Formatter receives normalized data and produces formatted strings (or structured content) ready for display.
Handled fields include:

  • timestamp
  • id
  • level
  • scope
  • message
  • meta
  • context
  • pid (Node.js only)
  • hostname (Node.js only)

Each part has its own formatter. All formatters support optional style customizations and can be overridden with custom logic.

Customization

Common format options apply to all formatter parts and include:

  • hide?: boolean;
  • prefix?: string;
  • suffix?: string;
  • lineBreaks?: number;
  • spaceAfter?: number;

Every formatter supports a customFormatter function, letting you override default behavior:

message: {
  customFormatter: ({ fieldValue, raw }) => ({
    fieldValue: `! ${fieldValue}`, // e.g., "msg" โ†’ "! msg"
    withAnsiStyle: `\x1b[42m${fieldValue}`, // Used in Node.js when `useAnsiStyle` is enabled
    cssStyle: `border: 1px solid blue`, // Used in browsers for console styling
  }),
},

You can also fine-tune behavior using extra options per part.

Platform Part Extra Options Available
Node.js ALL ansiStyle, useAnsiStyle
- scope showOnlyLatest, separator
- meta format, indent, all InspectOptions (for format: "raw")
- context format, indent, all InspectOptions (for format: "raw")
Browser ALL cssStyle
- scope showOnlyLatest, separator
- meta format, indent
- context format, indent

Note: InspectOptions refers to the options supported by Node.js util.inspect.

Platform Awareness

Formatter behavior automatically adapts to the runtime platform, whether it is Node.js or the browser.
This ensures that log outputs remain clear, styled, and consistent across environments.

The output behavior varies depending on the platform:

Platform Format output Styling mechanism
Node.js Returns { fieldValue: string, withAnsiStyle?: string } Uses ANSI escape codes (e.g. \x1b[31m)
Browser Returns { fieldValue: string, cssStyle?: string } Uses %c and inline CSS

In the browser, the final result will be used with console.log("%c...%c...%c...", styleA, styleB, ...), allowing for per-part CSS styling.

For example:

  • Timestamps appear as full ISO strings with ANSI colors in Node.js and as simplified text styled with CSS in the browser.
  • Meta shows full depth in Node, but gets a prefix like โ€œMETA | โ€œ in Browser.
  • Some fields (like level) can be hidden in one platform but shown in another.

You can define environment-specific behavior using the formatterConfig structure.
It can be set globally in logry(...), scoped to a logger.child(...), or overridden per log method:

const logger = logry({
  // ...
  formatterConfig: {
    node: {
      timestamp: {
        ansiStyle: "\x1b[33m", // Yellow text for timestamp in Node.js
      },
      meta: {
        depth: null, // Show full depth for meta in Node.js
      },
      lineBreaksAfter: 2, // Add extra spacing after logs
    },
    browser: {
      timestamp: {
        cssStyle: "font-weight: bold; color: orange;", // Bold orange timestamp in browsers
      },
      meta: {
        prefix: "META | ", // Prefix meta with label in browsers
      },
      level: {
        hide: true, // Hide level field in browsers
      },
    },
  },
  // ...
});

Upwards Button

Joystick Handlers

Handlers are modular units that define where and how a log should be delivered,
whether to a console, file, or third-party service.

They let a single logger send logs to multiple destinations simultaneously, managed smoothly behind the scenes.

๐Ÿ’ก Handlers receive the raw log payload and process it asynchronously, enabling efficient, non-blocking log delivery.

You can add or remove handlers dynamically at runtime::

logger.addHandler(handler, id?, position?); // Adds a handler, returns the assigned ID
logger.removeHandler(id); // Removes the handler by ID

Creating Custom Handlers with BaseHandler

You can create custom handlers from scratch by implementing your own handle() method,
or extend Logryโ€™s built-in BaseHandler to simplify the process.

๐Ÿงฑ BaseHandler provides core functionalities such as payload normalization, formatting, and JSON serialization,
plus a safe execution flow, so you only need to focus on implementing the actual log delivery logic.

The key method to implement is:

abstract handle(rawPayload: RawPayload): Promise<void>;

Here are some useful protected methods you can use inside your custom handler:

Method Signature Description
normalize (rawPayload: RawPayload) => NormalizedPayload Normalize the raw log payload into a consistent format.
format (normalized: NormalizedPayload) => FormattedPayload Format the normalized payload into a human-readable or styled format.
toJson (rawPayload: RawPayload, options?: { useNormalizer?: boolean; space?: number }) => string Convert the raw payload into a JSON string, optionally normalized and pretty-printed.

Example implementation:

import { NodeHandler } from "logry"; // ๐Ÿ“ฆ Use built-in handler classes from the "logry/handlers" module.

class MyCustomHandler extends BaseHandler {
  async handle(rawPayload: RawPayload) {
    const normalized = this.normalize(rawPayload);
    const formatted = this.format(normalized);
    const message = `${formatted.level} | ${formatted.message}`;
    // Or for JSON output: const message = this.toJson(rawPayload)
    await sendToExternalService(message);
  }
}

logger.addHandler(new MyCustomHandler()); // Register the custom handler

Platform-Specific Handlers

For more advanced scenarios, you can extend platform-specific base classes such as:

  • NodeHandler
  • BrowserHandler
  • EdgeHandler

These classes build upon BaseHandler, and additionally expose a platform-optimized compose() method that helps you generate the final log message string based on your formatter config and platform constraints.

Example implementation:

import { NodeHandler } from "logry"; // ๐Ÿ“ฆ Use built-in base handlers from the "logry/handlers" module.

class MyCustomHandler extends NodeHandler {
  async handle(rawPayload: RawPayload) {
    const message = await this.compose(payload); // Async only in Node.js to append pid and hostname
    await sendToExternalService(message);
  }
}

This makes it easy to build reliable and composable handlers,
whether you write files, send to remote servers, or push logs to cloud ingestion pipelines โ˜๏ธ

Upwards Button


Hammer and Wrench Devtools

Logry includes small tools to help you debug and inspect logger internals.

inspectLoggerCores()

List all registered LoggerCore instances.

import { inspectLoggerCores } from "logry/devtools";

inspectLoggerCores();

Helps you verify how loggers are created and linked.

inspectHandlerManagerConfig(logger)

Show the resolved handler config for a given logger.

import { inspectHandlerManagerConfig } from "logry/devtools";

inspectHandlerManagerConfig(myLogger);

Good for checking which rules and tasks are active.

Upwards Button


Construction Development Mode Detection

This function detects whether the runtime is in development mode.
It is primarily used to control internal logging and error reporting within the library, such as internal-log and internal-error messages.

  • In Node.js, it checks the NODE_ENV environment variable:

    • Returns true if NODE_ENV is not set to 'production'.
    • Defaults to true (development) if NODE_ENV is undefined. x
  • In Browsers, it checks the global flag __LOGRY_DEV__:

    • Returns true if the flag is truthy.
    • Defaults to false (production) if undefined.

This setup assumes Node defaults to development mode for easier local testing, while browsers default to production to avoid unnecessary debug logs.

Upwards Button