UNPKG

skywalking-backend-js-netease

Version:

The NodeJS agent for Apache SkyWalking

188 lines 9.27 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 }); const tslib_1 = require("tslib"); const SwPlugin_1 = require("../core/SwPlugin"); const url_1 = require("url"); 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 DummySpan_1 = tslib_1.__importDefault(require("../trace/span/DummySpan")); const AgentConfig_1 = require("../config/AgentConfig"); const CustomMonitor_1 = tslib_1.__importDefault(require("./CustomMonitor")); class HttpPlugin { constructor() { this.module = 'http'; this.versions = '*'; } install() { const http = require('http'); const https = require('https'); this.interceptClientRequest(http, 'http'); this.interceptServerRequest(http, 'http'); this.interceptClientRequest(https, 'https'); this.interceptServerRequest(https, 'https'); } interceptClientRequest(module, protocol) { const _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; const url = arguments[0]; const { host, pathname } = 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 || '/', }; const operation = pathname.replace(/\?.*$/g, ''); const method = ((_a = arguments[url instanceof url_1.URL || typeof url === 'string' ? 1 : 0]) === null || _a === void 0 ? void 0 : _a.method) || 'GET'; const 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)); const copyStatusAndWrapEmit = (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); }; const responseCB = function (res) { // may wrap callback instead of event because it procs first span.resync(); copyStatusAndWrapEmit(res); try { if (callback) return callback.apply(this, arguments); } catch (err) { span.error(err); throw err; } finally { span.async(); } }; const idxCallback = typeof arguments[2] === 'function' ? 2 : typeof arguments[1] === 'function' ? 1 : 0; const callback = arguments[idxCallback]; if (idxCallback) arguments[idxCallback] = responseCB; let arg0 = arguments[0]; const expect = arg0.headers && (arg0.headers.Expect || arg0.headers.expect); if (expect === '100-continue') { span.inject().items.forEach((item) => { arg0.headers[item.key] = item.value; }); } const req = _request.apply(this, arguments); span .inject() .items.filter((item) => expect != '100-continue') .forEach((item) => req.setHeader(item.key, item.value)); SwPlugin_1.wrapEmit(span, req, true, 'close'); req.on('timeout', () => span.log('Timeout', true)); req.on('abort', () => span.log('Abort', (span.errored = true))); if (!idxCallback) req.on('response', copyStatusAndWrapEmit); span.async(); return req; } catch (err) { span.error(err); span.stop(); throw err; } }; } interceptServerRequest(module, protocol) { const plugin = this; const _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, ...addArgs) { return _addListener.call(this, event, event === 'request' ? _sw_request : handler, ...addArgs); function _sw_request(req, res, ...reqArgs) { var _a; const carrier = ContextCarrier_1.ContextCarrier.from(req.headers || {}); const operation = (req.url || '/'); const 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)); // collect CustomMonitor_1.default(req, span); return plugin.wrapHttpResponse(span, req, res, () => handler.call(this, req, res, ...reqArgs)); } }; } wrapHttpResponse(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')); const ret = handler(); const stopper = (stopEvent) => { const stop = (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; }; const 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; } } } // noinspection JSUnusedGlobalSymbols exports.default = new HttpPlugin(); //# sourceMappingURL=HttpPlugin.js.map