UNPKG

@opentelemetry/instrumentation-grpc

Version:
301 lines 16.4 kB
"use strict"; /* * Copyright The OpenTelemetry Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.GrpcInstrumentation = void 0; const api_1 = require("@opentelemetry/api"); const instrumentation_1 = require("@opentelemetry/instrumentation"); const semantic_conventions_1 = require("@opentelemetry/semantic-conventions"); const semconv_1 = require("./semconv"); const serverUtils_1 = require("./serverUtils"); const clientUtils_1 = require("./clientUtils"); const utils_1 = require("./utils"); const AttributeValues_1 = require("./enums/AttributeValues"); const version_1 = require("./version"); class GrpcInstrumentation extends instrumentation_1.InstrumentationBase { _metadataCapture; _semconvStability; constructor(config = {}) { super('@opentelemetry/instrumentation-grpc', version_1.VERSION, config); this._metadataCapture = this._createMetadataCapture(); this._semconvStability = (0, instrumentation_1.semconvStabilityFromStr)('http', process.env.OTEL_SEMCONV_STABILITY_OPT_IN); } init() { return [ new instrumentation_1.InstrumentationNodeModuleDefinition('@grpc/grpc-js', ['^1.0.0'], moduleExports => { // Patch Server methods this._wrap(moduleExports.Server.prototype, 'register', this._patchServer()); // Patch Client methods this._wrap(moduleExports, 'makeGenericClientConstructor', this._patchClient(moduleExports)); this._wrap(moduleExports, 'makeClientConstructor', this._patchClient(moduleExports)); this._wrap(moduleExports, 'loadPackageDefinition', this._patchLoadPackageDefinition(moduleExports)); this._wrap(moduleExports.Client.prototype, 'makeUnaryRequest', this._patchClientRequestMethod(moduleExports, false)); this._wrap(moduleExports.Client.prototype, 'makeClientStreamRequest', this._patchClientRequestMethod(moduleExports, false)); this._wrap(moduleExports.Client.prototype, 'makeServerStreamRequest', this._patchClientRequestMethod(moduleExports, true)); this._wrap(moduleExports.Client.prototype, 'makeBidiStreamRequest', this._patchClientRequestMethod(moduleExports, true)); return moduleExports; }, moduleExports => { if (moduleExports === undefined) return; this._unwrap(moduleExports.Server.prototype, 'register'); this._unwrap(moduleExports, 'makeClientConstructor'); this._unwrap(moduleExports, 'makeGenericClientConstructor'); this._unwrap(moduleExports, 'loadPackageDefinition'); this._unwrap(moduleExports.Client.prototype, 'makeUnaryRequest'); this._unwrap(moduleExports.Client.prototype, 'makeClientStreamRequest'); this._unwrap(moduleExports.Client.prototype, 'makeServerStreamRequest'); this._unwrap(moduleExports.Client.prototype, 'makeBidiStreamRequest'); }), ]; } setConfig(config = {}) { super.setConfig(config); this._metadataCapture = this._createMetadataCapture(); } /** * Patch for grpc.Server.prototype.register(...) function. Provides auto-instrumentation for * client_stream, server_stream, bidi, unary server handler calls. */ _patchServer() { const instrumentation = this; return (originalRegister) => { const config = this.getConfig(); instrumentation._diag.debug('patched gRPC server'); return function register(name, handler, serialize, deserialize, type) { const originalRegisterResult = originalRegister.call(this, name, handler, serialize, deserialize, type); const handlerSet = this['handlers'].get(name); instrumentation._wrap(handlerSet, 'func', (originalFunc) => { return function func(call, callback) { const self = this; if ((0, serverUtils_1.shouldNotTraceServerCall)(name, config.ignoreGrpcMethods)) { return (0, serverUtils_1.handleUntracedServerFunction)(type, originalFunc, call, callback); } const spanName = `grpc.${name.replace('/', '')}`; const spanOptions = { kind: api_1.SpanKind.SERVER, }; instrumentation._diag.debug(`patch func: ${JSON.stringify(spanOptions)}`); api_1.context.with(api_1.propagation.extract(api_1.ROOT_CONTEXT, call.metadata, { get: (carrier, key) => carrier.get(key).map(String), keys: carrier => Object.keys(carrier.getMap()), }), () => { const { service, method } = (0, utils_1._extractMethodAndService)(name); const span = instrumentation.tracer .startSpan(spanName, spanOptions) .setAttributes({ [semconv_1.ATTR_RPC_SYSTEM]: AttributeValues_1.AttributeValues.RPC_SYSTEM, [semconv_1.ATTR_RPC_METHOD]: method, [semconv_1.ATTR_RPC_SERVICE]: service, }); instrumentation._metadataCapture.server.captureRequestMetadata(span, call.metadata); instrumentation._wrap(call, 'sendMetadata', originalSendMetadata => (responseMetadata) => { instrumentation._metadataCapture.server.captureResponseMetadata(span, responseMetadata); originalSendMetadata.call(call, responseMetadata); }); api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), () => { serverUtils_1.handleServerFunction.call(self, span, type, originalFunc, call, callback); }); }); }; }); return originalRegisterResult; }; }; } /** * Patch for grpc.Client.make*Request(...) functions. * Provides auto-instrumentation for client requests when using a Client without * makeGenericClientConstructor/makeClientConstructor */ _patchClientRequestMethod(grpcLib, hasResponseStream) { const instrumentation = this; return (original) => { instrumentation._diag.debug('patched makeClientStreamRequest on grpc client'); return function makeClientStreamRequest() { // method must always be at first position const method = arguments[0]; const { name, service, methodAttributeValue } = instrumentation._splitMethodString(method); // Do not attempt to trace/inject context if method is ignored if (method != null && (0, utils_1._methodIsIgnored)(methodAttributeValue, instrumentation.getConfig().ignoreGrpcMethods)) { return original.apply(this, [...arguments]); } const modifiedArgs = [...arguments]; const metadata = (0, clientUtils_1.extractMetadataOrSplice)(grpcLib, modifiedArgs, 4); const span = instrumentation.createClientSpan(name, methodAttributeValue, service, metadata); instrumentation.extractNetMetadata(this, span, instrumentation._semconvStability); // Callback is only present when there is no responseStream if (!hasResponseStream) { // Replace the callback with the patched one if it is there. // If the callback arg is not a function on the last position then the client will throw // and never call the callback -> so there's nothing to patch const lastArgIndex = modifiedArgs.length - 1; const callback = modifiedArgs[lastArgIndex]; if (typeof callback === 'function') { modifiedArgs[lastArgIndex] = (0, clientUtils_1.patchedCallback)(span, callback); } } return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), () => { (0, clientUtils_1.setSpanContext)(metadata); const call = original.apply(this, [...modifiedArgs]); (0, clientUtils_1.patchResponseMetadataEvent)(span, call, instrumentation._metadataCapture); // Subscribe to response stream events when there's a response stream. if (hasResponseStream) { (0, clientUtils_1.patchResponseStreamEvents)(span, call); } return call; }); }; }; } /** * Entry point for applying client patches to `grpc.makeClientConstructor(...)` equivalents * @param this GrpcJsPlugin */ _patchClient(grpcClient) { const instrumentation = this; return (original) => { instrumentation._diag.debug('patching client'); return function makeClientConstructor(methods, serviceName, options) { const client = original.call(this, methods, serviceName, options); instrumentation._massWrap(client.prototype, clientUtils_1.getMethodsToWrap.call(instrumentation, client, methods), instrumentation._getPatchedClientMethods(grpcClient)); return client; }; }; } /** * Entry point for client patching for grpc.loadPackageDefinition(...) * @param this - GrpcJsPlugin */ _patchLoadPackageDefinition(grpcClient) { const instrumentation = this; instrumentation._diag.debug('patching loadPackageDefinition'); return (original) => { return function patchedLoadPackageDefinition(packageDef) { const result = original.call(this, packageDef); instrumentation._patchLoadedPackage(grpcClient, result); return result; }; }; } /** * Parse initial client call properties and start a span to trace its execution */ _getPatchedClientMethods(grpcClient) { const instrumentation = this; return (original) => { instrumentation._diag.debug('patch all client methods'); function clientMethodTrace() { const name = `grpc.${original.path.replace('/', '')}`; const args = [...arguments]; const metadata = clientUtils_1.extractMetadataOrSpliceDefault.call(instrumentation, grpcClient, original, args); const { service, method } = (0, utils_1._extractMethodAndService)(original.path); const span = instrumentation.tracer .startSpan(name, { kind: api_1.SpanKind.CLIENT }) .setAttributes({ [semconv_1.ATTR_RPC_SYSTEM]: 'grpc', [semconv_1.ATTR_RPC_METHOD]: method, [semconv_1.ATTR_RPC_SERVICE]: service, }); instrumentation.extractNetMetadata(this, span, instrumentation._semconvStability); instrumentation._metadataCapture.client.captureRequestMetadata(span, metadata); return api_1.context.with(api_1.trace.setSpan(api_1.context.active(), span), () => (0, clientUtils_1.makeGrpcClientRemoteCall)(instrumentation._metadataCapture, original, args, metadata, this)(span)); } Object.assign(clientMethodTrace, original); return clientMethodTrace; }; } _splitMethodString(method) { if (method == null) { return { name: '', service: '', methodAttributeValue: '' }; } const name = `grpc.${method.replace('/', '')}`; const { service, method: methodAttributeValue } = (0, utils_1._extractMethodAndService)(method); return { name, service, methodAttributeValue }; } createClientSpan(name, methodAttributeValue, service, metadata) { const span = this.tracer .startSpan(name, { kind: api_1.SpanKind.CLIENT }) .setAttributes({ [semconv_1.ATTR_RPC_SYSTEM]: 'grpc', [semconv_1.ATTR_RPC_METHOD]: methodAttributeValue, [semconv_1.ATTR_RPC_SERVICE]: service, }); if (metadata != null) { this._metadataCapture.client.captureRequestMetadata(span, metadata); } return span; } extractNetMetadata(client, span, semconvStability) { // set net.peer.* from target (e.g., "dns:otel-productcatalogservice:8080") as a hint to APMs const parsedUri = utils_1.URI_REGEX.exec(client.getChannel().getTarget()); const hostname = parsedUri?.groups?.name; const port = parseInt(parsedUri?.groups?.port ?? ''); const oldAttributes = { [semconv_1.ATTR_NET_PEER_NAME]: hostname, [semconv_1.ATTR_NET_PEER_PORT]: port, }; const newAttributes = { [semantic_conventions_1.ATTR_SERVER_ADDRESS]: hostname, [semantic_conventions_1.ATTR_SERVER_PORT]: port, }; switch (semconvStability) { case instrumentation_1.SemconvStability.STABLE: span.setAttributes(newAttributes); break; case instrumentation_1.SemconvStability.OLD: span.setAttributes(oldAttributes); break; case instrumentation_1.SemconvStability.DUPLICATE: span.setAttributes({ ...oldAttributes, ...newAttributes }); break; default: span.setAttributes(oldAttributes); } } /** * Utility function to patch *all* functions loaded through a proto file. * Recursively searches for Client classes and patches all methods, reversing the * parsing done by grpc.loadPackageDefinition * https://github.com/grpc/grpc-node/blob/1d14203c382509c3f36132bd0244c99792cb6601/packages/grpc-js/src/make-client.ts#L200-L217 */ _patchLoadedPackage(grpcClient, result) { Object.values(result).forEach(service => { if (typeof service === 'function') { this._massWrap(service.prototype, clientUtils_1.getMethodsToWrap.call(this, service, service.service), this._getPatchedClientMethods.call(this, grpcClient)); } else if (typeof service.format !== 'string') { // GrpcObject this._patchLoadedPackage.call(this, grpcClient, service); } }); } _createMetadataCapture() { const config = this.getConfig(); return { client: { captureRequestMetadata: (0, utils_1.metadataCapture)('request', config.metadataToSpanAttributes?.client?.requestMetadata ?? []), captureResponseMetadata: (0, utils_1.metadataCapture)('response', config.metadataToSpanAttributes?.client?.responseMetadata ?? []), }, server: { captureRequestMetadata: (0, utils_1.metadataCapture)('request', config.metadataToSpanAttributes?.server?.requestMetadata ?? []), captureResponseMetadata: (0, utils_1.metadataCapture)('response', config.metadataToSpanAttributes?.server?.responseMetadata ?? []), }, }; } } exports.GrpcInstrumentation = GrpcInstrumentation; //# sourceMappingURL=instrumentation.js.map