alepha@docs:~/docs/guides/core$
cat 1-logging.md
2 min read
Last commit:

#Logging

Alepha provides structured logging via the $logger primitive from alepha/logger.

#Basic Usage

typescript
 1import { $logger } from "alepha/logger"; 2  3class UserService { 4  log = $logger(); 5  6  async createUser(name: string) { 7    this.log.info("Creating user", { name }); 8    // prints: [23:45:53.326] INFO <app.UserService>: Creating user {"name":"alice"} 9  }10}

$logger() returns a Logger instance. The logger name defaults to the class name. The module defaults to "app" (or the module name if the service belongs to a $module).

You can override the name:

typescript
1class App {2  log = $logger({ name: "Bootstrap" });3}

You can add "appName" to all log entries by setting the APP_NAME environment variable:

bash
APP_NAME=my-app

This is useful for identifying logs from different applications in a shared logging system.

#Log Levels

The LoggerInterface exposes five methods:

typescript
1export interface LoggerInterface {2  trace(message: string, data?: unknown): void;3  debug(message: string, data?: unknown): void;4  info(message: string, data?: unknown): void;5  warn(message: string, data?: unknown): void;6  error(message: string, data?: unknown): void;7}

Severity order from lowest to highest: TRACE, DEBUG, INFO, WARN, ERROR, SILENT.

A log call is only written to the destination if its level is at or above the configured threshold. SILENT suppresses all output.

#Configuration

#LOG_LEVEL

Set via the LOG_LEVEL environment variable. Case-insensitive.

bash
# Global level
LOG_LEVEL=debug

# Per-module level with global fallback
LOG_LEVEL=alepha.core:trace,info

# Multiple module overrides
LOG_LEVEL=alepha.core:trace,alepha.server:debug,my.app:error,info

The syntax is module_prefix:level pairs separated by commas or semicolons, with an optional global level at the end. Module matching uses prefix matching: alepha matches alepha.core, alepha.server, etc.

Wildcard patterns are supported:

bash
LOG_LEVEL=alepha.*:debug,*.test:silent,info

Defaults by environment:

  • dev: info
  • prod: info
  • test: trace (but logs go to memory, only printed on test failure)

#LOG_FORMAT

Set via the LOG_FORMAT environment variable.

Value Description Provider
pretty Colored, human-readable output with timestamps PrettyFormatterProvider
json Structured JSON, one object per line JsonFormatterProvider
raw Plain message text, no metadata RawFormatterProvider

If LOG_FORMAT is not set:

  • Production (non-browser): defaults to json
  • Everything else: defaults to pretty

#Log Entry Structure

Every log call produces a LogEntry:

typescript
 1interface LogEntry { 2  level: "SILENT" | "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR"; 3  message: string; 4  service: string;   // class name, e.g. "UserService" 5  module: string;    // module name, e.g. "app" or "my.project.users" 6  context?: string;  // request-scoped correlation ID (from AsyncLocalStorage) 7  app?: string;      // APP_NAME env variable 8  data?: unknown;    // arbitrary payload or Error object 9  timestamp: number; // milliseconds since epoch10}

#Log Events

Every log call emits a "log" event on the Alepha event system, regardless of whether the message was above the configured threshold. This allows external listeners to capture all log activity:

typescript
1alepha.events.on("log", (event) => {2  // event.message  - formatted string (or undefined if below threshold)3  // event.entry    - the raw LogEntry4});

#Testing

In test mode, Alepha routes logs to MemoryDestinationProvider by default. Logs are buffered in memory and only printed to the console if a test fails.

To capture and assert on logs in tests:

typescript
 1import { Alepha } from "alepha"; 2import { $logger, LogDestinationProvider, MemoryDestinationProvider } from "alepha/logger"; 3  4class App { 5  log = $logger(); 6} 7  8test("should log info message", ({ expect }) => { 9  const alepha = Alepha.create({10    env: { LOG_LEVEL: "trace" },11  }).with({12    provide: LogDestinationProvider,13    use: MemoryDestinationProvider,14  });15 16  const output = alepha.inject(MemoryDestinationProvider);17  const app = alepha.inject(App);18 19  app.log.info("Test log message");20 21  expect(output.logs[0].message).toBe("Test log message");22  expect(output.logs[0].level).toBe("INFO");23  expect(output.logs[0].service).toBe("App");24});

#Custom Destination

Replace the log destination by substituting LogDestinationProvider:

typescript
 1import { LogDestinationProvider } from "alepha/logger"; 2import type { LogEntry } from "alepha/logger"; 3  4class MyDestination extends LogDestinationProvider { 5  write(message: string, entry: LogEntry): void { 6    // send to external service, write to file, etc. 7  } 8} 9 10const alepha = Alepha.create().with({11  provide: LogDestinationProvider,12  use: MyDestination,13});

#Custom Formatter

Replace the log formatter by substituting LogFormatterProvider:

typescript
 1import { LogFormatterProvider } from "alepha/logger"; 2import type { LogEntry } from "alepha/logger"; 3  4class MyFormatter extends LogFormatterProvider { 5  format(entry: LogEntry): string { 6    return `[${entry.level}] ${entry.module}.${entry.service}: ${entry.message}`; 7  } 8} 9 10const alepha = Alepha.create().with({11  provide: LogFormatterProvider,12  use: MyFormatter,13});