@opentelemetry/instrumentation-grpc
Version:
OpenTelemetry instrumentation for `@grpc/grpc-js` rpc client and server for gRPC framework
165 lines • 6.92 kB
JavaScript
;
/*
* 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.shouldNotTraceServerCall = exports.handleUntracedServerFunction = exports.handleServerFunction = exports.CALL_SPAN_ENDED = void 0;
/**
* Symbol to include on grpc call if it has already emitted an error event.
* grpc events that emit 'error' will also emit 'finish' and so only the
* error event should be processed.
*/
const node_events_1 = require("node:events");
const api_1 = require("@opentelemetry/api");
const utils_1 = require("./utils");
const AttributeNames_1 = require("./enums/AttributeNames");
const semconv_1 = require("./semconv");
exports.CALL_SPAN_ENDED = Symbol('opentelemetry call span ended');
/**
* Handle patching for serverStream and Bidi type server handlers
*/
function serverStreamAndBidiHandler(span, call, original) {
let spanEnded = false;
const endSpan = () => {
if (!spanEnded) {
spanEnded = true;
span.end();
}
};
api_1.context.bind(api_1.context.active(), call);
call.on('finish', () => {
// @grpc/js does not expose a way to check if this call also emitted an error,
// e.g. call.status.code !== 0
if (call[exports.CALL_SPAN_ENDED]) {
return;
}
// Set the "grpc call had an error" flag
call[exports.CALL_SPAN_ENDED] = true;
span.setStatus({
code: api_1.SpanStatusCode.UNSET,
});
span.setAttribute(semconv_1.ATTR_RPC_GRPC_STATUS_CODE, semconv_1.RPC_GRPC_STATUS_CODE_VALUE_OK);
endSpan();
});
call.on(node_events_1.errorMonitor, (err) => {
if (call[exports.CALL_SPAN_ENDED]) {
return;
}
// Set the "grpc call had an error" flag
call[exports.CALL_SPAN_ENDED] = true;
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();
});
// TODO: Investigate this call/signature – it was inherited from very old
// code and the `this: {}` is highly suspicious, and likely isn't doing
// anything useful. There is probably a more precise cast we can do here.
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
return original.call({}, call);
}
/**
* Handle patching for clientStream and unary type server handlers
*/
function clientStreamAndUnaryHandler(span, call, callback, original) {
const patchedCallback = (err, value) => {
if (err) {
if (err.code) {
span.setStatus({
code: (0, utils_1._grpcStatusCodeToOpenTelemetryStatusCode)(err.code),
message: err.message,
});
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.setStatus({ code: api_1.SpanStatusCode.UNSET });
span.setAttribute(semconv_1.ATTR_RPC_GRPC_STATUS_CODE, semconv_1.RPC_GRPC_STATUS_CODE_VALUE_OK);
}
span.end();
return callback(err, value);
};
api_1.context.bind(api_1.context.active(), call);
// TODO: Investigate this call/signature – it was inherited from very old
// code and the `this: {}` is highly suspicious, and likely isn't doing
// anything useful. There is probably a more precise cast we can do here.
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
return original.call({}, call, patchedCallback);
}
/**
* Patch callback or EventEmitter provided by `originalFunc` and set appropriate `span`
* properties based on its result.
*/
function handleServerFunction(span, type, originalFunc, call, callback) {
switch (type) {
case 'unary':
case 'clientStream':
case 'client_stream':
return clientStreamAndUnaryHandler(span, call, callback, originalFunc);
case 'serverStream':
case 'server_stream':
case 'bidi':
return serverStreamAndBidiHandler(span, call, originalFunc);
default:
break;
}
}
exports.handleServerFunction = handleServerFunction;
/**
* Does not patch any callbacks or EventEmitters to omit tracing on requests
* that should not be traced.
*/
function handleUntracedServerFunction(type, originalFunc, call, callback) {
switch (type) {
case 'unary':
case 'clientStream':
case 'client_stream':
// TODO: Investigate this call/signature – it was inherited from very old
// code and the `this: {}` is highly suspicious, and likely isn't doing
// anything useful. There is probably a more precise cast we can do here.
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
return originalFunc.call({}, call, callback);
case 'serverStream':
case 'server_stream':
case 'bidi':
// TODO: Investigate this call/signature – it was inherited from very old
// code and the `this: {}` is highly suspicious, and likely isn't doing
// anything useful. There is probably a more precise cast we can do here.
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
return originalFunc.call({}, call);
default:
break;
}
}
exports.handleUntracedServerFunction = handleUntracedServerFunction;
/**
* Returns true if the server call should not be traced.
*/
function shouldNotTraceServerCall(methodName, ignoreGrpcMethods) {
const parsedName = methodName.split('/');
return (0, utils_1._methodIsIgnored)(parsedName[parsedName.length - 1] || methodName, ignoreGrpcMethods);
}
exports.shouldNotTraceServerCall = shouldNotTraceServerCall;
//# sourceMappingURL=serverUtils.js.map