skywalking-backend-js-netease
Version:
The NodeJS agent for Apache SkyWalking
385 lines • 16.4 kB
JavaScript
;
/*!
*
* 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