UNPKG

@uphold/opentelemetry-instrumentation-connect-node

Version:

OpenTelemetry instrumentation for `@connectrpc/connect-node` RPC client and server

166 lines 7.58 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createServerInterceptor = exports.createClientInterceptor = void 0; const incubating_1 = require("@opentelemetry/semantic-conventions/incubating"); const api_1 = require("@opentelemetry/api"); const utils_1 = require("./utils"); const lodash_1 = require("lodash"); const instrumentation_1 = require("@opentelemetry/instrumentation"); const createMetadataAttributesExtractor = (config, kind, phase) => { const metadataToSpanAttributes = config.metadataToSpanAttributes?.[kind]?.[phase] ?? []; const mappings = new Map(metadataToSpanAttributes.map(value => [value.toLowerCase(), value.toLowerCase().replace(/-/g, '_')])); if (mappings.size === 0) { return () => ({}); } // See: https://opentelemetry.io/docs/specs/semconv/rpc/ return (span, metadata) => { const rpcSystem = span.attributes[incubating_1.ATTR_RPC_SYSTEM]; const attributes = {}; if (rpcSystem && metadata) { for (const [key, mappedKey] of mappings) { const value = metadata.get(key); if (value != null) { attributes[`grpc.${rpcSystem}.${phase}.metadata.${mappedKey}`] = value; } } } return attributes; }; }; const createStartSpan = (config, tracer, kind) => { const extractMetadata = createMetadataAttributesExtractor(config, kind, 'request'); const parseUrl = (0, lodash_1.memoize)((url) => new URL(url)); // See: https://opentelemetry.io/docs/specs/semconv/rpc/ return (req) => { const fullName = `${req.service.typeName}/${req.method.name}`; const url = parseUrl(req.url); const rpcSystem = (0, utils_1.resolveRpcSystem)(req.header); const span = tracer.startSpan(fullName, { attributes: { [incubating_1.ATTR_RPC_METHOD]: req.method.name, [incubating_1.ATTR_RPC_SERVICE]: req.service.typeName, [incubating_1.ATTR_RPC_SYSTEM]: rpcSystem, [incubating_1.ATTR_SERVER_ADDRESS]: url.hostname, [incubating_1.ATTR_SERVER_PORT]: url.port || undefined }, kind: (0, utils_1.rpcKindToSpanKind)(kind) }); span.setAttributes(extractMetadata(span, req.header)); return span; }; }; const createEndSpanWithSuccess = (config, kind) => { const extractMetadata = createMetadataAttributesExtractor(config, kind, 'response'); // See: https://opentelemetry.io/docs/specs/semconv/rpc/ return (span, res) => { if (span.ended) { return; } const rpcSystem = span.attributes[incubating_1.ATTR_RPC_SYSTEM]; span.setStatus({ code: api_1.SpanStatusCode.OK }); if (rpcSystem === 'grpc') { span.setAttribute(incubating_1.ATTR_RPC_GRPC_STATUS_CODE, 0); } span.setAttributes(extractMetadata(span, res.header)); span.end(); }; }; const createEndSpanWithError = (config, kind) => { const extractMetadata = createMetadataAttributesExtractor(config, kind, 'response'); // See: https://opentelemetry.io/docs/specs/semconv/rpc/ return (span, err) => { if (span.ended) { return; } const error = err instanceof Error ? err : undefined; const connectError = (0, utils_1.isConnectError)(error) ? error : undefined; const rpcSystem = span.attributes[incubating_1.ATTR_RPC_SYSTEM]; span.setStatus({ code: api_1.SpanStatusCode.ERROR, message: error?.message }); if (rpcSystem === 'grpc') { span.setAttribute(incubating_1.ATTR_RPC_GRPC_STATUS_CODE, connectError?.code ?? 2); } else if (rpcSystem === 'connect_rpc') { span.setAttribute(incubating_1.ATTR_RPC_CONNECT_RPC_ERROR_CODE, (0, utils_1.errorCodeToString)(connectError?.code)); } span.setAttributes(extractMetadata(span, connectError?.metadata)); if (error) { span.recordException(error); } span.end(); }; }; const carrierSetterAndGetter = { get: (carrier, key) => carrier.get(key) ?? undefined, keys: (carrier) => Array.from(carrier.keys()), set: (carrier, key, value) => carrier.set(key, value) }; const createClientInterceptor = (config, diag, tracer) => { const startSpan = createStartSpan(config, tracer, 'client'); const endSpanWithSuccess = createEndSpanWithSuccess(config, 'client'); const endSpanWithError = createEndSpanWithError(config, 'client'); return (next) => async (req) => { // Only unary requests are supported due to a bug in Node.js async context propagation on generator functions. // See https://github.com/open-telemetry/opentelemetry-js/issues/2951 and https://github.com/nodejs/node/issues/42237 if (req.method.methodKind !== 'unary') { return await next(req); } const shouldIgnoreRequest = (0, instrumentation_1.safeExecuteInTheMiddle)(() => config.ignoreRequest?.(req) === true, (err) => { if (err != null) { diag.error('caught ignoreRequest error: ', err); } }, true); if (shouldIgnoreRequest) { return await next(req); } const span = startSpan(req); try { return await api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => { api_1.propagation.inject(api_1.context.active(), req.header, carrierSetterAndGetter); const res = await next(req); endSpanWithSuccess(span, res); return res; }); } catch (err) { endSpanWithError(span, err); throw err; } }; }; exports.createClientInterceptor = createClientInterceptor; const createServerInterceptor = (config, diag, tracer) => { const startSpan = createStartSpan(config, tracer, 'server'); const endSpanWithSuccess = createEndSpanWithSuccess(config, 'server'); const endSpanWithError = createEndSpanWithError(config, 'server'); return (next) => async (req) => { // Only unary requests are supported due to a bug in Node.js async context propagation on generator functions. // See https://github.com/open-telemetry/opentelemetry-js/issues/2951 and https://github.com/nodejs/node/issues/42237 if (req.method.methodKind !== 'unary') { return await next(req); } const shouldIgnoreRequest = (0, instrumentation_1.safeExecuteInTheMiddle)(() => config.ignoreRequest?.(req) === true, (err) => { if (err != null) { diag.error('caught ignoreRequest error: ', err); } }, true); if (shouldIgnoreRequest) { return await next(req); } return await api_1.context.with(api_1.propagation.extract(api_1.context.active(), req.header, carrierSetterAndGetter), async () => { const span = startSpan(req); try { return await api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), async () => { const res = await next(req); endSpanWithSuccess(span, res); return res; }); } catch (err) { endSpanWithError(span, err); throw err; } }); }; }; exports.createServerInterceptor = createServerInterceptor; //# sourceMappingURL=interceptor.js.map