skywalking-backend-js-netease
Version:
The NodeJS agent for Apache SkyWalking
244 lines • 11.6 kB
JavaScript
"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 });
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 Tag_1 = tslib_1.__importDefault(require("../Tag"));
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 DummySpan_1 = tslib_1.__importDefault(require("../trace/span/DummySpan"));
const AgentConfig_1 = require("../config/AgentConfig");
const AgentConfig_2 = tslib_1.__importDefault(require("../config/AgentConfig"));
const CustomMonitor_1 = tslib_1.__importStar(require("./CustomMonitor"));
const fs = tslib_1.__importStar(require("fs"));
const path = tslib_1.__importStar(require("path"));
const gTraceIdKey = 'ext.trace.id';
const operantionSuffix = 'operation.name.suffix';
const persistConnKey = 'conn.persist.count';
class WsPlugin {
constructor() {
this.module = 'ws'; // nodejs websocket
this.versions = '*';
}
getVersion(installer) {
let indexPath = installer.resolve(this.module);
let packageSJonStr = fs.readFileSync(`${path.dirname(indexPath)}${path.sep}package.json`, { encoding: 'utf-8' });
const pkg = JSON.parse(packageSJonStr);
return pkg.version;
}
install(installer) {
const ws = installer.require('ws');
// Server.handleUpgrade(req: http.IncomingMessage, socket: (net.Socket|tls.Socket), head: Buffer, cb: Function)
this.interceptOperation(ws.Server, 'handleUpgrade');
this.interceptLocal(ws.Server, 'emit', ['connection'], 1);
this.interceptLocal(ws, 'emit');
this.interceptExit(ws, 'send');
}
interceptOperation(Cls, operation) {
const _original = Cls.prototype[operation];
if (!_original) {
return;
}
Cls.prototype[operation] = function (req, socket, head, cb) {
var _a, _b;
const carrier = ContextCarrier_1.ContextCarrier.from(req.headers || {});
const operationName = (socket.encrypted ? 'wss:' : 'ws:') + (req.url || '/').replace(/\?.*/g, '');
const method = req.method || 'GET';
const span = AgentConfig_1.ignoreHttpMethodCheck(method)
? DummySpan_1.default.create()
: ContextManager_1.default.current.newEntrySpan(operationName, carrier, Component_1.Component.HTTP_SERVER);
const innerCarrier = new ContextCarrier_1.ContextCarrier(span.context.segment.relatedTraces[0], span.context.segment.segmentId, span.id, AgentConfig_2.default.serviceName, AgentConfig_2.default.serviceInstance, (socket.encrypted ? 'wss:' : 'ws:') + (req.url || '/'), span.peer, []);
span.start();
span.component = Component_1.Component.HTTP_SERVER;
span.tag(Tag_1.default.httpMethod(method));
span.tag(Tag_1.default.httpURL((socket.encrypted ? 'wss:' : 'ws:') + (req.url || '/')));
span.layer = Tracing_pb_1.SpanLayer.HTTP;
span.peer =
(typeof req.headers['x-forwarded-for'] === 'string' && req.headers['x-forwarded-for'].split(',').shift()) ||
(socket.remoteFamily === 'IPv6'
? `[${socket.remoteAddress}]:${socket.remotePort}`
: `${socket.remoteAddress}:${socket.remotePort}`);
span.tag({ key: gTraceIdKey, overridable: true, val: span.context.segment.relatedTraces[0].toString() });
// collect
const customId = CustomMonitor_1.default(req, span);
const hasCB = typeof arguments[arguments.length - 1] === 'function';
const self = this;
try {
if (hasCB) {
const wrappedCallback = SwPlugin_1.wrapCallback(span, cb, false);
// in case of immediate synchronous callback
arguments[arguments.length - 1] = function () {
arguments[0]._SKYWALKING_WS_OBJECT = {
operationName,
method,
url: (socket.encrypted ? 'wss:' : 'ws:') + (req.url || '/'),
peer: span.peer,
innerCarrier,
startTime: span.startTime,
wss: self,
customId,
};
wrappedCallback.apply(this, arguments);
};
}
let ret = _original.apply(this, arguments);
span.tag({
key: persistConnKey,
overridable: true,
val: `${((_a = this.clients) === null || _a === void 0 ? void 0 : _a.size) || 0}`,
});
if (!hasCB) {
if (ret && typeof ret.then === 'function') {
// generic Promise check
ret = SwPlugin_1.wrapPromise(span, ret);
}
else {
// no callback passed in and no Promise or Cursor returned, play it safe
span.stop();
return ret;
}
}
span.async();
return ret;
}
catch (err) {
span.error(err);
span.tag({
key: persistConnKey,
overridable: true,
val: `${((_b = this.clients) === null || _b === void 0 ? void 0 : _b.size) || 0}`,
});
span.stop();
throw err;
}
};
}
interceptLocal(Cls, operation, events, objIdx) {
const _original = Cls.prototype[operation];
if (!_original) {
return;
}
Cls.prototype[operation] = function (...args) {
var _a;
const event = args[0];
if (events && !events.includes(event)) {
return _original.apply(this, arguments);
}
const self = objIdx ? args[objIdx] : this;
if (!self._SKYWALKING_WS_OBJECT) {
return _original.apply(this, arguments);
}
const { operationName, method, url, peer, innerCarrier, startTime, wss, customId } = self._SKYWALKING_WS_OBJECT;
const span = AgentConfig_1.ignoreHttpMethodCheck(method)
? DummySpan_1.default.create()
: ContextManager_1.default.current.newEntrySpan(operationName);
const ref = SegmentRef_1.default.fromCarrier(innerCarrier);
ref.refType = 'CrossThread';
span.context.segment.relate(innerCarrier.traceId);
span.refer(ref);
span.start();
span.component = Component_1.Component.HTTP_SERVER;
span.tag(Tag_1.default.httpMethod(method));
span.tag(Tag_1.default.httpURL(url));
span.layer = Tracing_pb_1.SpanLayer.HTTP;
span.peer = peer;
span.tag({ key: gTraceIdKey, overridable: true, val: ref.traceId.toString() });
span.tag({ key: operantionSuffix, overridable: true, val: ' on ' + event });
span.tag({ key: 'root.start.time', overridable: true, val: `${startTime}` });
CustomMonitor_1.tagCustomIds(customId, span);
try {
if (event === 'error') {
span.error(arguments[1]);
}
return _original.apply(this, arguments);
}
catch (err) {
span.error(err);
throw err;
}
finally {
span.tag({ key: persistConnKey, overridable: true, val: `${((_a = wss === null || wss === void 0 ? void 0 : wss.clients) === null || _a === void 0 ? void 0 : _a.size) || 0}` });
span.stop();
}
};
}
interceptExit(Cls, operation) {
const _original = Cls.prototype[operation];
if (!_original) {
return;
}
Cls.prototype[operation] = function (...args) {
const self = this;
if (!self._SKYWALKING_WS_OBJECT) {
return _original.apply(this, arguments);
}
const { operationName, method, url, peer, innerCarrier, startTime, customId } = self._SKYWALKING_WS_OBJECT;
const span = AgentConfig_1.ignoreHttpMethodCheck(method)
? DummySpan_1.default.create()
: ContextManager_1.default.current.newEntrySpan(operationName);
const ref = SegmentRef_1.default.fromCarrier(innerCarrier);
ref.refType = 'CrossThread';
span.context.segment.relate(innerCarrier.traceId);
span.refer(ref);
span.start();
span.component = Component_1.Component.HTTP;
span.tag(Tag_1.default.httpMethod(method));
span.tag(Tag_1.default.httpURL(url));
span.layer = Tracing_pb_1.SpanLayer.HTTP;
span.peer = peer;
span.tag({ key: operantionSuffix, overridable: true, val: ' on ' + operation });
span.tag({ key: 'root.start.time', overridable: true, val: `${startTime}` });
CustomMonitor_1.tagCustomIds(customId, span);
const hasCB = typeof arguments[arguments.length - 1] === 'function';
try {
if (hasCB) {
const wrappedCallback = SwPlugin_1.wrapCallback(span, arguments[arguments.length - 1], 0);
arguments[arguments.length - 1] = function () {
// in case of immediate synchronous callback
wrappedCallback.apply(this, arguments);
};
}
let ret = _original.apply(this, arguments);
if (!hasCB) {
if (ret && typeof ret.then === 'function') {
// generic Promise check
ret = SwPlugin_1.wrapPromise(span, ret);
}
else {
span.stop();
return ret;
}
}
span.async();
return ret;
}
catch (err) {
span.error(err);
span.stop();
throw err;
}
};
}
}
exports.default = new WsPlugin();
//# sourceMappingURL=WsPlugin.js.map