autotel
Version:
Write Once, Observe Anywhere
172 lines (168 loc) • 5.46 kB
text/typescript
import { ReadableSpan, SpanProcessor } from '@opentelemetry/sdk-trace-base';
export { BatchSpanProcessor, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
import { AttributeValue } from '@opentelemetry/api';
import { Logger } from './logger.cjs';
import 'pino';
/**
* Canonical Log Line Processor
*
* Automatically emits spans as canonical log lines (wide events) when they end.
* Implements canonical log line" pattern: one comprehensive
* event per request with all context.
*
* When a span ends, this processor creates a log record with ALL span attributes,
* making the span itself the canonical log line that can be queried like structured data.
*
* @example
* ```typescript
* import { init } from 'autotel';
*
* init({
* service: 'my-app',
* canonicalLogLines: {
* enabled: true,
* rootSpansOnly: true, // One canonical log line per request
* },
* });
* ```
*/
/**
* Function to redact sensitive attribute values
*/
type AttributeRedactorFn = (key: string, value: AttributeValue) => AttributeValue;
interface CanonicalLogLineEvent {
span: ReadableSpan;
level: 'debug' | 'info' | 'warn' | 'error';
message: string;
event: Record<string, unknown>;
}
interface KeepCondition {
/** Keep events where HTTP status >= this value. */
status?: number;
/** Keep events where duration_ms >= this value. */
durationMs?: number;
/** Keep events matching this path pattern (simple prefix match). */
path?: string;
}
interface CanonicalLogLineOptions {
/** Logger to use for emitting canonical log lines (defaults to OTel Logs API) */
logger?: Logger;
/** Only emit canonical log lines for root spans (default: false) */
rootSpansOnly?: boolean;
/** Minimum log level for canonical log lines (default: 'info') */
minLevel?: 'debug' | 'info' | 'warn' | 'error';
/** Custom message format (default: uses span name) */
messageFormat?: (span: ReadableSpan) => string;
/** Whether to include resource attributes (default: true) */
includeResourceAttributes?: boolean;
/**
* Attribute redactor function to apply before logging.
* This ensures sensitive data is redacted in canonical log lines,
* matching the behavior of attributeRedactor in init().
*/
attributeRedactor?: AttributeRedactorFn;
/** Predicate to decide whether to emit (runs after event is built). */
shouldEmit?: (ctx: CanonicalLogLineEvent) => boolean;
/**
* Declarative tail sampling conditions (OR logic). If any condition matches,
* the event is kept. Ignored when `shouldEmit` is provided.
*
* @example
* keep: [{ status: 500 }, { durationMs: 1000 }]
*/
keep?: KeepCondition[];
/** Callback invoked after emit for custom fan-out. */
drain?: (ctx: CanonicalLogLineEvent) => void | Promise<void>;
/** Handler for drain failures. */
onDrainError?: (error: unknown, ctx: CanonicalLogLineEvent) => void;
/**
* Pretty-print canonical log lines to console in a tree format.
* Defaults to true when NODE_ENV is 'development'.
*/
pretty?: boolean;
}
/**
* Span processor that automatically emits spans as canonical log lines
*
* When a span ends, this processor creates a log record with ALL span attributes.
* This implements the "canonical log line" pattern: one comprehensive event
* per request with all context, queryable as structured data.
*
* **Key Benefits:**
* - One log line per request with all context (wide event)
* - High-cardinality, high-dimensionality data for powerful queries
* - Automatic - no manual logging needed
* - Works with any logger or OTel Logs API
*
* @example Basic usage
* ```typescript
* import { init } from 'autotel';
*
* init({
* service: 'checkout-api',
* canonicalLogLines: {
* enabled: true,
* rootSpansOnly: true, // One canonical log line per request
* },
* });
* ```
*
* @example With custom logger
* ```typescript
* import pino from 'pino';
* import { init } from 'autotel';
*
* const logger = pino();
* init({
* service: 'my-app',
* logger,
* canonicalLogLines: {
* enabled: true,
* logger, // Use Pino for canonical log lines
* rootSpansOnly: true,
* },
* });
* ```
*
* @example Custom message format
* ```typescript
* init({
* service: 'my-app',
* canonicalLogLines: {
* enabled: true,
* messageFormat: (span) => {
* const status = span.status.code === 2 ? 'ERROR' : 'SUCCESS';
* return `${span.name} [${status}]`;
* },
* },
* });
* ```
*/
declare class CanonicalLogLineProcessor implements SpanProcessor {
private logger?;
private rootSpansOnly;
private minLevel;
private messageFormat;
private includeResourceAttributes;
private attributeRedactor?;
private shouldEmit?;
private drain?;
private onDrainError?;
private pretty;
private getOTelLogger;
constructor(options?: CanonicalLogLineOptions);
private buildKeepPredicate;
onStart(): void;
onEnd(span: ReadableSpan): void;
private buildCanonicalLogLine;
private redactAttributes;
private emitViaLogger;
private emitViaOTel;
private getLogLevel;
private shouldLog;
private getSeverityNumber;
private reportInternalWarning;
forceFlush(): Promise<void>;
shutdown(): Promise<void>;
}
export { type CanonicalLogLineOptions, CanonicalLogLineProcessor };