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/07/26 03:51:29 UTC

[skywalking-nodejs] 04/23: Add plugin for built-in http.server module

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

commit aed0136bdb24a5ca784ef91ed1cbdfffcd03d925
Author: kezhenxu94 <ke...@163.com>
AuthorDate: Sun Jun 21 21:35:57 2020 +0800

    Add plugin for built-in http.server module
---
 src/index.ts                                     |  5 ++
 src/plugins/HttpPlugin.ts                        | 57 ++++++++++++++++++--
 src/trace/Component.ts                           |  1 +
 src/trace/context/{Carrier.ts => CarrierItem.ts} | 19 ++-----
 src/trace/context/Context.ts                     |  2 +-
 src/trace/context/ContextCarrier.ts              | 69 ++++++++++++++++++++++++
 src/trace/context/DummyContext.ts                |  2 +-
 src/trace/context/SegmentRef.ts                  | 16 +++---
 src/trace/context/SpanContext.ts                 | 12 +++--
 src/trace/span/EntrySpan.ts                      |  6 +--
 src/trace/span/ExitSpan.ts                       | 23 ++++----
 src/trace/span/Span.ts                           |  8 +--
 12 files changed, 170 insertions(+), 50 deletions(-)

diff --git a/src/index.ts b/src/index.ts
index 6a5acb0..260331f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -30,6 +30,11 @@ class Agent {
   protocol: Protocol = new GrpcProtocol();
 
   start(options: AgentConfig = {}): void {
+    if (process.env.SW_DISABLE === 'true') {
+      logger.info('SkyWalking agent is disabled by `SW_DISABLE=true`');
+      return;
+    }
+
     Object.assign(config, options);
 
     if (this.started) {
diff --git a/src/plugins/HttpPlugin.ts b/src/plugins/HttpPlugin.ts
index 0993029..f552e6f 100644
--- a/src/plugins/HttpPlugin.ts
+++ b/src/plugins/HttpPlugin.ts
@@ -19,11 +19,12 @@
 
 import SwPlugin from '@/core/SwPlugin';
 import { URL } from 'url';
-import { ClientRequest, IncomingMessage, RequestOptions } from 'http';
+import { ClientRequest, IncomingMessage, RequestOptions, 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';
 
 type RequestFunctionType = (
   url: string | URL,
@@ -37,19 +38,60 @@ class HttpPlugin implements SwPlugin {
 
   install(): void {
     this.interceptClientRequest();
+
+    const http = require('http');
+
+    (original => {
+      http.Server.prototype.emit = function(event: string | symbol, ...args: any[]): boolean {
+        if (event === 'request') {
+          if (!(args[0] instanceof IncomingMessage) && !(args[1] instanceof ServerResponse)) {
+            return original.apply(this, arguments);
+          }
+          const req = args[0] as IncomingMessage;
+          const res = args[1] as ServerResponse;
+
+          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 = new ContextCarrier();
+          carrier.items.forEach(item => {
+            if (headersMap.hasOwnProperty(item.key)) {
+              item.value = headersMap[item.key];
+            }
+          });
+
+          const span = ContextManager.current.newEntrySpan('/', carrier).start();
+          span.operation = req.url || '/';
+          span.component = Component.HTTP_SERVER;
+          span.layer = SpanLayer.HTTP;
+
+          res.on('finish', () => {
+            span
+              .tag(Tag.httpStatusCode(res.statusCode))
+              .tag(Tag.httpStatusMsg(res.statusMessage))
+              .stop();
+          });
+        }
+        return original.apply(this, arguments);
+      };
+    })(http.Server.prototype.emit);
   }
 
   private interceptClientRequest() {
     const http = require('http');
 
-    if (http.request === this.wrapHttpRequest) {
+    if (http.request === this.wrapHttpClientRequest) {
       return;
     }
 
-    http.request = this.wrapHttpRequest(http.request);
+    http.request = this.wrapHttpClientRequest(http.request);
   }
 
-  private wrapHttpRequest(originalRequest: RequestFunctionType): RequestFunctionType {
+  private wrapHttpClientRequest(originalRequest: RequestFunctionType): RequestFunctionType {
     return (
       url: string | URL,
       options: RequestOptions,
@@ -84,6 +126,13 @@ class HttpPlugin implements SwPlugin {
         callbackSpan.stop();
       });
 
+      span
+        .extract()
+        .items
+        .forEach(item => {
+          request.setHeader(item.key, item.value);
+        });
+
       span.stop();
 
       return request;
diff --git a/src/trace/Component.ts b/src/trace/Component.ts
index 4fd62f9..30d0ee4 100644
--- a/src/trace/Component.ts
+++ b/src/trace/Component.ts
@@ -20,6 +20,7 @@
 export class Component {
   static UNKNOWN = new Component(0);
   static HTTP = new Component(2);
+  static HTTP_SERVER = new Component(49);
 
   constructor(public id: number) {
   }
diff --git a/src/trace/context/Carrier.ts b/src/trace/context/CarrierItem.ts
similarity index 72%
rename from src/trace/context/Carrier.ts
rename to src/trace/context/CarrierItem.ts
index 1bcfe06..2d58a9f 100644
--- a/src/trace/context/Carrier.ts
+++ b/src/trace/context/CarrierItem.ts
@@ -17,20 +17,9 @@
  *
  */
 
-import ID from '@/trace/ID';
+export class CarrierItem {
+  value!: string;
 
-export interface ContextCarrier extends CarrierItem {
-  traceId: ID;
-  segmentId: ID;
-  spanId: number;
-  service: string;
-  serviceInstance: string;
-  endpoint: string;
-  clientAddress: string;
-  items: CarrierItem[];
-}
-
-export interface CarrierItem {
-  key: string;
-  value: string;
+  constructor(public key: string) {
+  }
 }
diff --git a/src/trace/context/Context.ts b/src/trace/context/Context.ts
index 23a21f5..590e0af 100644
--- a/src/trace/context/Context.ts
+++ b/src/trace/context/Context.ts
@@ -18,9 +18,9 @@
  */
 
 import Span from '@/trace/span/Span';
-import { ContextCarrier } from '@/trace/context/Carrier';
 import Segment from '@/trace/context/Segment';
 import Snapshot from '@/trace/context/Snapshot';
+import { ContextCarrier } from '@/trace/context/ContextCarrier';
 
 export default interface Context {
   segment: Segment;
diff --git a/src/trace/context/ContextCarrier.ts b/src/trace/context/ContextCarrier.ts
new file mode 100644
index 0000000..36a037a
--- /dev/null
+++ b/src/trace/context/ContextCarrier.ts
@@ -0,0 +1,69 @@
+/*!
+ *
+ * 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 ID from '@/trace/ID';
+import { CarrierItem } from '@/trace/context/CarrierItem';
+
+export class ContextCarrier extends CarrierItem {
+  constructor(
+    public traceId?: ID,
+    public segmentId?: ID,
+    public spanId?: number,
+    public service?: string,
+    public serviceInstance?: string,
+    public endpoint?: string,
+    public clientAddress?: string,
+    public items: CarrierItem[] = [],
+  ) {
+    super('sw8');
+    this.items.push(this);
+  }
+
+  private encode(s: string): string {
+    return Buffer.from(s).toString('base64');
+  }
+
+  private decode(s: string): string {
+    return Buffer.from(s, 'base64').toString();
+  }
+
+  get value(): string {
+    return [
+      '1',
+      this.encode(this.traceId!.toString()),
+      this.encode(this.segmentId!.toString()),
+      this.spanId!.toString(),
+      this.encode(this.service!),
+      this.encode(this.serviceInstance!),
+      this.encode(this.endpoint!),
+      this.encode(this.clientAddress!),
+    ].join('-');
+  }
+
+  set value(val) {
+    const parts = val.split('-');
+    this.traceId = new ID(this.decode(parts[1]));
+    this.segmentId = new ID(this.decode(parts[2]));
+    this.spanId = Number.parseInt(parts[3], 10);
+    this.service = this.decode(parts[4]);
+    this.serviceInstance = this.decode(parts[5]);
+    this.endpoint = this.decode(parts[6]);
+    this.clientAddress = this.decode(parts[7]);
+  }
+}
diff --git a/src/trace/context/DummyContext.ts b/src/trace/context/DummyContext.ts
index 820fd81..a86b4ee 100644
--- a/src/trace/context/DummyContext.ts
+++ b/src/trace/context/DummyContext.ts
@@ -18,13 +18,13 @@
  */
 
 import Context from '@/trace/context/Context';
-import { ContextCarrier } from '@/trace/context/Carrier';
 import Span from '@/trace/span/Span';
 import DummySpan from '@/trace/span/DummySpan';
 import Segment from '@/trace/context/Segment';
 import { SpanType } from '@/proto/language-agent/Tracing_pb';
 import Snapshot from '@/trace/context/Snapshot';
 import ID from '@/trace/ID';
+import { ContextCarrier } from '@/trace/context/ContextCarrier';
 
 export default class DummyContext implements Context {
   span: Span = new DummySpan({
diff --git a/src/trace/context/SegmentRef.ts b/src/trace/context/SegmentRef.ts
index bedef5b..9c07abc 100644
--- a/src/trace/context/SegmentRef.ts
+++ b/src/trace/context/SegmentRef.ts
@@ -17,10 +17,10 @@
  *
  */
 
-import { ContextCarrier } from '@/trace/context/Carrier';
 import Snapshot from '@/trace/context/Snapshot';
 import ID from '@/trace/ID';
 import config from '@/config/AgentConfig';
+import { ContextCarrier } from '@/trace/context/ContextCarrier';
 
 export default class SegmentRef {
   private constructor(
@@ -45,13 +45,13 @@ export default class SegmentRef {
   static fromCarrier(carrier: ContextCarrier): SegmentRef {
     return new SegmentRef(
       'CrossProcess',
-      carrier.traceId,
-      carrier.segmentId,
-      carrier.spanId,
-      carrier.service,
-      carrier.serviceInstance,
-      carrier.endpoint,
-      carrier.clientAddress,
+      carrier.traceId!,
+      carrier.segmentId!,
+      carrier.spanId!,
+      carrier.service!,
+      carrier.serviceInstance!,
+      carrier.endpoint!,
+      carrier.clientAddress!,
     );
   }
 
diff --git a/src/trace/context/SpanContext.ts b/src/trace/context/SpanContext.ts
index 793a9b5..821dc93 100644
--- a/src/trace/context/SpanContext.ts
+++ b/src/trace/context/SpanContext.ts
@@ -18,7 +18,6 @@
  */
 
 import Context from '@/trace/context/Context';
-import { ContextCarrier } from '@/trace/context/Carrier';
 import Span from '@/trace/span/Span';
 import Segment from '@/trace/context/Segment';
 import EntrySpan from '@/trace/span/EntrySpan';
@@ -30,6 +29,7 @@ import { createLogger } from '@/logging';
 import { executionAsyncId } from 'async_hooks';
 import Snapshot from '@/trace/context/Snapshot';
 import SegmentRef from '@/trace/context/SegmentRef';
+import { ContextCarrier } from '@/trace/context/ContextCarrier';
 
 const logger = createLogger(__filename);
 
@@ -60,15 +60,21 @@ export default class SpanContext implements Context {
       });
     }
 
-    return new EntrySpan({
+    const span = new EntrySpan({
       id: this.spanId++,
       parentId: this.parentId,
       context: this,
       operation,
     });
+
+    if (carrier) {
+      span.inject(carrier);
+    }
+
+    return span;
   }
 
-  newExitSpan(operation: string, peer: string, carrier?: ContextCarrier): Span {
+  newExitSpan(operation: string, peer: string): Span {
     if (logger.isDebugEnabled()) {
       logger.debug('Creating exit span', {
         parentId: this.parentId,
diff --git a/src/trace/span/EntrySpan.ts b/src/trace/span/EntrySpan.ts
index a04149b..d87be6e 100644
--- a/src/trace/span/EntrySpan.ts
+++ b/src/trace/span/EntrySpan.ts
@@ -20,9 +20,9 @@
 import StackedSpan from '@/trace/span/StackedSpan';
 import { Component } from '@/trace/Component';
 import { SpanCtorOptions } from '@/trace/span/Span';
-import { ContextCarrier } from '@/trace/context/Carrier';
 import SegmentRef from '@/trace/context/SegmentRef';
 import { SpanLayer, SpanType } from '@/proto/language-agent/Tracing_pb';
+import { ContextCarrier } from '@/trace/context/ContextCarrier';
 
 export default class EntrySpan extends StackedSpan {
   maxDepth = 0;
@@ -45,8 +45,8 @@ export default class EntrySpan extends StackedSpan {
     return this;
   }
 
-  extract(carrier: ContextCarrier): this {
-    super.extract(carrier);
+  inject(carrier: ContextCarrier): this {
+    super.inject(carrier);
 
     const ref = SegmentRef.fromCarrier(carrier);
 
diff --git a/src/trace/span/ExitSpan.ts b/src/trace/span/ExitSpan.ts
index 1e2e63d..fd0f516 100644
--- a/src/trace/span/ExitSpan.ts
+++ b/src/trace/span/ExitSpan.ts
@@ -19,9 +19,9 @@
 
 import StackedSpan from '@/trace/span/StackedSpan';
 import { SpanCtorOptions } from '@/trace/span/Span';
-import { ContextCarrier } from '@/trace/context/Carrier';
 import config from '@/config/AgentConfig';
 import { SpanType } from '@/proto/language-agent/Tracing_pb';
+import { ContextCarrier } from '@/trace/context/ContextCarrier';
 
 export default class ExitSpan extends StackedSpan {
   constructor(options: SpanCtorOptions) {
@@ -35,15 +35,16 @@ export default class ExitSpan extends StackedSpan {
     return super.start();
   }
 
-  inject(carrier: ContextCarrier): this {
-    carrier.traceId = this.context.segment.relatedTraces[0];
-    carrier.segmentId = this.context.segment.segmentId;
-    carrier.spanId = this.id;
-    carrier.service = config.serviceName;
-    carrier.serviceInstance = config.serviceInstance;
-    carrier.endpoint = this.context.spans[0].operation;
-    carrier.clientAddress = this.peer;
-
-    return this;
+  extract(): ContextCarrier {
+    return new ContextCarrier(
+      this.context.segment.relatedTraces[0],
+      this.context.segment.segmentId,
+      this.id,
+      config.serviceName,
+      config.serviceInstance,
+      this.context.spans[0].operation,
+      this.peer,
+      [],
+    );
   }
 }
diff --git a/src/trace/span/Span.ts b/src/trace/span/Span.ts
index 75d0d85..e893fd4 100644
--- a/src/trace/span/Span.ts
+++ b/src/trace/span/Span.ts
@@ -22,11 +22,11 @@ import { Component } from '@/trace/Component';
 import { Tag } from '@/Tag';
 import Log, { LogItem } from '@/Log';
 import Segment from '@/trace/context/Segment';
-import { ContextCarrier } from '@/trace/context/Carrier';
 import SegmentRef from '@/trace/context/SegmentRef';
 import { SpanLayer, SpanType } from '@/proto/language-agent/Tracing_pb';
 import { createLogger } from '@/logging';
 import * as packageInfo from 'package.json';
+import { ContextCarrier } from '@/trace/context/ContextCarrier';
 
 export type SpanCtorOptions = {
   context: Context;
@@ -92,15 +92,15 @@ export default abstract class Span {
   }
 
   // noinspection JSUnusedLocalSymbols
-  inject(carrier: ContextCarrier): this {
+  extract(): ContextCarrier {
     throw new Error(`
       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.
     `);
   }
 
-  extract(carrier: ContextCarrier): this {
-    this.context.segment.relate(carrier.traceId);
+  inject(carrier: ContextCarrier): this {
+    this.context.segment.relate(carrier.traceId!);
 
     return this;
   }