@vfarcic/dot-ai
Version:
AI-powered development productivity platform that enhances software development workflows through intelligent automation and AI-driven assistance
216 lines (215 loc) • 7.38 kB
JavaScript
;
/**
* OpenTelemetry Tracer Service
*
* Provides lazy initialization and management of distributed tracing.
* Follows OpenTelemetry best practices with support for multiple exporters.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getTracer = getTracer;
exports.shutdownTracer = shutdownTracer;
exports.withSpan = withSpan;
const sdk_node_1 = require("@opentelemetry/sdk-node");
const resources_1 = require("@opentelemetry/resources");
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
const sdk_trace_node_1 = require("@opentelemetry/sdk-trace-node");
const exporter_trace_otlp_http_1 = require("@opentelemetry/exporter-trace-otlp-http");
const api_1 = require("@opentelemetry/api");
const config_1 = require("./config");
/**
* Global tracer instance (singleton pattern with lazy initialization)
*/
let tracerInstance = null;
/**
* OpenTelemetry Tracer implementation
*/
class OpenTelemetryTracer {
sdk = null;
config;
initialized = false;
constructor(config) {
this.config = config;
}
/**
* Initialize the OpenTelemetry SDK (called lazily on first use)
*/
initialize() {
if (this.initialized) {
return; // Already initialized
}
if (!this.config.enabled) {
if (this.config.debug) {
console.log('[Tracing] Tracing is disabled, skipping initialization');
}
return;
}
try {
// Validate configuration
(0, config_1.validateTracingConfig)(this.config);
// Create resource with service identification
const serviceResource = (0, resources_1.resourceFromAttributes)({
[semantic_conventions_1.ATTR_SERVICE_NAME]: this.config.serviceName,
[semantic_conventions_1.ATTR_SERVICE_VERSION]: this.config.serviceVersion,
});
const resource = (0, resources_1.defaultResource)().merge(serviceResource);
// Create exporter based on configuration
const traceExporter = this.createExporter();
// Initialize Node SDK without auto-instrumentation
// All operations are manually instrumented with descriptive spans
this.sdk = new sdk_node_1.NodeSDK({
resource,
traceExporter,
instrumentations: [], // No auto-instrumentation needed
});
// Start the SDK
this.sdk.start();
this.initialized = true;
if (this.config.debug) {
console.log('[Tracing] OpenTelemetry initialized successfully', {
serviceName: this.config.serviceName,
serviceVersion: this.config.serviceVersion,
exporterType: this.config.exporterType,
});
}
}
catch (error) {
console.error('[Tracing] Failed to initialize OpenTelemetry:', error);
throw error;
}
}
/**
* Create exporter based on configuration
*/
createExporter() {
switch (this.config.exporterType) {
case 'console':
if (this.config.debug) {
console.log('[Tracing] Using console exporter (outputs to stderr)');
}
return new sdk_trace_node_1.ConsoleSpanExporter();
case 'otlp':
if (this.config.debug) {
console.log('[Tracing] Using OTLP exporter', {
endpoint: this.config.otlpEndpoint || 'http://localhost:4318/v1/traces'
});
}
return new exporter_trace_otlp_http_1.OTLPTraceExporter({
url: this.config.otlpEndpoint || 'http://localhost:4318/v1/traces',
});
case 'jaeger':
// Jaeger exporter will be added in Phase 3
throw new Error('Jaeger exporter not yet implemented - coming in Phase 3');
case 'zipkin':
// Zipkin exporter will be added in Phase 3
throw new Error('Zipkin exporter not yet implemented - coming in Phase 3');
default:
throw new Error(`Unknown exporter type: ${this.config.exporterType}`);
}
}
/**
* Check if tracing is enabled
*/
isEnabled() {
return this.config.enabled;
}
/**
* Create a new span with utility methods
*/
startSpan(name, options, parentContext) {
if (!this.config.enabled) {
// Return a no-op span if tracing is disabled
return this.createNoOpSpan();
}
// Lazy initialization on first span creation
if (!this.initialized) {
this.initialize();
}
const tracer = api_1.trace.getTracer(this.config.serviceName, this.config.serviceVersion);
const ctx = parentContext || api_1.context.active();
const span = tracer.startSpan(name, options, ctx);
return this.wrapSpan(span);
}
/**
* Wrap an OpenTelemetry span with utility methods
*/
wrapSpan(span) {
return {
span,
end() {
span.setStatus({ code: api_1.SpanStatusCode.OK });
span.end();
},
endWithError(error) {
span.recordException(error);
span.setStatus({
code: api_1.SpanStatusCode.ERROR,
message: error.message,
});
span.end();
},
setAttributes(attributes) {
span.setAttributes(attributes);
},
addEvent(name, attributes) {
span.addEvent(name, attributes);
},
};
}
/**
* Create a no-op span for when tracing is disabled
*/
createNoOpSpan() {
const noopSpan = api_1.trace.getTracer('noop').startSpan('noop');
return this.wrapSpan(noopSpan);
}
/**
* Shutdown the tracer gracefully
*/
async shutdown() {
if (this.sdk && this.initialized) {
if (this.config.debug) {
console.log('[Tracing] Shutting down OpenTelemetry SDK...');
}
await this.sdk.shutdown();
this.initialized = false;
if (this.config.debug) {
console.log('[Tracing] OpenTelemetry SDK shut down successfully');
}
}
}
}
/**
* Get or create the global tracer instance
*/
function getTracer() {
if (!tracerInstance) {
const config = (0, config_1.loadTracingConfig)();
tracerInstance = new OpenTelemetryTracer(config);
}
return tracerInstance;
}
/**
* Shutdown the global tracer instance
*/
async function shutdownTracer() {
if (tracerInstance) {
await tracerInstance.shutdown();
tracerInstance = null;
}
}
/**
* Helper function to wrap async operations with tracing
*/
async function withSpan(name, fn, options) {
const tracer = getTracer();
const span = tracer.startSpan(name, options);
try {
const result = await fn(span);
span.end();
return result;
}
catch (error) {
span.endWithError(error);
throw error;
}
}