@kangc/skywalking-backend-js
Version:
The NodeJS agent for Apache SkyWalking
195 lines • 9.86 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 });
var tslib_1 = require("tslib");
var SwPlugin_1 = require("../core/SwPlugin");
var url_1 = require("url");
var ContextManager_1 = tslib_1.__importDefault(require("../trace/context/ContextManager"));
var Component_1 = require("../trace/Component");
var Tag_1 = tslib_1.__importDefault(require("../Tag"));
var Tracing_pb_1 = require("../proto/language-agent/Tracing_pb");
var ContextCarrier_1 = require("../trace/context/ContextCarrier");
var DummySpan_1 = tslib_1.__importDefault(require("../trace/span/DummySpan"));
var AgentConfig_1 = require("../config/AgentConfig");
var HttpPlugin = /** @class */ (function () {
function HttpPlugin() {
this.module = 'http';
this.versions = '*';
}
HttpPlugin.prototype.install = function () {
var http = require('http');
var https = require('https');
this.interceptClientRequest(http, 'http');
this.interceptServerRequest(http, 'http');
this.interceptClientRequest(https, 'https');
this.interceptServerRequest(https, 'https');
};
HttpPlugin.prototype.interceptClientRequest = function (module, protocol) {
var _request = module.request; // BUG! this doesn't work with "import {request} from http", but haven't found an alternative yet
module.request = function () {
var _a;
var url = arguments[0];
var _b = url instanceof url_1.URL
? url
: typeof url === 'string'
? new url_1.URL(url) // TODO: this may throw invalid URL
: {
host: (url.host || url.hostname || 'unknown') + ':' + (url.port || 80),
pathname: url.path || '/',
}, host = _b.host, pathname = _b.pathname;
var operation = pathname.replace(/\?.*$/g, '');
var method = ((_a = arguments[url instanceof url_1.URL || typeof url === 'string' ? 1 : 0]) === null || _a === void 0 ? void 0 : _a.method) || 'GET';
var span = AgentConfig_1.ignoreHttpMethodCheck(method)
? DummySpan_1.default.create()
: ContextManager_1.default.current.newExitSpan(operation, Component_1.Component.HTTP);
if (span.depth)
// if we inherited from a higher level plugin then do nothing, higher level should do all the work and we don't duplicate here
return _request.apply(this, arguments);
span.start();
try {
span.component = Component_1.Component.HTTP;
span.layer = Tracing_pb_1.SpanLayer.HTTP;
span.peer = host;
span.tag(Tag_1.default.httpURL(protocol + '://' + host + pathname));
span.tag(Tag_1.default.httpMethod(method));
var copyStatusAndWrapEmit_1 = function (res) {
span.tag(Tag_1.default.httpStatusCode(res.statusCode));
if (res.statusCode && res.statusCode >= 400)
span.errored = true;
if (res.statusMessage)
span.tag(Tag_1.default.httpStatusMsg(res.statusMessage));
SwPlugin_1.wrapEmit(span, res, false);
};
var responseCB = function (res) {
// may wrap callback instead of event because it procs first
span.resync();
copyStatusAndWrapEmit_1(res);
try {
if (callback_1)
return callback_1.apply(this, arguments);
}
catch (err) {
span.error(err);
throw err;
}
finally {
span.async();
}
};
var idxCallback = typeof arguments[2] === 'function' ? 2 : typeof arguments[1] === 'function' ? 1 : 0;
var callback_1 = arguments[idxCallback];
if (idxCallback)
arguments[idxCallback] = responseCB;
var arg0_1 = arguments[0];
var expect_1 = arg0_1.headers && (arg0_1.headers.Expect || arg0_1.headers.expect);
if (expect_1 === '100-continue') {
span.inject().items.forEach(function (item) {
arg0_1.headers[item.key] = item.value;
});
}
var req_1 = _request.apply(this, arguments);
span
.inject()
.items.filter(function (item) { return expect_1 != '100-continue'; })
.forEach(function (item) { return req_1.setHeader(item.key, item.value); });
SwPlugin_1.wrapEmit(span, req_1, true, 'close');
req_1.on('timeout', function () { return span.log('Timeout', true); });
req_1.on('abort', function () { return span.log('Abort', (span.errored = true)); });
if (!idxCallback)
req_1.on('response', copyStatusAndWrapEmit_1);
span.async();
return req_1;
}
catch (err) {
span.error(err);
span.stop();
throw err;
}
};
};
HttpPlugin.prototype.interceptServerRequest = function (module, protocol) {
var plugin = this;
var _addListener = module.Server.prototype.addListener; // TODO? full event protocol support not currently implemented (prependListener(), removeListener(), etc...)
module.Server.prototype.addListener = module.Server.prototype.on = function (event, handler) {
var addArgs = [];
for (var _i = 2; _i < arguments.length; _i++) {
addArgs[_i - 2] = arguments[_i];
}
return _addListener.call.apply(_addListener, tslib_1.__spreadArrays([this, event, event === 'request' ? _sw_request : handler], addArgs));
function _sw_request(req, res) {
var _this = this;
var _a;
var reqArgs = [];
for (var _i = 2; _i < arguments.length; _i++) {
reqArgs[_i - 2] = arguments[_i];
}
var carrier = ContextCarrier_1.ContextCarrier.from(req.headers || {});
var operation = (req.url || '/').replace(/\?.*/g, '');
var span = AgentConfig_1.ignoreHttpMethodCheck((_a = req.method) !== null && _a !== void 0 ? _a : 'GET')
? DummySpan_1.default.create()
: ContextManager_1.default.current.newEntrySpan(operation, carrier);
span.component = Component_1.Component.HTTP_SERVER;
span.tag(Tag_1.default.httpURL(protocol + '://' + (req.headers.host || '') + req.url));
return plugin.wrapHttpResponse(span, req, res, function () { return handler.call.apply(handler, tslib_1.__spreadArrays([_this, req, res], reqArgs)); });
}
};
};
HttpPlugin.prototype.wrapHttpResponse = function (span, req, res, handler) {
var _a;
span.start();
try {
span.layer = Tracing_pb_1.SpanLayer.HTTP;
span.peer =
(typeof req.headers['x-forwarded-for'] === 'string' && req.headers['x-forwarded-for'].split(',').shift()) ||
(req.connection.remoteFamily === 'IPv6'
? "[" + req.connection.remoteAddress + "]:" + req.connection.remotePort
: req.connection.remoteAddress + ":" + req.connection.remotePort);
span.tag(Tag_1.default.httpMethod((_a = req.method) !== null && _a !== void 0 ? _a : 'GET'));
var ret = handler();
var stopper = function (stopEvent) {
var stop = function (emittedEvent) {
if (emittedEvent === stopEvent) {
span.tag(Tag_1.default.httpStatusCode(res.statusCode));
if (res.statusCode && res.statusCode >= 400)
span.errored = true;
if (res.statusMessage)
span.tag(Tag_1.default.httpStatusMsg(res.statusMessage));
return true;
}
};
return stop;
};
var isSub12 = process.version < 'v12';
SwPlugin_1.wrapEmit(span, req, true, isSub12 ? stopper('end') : NaN);
SwPlugin_1.wrapEmit(span, res, true, isSub12 ? NaN : stopper('close'));
span.async();
return ret;
}
catch (err) {
span.error(err);
span.stop();
throw err;
}
};
return HttpPlugin;
}());
// noinspection JSUnusedGlobalSymbols
exports.default = new HttpPlugin();
//# sourceMappingURL=HttpPlugin.js.map