UNPKG

@artinet/sdk

Version:

A TypeScript SDK for building collaborative AI agents.

194 lines (193 loc) 5.64 kB
/** * Copyright 2025 The Artinet Project * SPDX-License-Identifier: Apache-2.0 * * @fileoverview OpenTelemetry integration for Artinet SDK. * * Lightweight utilities for integrating user-configured OpenTelemetry * with the SDK's tracing and logging interfaces. * * @module @artinet/sdk/otel * * @example * ```typescript * import { trace } from '@opentelemetry/api'; * import { configure } from '@artinet/sdk'; * import { configureOtel, withSpan } from '@artinet/sdk/otel'; * * // User initializes their own OpenTelemetry setup * // ... NodeSDK setup, exporters, etc. ... * * // Get tracer and configure SDK * const tracer = trace.getTracer('my-agent'); * configure({ * tracer, * logger: configureOtel({ level: 'debug' }) * }); * ``` */ import { trace, context, SpanKind, SpanStatusCode, } from "@opentelemetry/api"; import { formatError } from "../config/observability.js"; /** * Level priority for filtering. */ const LEVEL_PRIORITY = { trace: 0, verbose: 1, debug: 2, info: 3, warn: 4, error: 5, silent: 6, }; /** * Create a logger that adds log events to the current span. * * This logger optionally wraps a base logger and adds all log messages * as span events in the current trace context. * * @param options - Logger options * @returns ILogger implementation * * @example * ```typescript * import { configure } from '@artinet/sdk'; * import { configureOtel } from '@artinet/sdk/otel'; * import { configurePino } from '@artinet/sdk/pino'; * import pino from 'pino'; * * // Logs go to both Pino AND span events * configure({ * logger: configureOtel({ * baseLogger: configurePino(pino()), * level: 'debug' * }) * }); * ``` */ export function configureOtel(options = {}) { let currentLevel = options.level ?? "info"; const baseLogger = options.baseLogger; function shouldLog(level) { return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[currentLevel]; } function addSpanEvent(level, msg, args) { const span = trace.getActiveSpan(); if (!span) return; const attributes = { "log.level": level, }; args.forEach((arg, idx) => { if (typeof arg === "object" && arg !== null) { Object.entries(arg).forEach(([key, value]) => { if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { attributes[key] = value; } else { attributes[key] = JSON.stringify(value); } }); } else if (arg !== undefined) { attributes[`arg${idx}`] = String(arg); } }); span.addEvent(msg, attributes); } return { get level() { return currentLevel; }, debug(msg, ...args) { if (!shouldLog("debug")) return; addSpanEvent("debug", msg, args); baseLogger?.debug(msg, ...args); }, info(msg, ...args) { if (!shouldLog("info")) return; addSpanEvent("info", msg, args); baseLogger?.info(msg, ...args); }, warn(msg, ...args) { if (!shouldLog("warn")) return; addSpanEvent("warn", msg, args); baseLogger?.warn(msg, ...args); }, error(msg, err) { if (!shouldLog("error")) return; addSpanEvent("error", msg, [formatError(err)]); baseLogger?.error(msg, err); }, setLevel(level) { currentLevel = level; baseLogger?.setLevel?.(level); }, getLevel() { return currentLevel; }, child(ctx) { return configureOtel({ baseLogger: baseLogger?.child?.(ctx), level: currentLevel, }); }, }; } /** * Execute a function within a new span. * * @param tracer - User's tracer instance * @param name - Span name * @param fn - Function to execute * @param options - Optional span options * @returns Result of the function * * @example * ```typescript * import { trace } from '@opentelemetry/api'; * import { withSpan } from '@artinet/sdk/otel'; * * const tracer = trace.getTracer('my-agent'); * const result = await withSpan(tracer, 'processTask', async (span) => { * span.setAttribute('taskId', '123'); * return await doWork(); * }); * ``` */ export async function withSpan(tracer, name, fn, options) { return tracer.startActiveSpan(name, { kind: options?.kind ?? SpanKind.INTERNAL }, async (span) => { if (options?.attributes) { Object.entries(options.attributes).forEach(([key, value]) => { span.setAttribute(key, value); }); } try { const result = await fn(span); span.setStatus({ code: SpanStatusCode.OK }); return result; } catch (error) { span.setStatus({ code: SpanStatusCode.ERROR, message: error instanceof Error ? error.message : String(error), }); span.recordException(error); throw error; } finally { span.end(); } }); } /** * Re-export OpenTelemetry API types for convenience. */ export { trace, context, SpanKind, SpanStatusCode, }; export default configureOtel;