UNPKG

@vfarcic/dot-ai

Version:

AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance

120 lines (119 loc) 4.15 kB
"use strict"; /** * HTTP Server Tracing Module * * Provides manual SERVER span creation for incoming HTTP requests. * Follows OpenTelemetry HTTP semantic conventions. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.createHttpServerSpan = createHttpServerSpan; exports.withHttpServerTracing = withHttpServerTracing; const api_1 = require("@opentelemetry/api"); /** * Extract trace context from HTTP headers * Follows W3C Trace Context specification */ function extractTraceContext(req) { return api_1.propagation.extract(api_1.context.active(), req.headers); } /** * Build span attributes from HTTP request */ function buildSpanAttributes(req) { const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`); const attributes = { 'http.request.method': req.method || 'UNKNOWN', 'url.path': url.pathname, 'url.scheme': url.protocol.replace(':', ''), 'http.route': url.pathname, // Can be enhanced with route templates }; // Add optional attributes if (req.headers.host) { const [hostname, port] = req.headers.host.split(':'); attributes['server.address'] = hostname; if (port) { attributes['server.port'] = parseInt(port, 10); } } // Client address (from headers or socket) const clientAddress = req.headers['x-forwarded-for']?.split(',')[0]?.trim() || req.socket.remoteAddress; if (clientAddress) { attributes['client.address'] = clientAddress; } // User agent if (req.headers['user-agent']) { attributes['user_agent.original'] = req.headers['user-agent']; } return attributes; } /** * Create and manage HTTP SERVER span for incoming request * * Returns a function to end the span with response status code */ function createHttpServerSpan(req) { // Extract parent trace context from headers const parentContext = extractTraceContext(req); // Build span name: "{METHOD} {route}" const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`); const spanName = `${req.method} ${url.pathname}`; // Get tracer instance (returns no-op if tracing disabled) const tracer = api_1.trace.getTracer('dot-ai-mcp'); // Create SERVER span with parent context const span = tracer.startSpan(spanName, { kind: api_1.SpanKind.SERVER, attributes: buildSpanAttributes(req), }, parentContext); // Return span and cleanup function return { span, endSpan: (statusCode) => { // Set response status code span.setAttribute('http.response.status_code', statusCode); // Set span status based on HTTP status code if (statusCode >= 500) { span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: `HTTP ${statusCode}`, }); } else { span.setStatus({ code: api_1.SpanStatusCode.OK }); } span.end(); }, }; } /** * Wrap HTTP request handler with tracing * * This is a higher-order function that wraps an existing HTTP request handler * with automatic SERVER span creation and management. * * @param handler - Original HTTP request handler * @returns Wrapped handler with tracing */ function withHttpServerTracing(handler) { return async (req, res) => { const { span, endSpan } = createHttpServerSpan(req); try { // Set span as active context await api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => { await handler(req, res); }); // End span with actual response status code endSpan(res.statusCode); } catch (error) { // Record exception and end span with error span.recordException(error); span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error.message, }); span.end(); throw error; } }; }