UNPKG

@kangc/skywalking-backend-js

Version:

The NodeJS agent for Apache SkyWalking

195 lines 9.86 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 }); 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