Перейти к основному содержимому

TypeScript API — обзор

Пакет @dagstack/logger реализует контракт логгера для Node.js (≥ 20). Phase 1 (опубликован на npmjs.org как v0.1.x) поставляет ядро API плюс три приёмника; Phase 2 добавит OTLP-экспортёр и цепочку LogProcessor'ов.

Публичные экспорты

Barrel-реэкспорты пакета:

СимволТипМодуль
Loggerкласс@dagstack/logger (src/logger.ts)
LogRecordтип@dagstack/logger (src/records.ts)
Resourceтип@dagstack/logger (src/records.ts)
InstrumentationScopeтип@dagstack/logger (src/records.ts)
Valueтип@dagstack/logger (src/records.ts)
Severityconst-объект + типы SeverityValue / SeverityText@dagstack/logger (src/severity.ts)
Sinkинтерфейс@dagstack/logger (src/sinks/base.ts)
ConsoleSink, FileSink, InMemorySinkклассы@dagstack/logger (src/sinks/)
ConsoleMode, ConsoleSinkOptions, FileSinkOptions, InMemorySinkOptions, FlushResultтипы@dagstack/logger (src/sinks/)
Subscription, SubscriptionInitкласс + тип@dagstack/logger (src/subscription.ts)
configure, ConfigureOptionsфункция + тип@dagstack/logger (src/configuration.ts)
MASKED_PLACEHOLDER, DEFAULT_SECRET_SUFFIXES, isSecretField, maskValue, redactAttributesконстанты + функции@dagstack/logger (src/redaction.ts)
getActiveTraceContext, getBaggageAttributes, DEFAULT_BAGGAGE_KEYSфункции + константа@dagstack/logger (src/context.ts)
toDagstackJsonl, toDagstackJsonlObjectфункции@dagstack/logger (src/wire.ts)
canonicalJsonStringify, canonicalJsonStringifyBytesфункции@dagstack/logger (src/canonical-json.ts)
traceIdToHex, spanIdToHex, hexToTraceId, hexToSpanId, otelTraceIdToBytes, otelSpanIdToBytesфункции@dagstack/logger (src/trace-ids.ts)
VERSIONстроковая константа@dagstack/logger (берётся из package.json)

Импортируй нужную поверхность:

import {
Logger,
ConsoleSink,
FileSink,
InMemorySink,
Severity,
configure,
type LogRecord,
type Sink,
} from "@dagstack/logger";

configure(options?) — глобальный bootstrap

interface ConfigureOptions {
readonly rootLevel?: string | number;
readonly sinks?: readonly Sink[];
readonly perLoggerLevels?: Readonly<Record<string, string | number>>;
readonly resourceAttributes?: Readonly<Record<string, Value>>;
}

function configure(options?: ConfigureOptions): void;

Инициализирует глобальное состояние логгера. Устанавливает порог уровня root-логгера, подключает приёмники, применяет переопределения по логгерам и засевает OTel Resource атрибутами уровня процесса. Вызывай один раз при старте приложения, до того как бизнес-код вызовет Logger.get(name).

Голый вызов configure() ставит rootLevel по умолчанию в "INFO", список приёмников — пустой, ресурс сбрасывается. Уровни принимают канонические имена ("INFO", "WARN", ...) или целые числа в диапазоне [1, 24] — передача неизвестного имени бросает RangeError.

Полный walkthrough — в guide Настройка логгера.

Logger — реестр именованных логгеров

class Logger {
static get(name?: string, version?: string): Logger;

readonly name: string;
readonly version: string | undefined;

// Отправки по уровням
trace(body: Value, attributes?: Readonly<Record<string, Value>>): void;
debug(body: Value, attributes?: Readonly<Record<string, Value>>): void;
info(body: Value, attributes?: Readonly<Record<string, Value>>): void;
warn(body: Value, attributes?: Readonly<Record<string, Value>>): void;
error(body: Value, attributes?: Readonly<Record<string, Value>>): void;
fatal(body: Value, attributes?: Readonly<Record<string, Value>>): void;
log(severityNumber: number, body: Value, attributes?: Readonly<Record<string, Value>>): void;
exception(err: unknown, options?: ExceptionOptions): void;

// Мутаторы конфигурации
setSinks(sinks: readonly Sink[]): void;
setMinSeverity(severityNumber: number): void;
setResource(resource: Resource | undefined): void;

// Локальные переопределения
withSinks(sinks: readonly Sink[]): Logger;
appendSinks(extras: readonly Sink[]): Logger;
withoutSinks(): Logger;
scopeSinks<T>(
sinks: readonly Sink[],
callback: (logger: Logger) => T | Promise<T>,
): Promise<T>;
child(attributes: Readonly<Record<string, Value>>): Logger;

// Жизненный цикл
flush(timeoutMs?: number): Promise<FlushResult>;
close(): Promise<void>;

// Интроспекция
effectiveSinks(): Sink[];
effectiveMinSeverity(): number;
effectiveResource(): Resource | undefined;

// Subscription (Phase 1 — неактивна)
onReconfigure(callback: () => void): Subscription;
}

Конструируется только через Logger.get(name, version); реестр кеширует один экземпляр на имя. Дочерние логгеры, созданные child(...), withSinks(...), appendSinks(...), withoutSinks(...), — отвязанные (некешированные); они наследуют конфигурацию родителя, но в глобальном реестре не участвуют.

scopeSinks — TypeScript-идиома лексически ограниченного переопределения по спеке (ближайший аналог Python with-context-manager'а). Принимает callback, подменяет приёмники на this на время выполнения callback'а и восстанавливает их по завершении или throw.

Severity — bucket-константы

const Severity = {
TRACE: 1,
DEBUG: 5,
INFO: 9,
WARN: 13,
ERROR: 17,
FATAL: 21,
} as const;

type SeverityValue = (typeof Severity)[keyof typeof Severity];
type SeverityText = "TRACE" | "DEBUG" | "INFO" | "WARN" | "ERROR" | "FATAL";

Хелперы: severityTextFor(severityNumber), isCanonicalSeverityText(text), isValidSeverityNumber(n). Полное перечисление 1-24 — на странице Справочная таблица уровней.

Приёмники

Три конкретных приёмника плюс интерфейс Sink:

interface Sink {
readonly id: string;
emit(record: LogRecord): void;
flush(timeoutMs?: number): Promise<FlushResult>;
close(): Promise<void>;
supportsSeverity(severityNumber: number): boolean;
}

type FlushResult =
| { ok: true }
| { ok: false; partial?: boolean; failedSinks?: { sinkId: string; error: string }[] };

type ConsoleMode = "auto" | "json" | "pretty";

interface ConsoleSinkOptions {
readonly mode?: ConsoleMode;
readonly stream?: ConsoleStream; // по умолчанию process.stderr
readonly minSeverity?: number;
}

class ConsoleSink implements Sink {
constructor(options?: ConsoleSinkOptions);
}

interface FileSinkOptions {
readonly maxBytes?: number; // 0 = без ротации
readonly keep?: number; // сколько архивных файлов хранить
readonly minSeverity?: number;
}

class FileSink implements Sink {
constructor(filePath: string, options?: FileSinkOptions);
}

interface InMemorySinkOptions {
readonly capacity?: number; // по умолчанию 1000
readonly minSeverity?: number;
}

class InMemorySink implements Sink {
readonly capacity: number;
constructor(options?: InMemorySinkOptions);
records(): LogRecord[]; // снимок (копия)
clear(): void;
}

ConsoleSink пишет в process.stderr по умолчанию. mode: "auto" выбирает pretty, когда поток — TTY, и JSON-lines в остальных случаях.

FileSink открывает путь в режиме append и ротирует по размеру (path.1, path.2, ... до keep). maxBytes: 0 отключает ротацию полностью.

InMemorySink — кольцевой буфер для тестов; самая старая запись отбрасывается при превышении capacity.

См. Приёмники для использования и руководство по custom-sink для реализации протокола.

LogRecord — типизированная запись

interface LogRecord {
readonly time_unix_nano: bigint;
readonly severity_number: number;
readonly severity_text: string;
readonly body: Value;
readonly attributes: Readonly<Record<string, Value>>;
readonly instrumentation_scope?: InstrumentationScope;
readonly resource?: Resource;
readonly trace_id?: Uint8Array; // 16 байт, когда задан
readonly span_id?: Uint8Array; // 8 байт, когда задан
readonly trace_flags: number;
readonly observed_time_unix_nano?: bigint;
}

По спеке §1 набор полей совпадает с OTel Log Data Model v1.24. Snake-case сохраняется на типизированной записи (и в формате передачи dagstack JSON-lines); только мутаторы и параметры конструктора биндинга (setMinSeverity, minSeverity, maxBytes) используют camelCase. Полная таблица семантики и владения полями — на странице Поля LogRecord.

Хелперы маскирования

const MASKED_PLACEHOLDER = "***";
const DEFAULT_SECRET_SUFFIXES: readonly string[] = [
"_key", "_secret", "_token", "_password", "_passphrase", "_credentials",
];

function isSecretField(key: string, suffixes?: readonly string[]): boolean;
function maskValue(key: string, value: Value, suffixes?: readonly string[]): Value;
function redactAttributes(
attrs: Readonly<Record<string, Value>>,
suffixes?: readonly string[],
): Record<string, Value>;

Логгер автоматически применяет redactAttributes внутри emit. Самостоятельные хелперы выставлены для реализаторов приёмников и тестов, которым нужно проверять маскированную форму напрямую.

Формат передачи

function toDagstackJsonl(record: LogRecord): string;
function toDagstackJsonlObject(record: LogRecord): Record<string, JsonValue>;

toDagstackJsonl производит одну строку Canonical JSON (snake_case-ключи, hex trace id'ы, целочисленные timestamp'ы, рекурсивно отсортированные ключи). Используется внутри ConsoleSink(mode: "json") и FileSink; экспонирован для вызывающих, собирающих собственные приёмники.

Проброс контекста

const DEFAULT_BAGGAGE_KEYS: readonly string[] = ["tenant.id", "request.id", "user.id"];

interface ActiveTraceContext {
readonly traceId: Uint8Array | undefined;
readonly spanId: Uint8Array | undefined;
readonly traceFlags: number;
}

function getActiveTraceContext(): ActiveTraceContext;
function getBaggageAttributes(allowedKeys?: readonly string[]): Record<string, Value>;

Оба хелпера читают из @opentelemetry/api напрямую; биндинг не вендорит параллельную реализацию контекста. Когда SDK не зарегистрирован, активна no-op-реализация OTel, и обе функции возвращают пустые значения.

Subscription — Phase 1 неактивна

Subscription зеркалит тип Subscription из config-specpath, active, inactiveReason. В Phase 1 логгер не подписывается на runtime-изменения конфига; Logger.onReconfigure(callback) возвращает неактивную подписку с inactiveReason: "Phase 1 logger does not support watch-based reconfigure" и эмитит одноразовое предупреждение. Phase 2 включит watch-путь.

Идиоматические отличия от Python

  • Мутаторы / опции в camelCase. setMinSeverity, withSinks, minSeverity, maxBytes — соглашение TypeScript. Ключи формата передачи (severity_number, trace_id и т. д.) и поля типизированного LogRecord остаются в snake_case в соответствии со спекой.
  • Promise-async жизненный цикл. flush(), close() и scopeSinks(callback)async (возвращают Promise). Emit'ы по уровням остаются синхронными с точки зрения вызывающего; биндинг может буферизовать внутри Phase 2-приёмников.
  • scopeSinks — callback-форма. Где Python использует with logger.scope_sinks(...):, TypeScript использует await logger.scopeSinks([...], async (scoped) => { ... }). Callback получает тот же экземпляр логгера с заменёнными приёмниками на время выполнения.
  • bigint-timestamp'ы. time_unix_nano — JavaScript bigint, чтобы сохранить наносекундную точность за пределами 2^53. Форматы передачи сериализуют как сырые JSON-целые (dagstack JSON-lines через bigint-поддержку Canonical JSON) или как десятичные строки (OTel JSON, Phase 2).
  • Severity — const-объект, а не enum. Tree-shaking-friendly и совпадает с современными соглашениями TypeScript; паттерн доступа Severity.INFO в месте вызова работает по-прежнему.

См. также