autotel
Version:
Write Once, Observe Anywhere
330 lines (328 loc) • 9.86 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const require_functional = require('./functional-C8B0Qa7o.cjs');
//#region src/semantic-helpers.ts
/**
* 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/
*/
/**
* 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
*/
function traceDB(config) {
return (fnFactory) => {
return require_functional.trace((ctx) => {
ctx.setAttribute("db.system", config.system);
if (config.operation) ctx.setAttribute("db.operation", config.operation);
if (config.database) ctx.setAttribute("db.name", config.database);
if (config.collection) ctx.setAttribute("db.collection.name", config.collection);
if (config.attributes) {
for (const [key, value] of Object.entries(config.attributes)) if (value !== void 0 && value !== null) {
const attrValue = typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : JSON.stringify(value);
ctx.setAttribute(key, attrValue);
}
}
return fnFactory(ctx);
});
};
}
/**
* 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
*/
function traceHTTP(config) {
return (fnFactory) => {
return require_functional.trace((ctx) => {
if (config.method) ctx.setAttribute("http.request.method", config.method);
if (config.url) ctx.setAttribute("url.full", config.url);
if (config.attributes) {
for (const [key, value] of Object.entries(config.attributes)) if (value !== void 0 && value !== null) {
const attrValue = typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : JSON.stringify(value);
ctx.setAttribute(key, attrValue);
}
}
return fnFactory(ctx);
});
};
}
/**
* 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
*/
function traceMessaging(config) {
return (fnFactory) => {
return require_functional.trace((ctx) => {
ctx.setAttribute("messaging.system", config.system);
if (config.operation) ctx.setAttribute("messaging.operation", config.operation);
if (config.destination) ctx.setAttribute("messaging.destination.name", config.destination);
if (config.attributes) {
for (const [key, value] of Object.entries(config.attributes)) if (value !== void 0 && value !== null) {
const attrValue = typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? value : JSON.stringify(value);
ctx.setAttribute(key, attrValue);
}
}
return fnFactory(ctx);
});
};
}
//#endregion
exports.traceDB = traceDB;
exports.traceHTTP = traceHTTP;
exports.traceMessaging = traceMessaging;
//# sourceMappingURL=semantic-helpers.cjs.map