autotel
Version:
Write Once, Observe Anywhere
443 lines (439 loc) • 13.5 kB
TypeScript
import { T as TraceContext } from './trace-context-t5X1AP-e.js';
import { Attributes } from '@opentelemetry/api';
/**
* Semantic convention helpers for OpenTelemetry
*
* Pre-configured trace helpers that follow OpenTelemetry semantic conventions
* for common operation types. Reduces boilerplate and ensures consistency.
*
* Based on: https://opentelemetry.io/docs/specs/semconv/
*/
/**
* Configuration for LLM (Large Language Model) operations
*
* Follows Gen AI semantic conventions:
* https://opentelemetry.io/docs/specs/semconv/gen-ai/
*/
interface LLMConfig {
/** Model name (e.g., 'gpt-4', 'claude-3-opus') */
model: string;
/** Operation type */
operation?: 'chat' | 'completion' | 'embedding';
/** Model provider (e.g., 'openai', 'anthropic', 'cohere') - maps to gen.ai.system */
provider?: string;
/** Additional attributes to add to the span */
attributes?: Attributes;
}
/**
* Configuration for database operations
*
* Follows DB semantic conventions:
* https://opentelemetry.io/docs/specs/semconv/database/
*/
interface DBConfig {
/** Database system (e.g., 'postgresql', 'mongodb', 'redis') */
system: string;
/** Operation type (e.g., 'SELECT', 'INSERT', 'find', 'get') */
operation?: string;
/** Database name */
database?: string;
/** Collection/table name */
collection?: string;
/** Additional attributes to add to the span */
attributes?: Attributes;
}
/**
* Configuration for HTTP client operations
*
* Follows HTTP semantic conventions:
* https://opentelemetry.io/docs/specs/semconv/http/
*/
interface HTTPConfig {
/** HTTP method (e.g., 'GET', 'POST') */
method?: string;
/** Target URL or URL template */
url?: string;
/** Additional attributes to add to the span */
attributes?: Attributes;
}
/**
* Configuration for messaging operations
*
* Follows Messaging semantic conventions:
* https://opentelemetry.io/docs/specs/semconv/messaging/
*/
interface MessagingConfig {
/** Messaging system (e.g., 'kafka', 'rabbitmq', 'sqs') */
system: string;
/** Operation type */
operation?: 'publish' | 'receive' | 'process';
/** Destination name (queue/topic) */
destination?: string;
/** Additional attributes to add to the span */
attributes?: Attributes;
}
/**
* Trace LLM operations with Gen AI semantic conventions
*
* Automatically adds standard attributes for LLM operations:
* - gen.ai.request.model
* - gen.ai.operation.name
* - gen.ai.system
*
* **Use Cases:**
* - Chat completions
* - Text generation
* - Embeddings
* - Multi-step LLM workflows
*
* @param config - LLM operation configuration
* @returns Traced function factory with Gen AI attributes
*
* @example Chat completion with OpenAI
* ```typescript
* import { traceLLM } from 'autotel/semantic-helpers'
* import OpenAI from 'openai'
*
* const openai = new OpenAI()
*
* export const generateResponse = traceLLM({
* model: 'gpt-4-turbo',
* operation: 'chat',
* provider: 'openai'
* })(ctx => async (prompt: string) => {
* const response = await openai.chat.completions.create({
* model: 'gpt-4-turbo',
* messages: [{ role: 'user', content: prompt }]
* })
*
* // Add usage metrics to span
* ctx.setAttribute('gen.ai.usage.completion_tokens', response.usage?.completion_tokens)
* ctx.setAttribute('gen.ai.usage.prompt_tokens', response.usage?.prompt_tokens)
*
* return response.choices[0].message.content
* })
* ```
*
* @example Anthropic Claude with streaming
* ```typescript
* import { traceLLM } from 'autotel/semantic-helpers'
* import Anthropic from '@anthropic-ai/sdk'
*
* const anthropic = new Anthropic()
*
* export const streamResponse = traceLLM({
* model: 'claude-3-opus-20240229',
* operation: 'chat',
* provider: 'anthropic'
* })(ctx => async function* (prompt: string) {
* const stream = await anthropic.messages.create({
* model: 'claude-3-opus-20240229',
* messages: [{ role: 'user', content: prompt }],
* stream: true,
* max_tokens: 1024
* })
*
* let totalTokens = 0
* for await (const event of stream) {
* if (event.type === 'content_block_delta') {
* yield event.delta.text
* }
* if (event.type === 'message_stop') {
* ctx.setAttribute('gen.ai.usage.completion_tokens', event.message.usage.output_tokens)
* totalTokens = event.message.usage.output_tokens
* }
* }
*
* return totalTokens
* })
* ```
*
* @example Embeddings
* ```typescript
* import { traceLLM } from 'autotel/semantic-helpers'
* import { OpenAIEmbeddings } from '@langchain/openai'
*
* const embeddings = new OpenAIEmbeddings()
*
* export const embed = traceLLM({
* model: 'text-embedding-3-small',
* operation: 'embedding',
* provider: 'openai'
* })(ctx => async (text: string) => {
* const result = await embeddings.embedQuery(text)
* ctx.setAttribute('gen.ai.response.embedding_length', result.length)
* return result
* })
* ```
*
* @public
*/
declare function traceLLM<TArgs extends unknown[], TReturn>(config: LLMConfig): (fnFactory: (ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>) => ((...args: TArgs) => Promise<TReturn>);
/**
* Trace database operations with DB semantic conventions
*
* Automatically adds standard attributes for database operations:
* - db.system
* - db.operation
* - db.name
* - db.collection.name (for NoSQL)
*
* **Use Cases:**
* - SQL queries (PostgreSQL, MySQL, SQLite)
* - NoSQL operations (MongoDB, DynamoDB, Redis)
* - ORM queries (Prisma, TypeORM, Drizzle)
*
* @param config - Database operation configuration
* @returns Traced function factory with DB attributes
*
* @example PostgreSQL query
* ```typescript
* import { traceDB } from 'autotel/semantic-helpers'
* import { pool } from './db'
*
* export const getUser = traceDB({
* system: 'postgresql',
* operation: 'SELECT',
* database: 'app_db',
* collection: 'users'
* })(ctx => async (userId: string) => {
* const query = 'SELECT * FROM users WHERE id = $1'
* ctx.setAttribute('db.statement', query)
*
* const result = await pool.query(query, [userId])
* ctx.setAttribute('db.rows_affected', result.rowCount)
*
* return result.rows[0]
* })
* ```
*
* @example MongoDB with Mongoose
* ```typescript
* import { traceDB } from 'autotel/semantic-helpers'
* import { User } from './models/User'
*
* export const findUsers = traceDB({
* system: 'mongodb',
* operation: 'find',
* database: 'app_db',
* collection: 'users'
* })(ctx => async (filter: object) => {
* ctx.setAttribute('db.mongodb.filter', JSON.stringify(filter))
*
* const users = await User.find(filter).limit(100)
* ctx.setAttribute('db.response.count', users.length)
*
* return users
* })
* ```
*
* @example Redis operations
* ```typescript
* import { traceDB } from 'autotel/semantic-helpers'
* import { redis } from './redis'
*
* export const cacheGet = traceDB({
* system: 'redis',
* operation: 'GET'
* })(ctx => async (key: string) => {
* ctx.setAttribute('db.redis.key', key)
*
* const value = await redis.get(key)
* ctx.setAttribute('db.response.cache_hit', value !== null)
*
* return value
* })
* ```
*
* @example Prisma with detailed query info
* ```typescript
* import { traceDB } from 'autotel/semantic-helpers'
* import { prisma } from './prisma'
*
* export const createPost = traceDB({
* system: 'postgresql',
* operation: 'INSERT',
* database: 'app_db',
* collection: 'posts'
* })(ctx => async (data: { title: string; content: string; authorId: string }) => {
* ctx.setAttribute('db.prisma.model', 'Post')
* ctx.setAttribute('db.prisma.action', 'create')
*
* const post = await prisma.post.create({ data })
*
* ctx.setAttribute('db.response.id', post.id)
* return post
* })
* ```
*
* @public
*/
declare function traceDB<TArgs extends unknown[], TReturn>(config: DBConfig): (fnFactory: (ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>) => ((...args: TArgs) => Promise<TReturn>);
/**
* Trace HTTP client operations with HTTP semantic conventions
*
* Automatically adds standard attributes for HTTP requests:
* - http.request.method
* - url.full
*
* **Use Cases:**
* - External API calls
* - Microservice communication
* - Third-party integrations
*
* @param config - HTTP operation configuration
* @returns Traced function factory with HTTP attributes
*
* @example Fetch API
* ```typescript
* import { traceHTTP } from 'autotel/semantic-helpers'
*
* export const fetchUser = traceHTTP({
* method: 'GET',
* url: 'https://api.example.com/users/:id'
* })(ctx => async (userId: string) => {
* const url = `https://api.example.com/users/${userId}`
* ctx.setAttribute('url.full', url)
*
* const response = await fetch(url)
* ctx.setAttribute('http.response.status_code', response.status)
*
* if (!response.ok) {
* ctx.setAttribute('error', true)
* throw new Error(`HTTP ${response.status}: ${response.statusText}`)
* }
*
* return response.json()
* })
* ```
*
* @example Axios with retry logic
* ```typescript
* import { traceHTTP } from 'autotel/semantic-helpers'
* import axios from 'axios'
*
* export const sendWebhook = traceHTTP({
* method: 'POST',
* url: 'https://webhook.example.com/events'
* })(ctx => async (payload: object) => {
* let attempts = 0
* const maxAttempts = 3
*
* while (attempts < maxAttempts) {
* try {
* attempts++
* ctx.setAttribute('http.request.resend_count', attempts - 1)
*
* const response = await axios.post('https://webhook.example.com/events', payload)
* ctx.setAttribute('http.response.status_code', response.status)
* return response.data
* } catch (error) {
* if (attempts >= maxAttempts) throw error
* await new Promise(resolve => setTimeout(resolve, 1000 * attempts))
* }
* }
* })
* ```
*
* @public
*/
declare function traceHTTP<TArgs extends unknown[], TReturn>(config: HTTPConfig): (fnFactory: (ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>) => ((...args: TArgs) => Promise<TReturn>);
/**
* Trace messaging operations with Messaging semantic conventions
*
* Automatically adds standard attributes for messaging:
* - messaging.system
* - messaging.operation
* - messaging.destination.name
*
* **Use Cases:**
* - Publishing messages to queues/topics
* - Consuming messages from queues/topics
* - Event-driven architectures
*
* @param config - Messaging operation configuration
* @returns Traced function factory with Messaging attributes
*
* @example Publishing to Kafka
* ```typescript
* import { traceMessaging } from 'autotel/semantic-helpers'
* import { kafka } from './kafka'
*
* const producer = kafka.producer()
*
* export const publishEvent = traceMessaging({
* system: 'kafka',
* operation: 'publish',
* destination: 'user-events'
* })(ctx => async (event: { type: string; userId: string; data: object }) => {
* ctx.setAttribute('messaging.message.type', event.type)
* ctx.setAttribute('messaging.kafka.partition', 0)
*
* await producer.send({
* topic: 'user-events',
* messages: [
* {
* key: event.userId,
* value: JSON.stringify(event.data)
* }
* ]
* })
*
* ctx.setAttribute('messaging.message.id', event.userId)
* })
* ```
*
* @example Consuming from RabbitMQ
* ```typescript
* import { traceMessaging } from 'autotel/semantic-helpers'
* import { channel } from './rabbitmq'
*
* export const processOrder = traceMessaging({
* system: 'rabbitmq',
* operation: 'process',
* destination: 'orders'
* })(ctx => async (message: { orderId: string; items: object[] }) => {
* ctx.setAttribute('messaging.message.id', message.orderId)
* ctx.setAttribute('messaging.message.body.size', JSON.stringify(message).length)
*
* // Process order logic
* const result = await processOrderInternal(message)
*
* ctx.setAttribute('messaging.operation.result', 'success')
* return result
* })
* ```
*
* @example AWS SQS with batch processing
* ```typescript
* import { traceMessaging } from 'autotel/semantic-helpers'
* import { SQS } from '@aws-sdk/client-sqs'
*
* const sqs = new SQS()
*
* export const sendBatch = traceMessaging({
* system: 'aws_sqs',
* operation: 'publish',
* destination: 'notifications-queue'
* })(ctx => async (messages: Array<{ id: string; body: object }>) => {
* ctx.setAttribute('messaging.batch.message_count', messages.length)
*
* const result = await sqs.sendMessageBatch({
* QueueUrl: process.env.QUEUE_URL,
* Entries: messages.map(msg => ({
* Id: msg.id,
* MessageBody: JSON.stringify(msg.body)
* }))
* })
*
* ctx.setAttribute('messaging.operation.success_count', result.Successful?.length || 0)
* ctx.setAttribute('messaging.operation.failed_count', result.Failed?.length || 0)
*
* return result
* })
* ```
*
* @public
*/
declare function traceMessaging<TArgs extends unknown[], TReturn>(config: MessagingConfig): (fnFactory: (ctx: TraceContext) => (...args: TArgs) => Promise<TReturn>) => ((...args: TArgs) => Promise<TReturn>);
export { type DBConfig, type HTTPConfig, type LLMConfig, type MessagingConfig, traceDB, traceHTTP, traceLLM, traceMessaging };