{"version":3,"file":"evlog.mjs","names":[],"sources":["../../src/logger/evlog.ts"],"sourcesContent":["import type { Attributes } from '@opentelemetry/api';\nimport {\n  context,\n  trace,\n  isSpanContextValid,\n} from '@opentelemetry/api';\nimport type {\n  AuditFields,\n  DrainContext,\n  EnrichContext,\n  WideEvent,\n} from 'evlog';\nimport { auditOnly } from 'evlog';\nimport { OTelLogger, type OTelLogEmitOptions } from './otelLogger.ts';\nimport type { ResolvedSDKConfig } from '../sdk/types.ts';\nimport { resolveSDKConfig } from '../sdk/config.ts';\nimport { getActiveSDKConfig } from '../sdk/state.ts';\nimport { getInitializedOTelLogger } from '../sdk/initSDK.ts';\n\nconst LEVEL_SEVERITY: Record<\n  string,\n  { number: number; text: string }\n> = {\n  debug: { number: 5, text: 'DEBUG' },\n  info: { number: 9, text: 'INFO' },\n  warn: { number: 13, text: 'WARN' },\n  error: { number: 17, text: 'ERROR' },\n};\n\nconst FLATTEN_MAX_DEPTH = 10;\n\nexport type EvlogDrainOptions = {\n  config?: ResolvedSDKConfig;\n  resourceAttributes?: Attributes;\n  otelLogger?: OTelLogger;\n  /** OTLP instrumentation scope name. */\n  scope?: string;\n  /** Emit audit-shaped records (severity AUDIT, promoted audit.* attributes). */\n  auditMode?: boolean;\n};\n\nexport type ParsedEvlogEmit = {\n  levelLabel: string;\n  body: string;\n  attributes: Attributes;\n  emitOpts: OTelLogEmitOptions;\n};\n\nlet fallbackOTelLogger: OTelLogger | undefined;\n\nfunction resolveOTelLogger(options?: EvlogDrainOptions): OTelLogger {\n  if (options?.otelLogger) {\n    return options.otelLogger;\n  }\n  const initialized = getInitializedOTelLogger();\n  if (initialized) {\n    return initialized;\n  }\n  if (!fallbackOTelLogger) {\n    const config =\n      options?.config ?? getActiveSDKConfig() ?? resolveSDKConfig();\n    fallbackOTelLogger = new OTelLogger({\n      config,\n      resourceAttributes: options?.resourceAttributes,\n    });\n  }\n  return fallbackOTelLogger;\n}\n\nfunction appendFlatten(\n  value: unknown,\n  prefix: string,\n  out: Attributes,\n  depth: number,\n): void {\n  if (depth > FLATTEN_MAX_DEPTH) {\n    out[prefix] =\n      typeof value === 'string' ? value : JSON.stringify(value);\n    return;\n  }\n  if (value === undefined || value === null) {\n    return;\n  }\n  const t = typeof value;\n  if (t === 'string' || t === 'number' || t === 'boolean') {\n    out[prefix] = value as string | number | boolean;\n    return;\n  }\n  if (Array.isArray(value)) {\n    out[prefix] = JSON.stringify(value);\n    return;\n  }\n  if (t === 'object') {\n    for (const [k, v] of Object.entries(value as Record<string, unknown>)) {\n      appendFlatten(v, `${prefix}.${k}`, out, depth + 1);\n    }\n  }\n}\n\nfunction durationToMs(duration: unknown): number | undefined {\n  if (duration == null) {\n    return undefined;\n  }\n  if (typeof duration === 'number' && Number.isFinite(duration)) {\n    return duration;\n  }\n  if (typeof duration === 'string') {\n    const s = duration.trim();\n    const msMatch = s.match(/^([\\d.]+)\\s*ms$/i);\n    if (msMatch) {\n      return Math.round(Number.parseFloat(msMatch[1]!));\n    }\n    const secMatch = s.match(/^([\\d.]+)\\s*s$/i);\n    if (secMatch) {\n      return Math.round(Number.parseFloat(secMatch[1]!) * 1000);\n    }\n    const n = Number.parseFloat(s);\n    if (!Number.isNaN(n)) {\n      return Math.round(n);\n    }\n  }\n  return undefined;\n}\n\nfunction buildEvlogBody(event: WideEvent): string {\n  const rec = event as Record<string, unknown>;\n  const method = typeof rec.method === 'string' ? rec.method : 'EVENT';\n  const path = typeof rec.path === 'string' ? rec.path : '';\n  const status = rec.status != null ? String(rec.status) : '';\n  const duration = rec.duration != null ? String(rec.duration) : '';\n  const durPart = duration ? `in ${duration}` : '';\n  return [method, path, status, durPart].filter(Boolean).join(' ');\n}\n\nfunction promoteAuditFields(audit: AuditFields, attrs: Attributes): void {\n  attrs['audit.action'] = audit.action;\n  attrs['audit.outcome'] = audit.outcome;\n  attrs['audit.actor.type'] = audit.actor.type;\n  attrs['audit.actor.id'] = audit.actor.id;\n  if (audit.actor.email) {\n    attrs['audit.actor.email'] = audit.actor.email;\n  }\n  if (audit.actor.displayName) {\n    attrs['audit.actor.displayName'] = audit.actor.displayName;\n  }\n  if (audit.target?.type) {\n    attrs['audit.target.type'] = audit.target.type;\n  }\n  if (audit.target?.id) {\n    attrs['audit.target.id'] = audit.target.id;\n  }\n  if (audit.reason) {\n    attrs['audit.reason'] = audit.reason;\n  }\n  if (audit.idempotencyKey) {\n    attrs['audit.idempotencyKey'] = audit.idempotencyKey;\n  }\n  const ctxTrace = audit.context?.traceId;\n  if (typeof ctxTrace === 'string') {\n    attrs['audit.context.traceId'] = ctxTrace;\n  }\n  if (audit.signature) {\n    attrs['audit.signature'] = audit.signature;\n  }\n  if (audit.prevHash) {\n    attrs['audit.prevHash'] = audit.prevHash;\n  }\n  if (audit.hash) {\n    attrs['audit.hash'] = audit.hash;\n  }\n}\n\nfunction attachTraceCorrelation(event: WideEvent, attrs: Attributes): void {\n  const traceId =\n    typeof event.traceId === 'string' ? event.traceId : undefined;\n  const spanId =\n    typeof event.spanId === 'string' ? event.spanId : undefined;\n  const tc = event.traceContext as\n    | { traceFlags?: number }\n    | undefined;\n  const traceFlags =\n    typeof tc?.traceFlags === 'number' ? tc.traceFlags : 1;\n\n  if (traceId) {\n    attrs.trace_id = traceId;\n  }\n  if (spanId) {\n    attrs.span_id = spanId;\n  }\n  if (traceId && spanId) {\n    attrs.trace_flags = traceFlags;\n  }\n}\n\n/**\n * Map an evlog {@link WideEvent} to OTLP log fields for {@link OTelLogger.postMessage}.\n */\nexport function parseEvlogEvent(\n  event: WideEvent,\n  ctx?: Pick<DrainContext, 'request' | 'headers'>,\n  options?: EvlogDrainOptions & { auditMode?: boolean },\n): ParsedEvlogEmit {\n  const auditMode = options?.auditMode ?? false;\n  const levelKey =\n    typeof event.level === 'string' ? event.level : 'info';\n  const levelSeverity = LEVEL_SEVERITY[levelKey] ?? LEVEL_SEVERITY.info;\n\n  const attributes: Attributes = {};\n  const rec = event as Record<string, unknown>;\n\n  for (const [key, value] of Object.entries(rec)) {\n    appendFlatten(value, `mxl.${key}`, attributes, 0);\n  }\n\n  attributes['mxl.kind'] = auditMode ? 'audit-event' : 'wide-event';\n\n  const requestId =\n    ctx?.request?.requestId ??\n    (typeof rec.requestId === 'string' ? rec.requestId : undefined);\n  if (requestId) {\n    attributes['mxl.request_id'] = requestId;\n  }\n\n  const durationMs = durationToMs(rec.duration);\n  if (durationMs != null) {\n    attributes['mxl.duration_ms'] = durationMs;\n  }\n  if (rec.status != null) {\n    attributes['mxl.status'] = rec.status as Attributes[string];\n  }\n\n  attachTraceCorrelation(event, attributes);\n\n  let emitOpts: OTelLogEmitOptions;\n\n  if (auditMode) {\n    emitOpts = {\n      scope: options?.scope ?? 'mxl.audit',\n      severityNumber: 17,\n      severityText: 'AUDIT',\n    };\n    if (event.audit) {\n      promoteAuditFields(event.audit, attributes);\n    }\n  } else {\n    emitOpts = {\n      scope: options?.scope ?? 'mxl',\n      severityNumber: levelSeverity.number,\n      severityText: levelSeverity.text,\n    };\n  }\n\n  return {\n    levelLabel: levelKey,\n    body: buildEvlogBody(event),\n    attributes,\n    emitOpts,\n  };\n}\n\nexport class OTelEvlogBridge {\n  constructor(private readonly options?: EvlogDrainOptions) {}\n\n  emit(ctx: DrainContext): void {\n    const otel = resolveOTelLogger(this.options);\n    const parsed = parseEvlogEvent(ctx.event, ctx, {\n      ...this.options,\n      auditMode: this.options?.auditMode ?? false,\n    });\n    otel.postMessage(\n      parsed.levelLabel,\n      parsed.body,\n      parsed.attributes,\n      parsed.emitOpts,\n    );\n  }\n}\n\n/**\n * mxl drain that forwards wide events through the MXL {@link OTelLogger}\n * pipeline (same batching/export path as {@link initSDK} when initialized).\n */\nexport function createEvlogDrain(options?: EvlogDrainOptions) {\n  const bridge = new OTelEvlogBridge(options);\n  return async (ctx: DrainContext): Promise<void> => {\n    bridge.emit(ctx);\n  };\n}\n\n/**\n * Enrich mxl wide events with trace id / span id from the active OpenTelemetry span.\n */\nexport function createOtelTraceEnricher(opts: { overwrite?: boolean } = {}) {\n  return (ctx: EnrichContext): void => {\n    const span = trace.getSpan(context.active());\n    const sc = span?.spanContext();\n    if (!sc || !isSpanContextValid(sc)) {\n      return;\n    }\n    if (opts.overwrite || ctx.event.traceId == null) {\n      ctx.event.traceId = sc.traceId;\n    }\n    if (opts.overwrite || ctx.event.spanId == null) {\n      ctx.event.spanId = sc.spanId;\n    }\n    ctx.event.traceContext = {\n      ...(ctx.event.traceContext as object | undefined),\n      traceId: sc.traceId,\n      spanId: sc.spanId,\n      traceFlags: sc.traceFlags,\n    };\n  };\n}\n\n/**\n * Routes only audit-bearing wide events to OTLP under scope `mxl.audit`.\n */\nexport function createAuditOtelDrain(options?: EvlogDrainOptions) {\n  return auditOnly(\n    createEvlogDrain({\n      ...options,\n      auditMode: true,\n      scope: options?.scope ?? 'mxl.audit',\n    }),\n    { await: true },\n  );\n}\n"],"mappings":"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"}