@probitas/logger

Unified logging interface for Probitas packages.

This package provides a centralized logging system built on LogTape. It offers consistent logging across all Probitas packages with configurable log levels, pretty formatting, and hierarchical categories.

All Probitas packages use this logger internally: @probitas/runner, @probitas/reporter, @probitas/discover, @probitas/cli

Core Functions

Types

  • Logger - Logger interface from LogTape
  • LogLevel - Available log levels

Log Levels

From least to most verbose:

  • "fatal" - Critical errors that stop execution
  • "error" - Errors that don't stop execution
  • "warning" - Potential issues (default level)
  • "info" - Informational messages about execution
  • "debug" - Detailed debugging information

Category Hierarchy

Loggers use hierarchical categories following the package structure:

  • ["probitas"] - Root category for all Probitas logs
  • ["probitas", "runner"] - Runner package logs
  • ["probitas", "cli", "run"] - Specific command logs

Installation

deno add jsr:@probitas/logger

Interfaces

interface

#Logger

interface Logger

A logger interface. It provides methods to log messages at different severity levels.

const logger = getLogger("category");
logger.trace `A trace message with ${value}`
logger.debug `A debug message with ${value}.`;
logger.info `An info message with ${value}.`;
logger.warn `A warning message with ${value}.`;
logger.error `An error message with ${value}.`;
logger.fatal `A fatal error message with ${value}.`;
NameDescription
categoryThe category of the logger.
parentThe logger with the supercategory of the current logger.
getChild()Get a child logger with the given subcategory.
with()Get a logger with contextual properties.
trace()Log a trace message.
trace()Log a trace message with properties.
trace()Log a trace values with no message.
trace()Lazily log a trace message.
debug()Log a debug message.
debug()Log a debug message with properties.
debug()Log a debug values with no message.
debug()Lazily log a debug message.
info()Log an informational message.
info()Log an informational message with properties.
info()Log an informational values with no message.
info()Lazily log an informational message.
warn()Log a warning message.
warn()Log a warning message with properties.
warn()Log a warning values with no message.
warn()Lazily log a warning message.
warning()Log a warning message.
warning()Log a warning message with properties.
warning()Log a warning values with no message.
warning()Lazily log a warning message.
error()Log an error message.
error()Log an error message with properties.
error()Log an error values with no message.
error()Lazily log an error message.
fatal()Log a fatal error message.
fatal()Log a fatal error message with properties.
fatal()Log a fatal error values with no message.
fatal()Lazily log a fatal error message.
emit()Emits a log record with custom fields while using this logger's category.
Properties
  • readonlycategoryreadonly string[]

    The category of the logger. It is an array of strings.

  • readonlyparentLogger | null

    The logger with the supercategory of the current logger. If the current logger is the root logger, this is null.

Methods
getChild(subcategory: string | readonly [string] | readonly [string, unknown]): Logger

Get a child logger with the given subcategory.

const logger = getLogger("category");
const subLogger = logger.getChild("sub-category");

The above code is equivalent to:

const logger = getLogger("category");
const subLogger = getLogger(["category", "sub-category"]);
Parameters
  • subcategorystring | readonly [string] | readonly [string, unknown]

    The subcategory.

with(properties: Record<string, unknown>): Logger

Get a logger with contextual properties. This is useful for log multiple messages with the shared set of properties.

const logger = getLogger("category");
const ctx = logger.with({ foo: 123, bar: "abc" });
ctx.info("A message with {foo} and {bar}.");
ctx.warn("Another message with {foo}, {bar}, and {baz}.", { baz: true });

The above code is equivalent to:

const logger = getLogger("category");
logger.info("A message with {foo} and {bar}.", { foo: 123, bar: "abc" });
logger.warn(
  "Another message with {foo}, {bar}, and {baz}.",
  { foo: 123, bar: "abc", baz: true },
);
Parameters
  • propertiesRecord<string, unknown>
trace(message: TemplateStringsArray, _: readonly unknown[]): void

Log a trace message. Use this as a template string prefix.

logger.trace `A trace message with ${value}.`;
Parameters
  • messageTemplateStringsArray

    The message template strings array.

  • _readonly unknown[]
trace(message: string, properties?: Record<string, unknown> | unknown): void

Log a trace message with properties.

logger.trace('A trace message with {value}.', { value });

If the properties are expensive to compute, you can pass a callback that returns the properties:

logger.trace(
  'A trace message with {value}.',
  () => ({ value: expensiveComputation() })
);
Parameters
  • messagestring

    The message template. Placeholders to be replaced with values are indicated by keys in curly braces (e.g., {value}).

  • properties?Record<string, unknown> | unknown

    The values to replace placeholders with. For lazy evaluation, this can be a callback that returns the properties.

trace(properties: Record<string, unknown>): void

Log a trace values with no message. This is useful when you want to log properties without a message, e.g., when you want to log the context of a request or an operation.

logger.trace({ method: 'GET', url: '/api/v1/resource' });

Note that this is a shorthand for:

logger.trace('{*}', { method: 'GET', url: '/api/v1/resource' });

If the properties are expensive to compute, you cannot use this shorthand and should use the following syntax instead:

logger.trace('{*}', () => ({
  method: expensiveMethod(),
  url: expensiveUrl(),
}));
Parameters
  • propertiesRecord<string, unknown>

    The values to log. Note that this does not take a callback.

trace(callback: LogCallback): void

Lazily log a trace message. Use this when the message values are expensive to compute and should only be computed if the message is actually logged.

logger.trace(l => l`A trace message with ${expensiveValue()}.`);
Parameters
  • callbackLogCallback

    A callback that returns the message template prefix.

debug(message: TemplateStringsArray, _: readonly unknown[]): void

Log a debug message. Use this as a template string prefix.

logger.debug `A debug message with ${value}.`;
Parameters
  • messageTemplateStringsArray

    The message template strings array.

  • _readonly unknown[]
debug(message: string, properties?: Record<string, unknown> | unknown): void

Log a debug message with properties.

logger.debug('A debug message with {value}.', { value });

If the properties are expensive to compute, you can pass a callback that returns the properties:

logger.debug(
  'A debug message with {value}.',
  () => ({ value: expensiveComputation() })
);
Parameters
  • messagestring

    The message template. Placeholders to be replaced with values are indicated by keys in curly braces (e.g., {value}).

  • properties?Record<string, unknown> | unknown

    The values to replace placeholders with. For lazy evaluation, this can be a callback that returns the properties.

debug(properties: Record<string, unknown>): void

Log a debug values with no message. This is useful when you want to log properties without a message, e.g., when you want to log the context of a request or an operation.

logger.debug({ method: 'GET', url: '/api/v1/resource' });

Note that this is a shorthand for:

logger.debug('{*}', { method: 'GET', url: '/api/v1/resource' });

If the properties are expensive to compute, you cannot use this shorthand and should use the following syntax instead:

logger.debug('{*}', () => ({
  method: expensiveMethod(),
  url: expensiveUrl(),
}));
Parameters
  • propertiesRecord<string, unknown>

    The values to log. Note that this does not take a callback.

debug(callback: LogCallback): void

Lazily log a debug message. Use this when the message values are expensive to compute and should only be computed if the message is actually logged.

logger.debug(l => l`A debug message with ${expensiveValue()}.`);
Parameters
  • callbackLogCallback

    A callback that returns the message template prefix.

info(message: TemplateStringsArray, _: readonly unknown[]): void

Log an informational message. Use this as a template string prefix.

logger.info `An info message with ${value}.`;
Parameters
  • messageTemplateStringsArray

    The message template strings array.

  • _readonly unknown[]
info(message: string, properties?: Record<string, unknown> | unknown): void

Log an informational message with properties.

logger.info('An info message with {value}.', { value });

If the properties are expensive to compute, you can pass a callback that returns the properties:

logger.info(
  'An info message with {value}.',
  () => ({ value: expensiveComputation() })
);
Parameters
  • messagestring

    The message template. Placeholders to be replaced with values are indicated by keys in curly braces (e.g., {value}).

  • properties?Record<string, unknown> | unknown

    The values to replace placeholders with. For lazy evaluation, this can be a callback that returns the properties.

info(properties: Record<string, unknown>): void

Log an informational values with no message. This is useful when you want to log properties without a message, e.g., when you want to log the context of a request or an operation.

logger.info({ method: 'GET', url: '/api/v1/resource' });

Note that this is a shorthand for:

logger.info('{*}', { method: 'GET', url: '/api/v1/resource' });

If the properties are expensive to compute, you cannot use this shorthand and should use the following syntax instead:

logger.info('{*}', () => ({
  method: expensiveMethod(),
  url: expensiveUrl(),
}));
Parameters
  • propertiesRecord<string, unknown>

    The values to log. Note that this does not take a callback.

info(callback: LogCallback): void

Lazily log an informational message. Use this when the message values are expensive to compute and should only be computed if the message is actually logged.

logger.info(l => l`An info message with ${expensiveValue()}.`);
Parameters
  • callbackLogCallback

    A callback that returns the message template prefix.

warn(message: TemplateStringsArray, _: readonly unknown[]): void

Log a warning message. Use this as a template string prefix.

logger.warn `A warning message with ${value}.`;
Parameters
  • messageTemplateStringsArray

    The message template strings array.

  • _readonly unknown[]
warn(message: string, properties?: Record<string, unknown> | unknown): void

Log a warning message with properties.

logger.warn('A warning message with {value}.', { value });

If the properties are expensive to compute, you can pass a callback that returns the properties:

logger.warn(
  'A warning message with {value}.',
  () => ({ value: expensiveComputation() })
);
Parameters
  • messagestring

    The message template. Placeholders to be replaced with values are indicated by keys in curly braces (e.g., {value}).

  • properties?Record<string, unknown> | unknown

    The values to replace placeholders with. For lazy evaluation, this can be a callback that returns the properties.

warn(properties: Record<string, unknown>): void

Log a warning values with no message. This is useful when you want to log properties without a message, e.g., when you want to log the context of a request or an operation.

logger.warn({ method: 'GET', url: '/api/v1/resource' });

Note that this is a shorthand for:

logger.warn('{*}', { method: 'GET', url: '/api/v1/resource' });

If the properties are expensive to compute, you cannot use this shorthand and should use the following syntax instead:

logger.warn('{*}', () => ({
  method: expensiveMethod(),
  url: expensiveUrl(),
}));
Parameters
  • propertiesRecord<string, unknown>

    The values to log. Note that this does not take a callback.

warn(callback: LogCallback): void

Lazily log a warning message. Use this when the message values are expensive to compute and should only be computed if the message is actually logged.

logger.warn(l => l`A warning message with ${expensiveValue()}.`);
Parameters
  • callbackLogCallback

    A callback that returns the message template prefix.

warning(message: TemplateStringsArray, _: readonly unknown[]): void

Log a warning message. Use this as a template string prefix.

logger.warning `A warning message with ${value}.`;
Parameters
  • messageTemplateStringsArray

    The message template strings array.

  • _readonly unknown[]
warning(message: string, properties?: Record<string, unknown> | unknown): void

Log a warning message with properties.

logger.warning('A warning message with {value}.', { value });

If the properties are expensive to compute, you can pass a callback that returns the properties:

logger.warning(
  'A warning message with {value}.',
  () => ({ value: expensiveComputation() })
);
Parameters
  • messagestring

    The message template. Placeholders to be replaced with values are indicated by keys in curly braces (e.g., {value}).

  • properties?Record<string, unknown> | unknown

    The values to replace placeholders with. For lazy evaluation, this can be a callback that returns the properties.

warning(properties: Record<string, unknown>): void

Log a warning values with no message. This is useful when you want to log properties without a message, e.g., when you want to log the context of a request or an operation.

logger.warning({ method: 'GET', url: '/api/v1/resource' });

Note that this is a shorthand for:

logger.warning('{*}', { method: 'GET', url: '/api/v1/resource' });

If the properties are expensive to compute, you cannot use this shorthand and should use the following syntax instead:

logger.warning('{*}', () => ({
  method: expensiveMethod(),
  url: expensiveUrl(),
}));
Parameters
  • propertiesRecord<string, unknown>

    The values to log. Note that this does not take a callback.

warning(callback: LogCallback): void

Lazily log a warning message. Use this when the message values are expensive to compute and should only be computed if the message is actually logged.

logger.warning(l => l`A warning message with ${expensiveValue()}.`);
Parameters
  • callbackLogCallback

    A callback that returns the message template prefix.

error(message: TemplateStringsArray, _: readonly unknown[]): void

Log an error message. Use this as a template string prefix.

logger.error `An error message with ${value}.`;
Parameters
  • messageTemplateStringsArray

    The message template strings array.

  • _readonly unknown[]
error(message: string, properties?: Record<string, unknown> | unknown): void

Log an error message with properties.

logger.warn('An error message with {value}.', { value });

If the properties are expensive to compute, you can pass a callback that returns the properties:

logger.error(
  'An error message with {value}.',
  () => ({ value: expensiveComputation() })
);
Parameters
  • messagestring

    The message template. Placeholders to be replaced with values are indicated by keys in curly braces (e.g., {value}).

  • properties?Record<string, unknown> | unknown

    The values to replace placeholders with. For lazy evaluation, this can be a callback that returns the properties.

error(properties: Record<string, unknown>): void

Log an error values with no message. This is useful when you want to log properties without a message, e.g., when you want to log the context of a request or an operation.

logger.error({ method: 'GET', url: '/api/v1/resource' });

Note that this is a shorthand for:

logger.error('{*}', { method: 'GET', url: '/api/v1/resource' });

If the properties are expensive to compute, you cannot use this shorthand and should use the following syntax instead:

logger.error('{*}', () => ({
  method: expensiveMethod(),
  url: expensiveUrl(),
}));
Parameters
  • propertiesRecord<string, unknown>

    The values to log. Note that this does not take a callback.

error(callback: LogCallback): void

Lazily log an error message. Use this when the message values are expensive to compute and should only be computed if the message is actually logged.

logger.error(l => l`An error message with ${expensiveValue()}.`);
Parameters
  • callbackLogCallback

    A callback that returns the message template prefix.

fatal(message: TemplateStringsArray, _: readonly unknown[]): void

Log a fatal error message. Use this as a template string prefix.

logger.fatal `A fatal error message with ${value}.`;
Parameters
  • messageTemplateStringsArray

    The message template strings array.

  • _readonly unknown[]
fatal(message: string, properties?: Record<string, unknown> | unknown): void

Log a fatal error message with properties.

logger.warn('A fatal error message with {value}.', { value });

If the properties are expensive to compute, you can pass a callback that returns the properties:

logger.fatal(
  'A fatal error message with {value}.',
  () => ({ value: expensiveComputation() })
);
Parameters
  • messagestring

    The message template. Placeholders to be replaced with values are indicated by keys in curly braces (e.g., {value}).

  • properties?Record<string, unknown> | unknown

    The values to replace placeholders with. For lazy evaluation, this can be a callback that returns the properties.

fatal(properties: Record<string, unknown>): void

Log a fatal error values with no message. This is useful when you want to log properties without a message, e.g., when you want to log the context of a request or an operation.

logger.fatal({ method: 'GET', url: '/api/v1/resource' });

Note that this is a shorthand for:

logger.fatal('{*}', { method: 'GET', url: '/api/v1/resource' });

If the properties are expensive to compute, you cannot use this shorthand and should use the following syntax instead:

logger.fatal('{*}', () => ({
  method: expensiveMethod(),
  url: expensiveUrl(),
}));
Parameters
  • propertiesRecord<string, unknown>

    The values to log. Note that this does not take a callback.

fatal(callback: LogCallback): void

Lazily log a fatal error message. Use this when the message values are expensive to compute and should only be computed if the message is actually logged.

logger.fatal(l => l`A fatal error message with ${expensiveValue()}.`);
Parameters
  • callbackLogCallback

    A callback that returns the message template prefix.

emit(record: Omit<LogRecord, "category">): void

Emits a log record with custom fields while using this logger's category.

This is a low-level API for integration scenarios where you need full control over the log record, particularly for preserving timestamps from external systems.

const logger = getLogger(["my-app", "integration"]);

// Emit a log with a custom timestamp
logger.emit({
  timestamp: kafkaLog.originalTimestamp,
  level: "info",
  message: [kafkaLog.message],
  rawMessage: kafkaLog.message,
  properties: {
    source: "kafka",
    partition: kafkaLog.partition,
    offset: kafkaLog.offset,
  },
});
Parameters
  • recordOmit<LogRecord, "category">

    Log record without category field (category comes from the logger instance)

Functions

function

#configureLogging

async function configureLogging(_: unknown): Promise<void>

Configure the logging system with the specified log level.

Initializes LogTape with pretty-printed console output. Can be called multiple times to change the log level during execution.

Parameters
  • _unknown
Examples

Basic configuration

import { configureLogging } from "@probitas/logger";

// Enable debug logging for troubleshooting
await configureLogging("debug");

In test setup

// Suppress logs during tests
await configureLogging("fatal");

// ... run tests ...

await resetLogging();
function

#getLogger

function getLogger(_: string[]): Logger

Get a logger instance for the specified category.

Categories follow a hierarchical structure matching the package organization. Child loggers inherit configuration from their parents.

Parameters
  • _string[]
Returns

Logger — Logger instance with `debug`, `info`, `warning`, `error`, `fatal` methods

Examples

Basic usage

import { getLogger } from "@probitas/logger";

const logger = getLogger("probitas", "cli", "run");

logger.info("Starting run command");
logger.debug("Processing options", { maxConcurrency: 4 });
logger.error("Failed to load scenario", { file, error });

In a module

// At module top level
const logger = getLogger("probitas", "runner");

export function runScenario(scenario: ScenarioDefinition) {
  logger.info("Running scenario", { name: scenario.name });
  // ...
}
function

#resetLogging

async function resetLogging(): Promise<void>

Reset the logging configuration to its initial state.

Clears all configured sinks and loggers. Primarily used in tests to ensure clean state between test cases.

Examples

Test cleanup

import { configureLogging, resetLogging } from "@probitas/logger";

Deno.test("my test", async () => {
  await configureLogging("fatal");  // Suppress logs during test

  // ... test code ...

  await resetLogging();  // Clean up
});

Type Aliases

type

#LogLevel

type LogLevel = unknown

The severity level of a LogRecord.

Search Documentation