You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by ke...@apache.org on 2020/12/08 03:23:39 UTC
[skywalking-nodejs] branch master updated: Express plugin,
make spans actually work (#9)
This is an automated email from the ASF dual-hosted git repository.
kezhenxu94 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking-nodejs.git
The following commit(s) were added to refs/heads/master by this push:
new eb26b21 Express plugin, make spans actually work (#9)
eb26b21 is described below
commit eb26b218735ee64935d5708fc527ba92403d9290
Author: Tomasz Pytel <to...@gmail.com>
AuthorDate: Tue Dec 8 00:09:32 2020 -0300
Express plugin, make spans actually work (#9)
* [WIP] express plugin, make spans actually work
* [Fix] Swapped inject() and extract()
* [Plugin] Express plugin working
* added express component value
* [Plugin] added https support to HttpPlugin
* [Enhancement] added ignore suffix and path
* [Plugin] axios
---
src/agent/Buffer.ts | 4 +-
src/config/AgentConfig.ts | 25 ++++-
src/core/PluginInstaller.ts | 37 ++++---
src/index.ts | 3 +-
src/plugins/AxiosPlugin.ts | 121 +++++++++++++++++++++++
src/plugins/ExpressPlugin.ts | 103 ++++++++++++++++++++
src/plugins/HttpPlugin.ts | 177 ++++++++++++++++++----------------
src/trace/Component.ts | 1 +
src/trace/context/SpanContext.ts | 96 +++++++++++++-----
src/trace/span/DummySpan.ts | 11 ++-
src/trace/span/EntrySpan.ts | 4 +-
src/trace/span/ExitSpan.ts | 2 +-
src/trace/span/Span.ts | 9 +-
src/trace/span/StackedSpan.ts | 7 +-
tests/plugins/http/expected.data.yaml | 4 +-
15 files changed, 464 insertions(+), 140 deletions(-)
diff --git a/src/agent/Buffer.ts b/src/agent/Buffer.ts
index a1cda3b..8f4f061 100644
--- a/src/agent/Buffer.ts
+++ b/src/agent/Buffer.ts
@@ -49,6 +49,4 @@ class Buffer {
}
}
-export default new Buffer(
- Number.isSafeInteger(config.maxBufferSize) ? Number.parseInt(config.maxBufferSize, 10) : 1000,
-);
+export default new Buffer(config.maxBufferSize);
diff --git a/src/config/AgentConfig.ts b/src/config/AgentConfig.ts
index 9b5a66e..cdae60f 100644
--- a/src/config/AgentConfig.ts
+++ b/src/config/AgentConfig.ts
@@ -25,8 +25,27 @@ export type AgentConfig = {
collectorAddress?: string;
authorization?: string;
maxBufferSize?: number;
+ ignoreSuffix?: string;
+ traceIgnorePath?: string;
+ // the following is internal state computed from config values
+ reIgnoreOperation?: RegExp;
};
+export function finalizeConfig(config: AgentConfig): void {
+ const escapeRegExp = (s: string) => s.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1");
+
+ const ignoreSuffix =`^.+(?:${config.ignoreSuffix!.split(',').map(s => escapeRegExp(s.trim())).join('|')})$`;
+ const ignorePath = '^(?:' + config.traceIgnorePath!.split(',').map(
+ s1 => s1.trim().split('**').map(
+ s2 => s2.split('*').map(
+ s3 => s3.split('?').map(escapeRegExp).join('[^/]') // replaces "**"
+ ).join('[^/]*') // replaces "*"
+ ).join('(?:(?:[^/]+\.)*[^/]+)?') // replaces "?"
+ ).join('|') + ')$'; // replaces ","
+
+ config.reIgnoreOperation = RegExp(`${ignoreSuffix}|${ignorePath}`);
+}
+
export default {
serviceName: process.env.SW_AGENT_NAME || 'your-nodejs-service',
serviceInstance:
@@ -36,5 +55,9 @@ export default {
})(),
collectorAddress: process.env.SW_AGENT_COLLECTOR_BACKEND_SERVICES || '127.0.0.1:11800',
authorization: process.env.SW_AGENT_AUTHENTICATION,
- maxBufferSize: process.env.SW_AGENT_MAX_BUFFER_SIZE || '1000',
+ maxBufferSize: Number.isSafeInteger(process.env.SW_AGENT_MAX_BUFFER_SIZE) ?
+ Number.parseInt(process.env.SW_AGENT_MAX_BUFFER_SIZE as string, 10) : 1000,
+ 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 || '',
+ reIgnoreOperation: RegExp(''), // temporary placeholder so Typescript doesn't throw a fit
};
diff --git a/src/core/PluginInstaller.ts b/src/core/PluginInstaller.ts
index 887cd6f..80ce569 100644
--- a/src/core/PluginInstaller.ts
+++ b/src/core/PluginInstaller.ts
@@ -28,16 +28,16 @@ const logger = createLogger(__filename);
let topModule = module;
for (; topModule.parent; topModule = topModule.parent);
-const topResolve = (request: string) => (module.constructor as any)._resolveFilename(request, topModule)
-
class PluginInstaller {
pluginDir: string;
+ require: (name: string) => any = topModule.require.bind(topModule);
+ resolve = (request: string) => (module.constructor as any)._resolveFilename(request, topModule);
constructor() {
this.pluginDir = path.resolve(__dirname, '..', 'plugins');
}
- private isBuiltIn = (module: string): boolean => topResolve(module) === module;
+ private isBuiltIn = (module: string): boolean => this.resolve(module) === module;
private checkModuleVersion = (plugin: SwPlugin): { version: string; isSupported: boolean } => {
try {
@@ -54,8 +54,8 @@ class PluginInstaller {
};
}
- const packageJsonPath = topResolve(`${plugin.module}/package.json`);
- const version = topModule.require(packageJsonPath).version;
+ const packageJsonPath = this.resolve(`${plugin.module}/package.json`);
+ const version = this.require(packageJsonPath).version;
if (!semver.satisfies(version, plugin.versions)) {
logger.info(`Plugin ${plugin.module} ${version} doesn't satisfy the supported version ${plugin.versions}`);
@@ -74,18 +74,29 @@ class PluginInstaller {
fs.readdirSync(this.pluginDir)
.filter((file) => !(file.endsWith('.d.ts') || file.endsWith('.js.map')))
.forEach((file) => {
- const plugin = require(path.join(this.pluginDir, file)).default as SwPlugin;
+ let plugin;
+ const pluginFile = path.join(this.pluginDir, file);
- const { isSupported, version } = this.checkModuleVersion(plugin);
+ try {
+ plugin = require(pluginFile).default as SwPlugin;
+ const { isSupported, version } = this.checkModuleVersion(plugin);
- if (!isSupported) {
- logger.info(`Plugin ${plugin.module} ${version} doesn't satisfy the supported version ${plugin.versions}`);
- return;
- }
+ if (!isSupported) {
+ logger.info(`Plugin ${plugin.module} ${version} doesn't satisfy the supported version ${plugin.versions}`);
+ return;
+ }
- logger.info(`Installing plugin ${plugin.module} ${plugin.versions}`);
+ logger.info(`Installing plugin ${plugin.module} ${plugin.versions}`);
- plugin.install();
+ plugin.install();
+
+ } catch (e) {
+ if (plugin) {
+ logger.error(`Error installing plugin ${plugin.module} ${plugin.versions}`);
+ } else {
+ logger.error(`Error processing plugin ${pluginFile}`);
+ }
+ }
});
}
}
diff --git a/src/index.ts b/src/index.ts
index 87c35cd..698e22e 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -17,7 +17,7 @@
*
*/
-import config, { AgentConfig } from './config/AgentConfig';
+import config, { AgentConfig, finalizeConfig } from './config/AgentConfig';
import GrpcProtocol from './agent/protocol/grpc/GrpcProtocol';
import { createLogger } from './logging';
import Protocol from './agent/protocol/Protocol';
@@ -36,6 +36,7 @@ class Agent {
}
Object.assign(config, options);
+ finalizeConfig(config);
if (this.started) {
throw new Error('SkyWalking agent is already started and can only be started once.');
diff --git a/src/plugins/AxiosPlugin.ts b/src/plugins/AxiosPlugin.ts
new file mode 100644
index 0000000..09ff877
--- /dev/null
+++ b/src/plugins/AxiosPlugin.ts
@@ -0,0 +1,121 @@
+/*!
+ *
+ * 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 SwPlugin from '../core/SwPlugin';
+import { URL } from 'url';
+import ContextManager from '../trace/context/ContextManager';
+import { Component } from '../trace/Component';
+import Span from '../trace/span/Span';
+import Tag from '../Tag';
+import { SpanLayer } from '../proto/language-agent/Tracing_pb';
+import { createLogger } from '../logging';
+import PluginInstaller from '../core/PluginInstaller';
+
+const logger = createLogger(__filename);
+
+class AxiosPlugin implements SwPlugin {
+ readonly module = 'axios';
+ readonly versions = '*';
+ axios = PluginInstaller.require('axios').default;
+
+ install(): void {
+ if (logger.isDebugEnabled()) {
+ logger.debug('installing axios plugin');
+ }
+ this.interceptClientRequest();
+ }
+
+ private interceptClientRequest() {
+ const copyStatusAndStop = (span: Span, response: any) => {
+ if (response) {
+ if (response.status) {
+ span.tag(Tag.httpStatusCode(response.status));
+ }
+ if (response.statusText) {
+ span.tag(Tag.httpStatusMsg(response.statusText));
+ }
+ }
+
+ span.stop();
+ }
+
+ this.axios.interceptors.request.use(
+ (config: any) => {
+ config.span.resync();
+
+ (config.span as Span).inject().items.forEach((item) => {
+ config.headers.common[item.key] = item.value;
+ });
+
+ return config;
+ },
+
+ (error: any) => {
+ error.config.span.error(error);
+ error.config.span.stop();
+
+ return Promise.reject(error);
+ }
+ );
+
+ this.axios.interceptors.response.use(
+ (response: any) => {
+ copyStatusAndStop(response.config.span, response);
+
+ return response;
+ },
+
+ (error: any) => {
+ error.config.span.error(error);
+
+ copyStatusAndStop(error.config.span, error.response);
+
+ return Promise.reject(error);
+ }
+ );
+
+ const _request = this.axios.Axios.prototype.request;
+
+ this.axios.Axios.prototype.request = function (config: any) {
+ const { host, pathname: operation } = new URL(config.url); // TODO: this may throw invalid URL
+ const span = ContextManager.current.newExitSpan(operation, host).start();
+
+ try {
+ span.component = Component.UNKNOWN;
+ span.layer = SpanLayer.HTTP;
+ span.peer = host;
+ span.tag(Tag.httpURL(host + operation));
+ span.async();
+
+ const request = _request.call(this, {...config, span});
+
+ return request;
+
+ } catch (e) {
+ span.error(e);
+ span.stop();
+
+ throw e;
+ }
+ };
+ }
+}
+
+// noinspection JSUnusedGlobalSymbols
+export default new AxiosPlugin();
diff --git a/src/plugins/ExpressPlugin.ts b/src/plugins/ExpressPlugin.ts
new file mode 100644
index 0000000..1cd3172
--- /dev/null
+++ b/src/plugins/ExpressPlugin.ts
@@ -0,0 +1,103 @@
+/*!
+ *
+ * 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 SwPlugin from '../core/SwPlugin';
+import { IncomingMessage, ServerResponse } from 'http';
+import ContextManager from '../trace/context/ContextManager';
+import { Component } from '../trace/Component';
+import Tag from '../Tag';
+import { SpanLayer } from '../proto/language-agent/Tracing_pb';
+import { ContextCarrier } from '../trace/context/ContextCarrier';
+import { createLogger } from '../logging';
+import PluginInstaller from '../core/PluginInstaller';
+
+const logger = createLogger(__filename);
+
+class ExpressPlugin implements SwPlugin {
+ readonly module = 'express';
+ readonly versions = '*';
+
+ install(): void {
+ if (logger.isDebugEnabled()) {
+ logger.debug('installing express plugin');
+ }
+ this.interceptServerRequest();
+ }
+
+ private interceptServerRequest() {
+ const onFinished = PluginInstaller.require('on-finished');
+ const router = PluginInstaller.require('express/lib/router');
+ const _handle = router.handle;
+
+ router.handle = function(req: IncomingMessage, res: ServerResponse, out: any) {
+ const headers = req.rawHeaders || [];
+ const headersMap: { [key: string]: string } = {};
+
+ for (let i = 0; i < headers.length / 2; i += 2) {
+ headersMap[headers[i]] = headers[i + 1];
+ }
+
+ const carrier = ContextCarrier.from(headersMap);
+ const operation = (req.url || '/').replace(/\?.*/g, '');
+ const span = ContextManager.current.newEntrySpan(operation, carrier).start();
+
+ let stopped = 0;
+ const stopIfNotStopped = () => {
+ if (!stopped++) {
+ span.stop();
+ span.tag(Tag.httpStatusCode(res.statusCode));
+ if (res.statusCode && res.statusCode >= 400) {
+ span.errored = true;
+ }
+ if (res.statusMessage) {
+ span.tag(Tag.httpStatusMsg(res.statusMessage));
+ }
+ }
+ };
+
+ try {
+ span.layer = SpanLayer.HTTP;
+ span.component = Component.EXPRESS;
+ span.peer = req.headers.host || '';
+ span.tag(Tag.httpURL(span.peer + req.url));
+
+ const ret = _handle.call(this, req, res, (err: Error) => {
+ if (err)
+ span.error(err);
+ else
+ span.errored = true;
+ out.call(this, err);
+ stopped -= 1; // skip first stop attempt, make sure stop executes once status code and message is set
+ onFinished(req, stopIfNotStopped); // this must run after any handlers deferred in 'out'
+ });
+ onFinished(req, stopIfNotStopped);
+
+ return ret;
+
+ } catch (e) {
+ span.error(e);
+ stopIfNotStopped();
+ throw e;
+ }
+ };
+ }
+}
+
+// noinspection JSUnusedGlobalSymbols
+export default new ExpressPlugin();
diff --git a/src/plugins/HttpPlugin.ts b/src/plugins/HttpPlugin.ts
index 0a1f1db..b237c8b 100644
--- a/src/plugins/HttpPlugin.ts
+++ b/src/plugins/HttpPlugin.ts
@@ -23,6 +23,7 @@ import { ClientRequest, IncomingMessage, RequestOptions, ServerResponse } from '
import ContextManager from '../trace/context/ContextManager';
import { Component } from '../trace/Component';
import Tag from '../Tag';
+import ExitSpan from '../trace/span/ExitSpan';
import { SpanLayer } from '../proto/language-agent/Tracing_pb';
import { ContextCarrier } from '../trace/context/ContextCarrier';
import { createLogger } from '../logging';
@@ -37,117 +38,123 @@ class HttpPlugin implements SwPlugin {
if (logger.isDebugEnabled()) {
logger.debug('installing http plugin');
}
- this.interceptClientRequest();
- this.interceptServerRequest();
- }
- private interceptClientRequest() {
const http = require('http');
+ const https = require('https');
+
+ this.interceptClientRequest(http);
+ this.interceptServerRequest(http);
+ this.interceptClientRequest(https);
+ this.interceptServerRequest(https);
+ }
- ((original) => {
- http.request = function () {
- const url: URL | string | RequestOptions = arguments[0];
-
- const { host, pathname } =
- url instanceof URL
- ? url
- : typeof url === 'string'
- ? new URL(url)
- : {
- host: (url.host || url.hostname || 'unknown') + ':' + url.port,
- pathname: url.path || '/',
- };
- const operation = pathname.replace(/\?.*$/g, '');
-
- let stopped = 0; // compensating if request aborted right after creation 'close' is not emitted
- const span = ContextManager.current.newExitSpan(operation, host).start();
- const stopIfNotStopped = () => !stopped++ ? span.stop() : null; // make sure we stop only once
-
- try {
+ private interceptClientRequest(module: any) {
+ const _request = module.request;
+
+ module.request = function () {
+ const url: URL | string | RequestOptions = arguments[0];
+
+ const { host, pathname } =
+ url instanceof URL
+ ? url
+ : typeof url === 'string'
+ ? new URL(url) // TODO: this may throw invalid URL
+ : {
+ host: (url.host || url.hostname || 'unknown') + ':' + url.port,
+ pathname: url.path || '/',
+ };
+ const operation = pathname.replace(/\?.*$/g, '');
+
+ let stopped = 0; // compensating if request aborted right after creation 'close' is not emitted
+ const stopIfNotStopped = () => !stopped++ ? span.stop() : null; // make sure we stop only once
+ const span: ExitSpan = ContextManager.current.newExitSpan(operation, host).start() as ExitSpan;
+
+ try {
+ if (span.depth === 1) {
span.component = Component.HTTP;
span.layer = SpanLayer.HTTP;
+ span.peer = host;
span.tag(Tag.httpURL(host + pathname));
+ }
- const request: ClientRequest = original.apply(this, arguments);
-
- span.extract().items.forEach((item) => {
- request.setHeader(item.key, item.value);
- });
+ const request: ClientRequest = _request.apply(this, arguments);
- request.on('close', stopIfNotStopped);
- request.on('abort', () => (span.errored = true, stopIfNotStopped()));
- request.on('error', (err) => (span.error(err), stopIfNotStopped()));
+ span.inject().items.forEach((item) => {
+ request.setHeader(item.key, item.value);
+ });
- request.prependListener('response', (res) => {
- span.resync();
- span.tag(Tag.httpStatusCode(res.statusCode));
- if (res.statusCode && res.statusCode >= 400) {
- span.errored = true;
- }
- if (res.statusMessage) {
- span.tag(Tag.httpStatusMsg(res.statusMessage));
- }
- stopIfNotStopped();
- });
+ request.on('close', stopIfNotStopped);
+ request.on('abort', () => (span.errored = true, stopIfNotStopped()));
+ request.on('error', (err) => (span.error(err), stopIfNotStopped()));
- span.async();
+ request.prependListener('response', (res) => {
+ span.resync();
+ span.tag(Tag.httpStatusCode(res.statusCode));
+ if (res.statusCode && res.statusCode >= 400) {
+ span.errored = true;
+ }
+ if (res.statusMessage) {
+ span.tag(Tag.httpStatusMsg(res.statusMessage));
+ }
+ stopIfNotStopped();
+ });
- return request;
+ span.async();
- } catch (e) {
- if (!stopped) {
- span.error(e);
- stopIfNotStopped();
- }
+ return request;
- throw e;
+ } catch (e) {
+ if (!stopped) {
+ span.error(e);
+ stopIfNotStopped();
}
- };
- })(http.request);
+
+ throw e;
+ }
+ };
}
- private interceptServerRequest() {
- const http = require('http');
+ private interceptServerRequest(module: any) {
+ const _emit = module.Server.prototype.emit;
- ((original) => {
- http.Server.prototype.emit = function () {
- if (arguments[0] !== 'request') {
- return original.apply(this, arguments);
- }
+ module.Server.prototype.emit = function () {
+ if (arguments[0] !== 'request') {
+ return _emit.apply(this, arguments);
+ }
- const [req, res] = [arguments[1] as IncomingMessage, arguments[2] as ServerResponse];
+ const [req, res] = [arguments[1] as IncomingMessage, arguments[2] as ServerResponse];
- const headers = req.rawHeaders || [];
- const headersMap: { [key: string]: string } = {};
+ const headers = req.rawHeaders || [];
+ const headersMap: { [key: string]: string } = {};
- for (let i = 0; i < headers.length / 2; i += 2) {
- headersMap[headers[i]] = headers[i + 1];
- }
+ for (let i = 0; i < headers.length / 2; i += 2) {
+ headersMap[headers[i]] = headers[i + 1];
+ }
- const carrier = ContextCarrier.from(headersMap);
- const operation = (req.url || '/').replace(/\?.*/g, '');
- const span = ContextManager.current.newEntrySpan(operation, carrier);
+ const carrier = ContextCarrier.from(headersMap);
+ const operation = (req.url || '/').replace(/\?.*/g, '');
+ const span = ContextManager.current.newEntrySpan(operation, carrier);
- return ContextManager.withSpan(span, (self, args) => {
- span.component = Component.HTTP_SERVER;
- span.layer = SpanLayer.HTTP;
- span.tag(Tag.httpURL((req.headers.host || '') + req.url));
+ return ContextManager.withSpan(span, (self, args) => {
+ span.component = Component.HTTP_SERVER;
+ span.layer = SpanLayer.HTTP;
+ span.peer = req.headers.host || '';
+ span.tag(Tag.httpURL(span.peer + req.url));
- const ret = original.apply(self, args);
+ const ret = _emit.apply(self, args);
- span.tag(Tag.httpStatusCode(res.statusCode));
- if (res.statusCode && res.statusCode >= 400) {
- span.errored = true;
- }
- if (res.statusMessage) {
- span.tag(Tag.httpStatusMsg(res.statusMessage));
- }
+ span.tag(Tag.httpStatusCode(res.statusCode));
+ if (res.statusCode && res.statusCode >= 400) {
+ span.errored = true;
+ }
+ if (res.statusMessage) {
+ span.tag(Tag.httpStatusMsg(res.statusMessage));
+ }
- return ret;
+ return ret;
- }, this, arguments);
- };
- })(http.Server.prototype.emit);
+ }, this, arguments);
+ };
}
}
diff --git a/src/trace/Component.ts b/src/trace/Component.ts
index 54b61c4..60c4c5d 100644
--- a/src/trace/Component.ts
+++ b/src/trace/Component.ts
@@ -22,6 +22,7 @@ export class Component {
static HTTP = new Component(2);
static MONGODB = new Component(9);
static HTTP_SERVER = new Component(49);
+ static EXPRESS = new Component(4002);
constructor(public id: number) {}
}
diff --git a/src/trace/context/SpanContext.ts b/src/trace/context/SpanContext.ts
index fab8305..f1daaa3 100644
--- a/src/trace/context/SpanContext.ts
+++ b/src/trace/context/SpanContext.ts
@@ -17,8 +17,11 @@
*
*/
+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';
import EntrySpan from '../../trace/span/EntrySpan';
import ExitSpan from '../../trace/span/ExitSpan';
@@ -30,6 +33,7 @@ import Snapshot from '../../trace/context/Snapshot';
import SegmentRef from '../../trace/context/SegmentRef';
import { ContextCarrier } from './ContextCarrier';
import ContextManager from './ContextManager';
+import { SpanType } from '../../proto/language-agent/Tracing_pb';
const logger = createLogger(__filename);
@@ -49,6 +53,18 @@ export default class SpanContext implements Context {
return this.parent ? this.parent.id : -1;
}
+ ignoreCheck(operation: string, type: SpanType): Span | undefined {
+ if (operation.match(config.reIgnoreOperation)) {
+ return new DummySpan({
+ context: new DummyContext(),
+ operation: '',
+ type,
+ });
+ }
+
+ return undefined;
+ }
+
newEntrySpan(operation: string, carrier?: ContextCarrier): Span {
if (logger.isDebugEnabled()) {
logger.debug('Creating entry span', {
@@ -57,23 +73,35 @@ export default class SpanContext implements Context {
});
}
- ContextManager.spansDup();
+ let span = this.ignoreCheck(operation, SpanType.ENTRY);
- const span = new EntrySpan({
- id: this.spanId++,
- parentId: this.parentId,
- context: this,
- operation,
- });
+ if (span)
+ return span;
+
+ const spans = ContextManager.spansDup();
+ const parent = spans[spans.length - 1];
+
+ if (parent && parent.type === SpanType.ENTRY) {
+ span = parent;
+ parent.operation = operation;
+
+ } else {
+ span = new EntrySpan({
+ id: this.spanId++,
+ parentId: this.parentId,
+ context: this,
+ operation,
+ });
- if (carrier && carrier.isValid()) {
- span.inject(carrier);
+ if (carrier && carrier.isValid()) {
+ span.extract(carrier);
+ }
}
return span;
}
- newExitSpan(operation: string, peer: string): Span {
+ newExitSpan(operation: string, peer: string, carrier?: ContextCarrier): Span {
if (logger.isDebugEnabled()) {
logger.debug('Creating exit span', {
parentId: this.parentId,
@@ -81,15 +109,32 @@ export default class SpanContext implements Context {
});
}
- ContextManager.spansDup();
+ let span = this.ignoreCheck(operation, SpanType.EXIT);
- return new ExitSpan({
- id: this.spanId++,
- parentId: this.parentId,
- context: this,
- peer,
- operation,
- });
+ if (span)
+ return span;
+
+ const spans = ContextManager.spansDup();
+ const parent = spans[spans.length - 1];
+
+ if (parent && parent.type === SpanType.EXIT) {
+ span = parent;
+
+ } else {
+ span = new ExitSpan({
+ id: this.spanId++,
+ parentId: this.parentId,
+ context: this,
+ peer,
+ operation,
+ });
+
+ // if (carrier && carrier.isValid()) { // is this right?
+ // Object.assign(carrier, span.inject());
+ // }
+ }
+
+ return span;
}
newLocalSpan(operation: string): Span {
@@ -100,6 +145,11 @@ export default class SpanContext implements Context {
});
}
+ let span = this.ignoreCheck(operation, SpanType.LOCAL);
+
+ if (span)
+ return span;
+
ContextManager.spansDup();
return new LocalSpan({
@@ -124,11 +174,11 @@ export default class SpanContext implements Context {
stop(span: Span): boolean {
logger.debug('Stopping span', { span: span.operation, spans: ContextManager.spans, nSpans: this.nSpans });
- if (span.finish(this.segment)) {
- const idx = ContextManager.spans.indexOf(span);
- if (idx !== -1) {
- ContextManager.spans.splice(idx, 1);
- }
+ span.finish(this.segment);
+
+ const idx = ContextManager.spans.indexOf(span);
+ if (idx !== -1) {
+ ContextManager.spans.splice(idx, 1);
}
if (--this.nSpans == 0) {
diff --git a/src/trace/span/DummySpan.ts b/src/trace/span/DummySpan.ts
index f5387c8..8657bfb 100644
--- a/src/trace/span/DummySpan.ts
+++ b/src/trace/span/DummySpan.ts
@@ -18,5 +18,14 @@
*/
import Span from '../../trace/span/Span';
+import { ContextCarrier } from '../context/ContextCarrier';
-export default class DummySpan extends Span {}
+export default class DummySpan extends Span {
+ inject(): ContextCarrier {
+ return new ContextCarrier();
+ }
+
+ extract(carrier: ContextCarrier): this {
+ return this;
+ }
+}
diff --git a/src/trace/span/EntrySpan.ts b/src/trace/span/EntrySpan.ts
index 4b8126e..59e841b 100644
--- a/src/trace/span/EntrySpan.ts
+++ b/src/trace/span/EntrySpan.ts
@@ -42,8 +42,8 @@ export default class EntrySpan extends StackedSpan {
return this;
}
- inject(carrier: ContextCarrier): this {
- super.inject(carrier);
+ extract(carrier: ContextCarrier): this {
+ super.extract(carrier);
const ref = SegmentRef.fromCarrier(carrier);
diff --git a/src/trace/span/ExitSpan.ts b/src/trace/span/ExitSpan.ts
index 8c16db6..745e4c4 100644
--- a/src/trace/span/ExitSpan.ts
+++ b/src/trace/span/ExitSpan.ts
@@ -33,7 +33,7 @@ export default class ExitSpan extends StackedSpan {
);
}
- extract(): ContextCarrier {
+ inject(): ContextCarrier {
return new ContextCarrier(
this.context.segment.relatedTraces[0],
this.context.segment.segmentId,
diff --git a/src/trace/span/Span.ts b/src/trace/span/Span.ts
index d854e9a..3e245a2 100644
--- a/src/trace/span/Span.ts
+++ b/src/trace/span/Span.ts
@@ -80,9 +80,6 @@ export default abstract class Span {
stop(): this {
logger.debug(`Stopping span ${this.operation}`, this);
- if (this.operation === '/test') {
- console.info('kkkkkkkkkkkkkkkkkkkkkkkkkl')
- }
this.context.stop(this);
return this;
}
@@ -107,14 +104,14 @@ export default abstract class Span {
}
// noinspection JSUnusedLocalSymbols
- extract(): ContextCarrier {
+ inject(): ContextCarrier {
throw new Error(`
- can only extract context carrier into ExitSpan, this may be a potential bug in the agent,
+ can only inject context carrier into ExitSpan, this may be a potential bug in the agent,
please report this in ${packageInfo.bugs.url} if you encounter this.
`);
}
- inject(carrier: ContextCarrier): this {
+ extract(carrier: ContextCarrier): this {
this.context.segment.relate(carrier.traceId!);
return this;
diff --git a/src/trace/span/StackedSpan.ts b/src/trace/span/StackedSpan.ts
index a06f1c2..8c1886f 100644
--- a/src/trace/span/StackedSpan.ts
+++ b/src/trace/span/StackedSpan.ts
@@ -38,7 +38,10 @@ export default class StackedSpan extends Span {
return this;
}
- finish(segment: Segment): boolean {
- return --this.depth === 0 && super.finish(segment);
+ stop(): this {
+ if (--this.depth === 0) {
+ super.stop();
+ }
+ return this;
}
}
diff --git a/tests/plugins/http/expected.data.yaml b/tests/plugins/http/expected.data.yaml
index 288515b..29e6cff 100644
--- a/tests/plugins/http/expected.data.yaml
+++ b/tests/plugins/http/expected.data.yaml
@@ -31,7 +31,7 @@ segmentItems:
componentId: 49
isError: false
spanType: Entry
- peer: ''
+ peer: server:5000
skipAnalysis: false
tags:
- { key: http.url, value: server:5000/test }
@@ -71,7 +71,7 @@ segmentItems:
componentId: 49
isError: false
spanType: Entry
- peer: ''
+ peer: localhost:5001
skipAnalysis: false
tags:
- { key: http.url, value: localhost:5001/test }