diff --git a/README.md b/README.md index 1ff9800..de6acf0 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ Environment Variable | Description | Default | `SW_AGENT_DISABLE_PLUGINS` | Comma-delimited list of plugins to disable in the plugins directory (e.g. "mysql", "express"). | `` | | `SW_IGNORE_SUFFIX` | The suffices of endpoints that will be ignored (not traced), comma separated | `.jpg,.jpeg,.js,.css,.png,.bmp,.gif,.ico,.mp3,.mp4,.html,.svg` | | `SW_TRACE_IGNORE_PATH` | The paths of endpoints that will be ignored (not traced), comma separated | `` | +| `SW_HTTP_IGNORE_METHOD` | Comma-delimited list of http methods to ignore (GET, POST, HEAD, OPTIONS, etc...) | `` | | `SW_SQL_TRACE_PARAMETERS` | If set to 'true' then SQL query parameters will be included | `false` | | `SW_SQL_PARAMETERS_MAX_LENGTH` | The maximum string length of SQL parameters to log | `512` | | `SW_MONGO_TRACE_PARAMETERS` | If set to 'true' then mongodb query parameters will be included | `false` | diff --git a/src/config/AgentConfig.ts b/src/config/AgentConfig.ts index eb74b0d..59c2c04 100644 --- a/src/config/AgentConfig.ts +++ b/src/config/AgentConfig.ts @@ -29,6 +29,7 @@ export type AgentConfig = { disablePlugins?: string; ignoreSuffix?: string; traceIgnorePath?: string; + httpIgnoreMethod?: string; sqlTraceParameters?: boolean; sqlParametersMaxLength?: number; mongoTraceParameters?: boolean; @@ -36,6 +37,7 @@ export type AgentConfig = { // the following is internal state computed from config values reDisablePlugins?: RegExp; reIgnoreOperation?: RegExp; + reHttpIgnoreMethod?: RegExp; }; export function finalizeConfig(config: AgentConfig): void { @@ -53,9 +55,10 @@ export function finalizeConfig(config: AgentConfig): void { ).join('|') + ')$'; // replaces "," config.reIgnoreOperation = RegExp(`${ignoreSuffix}|${ignorePath}`); + config.reHttpIgnoreMethod = RegExp(`^(?:${config.httpIgnoreMethod!.split(',').map((s) => s.trim()).join('|')})$`, 'i'); } -export default { +const _config = { serviceName: process.env.SW_AGENT_NAME || 'your-nodejs-service', serviceInstance: process.env.SW_AGENT_INSTANCE || @@ -70,10 +73,18 @@ export default { disablePlugins: process.env.SW_AGENT_DISABLE_PLUGINS || '', ignoreSuffix: process.env.SW_IGNORE_SUFFIX ?? '.jpg,.jpeg,.js,.css,.png,.bmp,.gif,.ico,.mp3,.mp4,.html,.svg', traceIgnorePath: process.env.SW_TRACE_IGNORE_PATH || '', + httpIgnoreMethod: process.env.SW_HTTP_IGNORE_METHOD || '', sqlTraceParameters: (process.env.SW_SQL_TRACE_PARAMETERS || '').toLowerCase() === 'true', sqlParametersMaxLength: Math.trunc(Math.max(0, Number(process.env.SW_SQL_PARAMETERS_MAX_LENGTH))) || 512, mongoTraceParameters: (process.env.SW_MONGO_TRACE_PARAMETERS || '').toLowerCase() === 'true', mongoParametersMaxLength: Math.trunc(Math.max(0, Number(process.env.SW_MONGO_PARAMETERS_MAX_LENGTH))) || 512, reDisablePlugins: RegExp(''), // temporary placeholder so Typescript doesn't throw a fit reIgnoreOperation: RegExp(''), + reHttpIgnoreMethod: RegExp(''), }; + +export default _config; + +export function ignoreHttpMethodCheck(method: string): boolean { + return Boolean(method.match(_config.reHttpIgnoreMethod)); +} diff --git a/src/index.ts b/src/index.ts index 1dfae71..3f455b8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -33,12 +33,14 @@ class Agent { return; } + if (this.started) { + logger.warn('SkyWalking agent started more than once, subsequent options to start ignored.'); + return; + } + Object.assign(config, options); finalizeConfig(config); - if (this.started) { - throw new Error('SkyWalking agent is already started and can only be started once.'); - } logger.debug('Starting SkyWalking agent'); this.started = true; diff --git a/src/plugins/AxiosPlugin.ts b/src/plugins/AxiosPlugin.ts index 1684c99..1eedd67 100644 --- a/src/plugins/AxiosPlugin.ts +++ b/src/plugins/AxiosPlugin.ts @@ -17,12 +17,14 @@ * */ -import SwPlugin, {wrapPromise} from '../core/SwPlugin'; +import SwPlugin from '../core/SwPlugin'; import { URL } from 'url'; import ContextManager from '../trace/context/ContextManager'; import { Component } from '../trace/Component'; import Tag from '../Tag'; import { SpanLayer } from '../proto/language-agent/Tracing_pb'; +import DummySpan from '../trace/span/DummySpan'; +import { ignoreHttpMethodCheck } from '../config/AgentConfig'; import PluginInstaller from '../core/PluginInstaller'; class AxiosPlugin implements SwPlugin { @@ -44,7 +46,10 @@ class AxiosPlugin implements SwPlugin { config = url ? {...url} : {}; const {origin, host, pathname: operation} = new URL(config.url); // TODO: this may throw invalid URL - const span = ContextManager.current.newExitSpan(operation, host, Component.AXIOS, Component.HTTP); + const method = (config.method || 'GET').toUpperCase(); + const span = ignoreHttpMethodCheck(method) + ? DummySpan.create() + : ContextManager.current.newExitSpan(operation, host, Component.AXIOS, Component.HTTP); span.start(); @@ -56,7 +61,7 @@ class AxiosPlugin implements SwPlugin { span.peer = host; span.tag(Tag.httpURL(origin + operation)); - span.tag(Tag.httpMethod((config.method || 'GET').toUpperCase())); + span.tag(Tag.httpMethod(method)); span.inject().items.forEach((item) => config.headers[item.key] = item.value); diff --git a/src/plugins/ExpressPlugin.ts b/src/plugins/ExpressPlugin.ts index 10ae4f9..d9b25f0 100644 --- a/src/plugins/ExpressPlugin.ts +++ b/src/plugins/ExpressPlugin.ts @@ -22,8 +22,9 @@ import { IncomingMessage, ServerResponse } from 'http'; import ContextManager from '../trace/context/ContextManager'; import { Component } from '../trace/Component'; import Tag from '../Tag'; -import EntrySpan from '../trace/span/EntrySpan'; import { ContextCarrier } from '../trace/context/ContextCarrier'; +import DummySpan from '../trace/span/DummySpan'; +import { ignoreHttpMethodCheck } from '../config/AgentConfig'; import PluginInstaller from '../core/PluginInstaller'; import HttpPlugin from './HttpPlugin'; @@ -42,7 +43,9 @@ class ExpressPlugin implements SwPlugin { router.handle = function(req: IncomingMessage, res: ServerResponse, next: any) { const carrier = ContextCarrier.from((req as any).headers || {}); const operation = (req.url || '/').replace(/\?.*/g, ''); - const span: EntrySpan = ContextManager.current.newEntrySpan(operation, carrier, Component.HTTP_SERVER) as EntrySpan; + const span = ignoreHttpMethodCheck(req.method ?? 'GET') + ? DummySpan.create() + : ContextManager.current.newEntrySpan(operation, carrier, Component.HTTP_SERVER); span.component = Component.EXPRESS; diff --git a/src/plugins/HttpPlugin.ts b/src/plugins/HttpPlugin.ts index c9936e3..22c37f4 100644 --- a/src/plugins/HttpPlugin.ts +++ b/src/plugins/HttpPlugin.ts @@ -24,9 +24,10 @@ import ContextManager from '../trace/context/ContextManager'; import { Component } from '../trace/Component'; import Tag from '../Tag'; import Span from '../trace/span/Span'; -import ExitSpan from '../trace/span/ExitSpan'; import { SpanLayer } from '../proto/language-agent/Tracing_pb'; import { ContextCarrier } from '../trace/context/ContextCarrier'; +import DummySpan from '../trace/span/DummySpan'; +import { ignoreHttpMethodCheck } from '../config/AgentConfig'; class HttpPlugin implements SwPlugin { readonly module = 'http'; @@ -59,7 +60,10 @@ class HttpPlugin implements SwPlugin { }; const operation = pathname.replace(/\?.*$/g, ''); - const span: ExitSpan = ContextManager.current.newExitSpan(operation, host, Component.HTTP) as ExitSpan; + const method = arguments[url instanceof URL || typeof url === 'string' ? 1 : 0]?.method || 'GET'; + const span = ignoreHttpMethodCheck(method) + ? DummySpan.create() + : ContextManager.current.newExitSpan(operation, host, 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); @@ -72,7 +76,7 @@ class HttpPlugin implements SwPlugin { span.peer = host; span.tag(Tag.httpURL(protocol + '://' + host + pathname)); - span.tag(Tag.httpMethod(arguments[url instanceof URL || typeof url === 'string' ? 1 : 0]?.method || 'GET')); + span.tag(Tag.httpMethod(method)); const copyStatusAndWrapEmit = (res: any) => { span.tag(Tag.httpStatusCode(res.statusCode)); @@ -146,7 +150,9 @@ class HttpPlugin implements SwPlugin { function _sw_request(this: any, req: IncomingMessage, res: ServerResponse, ...reqArgs: any[]) { const carrier = ContextCarrier.from((req as any).headers || {}); const operation = (req.url || '/').replace(/\?.*/g, ''); - const span = ContextManager.current.newEntrySpan(operation, carrier); + const span = ignoreHttpMethodCheck(req.method ?? 'GET') + ? DummySpan.create() + : ContextManager.current.newEntrySpan(operation, carrier); span.component = Component.HTTP_SERVER; @@ -168,7 +174,7 @@ class HttpPlugin implements SwPlugin { ? `[${req.connection.remoteAddress}]:${req.connection.remotePort}` : `${req.connection.remoteAddress}:${req.connection.remotePort}`); - span.tag(Tag.httpMethod(req.method)); + span.tag(Tag.httpMethod(req.method ?? 'GET')); const ret = handler(); diff --git a/src/trace/context/DummyContext.ts b/src/trace/context/DummyContext.ts index f08c93d..2d5c4fb 100644 --- a/src/trace/context/DummyContext.ts +++ b/src/trace/context/DummyContext.ts @@ -22,15 +22,10 @@ import Span from '../../trace/span/Span'; import DummySpan from '../../trace/span/DummySpan'; import Segment from '../../trace/context/Segment'; import { Component } from '../../trace/Component'; -import { SpanType } from '../../proto/language-agent/Tracing_pb'; import { ContextCarrier } from './ContextCarrier'; export default class DummyContext implements Context { - span: Span = new DummySpan({ - context: this, - operation: '', - type: SpanType.LOCAL, - }); + span: Span = DummySpan.create(this); segment: Segment = new Segment(); nSpans = 0; diff --git a/src/trace/context/SpanContext.ts b/src/trace/context/SpanContext.ts index 29241cd..2436bd0 100644 --- a/src/trace/context/SpanContext.ts +++ b/src/trace/context/SpanContext.ts @@ -19,7 +19,6 @@ import config from '../../config/AgentConfig'; import Context from '../../trace/context/Context'; -import DummyContext from '../../trace/context/DummyContext'; import Span from '../../trace/span/Span'; import DummySpan from '../../trace/span/DummySpan'; import Segment from '../../trace/context/Segment'; @@ -53,13 +52,8 @@ export default class SpanContext implements Context { } ignoreCheck(operation: string, type: SpanType): Span | undefined { - if (operation.match(config.reIgnoreOperation)) { - return new DummySpan({ - context: new DummyContext(), - operation: '', - type, - }); - } + if (operation.match(config.reIgnoreOperation)) + return DummySpan.create(); return undefined; } diff --git a/src/trace/span/DummySpan.ts b/src/trace/span/DummySpan.ts index 8657bfb..c2b1571 100644 --- a/src/trace/span/DummySpan.ts +++ b/src/trace/span/DummySpan.ts @@ -19,8 +19,19 @@ import Span from '../../trace/span/Span'; import { ContextCarrier } from '../context/ContextCarrier'; +import Context from '../context/Context'; +import { SpanType } from '../../proto/language-agent/Tracing_pb'; +import DummyContext from '../context/DummyContext'; export default class DummySpan extends Span { + static create(context?: Context): DummySpan { + return new DummySpan({ + context: context ?? new DummyContext(), + operation: '', + type: SpanType.LOCAL, + }); + } + inject(): ContextCarrier { return new ContextCarrier(); } diff --git a/src/trace/span/EntrySpan.ts b/src/trace/span/EntrySpan.ts index 511b0a5..3532c75 100644 --- a/src/trace/span/EntrySpan.ts +++ b/src/trace/span/EntrySpan.ts @@ -17,13 +17,13 @@ * */ -import StackedSpan from '../../trace/span/StackedSpan'; +import Span from '../../trace/span/Span'; import { SpanCtorOptions } from './Span'; import SegmentRef from '../../trace/context/SegmentRef'; import { SpanType } from '../../proto/language-agent/Tracing_pb'; import { ContextCarrier } from '../context/ContextCarrier'; -export default class EntrySpan extends StackedSpan { +export default class EntrySpan extends Span { constructor(options: SpanCtorOptions) { super( Object.assign(options, { diff --git a/src/trace/span/ExitSpan.ts b/src/trace/span/ExitSpan.ts index 745e4c4..a120366 100644 --- a/src/trace/span/ExitSpan.ts +++ b/src/trace/span/ExitSpan.ts @@ -17,14 +17,14 @@ * */ -import StackedSpan from '../../trace/span/StackedSpan'; +import Span from '../../trace/span/Span'; import { SpanCtorOptions } from './Span'; import config from '../../config/AgentConfig'; import { SpanType } from '../../proto/language-agent/Tracing_pb'; import { ContextCarrier } from '../context/ContextCarrier'; import ContextManager from '../context/ContextManager'; -export default class ExitSpan extends StackedSpan { +export default class ExitSpan extends Span { constructor(options: SpanCtorOptions) { super( Object.assign(options, { diff --git a/src/trace/span/Span.ts b/src/trace/span/Span.ts index 2899a45..a240c8a 100644 --- a/src/trace/span/Span.ts +++ b/src/trace/span/Span.ts @@ -50,6 +50,7 @@ export default abstract class Span { operation: string; layer = SpanLayer.UNKNOWN; component = Component.UNKNOWN; + depth = 0; inherit?: Component; readonly tags: Tag[] = []; @@ -74,12 +75,15 @@ export default abstract class Span { } start(): void { - this.startTime = new Date().getTime(); - this.context.start(this); + if (++this.depth === 1) { + this.startTime = new Date().getTime(); + this.context.start(this); + } } stop(): void { - this.context.stop(this); + if (--this.depth === 0) + this.context.stop(this); } async(): void { diff --git a/src/trace/span/StackedSpan.ts b/src/trace/span/StackedSpan.ts deleted file mode 100644 index 77e3243..0000000 --- a/src/trace/span/StackedSpan.ts +++ /dev/null @@ -1,41 +0,0 @@ -/*! - * - * 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. - * - */ - -import Span, { SpanCtorOptions } from '../../trace/span/Span'; -import { SpanType } from '../../proto/language-agent/Tracing_pb'; - -export default class StackedSpan extends Span { - depth = 0; - - constructor(options: SpanCtorOptions & { type: SpanType }) { - super(options); - } - - start(): void { - if (++this.depth === 1) { - super.start(); - } - } - - stop(): void { - if (--this.depth === 0) { - super.stop(); - } - } -}