@gati-framework/observability
Version:
Observability stack for Gati framework - Prometheus, Grafana, Loki, and Tracing
104 lines (103 loc) • 3.38 kB
JavaScript
import { NodeSDK } from '@opentelemetry/sdk-node';
import { resourceFromAttributes } from '@opentelemetry/resources';
import { SEMRESATTRS_SERVICE_NAME } from '@opentelemetry/semantic-conventions';
import * as api from '@opentelemetry/api';
export class DistributedTracing {
sdk;
tracer;
constructor(config) {
const resource = resourceFromAttributes({
[SEMRESATTRS_SERVICE_NAME]: config.serviceName,
'service.version': config.serviceVersion || '1.0.0',
'deployment.environment': config.environment || 'production',
});
if (config.autoInstrument !== false) {
this.sdk = new NodeSDK({
resource,
});
this.sdk.start();
}
this.tracer = api.trace.getTracer(config.serviceName, config.serviceVersion || '1.0.0');
}
createSpan(name, attributes) {
return this.tracer.startSpan(name, {
attributes,
});
}
async withSpan(name, fn, attributes) {
const span = this.createSpan(name, attributes);
try {
const result = await api.context.with(api.trace.setSpan(api.context.active(), span), async () => await fn(span));
span.setStatus({ code: api.SpanStatusCode.OK });
return result;
}
catch (error) {
span.setStatus({
code: api.SpanStatusCode.ERROR,
message: error instanceof Error ? error.message : 'Unknown error',
});
span.recordException(error);
throw error;
}
finally {
span.end();
}
}
addEvent(name, attributes) {
const span = api.trace.getActiveSpan();
if (span) {
span.addEvent(name, attributes);
}
}
setAttribute(key, value) {
const span = api.trace.getActiveSpan();
if (span) {
span.setAttribute(key, value);
}
}
recordException(error) {
const span = api.trace.getActiveSpan();
if (span) {
span.recordException(error);
}
}
getTraceContext() {
const span = api.trace.getActiveSpan();
if (!span)
return undefined;
const spanContext = span.spanContext();
return `${spanContext.traceId}-${spanContext.spanId}`;
}
async shutdown() {
if (this.sdk) {
await this.sdk.shutdown();
}
}
}
export function createTracingMiddleware(tracing) {
return (req, res, next) => {
const span = tracing.createSpan(`HTTP ${req.method} ${req.path}`, {
'http.method': req.method,
'http.url': req.url,
'http.route': req.route?.path || req.path,
});
const traceContext = tracing.getTraceContext();
if (traceContext) {
res.setHeader('X-Trace-Id', traceContext);
}
res.on('finish', () => {
span.setAttribute('http.status_code', res.statusCode);
if (res.statusCode >= 400) {
span.setStatus({
code: api.SpanStatusCode.ERROR,
message: `HTTP ${res.statusCode}`,
});
}
else {
span.setStatus({ code: api.SpanStatusCode.OK });
}
span.end();
});
next();
};
}