Skip to main content
The @ooneex/logger component is a multi-backend logging layer. Every logger implements the same ILogger interface — init, error, warn, info, debug, log, success — so you can write to the terminal in development and ship to a database in production without changing a single call site. Each method takes a message, an optional structured data object, and per-call display options, and error additionally accepts an IException to log its name, status, and stack trace.

Why this component

  • One interface, many backends. TerminalLogger and DatabaseLogger both implement ILogger — swap backends without touching callers.
  • Six log levels. error, warn, info, debug, log, and success map to the ELogLevel enum, each with its own color and symbol in the terminal.
  • Structured data. Pass a typed data object alongside the message; backends serialize it for you.
  • Exception-aware. error() accepts a string or an IException and records its name, status, and structured stack trace.
  • Container-managed. Register a logger class with a decorator and resolve it from the container.

How it works

You pick (or implement) a backend and register it. Calls go through the ILogger methods; the backend handles formatting, serialization, and transport.
MethodPurpose
init()Set up the logger (open handles, configure transports). Returns void or Promise<void>.
error(message, data?, options?)Log an error. message may be a string or an IException.
warn(message, data?, options?)Log a warning.
info(message, data?, options?)Log informational output.
debug(message, data?, options?)Log debug detail.
log(message, data?, options?)Log a general message.
success(message, data?, options?)Log a success message.
Every method shares the same ELogLevel levels — ERROR, WARN, INFO, DEBUG, LOG, SUCCESS. The built-in backends differ in where logs go, not in how you call them:
BackendDestinationBest for
TerminalLoggerstdout / stderrLocal development and CLI output
DatabaseLoggerPostgreSQL via LogsRepositoryPersisting application and request logs
TerminalLogger accepts LoggerOptionsType per call — showArrow, showTimestamp, showLevel, and useSymbol — to control how each line is rendered. Error and ERROR-level lines go to stderr; everything else goes to stdout.

Environment variables

Only the production backends need configuration. TerminalLogger needs none.
VariableBackendRequiredPurpose
LOGS_DATABASE_URLDatabaseLoggerYesPostgreSQL connection string for the logs database.
LOGS_DATABASE_URL=postgres://user:password@localhost:5432/logs

Decorator and usage

@decorator.logger()

Registers a logger class with the container. It accepts an optional scope (defaults to EContainerScope.Singleton). All built-in loggers are decorated, and you use it to register custom loggers that implement ILogger.
import { decorator } from "@ooneex/logger";
import type { ILogger } from "@ooneex/logger";

@decorator.logger()
export class AuditLogger implements ILogger {
  // ...implement init, error, warn, info, debug, log, success
}
Resolve a logger from the container and call its level methods:
import { TerminalLogger } from "@ooneex/logger";
import { container } from "@ooneex/container";

const logger = container.get(TerminalLogger);

logger.info("User signed in", { userId: "123", email: "john@example.com" });
logger.success("Payment captured", { orderId: "ord_456", amount: 4999 });
logger.warn("Rate limit approaching", { remaining: 5 });

// Control terminal rendering per call
logger.debug("Cache miss", { key: "user:123" }, { useSymbol: true, showTimestamp: false });
error() accepts a plain message or an IException — when given an exception it records the name, status, and stack trace automatically:
import { TerminalLogger } from "@ooneex/logger";
import { container } from "@ooneex/container";

const logger = container.get(TerminalLogger);

try {
  await processPayment(order);
} catch (error) {
  // Logs the exception name, status, and structured stack trace
  logger.error(error as IException, { orderId: order.id });
}
Inject a logger where you need it:
import { inject } from "@ooneex/container";
import { TerminalLogger } from "@ooneex/logger";

export class PaymentService {
  constructor(@inject(TerminalLogger) private readonly logger: TerminalLogger) {}
}

Exceptions

The component throws LoggerException when a backend is misconfigured. It carries a machine-readable key, a human-readable message, a data object, and an InternalServerError status.
import { LoggerException } from "@ooneex/logger";

try {
  const logger = container.get(DatabaseLogger);
} catch (error) {
  if (error instanceof LoggerException) {
    console.error(`Logger error [${error.key}]: ${error.message}`, error.data);
  } else {
    throw error;
  }
}

Best practices

  • Pick the backend per environment. TerminalLogger for local/dev and DatabaseLogger for production — the call sites stay identical.
  • Pass structured data, not interpolated strings. Keep the message stable and put variable detail in the data object so logs stay searchable.
  • Match the level to the meaning. Reserve error for failures, warn for recoverable issues, success for completed operations, and debug for diagnostics.
  • Log exceptions as exceptions. Pass the IException to error() rather than its message string so the name, status, and stack trace are captured.
  • Keep data serializable. Backends serialize values; avoid class instances, functions, and circular references.
  • Throw LoggerException with a stable key. In custom backends, keep keys constant and put variable detail in data.

CLI command

Scaffold a logger class and its test file with the generator. It writes the class under modules/<module>/src/loggers/<Name>Logger.ts and installs @ooneex/logger if it is missing.
# Interactive: prompts for the name
ooneex logger:create

# Provide the name
ooneex logger:create --name=Audit

# Target a module and overwrite
ooneex logger:create --name=Audit --module=auth --override
OptionDescriptionDefault
--nameLogger class name. The Logger suffix is appended automatically.Prompted if omitted
--moduleTarget module the class is generated into.shared
--overrideOverwrite an existing class without prompting.false
The generated class starts as an ILogger stub, ready for you to implement each level method:
import type { IException } from "@ooneex/exception";
import type { ILogger } from "@ooneex/logger";
import type { ScalarType } from "@ooneex/types";
import { decorator } from "@ooneex/logger";

@decorator.logger()
export class AuditLogger implements ILogger {
  public async init(): Promise<void> {
    // Initialize your logger here
  }

  public log(message: string, data?: Record<string, ScalarType>): void {
    // Handle general logging
  }

  public debug(message: string, data?: Record<string, ScalarType>): void {
    // Handle debug logging
  }

  public info(message: string, data?: Record<string, ScalarType>): void {
    // Handle info logging
  }

  public success(message: string, data?: Record<string, ScalarType>): void {
    // Handle success logging
  }

  public warn(message: string, data?: Record<string, ScalarType>): void {
    // Handle warning logging
  }

  public error(message: string | IException, data?: Record<string, ScalarType>): void {
    // Handle error logging
  }
}
See logger:create for the full command reference.

Use with Claude and Codex

The generator ships a matching logger:create skill. It runs the scaffold and then guides your AI agent through completing the logger — implementing init, log, debug, info, success, warn, and error against a real transport and wiring up its dependencies. Initialize the skills once for your agent:
ooneex claude:init
Then ask Claude in natural language — it maps the request to the generator, runs it, and fills in the implementation:
Prompt
Create a logger that writes structured logs for the payments module.
For example, the prompt above maps to logger:create --name=Payments --module=payments, then implements the ILogger methods against your chosen transport.