UNPKG

skywalking-backend-js-netease

Version:

The NodeJS agent for Apache SkyWalking

385 lines 16.4 kB
"use strict"; /*! * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 * * http://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.TracingRequester = void 0; const tslib_1 = require("tslib"); const SwPlugin_1 = require("../core/SwPlugin"); const ContextManager_1 = tslib_1.__importDefault(require("../trace/context/ContextManager")); const Component_1 = require("../trace/Component"); const NewID_1 = tslib_1.__importDefault(require("../trace/NewID")); const Tracing_pb_1 = require("../proto/language-agent/Tracing_pb"); const ContextCarrier_1 = require("../trace/context/ContextCarrier"); const SegmentRef_1 = tslib_1.__importDefault(require("../trace/context/SegmentRef")); const AgentConfig_1 = tslib_1.__importDefault(require("../config/AgentConfig")); const Tracing_grpc_pb_1 = require("../proto/language-agent/Tracing_grpc_pb"); const Management_grpc_pb_1 = require("../proto/management/Management_grpc_pb"); const http_grpc_pb_1 = require("../proto/tunnel/http_grpc_pb"); const grpc_js_1 = require("@grpc/grpc-js"); const gTraceIdKey = 'ext.trace.id'; const GRPC = new Component_1.Component(23); function statusCode(val) { return { key: 'status_code', overridable: true, val: `${val}`, }; } const IGNORE_METHODS = [ Tracing_grpc_pb_1.TraceSegmentReportServiceService.collect.path, Management_grpc_pb_1.ManagementServiceService.keepAlive.path, Management_grpc_pb_1.ManagementServiceService.reportInstanceProperties.path, http_grpc_pb_1.HttpReportServiceService.invoke.path, ]; const CODE_STATUS_MAP = new Map(); for (const key in grpc_js_1.status) { CODE_STATUS_MAP.set(grpc_js_1.status[key], key); } const CLIENT = '/client'; const SERVER = '/server'; const REQUEST_SEND_MESSAGE_OPERATION_NAME = '/Request/sendMessage'; const REQUEST_HALF_CLOSE_OPERATION_NAME = '/Request/halfClose'; const REQUEST_RECEIVE_MESSAGE_OPERATION_NAME = '/Request/onMessage'; const REQUEST_RECEIVE_STATUS_OPERATION_NAME = '/Request/onStatus'; const REQUEST_CANCEL_OPERATION_NAME = '/Request/cancel'; class GrpcPlugin { constructor() { this.module = 'grpc'; // nodejs websocket this.versions = '*'; } install(installer) { const grpc = installer.require('grpc'); // grpc.makeGenericClientConstructor -> client.makeClientConstructor -> function Client(address, credentials, options) this.interceptClientConstruct(grpc, grpc.Client, 'call'); this.interceptServerOperation(grpc, grpc.Server, 'addService'); } interceptClientConstruct(Mod, Cls, func) { if (!Cls[func]) { return; } const _that = this; Cls._$constructor = Cls[func]; Cls[func] = function (self, address, credentials, options) { return Cls._$constructor(self, address, credentials, _that.addClientIntercepter(Mod, address, options)); }; } addClientIntercepter(Mod, address, options) { if (!options) { options = {}; } options.interceptors = options.interceptors || []; options.interceptors.push(function (opts, nextCall) { const tracer = new TracingRequester(address, opts); const interceptc = new Mod.InterceptingCall(nextCall(opts), tracer); interceptc.requester._SKYWALKING_GRPC_OBJ = tracer._SKYWALKING_GRPC_OBJ; return interceptc; }); return options; } interceptServerOperation(Mod, Cls, operation) { const _original = Cls.prototype[operation]; if (!_original) { return; } const self = this; Cls.prototype[operation] = function (service, implementation) { if (!isObject(service) || !isObject(implementation) || Object.keys(service).length === 0 || this.started) { return _original.apply(this, arguments); } Object.keys(service).forEach(name => { const attrs = service[name]; const _originalImpl = implementation[name]; const streaming = attrs.responseStream; if (_originalImpl) { const operationName = formatOperationName(attrs.path) + SERVER; implementation[name] = function () { var _a, _b; const header = (_b = (_a = arguments[0]) === null || _a === void 0 ? void 0 : _a.metadata) === null || _b === void 0 ? void 0 : _b.getMap(); const carrier = ContextCarrier_1.ContextCarrier.from(header || {}); const span = ContextManager_1.default.current.newEntrySpan(operationName, carrier, GRPC); span.component = GRPC; span.layer = Tracing_pb_1.SpanLayer.RPCFRAMEWORK; span.peer = getPeer(arguments[0].getPeer()); const innerCarrier = new ContextCarrier_1.ContextCarrier(span.context.segment.relatedTraces[0], span.context.segment.segmentId, span.id, AgentConfig_1.default.serviceName, AgentConfig_1.default.serviceInstance, span.operation, span.peer, []); // start span.start(); const hasCB = typeof arguments[arguments.length - 1] === 'function'; try { let args = arguments.length > 0 ? Array.from(arguments) : []; if (!streaming) { if (!hasCB) { args.push(function () { }); // empty function } const wrappedCallback = SwPlugin_1.wrapCallback(span, args[args.length - 1], 0); args[args.length - 1] = function () { wrappedCallback.apply(this, arguments); }; } else { // ServerWritableStreamImpl | ServerDuplexStreamImpl arguments[0]._SKYWALKING_GRPC_OBJECT = { operation: operationName, peer: span.peer, innerCarrier, }; self.inteceptServerLocal(arguments[0], 'emit'); } let ret = _originalImpl.apply(this, args); if (!streaming) { span.async(); } else { span.stop(); // stream, long call, stop span } return ret; } catch (err) { span.error(err); span.stop(); throw err; } }; } }); return _original.apply(this, arguments); }; } inteceptServerLocal(Cls, name) { const _original = Cls[name]; if (!_original) { return; } Cls[name] = function (...args) { if (!this._SKYWALKING_GRPC_OBJECT || !['data', 'end', 'error'].includes(args[0])) { return _original.apply(this, arguments); } const { operation, peer, innerCarrier } = this._SKYWALKING_GRPC_OBJECT; const event = args[0]; const span = ContextManager_1.default.current.newLocalSpan(operation + '/on/' + event); const ref = SegmentRef_1.default.fromCarrier(innerCarrier); ref.refType = 'CrossThread'; span.context.segment.relate(innerCarrier.traceId); span.refer(ref); span.start(); span.component = GRPC; span.layer = Tracing_pb_1.SpanLayer.RPCFRAMEWORK; span.peer = peer; span.tag({ key: gTraceIdKey, overridable: true, val: ref.traceId.toString(), }); try { if (event === 'error') { span.error(arguments[1]); } return _original.apply(this, arguments); } catch (err) { span.error(err); throw err; } finally { span.stop(); } }; } } /** * @param opts { method_definition: { path: "/TraceSegmentReportService/collect" } } */ class TracingRequester { constructor(address, opts) { this._SKYWALKING_GRPC_OBJ = this.getSkywalkingObj(address, opts); } getSkywalkingObj(address, opts) { const serviceName = formatOperationName(opts.method_definition.path); return { uid: new NewID_1.default(), disable: IGNORE_METHODS.includes(opts.method_definition.path), serviceName: serviceName, operationPrefix: serviceName + CLIENT, peer: address, doWithSpan: function (suffix, func, parent) { // local span const span = doLocalSpan(ContextManager_1.default.current.newLocalSpan(this.operationPrefix + suffix), this.peer, this.carrier); try { func(); span.tag(statusCode('OK')); parent === null || parent === void 0 ? void 0 : parent.tag(statusCode('OK')); } catch (err) { span.error(err); parent === null || parent === void 0 ? void 0 : parent.error(err); throw err; } finally { span.stop(); if (parent) { parent.stop(); this.span = undefined; } } } }; } start(metadata, listener, next) { const self = this._SKYWALKING_GRPC_OBJ; if (!self || self.disable) { next(metadata, listener); return; } // exist span const span = ContextManager_1.default.current.newExitSpan(self.serviceName, GRPC); span.layer = Tracing_pb_1.SpanLayer.RPCFRAMEWORK; span.component = GRPC; span.peer = self.peer; span.start(); const cloned = metadata.clone(); span.inject().items.forEach((item) => cloned.set(item.key, item.value)); const carrier = new ContextCarrier_1.ContextCarrier(span.context.segment.relatedTraces[0], span.context.segment.segmentId, span.id, AgentConfig_1.default.serviceName, AgentConfig_1.default.serviceInstance, span.operation, span.peer, []); self.span = span; self.carrier = carrier; span.tag({ key: gTraceIdKey, overridable: true, val: span.context.segment.relatedTraces[0].toString(), }); span.async(); // asnyc start span try { next(cloned, new (class { onReceiveStatus(status, next) { var _a, _b, _c, _d; // local span const span = doLocalSpan(ContextManager_1.default.current.newLocalSpan(self.operationPrefix + REQUEST_RECEIVE_STATUS_OPERATION_NAME), self.peer, self.carrier); switch (status.code) { case status.OK: break; case status.UNKNOWN: case status.INTERNAL: if (status.details) { span.log('event', 'error'); span.log('message', status.details); (_a = self.span) === null || _a === void 0 ? void 0 : _a.log('event', 'error'); (_b = self.span) === null || _b === void 0 ? void 0 : _b.log('message', status.details); } break; default: if (status.details) { span.log('message', status.details); (_c = self.span) === null || _c === void 0 ? void 0 : _c.log('message', status.details); } } span.tag(statusCode(findCode(status.code))); (_d = self.span) === null || _d === void 0 ? void 0 : _d.tag(statusCode(findCode(status.code))); try { next(status); } catch (err) { span.error(err); if (self.span) { self.span.error(err); self.span.stop(); self.span = undefined; } throw err; } finally { span.stop(); } } onReceiveMessage(message, next) { self.doWithSpan(REQUEST_RECEIVE_MESSAGE_OPERATION_NAME, () => next(message), self.span); } })()); } catch (err) { span.error(err); throw err; } } sendMessage(message, next) { const self = this._SKYWALKING_GRPC_OBJ; if (!self || self.disable) { next(message); return; } self.doWithSpan(REQUEST_SEND_MESSAGE_OPERATION_NAME, () => next(message)); } halfClose(next) { const self = this._SKYWALKING_GRPC_OBJ; if (!self || self.disable) { next(); return; } self.doWithSpan(REQUEST_HALF_CLOSE_OPERATION_NAME, next); } cancel(next) { const self = this._SKYWALKING_GRPC_OBJ; if (!self || self.disable) { next(); return; } self.doWithSpan(REQUEST_CANCEL_OPERATION_NAME, next, self.span); } } exports.TracingRequester = TracingRequester; function doLocalSpan(span, peer, carrier) { // local span span.layer = Tracing_pb_1.SpanLayer.RPCFRAMEWORK; span.component = GRPC; span.peer = peer; const ref = SegmentRef_1.default.fromCarrier(carrier); ref.refType = 'CrossThread'; span.context.segment.relate(carrier === null || carrier === void 0 ? void 0 : carrier.traceId); span.refer(ref); span.start(); span.tag({ key: gTraceIdKey, overridable: true, val: ref.traceId.toString(), }); span.async(); return span; } function formatOperationName(name) { return formatServiceName(name) + '.' + formatMethodName(name); } function formatServiceName(name) { return name.substr(0, name.lastIndexOf('/')); } function formatMethodName(name) { const method = name.substr(name.lastIndexOf('/') + 1); return method[0].toLocaleLowerCase() + method.substring(1); } function findCode(code) { return CODE_STATUS_MAP.get(code) || 'UNKNOWN'; } function getPeer(peer) { if (!peer) { return ''; } const t = peer.split(':'); return t.length >= 2 ? t.slice(peer.length - 2).join(':') : peer; } function isObject(thing) { return (typeof thing === 'object' || typeof thing === 'function') && thing !== null; } exports.default = new GrpcPlugin(); //# sourceMappingURL=GrpcPlugin.js.map