UNPKG

@opentelemetry/instrumentation-grpc

Version:
192 lines 7.71 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.setSpanContext = exports.extractMetadataOrSpliceDefault = exports.extractMetadataOrSplice = exports.getMetadataIndex = exports.makeGrpcClientRemoteCall = exports.patchResponseStreamEvents = exports.patchResponseMetadataEvent = exports.patchedCallback = exports.getMethodsToWrap = void 0; const api_1 = require("@opentelemetry/api"); const AttributeNames_1 = require("./enums/AttributeNames"); const semconv_1 = require("./semconv"); const utils_1 = require("./utils"); const events_1 = require("events"); /** * Parse a package method list and return a list of methods to patch * with both possible casings e.g. "TestMethod" & "testMethod" */ function getMethodsToWrap(client, methods) { const methodList = []; // For a method defined in .proto as "UnaryMethod" Object.entries(methods).forEach(([name, { originalName }]) => { if (!(0, utils_1._methodIsIgnored)(name, this.getConfig().ignoreGrpcMethods)) { methodList.push(name); // adds camel case method name: "unaryMethod" if (originalName && // eslint-disable-next-line no-prototype-builtins client.prototype.hasOwnProperty(originalName) && name !== originalName // do not add duplicates ) { // adds original method name: "UnaryMethod", methodList.push(originalName); } } }); return methodList; } exports.getMethodsToWrap = getMethodsToWrap; /** * Patches a callback so that the current span for this trace is also ended * when the callback is invoked. */ function patchedCallback(span, callback) { const wrappedFn = (err, res) => { if (err) { if (err.code) { span.setStatus((0, utils_1._grpcStatusCodeToSpanStatus)(err.code)); span.setAttribute(semconv_1.ATTR_RPC_GRPC_STATUS_CODE, err.code); } span.setAttributes({ [AttributeNames_1.AttributeNames.GRPC_ERROR_NAME]: err.name, [AttributeNames_1.AttributeNames.GRPC_ERROR_MESSAGE]: err.message, }); } else { span.setAttribute(semconv_1.ATTR_RPC_GRPC_STATUS_CODE, semconv_1.RPC_GRPC_STATUS_CODE_VALUE_OK); } span.end(); callback(err, res); }; return api_1.context.bind(api_1.context.active(), wrappedFn); } exports.patchedCallback = patchedCallback; function patchResponseMetadataEvent(span, call, metadataCapture) { call.on('metadata', (responseMetadata) => { metadataCapture.client.captureResponseMetadata(span, responseMetadata); }); } exports.patchResponseMetadataEvent = patchResponseMetadataEvent; function patchResponseStreamEvents(span, call) { // Both error and status events can be emitted // the first one emitted set spanEnded to true let spanEnded = false; const endSpan = () => { if (!spanEnded) { span.end(); spanEnded = true; } }; api_1.context.bind(api_1.context.active(), call); call.on(events_1.errorMonitor, (err) => { if (spanEnded) { return; } span.setStatus({ code: (0, utils_1._grpcStatusCodeToOpenTelemetryStatusCode)(err.code), message: err.message, }); span.setAttributes({ [AttributeNames_1.AttributeNames.GRPC_ERROR_NAME]: err.name, [AttributeNames_1.AttributeNames.GRPC_ERROR_MESSAGE]: err.message, [semconv_1.ATTR_RPC_GRPC_STATUS_CODE]: err.code, }); endSpan(); }); call.on('status', (status) => { if (spanEnded) { return; } span.setStatus((0, utils_1._grpcStatusCodeToSpanStatus)(status.code)); span.setAttribute(semconv_1.ATTR_RPC_GRPC_STATUS_CODE, status.code); endSpan(); }); } exports.patchResponseStreamEvents = patchResponseStreamEvents; /** * Execute grpc client call. Apply completion span properties and end the * span on callback or receiving an emitted event. */ function makeGrpcClientRemoteCall(metadataCapture, original, args, metadata, self) { return (span) => { // if unary or clientStream if (!original.responseStream) { const callbackFuncIndex = args.findIndex(arg => { return typeof arg === 'function'; }); if (callbackFuncIndex !== -1) { args[callbackFuncIndex] = patchedCallback(span, args[callbackFuncIndex]); } } setSpanContext(metadata); const call = original.apply(self, args); call.on('metadata', responseMetadata => { metadataCapture.client.captureResponseMetadata(span, responseMetadata); }); // if server stream or bidi if (original.responseStream) { patchResponseStreamEvents(span, call); } return call; }; } exports.makeGrpcClientRemoteCall = makeGrpcClientRemoteCall; function getMetadataIndex(args) { // This finds an instance of Metadata among the arguments. // A possible issue that could occur is if the 'options' parameter from // the user contains an '_internal_repr' as well as a 'getMap' function, // but this is an extremely rare case. return args.findIndex((arg) => { return (arg && typeof arg === 'object' && arg['internalRepr'] && // changed from _internal_repr in grpc --> @grpc/grpc-js https://github.com/grpc/grpc-node/blob/95289edcaf36979cccf12797cc27335da8d01f03/packages/grpc-js/src/metadata.ts#L88 typeof arg.getMap === 'function'); }); } exports.getMetadataIndex = getMetadataIndex; /** * Returns the metadata argument from user provided arguments (`args`) * If no metadata is provided in `args`: adds empty metadata to `args` and returns that empty metadata */ function extractMetadataOrSplice(grpcLib, args, spliceIndex) { let metadata; const metadataIndex = getMetadataIndex(args); if (metadataIndex === -1) { // Create metadata if it does not exist metadata = new grpcLib.Metadata(); args.splice(spliceIndex, 0, metadata); } else { metadata = args[metadataIndex]; } return metadata; } exports.extractMetadataOrSplice = extractMetadataOrSplice; /** * Returns the metadata argument from user provided arguments (`args`) * Adds empty metadata to arguments if the default is used. */ function extractMetadataOrSpliceDefault(grpcClient, original, args) { return extractMetadataOrSplice(grpcClient, args, original.requestStream ? 0 : 1); } exports.extractMetadataOrSpliceDefault = extractMetadataOrSpliceDefault; /** * Inject opentelemetry trace context into `metadata` for use by another * grpc receiver * @param metadata */ function setSpanContext(metadata) { api_1.propagation.inject(api_1.context.active(), metadata, { set: (meta, k, v) => meta.set(k, v), }); } exports.setSpanContext = setSpanContext; //# sourceMappingURL=clientUtils.js.map