UNPKG

@comprehend/telemetry-browser

Version:

Integration of comprehend.dev with OpenTelemetry in browser environments.

191 lines (190 loc) 7.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ComprehendDevSpanProcessor = void 0; const sha2_1 = require("@noble/hashes/sha2"); const utils_1 = require("@noble/hashes/utils"); const WebSocketConnection_1 = require("./WebSocketConnection"); class ComprehendDevSpanProcessor { connection; observedServices = []; observedHttpServices = []; observedInteractions = new Map(); observationsSeq = 1; constructor(options) { this.connection = new WebSocketConnection_1.WebSocketConnection(options.organization, options.token, options.debug === true ? console.log : options.debug === false ? undefined : options.debug); } onStart(span, parentContext) { } onEnd(span) { const currentService = this.discoverService(span); if (!currentService) return; const attrs = span.attributes; if (span.kind === 2) { // CLIENT span kind if (attrs['http.url']) { this.processHttpRequest(currentService, attrs['http.url'], span); } } } discoverService(span) { // Look for an existing matching entry. const resAttrs = span.resource.attributes; const name = resAttrs['service.name']; if (!name) return; const namespace = resAttrs['service.namespace']; const environment = resAttrs['deployment.environment']; const existing = this.observedServices.find(s => s.name === name && s.namespace === namespace && s.environment === environment); if (existing) return existing; // New; hash it and add it to the observed services. const idString = `service:${name}:${namespace ?? ''}:${environment ?? ''}`; const hash = hashIdString(idString); const newService = { name, namespace, environment, hash }; this.observedServices.push(newService); // Ingest its existence. const message = { event: "new-entity", type: "service", hash, name, ...(namespace ? { namespace } : {}), ...(environment ? { environment } : {}) }; this.ingestMessage(message); return newService; } processHttpRequest(currentService, url, span) { // Build identity based upon protocol, host, and port. const parsed = new URL(url); const protocol = parsed.protocol.replace(':', ''); // Remove trailing colon const host = parsed.hostname; const port = parsed.port ? parseInt(parsed.port) : (protocol === 'https' ? 443 : 80); const idString = `http-service:${protocol}:${host}:${port}`; const hash = hashIdString(idString); // Ingest it if it's not already observed. let observedHttpService = this.observedHttpServices.find(s => s.protocol === protocol && s.host === host && s.port === port); if (!observedHttpService) { observedHttpService = { protocol, host, port, hash }; this.observedHttpServices.push(observedHttpService); // The existence of the service. const message = { event: "new-entity", type: "http-service", hash, protocol, host, port }; this.ingestMessage(message); } // Ingest the interaction if first observed. const interactions = this.getInteractions(currentService.hash, observedHttpService.hash); if (!interactions.httpRequest) { const idString = `http-request:${currentService.hash}:${observedHttpService.hash}`; const hash = hashIdString(idString); interactions.httpRequest = { hash }; const message = { event: "new-interaction", type: "http-request", hash, from: currentService.hash, to: observedHttpService.hash }; this.ingestMessage(message); } // Build and ingest observation. const attrs = span.attributes; const path = parsed.pathname || '/'; const method = span.attributes['http.method']; if (!method) // Really should always be there return; const status = attrs['http.status_code']; const duration = span.duration; const httpVersion = span.attributes['http.flavor']; const requestBytes = attrs['http.request_content_length']; const responseBytes = attrs['http.response_content_length']; const { message: errorMessage, type: errorType, stack } = extractErrorInfo(span); const observation = { type: "http-client", subject: interactions.httpRequest.hash, timestamp: span.startTime, path, method, ...(status !== undefined ? { status } : {}), duration, ...(httpVersion !== undefined ? { httpVersion } : {}), ...(requestBytes !== undefined ? { requestBytes } : {}), ...(responseBytes !== undefined ? { responseBytes } : {}), ...(errorMessage ? { errorMessage } : {}), ...(errorType ? { errorType } : {}), ...(stack ? { stack } : {}) }; this.ingestMessage({ event: "observations", seq: this.observationsSeq++, observations: [observation] }); } getInteractions(from, to) { let fromMap = this.observedInteractions.get(from); if (!fromMap) { fromMap = new Map(); this.observedInteractions.set(from, fromMap); } let interactions = fromMap.get(to); if (!interactions) { interactions = { httpRequest: undefined }; fromMap.set(to, interactions); } return interactions; } ingestMessage(message) { this.connection.sendMessage(message); } async forceFlush() { } async shutdown() { this.connection.close(); } } exports.ComprehendDevSpanProcessor = ComprehendDevSpanProcessor; function hashIdString(idString) { return Array.from((0, sha2_1.sha256)((0, utils_1.utf8ToBytes)(idString))) .map(b => b.toString(16).padStart(2, '0')) .join(''); } /** Try pretty hard to get error info out of a span, if it has any. Handles it being there as an event, * directly on the span with error semantics, and some other more ad-hoc cases. */ function extractErrorInfo(span) { const attrs = span.attributes; // Try to extract from a structured 'exception' event, as it should have more detail const exceptionEvent = span.events.find(e => e.name === 'exception'); if (exceptionEvent?.attributes) { const message = exceptionEvent.attributes['exception.message']; const type = exceptionEvent.attributes['exception.type']; const stack = exceptionEvent.attributes['exception.stacktrace']; if (message || type || stack) { return { message, type, stack }; } } // Fallback to attributes directly on the span. const isError = span.status.code === 2; const message = attrs['exception.message'] ?? attrs['http.error_message'] ?? (isError ? attrs['otel.status_description'] : undefined) ?? (isError ? span.status.message : undefined); const type = attrs['exception.type'] ?? attrs['error.type'] ?? attrs['http.error_name']; const stack = attrs['exception.stacktrace']; return { message, type, stack }; }