@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
JavaScript
;
/**
* 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;
}
};
}