From 602f7ed4228e669e2415262fd5aa1bc89f71a642 Mon Sep 17 00:00:00 2001 From: Raymond Feng Date: Wed, 17 Jul 2019 22:56:49 -0700 Subject: [PATCH] feat(extension-tracing): add tracing extension for open tracing --- extensions/tracing/.npmrc | 1 + extensions/tracing/LICENSE | 25 +++ extensions/tracing/README.md | 52 +++++ extensions/tracing/index.d.ts | 6 + extensions/tracing/index.js | 6 + extensions/tracing/index.ts | 8 + extensions/tracing/package-lock.json | 194 ++++++++++++++++++ extensions/tracing/package.json | 56 +++++ .../acceptance/tracing.acceptance.ts | 97 +++++++++ extensions/tracing/src/index.ts | 9 + .../src/interceptors/tracing.interceptor.ts | 73 +++++++ extensions/tracing/src/keys.ts | 31 +++ extensions/tracing/src/tracing.component.ts | 40 ++++ extensions/tracing/src/types.ts | 11 + extensions/tracing/tsconfig.build.json | 9 + 15 files changed, 618 insertions(+) create mode 100644 extensions/tracing/.npmrc create mode 100644 extensions/tracing/LICENSE create mode 100644 extensions/tracing/README.md create mode 100644 extensions/tracing/index.d.ts create mode 100644 extensions/tracing/index.js create mode 100644 extensions/tracing/index.ts create mode 100644 extensions/tracing/package-lock.json create mode 100644 extensions/tracing/package.json create mode 100644 extensions/tracing/src/__tests__/acceptance/tracing.acceptance.ts create mode 100644 extensions/tracing/src/index.ts create mode 100644 extensions/tracing/src/interceptors/tracing.interceptor.ts create mode 100644 extensions/tracing/src/keys.ts create mode 100644 extensions/tracing/src/tracing.component.ts create mode 100644 extensions/tracing/src/types.ts create mode 100644 extensions/tracing/tsconfig.build.json diff --git a/extensions/tracing/.npmrc b/extensions/tracing/.npmrc new file mode 100644 index 000000000000..cafe685a112d --- /dev/null +++ b/extensions/tracing/.npmrc @@ -0,0 +1 @@ +package-lock=true diff --git a/extensions/tracing/LICENSE b/extensions/tracing/LICENSE new file mode 100644 index 000000000000..212195e6f33a --- /dev/null +++ b/extensions/tracing/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) IBM Corp. 2019. All Rights Reserved. +Node module: @loopback/extension-tracing +This project is licensed under the MIT License, full text below. + +-------- + +MIT license + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/extensions/tracing/README.md b/extensions/tracing/README.md new file mode 100644 index 000000000000..0654de41b622 --- /dev/null +++ b/extensions/tracing/README.md @@ -0,0 +1,52 @@ +# @loopback/extension-tracing + +This module contains a component to report tracing status using +[Jaeger Tracing](https://github.com/jaegertracing/jaeger-client-node). + +## Stability: :warning:Experimental:warning: + +> Experimental packages provide early access to advanced or experimental +> functionality to get community feedback. Such modules are published to npm +> using `0.x.y` versions. Their APIs and functionality may be subject to +> breaking changes in future releases. + +## Installation + +```sh +npm install --save @loopback/extension-tracing +``` + +## Basic use + +The component should be loaded in the constructor of your custom Application +class. + +Start by importing the component class: + +```ts +import {TracingComponent} from '@loopback/extension-tracing'; +``` + +In the constructor, add the component to your application: + +```ts +this.component(TracingComponent); +``` + +## Contributions + +- [Guidelines](https://github.com/strongloop/loopback-next/blob/master/docs/CONTRIBUTING.md) +- [Join the team](https://github.com/strongloop/loopback-next/issues/110) + +## Tests + +Run `npm test` from the root folder. + +## Contributors + +See +[all contributors](https://github.com/strongloop/loopback-next/graphs/contributors). + +## License + +MIT diff --git a/extensions/tracing/index.d.ts b/extensions/tracing/index.d.ts new file mode 100644 index 000000000000..52abf14d109f --- /dev/null +++ b/extensions/tracing/index.d.ts @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/extension-tracing +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './dist'; diff --git a/extensions/tracing/index.js b/extensions/tracing/index.js new file mode 100644 index 000000000000..6c9adc819653 --- /dev/null +++ b/extensions/tracing/index.js @@ -0,0 +1,6 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/extension-tracing +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +module.exports = require('./dist'); diff --git a/extensions/tracing/index.ts b/extensions/tracing/index.ts new file mode 100644 index 000000000000..ebaed23301ae --- /dev/null +++ b/extensions/tracing/index.ts @@ -0,0 +1,8 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/extension-tracing +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +// DO NOT EDIT THIS FILE +// Add any additional (re)exports to src/index.ts instead. +export * from './src'; diff --git a/extensions/tracing/package-lock.json b/extensions/tracing/package-lock.json new file mode 100644 index 000000000000..91a17cc57f7d --- /dev/null +++ b/extensions/tracing/package-lock.json @@ -0,0 +1,194 @@ +{ + "name": "@loopback/extension-tracing", + "version": "0.0.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", + "dev": true + }, + "@types/jaeger-client": { + "version": "3.15.1", + "resolved": "https://registry.npmjs.org/@types/jaeger-client/-/jaeger-client-3.15.1.tgz", + "integrity": "sha512-7NPTwEbw5sBU68O3B9qLroAXQ3gkxjbaWCXU5Sa82/1opUg1BDgpVSLOo70npUfay4kIHRKuVhcx/Tp37g0Cxw==", + "requires": { + "opentracing": "~0.14.3", + "prom-client": "~11.3.0" + } + }, + "@types/node": { + "version": "10.17.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.3.tgz", + "integrity": "sha512-QZ9CjUB3QoA3f2afw3utKlfRPhpmufB7jC2+oDhLWnXqoyx333fhKSQDLQu2EK7OE0a15X67eYiRAaJsHXrpMA==", + "dev": true + }, + "ansi-color": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/ansi-color/-/ansi-color-0.2.1.tgz", + "integrity": "sha1-PnXAN0dSF1RO12Oo21cJ+prlv5o=" + }, + "bintrees": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.1.tgz", + "integrity": "sha1-DmVcm5wkNeqraL9AJyJtK1WjRSQ=" + }, + "bufrw": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/bufrw/-/bufrw-1.3.0.tgz", + "integrity": "sha512-jzQnSbdJqhIltU9O5KUiTtljP9ccw2u5ix59McQy4pV2xGhVLhRZIndY8GIrgh5HjXa6+QJ9AQhOd2QWQizJFQ==", + "requires": { + "ansi-color": "^0.2.1", + "error": "^7.0.0", + "hexer": "^1.5.0", + "xtend": "^4.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "error": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", + "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", + "requires": { + "string-template": "~0.2.1", + "xtend": "~4.0.0" + } + }, + "hexer": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/hexer/-/hexer-1.5.0.tgz", + "integrity": "sha1-uGzoCFmOip0YksVx887dhvyfBlM=", + "requires": { + "ansi-color": "^0.2.1", + "minimist": "^1.1.0", + "process": "^0.10.0", + "xtend": "^4.0.0" + } + }, + "jaeger-client": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/jaeger-client/-/jaeger-client-3.17.1.tgz", + "integrity": "sha512-S3fS3vk7dcWTWUWGqMWD9fGa/diLhPIP9h0S8L+OQdz24+7hR7cdALk+AOZD1VzbvqUIQbj6uUELp31J4Frgcw==", + "requires": { + "node-int64": "^0.4.0", + "opentracing": "^0.13.0", + "thriftrw": "^3.5.0", + "uuid": "^3.2.1", + "xorshift": "^0.2.0" + }, + "dependencies": { + "opentracing": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/opentracing/-/opentracing-0.13.0.tgz", + "integrity": "sha1-ajQUQvCdfYZrwR7QPeHjgo49aqs=" + } + } + }, + "long": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/long/-/long-2.4.0.tgz", + "integrity": "sha1-n6GAux2VAM3CnEFWdmoZleH0Uk8=" + }, + "minimist": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node-int64": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", + "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=" + }, + "opentracing": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/opentracing/-/opentracing-0.14.4.tgz", + "integrity": "sha512-nNnZDkUNExBwEpb7LZaeMeQgvrlO8l4bgY/LvGNZCR0xG/dGWqHqjKrAmR5GUoYo0FIz38kxasvA1aevxWs2CA==" + }, + "p-event": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-event/-/p-event-4.1.0.tgz", + "integrity": "sha512-4vAd06GCsgflX4wHN1JqrMzBh/8QZ4j+rzp0cd2scXRwuBEv+QR3wrVA5aLhWDLw4y2WgDKvzWF3CCLmVM1UgA==", + "requires": { + "p-timeout": "^2.0.1" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-timeout": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-2.0.1.tgz", + "integrity": "sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==", + "requires": { + "p-finally": "^1.0.0" + } + }, + "process": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/process/-/process-0.10.1.tgz", + "integrity": "sha1-hCRXzFHP7XLcd1r+6vuMYDQ3JyU=" + }, + "prom-client": { + "version": "11.3.0", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-11.3.0.tgz", + "integrity": "sha512-OqSf5WOvpGZXkfqPXUHNHpjrbEE/q8jxjktO0i7zg1cnULAtf0ET67/J5R4e4iA4MZx2260tzTzSFSWgMdTZmQ==", + "requires": { + "tdigest": "^0.1.1" + } + }, + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=" + }, + "tdigest": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.1.tgz", + "integrity": "sha1-Ljyyw56kSeVdHmzZEReszKRYgCE=", + "requires": { + "bintrees": "1.0.1" + } + }, + "thriftrw": { + "version": "3.11.3", + "resolved": "https://registry.npmjs.org/thriftrw/-/thriftrw-3.11.3.tgz", + "integrity": "sha512-mnte80Go5MCfYyOQ9nk6SljaEicCXlwLchupHR+/zlx0MKzXwAiyt38CHjLZVvKtoyEzirasXuNYtkEjgghqCw==", + "requires": { + "bufrw": "^1.2.1", + "error": "7.0.2", + "long": "^2.4.0" + } + }, + "uuid": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz", + "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ==" + }, + "xorshift": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/xorshift/-/xorshift-0.2.1.tgz", + "integrity": "sha1-/NgiZ+k1HBPw+5xzMH8lMx0pxjo=" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + } + } +} diff --git a/extensions/tracing/package.json b/extensions/tracing/package.json new file mode 100644 index 000000000000..82152b972f31 --- /dev/null +++ b/extensions/tracing/package.json @@ -0,0 +1,56 @@ +{ + "name": "@loopback/extension-tracing", + "version": "0.0.1", + "description": "LoopBack Distributed Tracing", + "engines": { + "node": ">=8.9" + }, + "scripts": { + "build": "lb-tsc", + "clean": "lb-clean loopback-extension-tracing*.tgz dist tsconfig.build.tsbuildinfo package", + "pretest": "npm run build", + "test": "lb-mocha \"dist/__tests__/**/*.js\"", + "verify": "npm pack && tar xf loopback-extension-tracing*.tgz && tree package && npm run clean", + "demo": "./src/__examples__/demo.sh" + }, + "author": "IBM Corp.", + "copyright.owner": "IBM Corp.", + "license": "MIT", + "dependencies": { + "@loopback/core": "^1.10.6", + "@loopback/rest": "^1.22.0", + "@types/jaeger-client": "^3.15.1", + "debug": "^4.1.1", + "jaeger-client": "^3.17.1", + "opentracing": "^0.14.4", + "p-event": "^4.1.0" + }, + "devDependencies": { + "@loopback/build": "^2.0.15", + "@loopback/eslint-config": "^4.1.3", + "@loopback/testlab": "^1.9.3", + "@types/debug": "^4.1.5", + "@types/node": "^10.14.13" + }, + "keywords": [ + "LoopBack", + "Cloud Native", + "Tracing" + ], + "files": [ + "README.md", + "index.js", + "index.d.ts", + "dist", + "src", + "!*/__tests__" + ], + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/strongloop/loopback-next.git", + "directory": "extensions/tracing" + } +} diff --git a/extensions/tracing/src/__tests__/acceptance/tracing.acceptance.ts b/extensions/tracing/src/__tests__/acceptance/tracing.acceptance.ts new file mode 100644 index 000000000000..e71e6494fb43 --- /dev/null +++ b/extensions/tracing/src/__tests__/acceptance/tracing.acceptance.ts @@ -0,0 +1,97 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/rest-explorer +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {inject} from '@loopback/core'; +import {get, RestApplication, RestServerConfig} from '@loopback/rest'; +import { + Client, + createRestAppClient, + givenHttpServerConfig, +} from '@loopback/testlab'; +import {initTracer} from 'jaeger-client'; +import {Span} from 'opentracing'; +import {TracingComponent} from '../..'; +import {TracingBindings} from '../../keys'; +import {LOOPBACK_TRACE_ID} from '../../types'; + +describe('Tracing (acceptance)', () => { + let app: RestApplication; + let request: Client; + + afterEach(async () => { + if (app) await app.stop(); + (app as unknown) = undefined; + }); + + context('with default config', () => { + beforeEach(async () => { + app = givenRestApplication(); + app.controller(MyController); + app.service(GreetingService); + app.component(TracingComponent); + await app.start(); + request = createRestAppClient(app); + }); + + it('extracts existing traceId', async () => { + const tracer = initTracer({serviceName: 'client'}, { + contextKey: LOOPBACK_TRACE_ID, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any); + const span = tracer.startSpan('ping'); + const spanStr = span.context().toString(); + await request + .get('/ping') + .set(LOOPBACK_TRACE_ID, spanStr) + .expect( + 200, + new RegExp( + `\\[${getTraceId(span)}\\] ping - \\[[\\da-f]+\\] Hello, World\\.`, + ), + ); + }); + + it('starts a new traceId', async () => { + await request + .get('/ping') + .expect(200) + .expect(200, /\[[\da-f]+\] ping - \[[\da-f]+\] Hello, World\./); + }); + }); + + function givenRestApplication(config?: RestServerConfig) { + const rest = Object.assign({}, givenHttpServerConfig(), config); + return new RestApplication({rest}); + } + + function getTraceId(span: Span) { + return span + .context() + .toString() + .split(':')[0]; + } + + class GreetingService { + constructor(@inject(TracingBindings.SPAN) private span: Span) {} + async greet(name: string) { + const traceId = getTraceId(this.span); + return `[${traceId}] Hello, ${name}.`; + } + } + + class MyController { + @get('/ping') + async ping( + // Inject the service as a parameter so it can receive the span + @inject('services.GreetingService', {asProxyWithInterceptors: true}) + greetingService: GreetingService, + @inject(TracingBindings.SPAN) span: Span, + ) { + const traceId = getTraceId(span); + const greeting = await greetingService.greet('World'); + return `[${traceId}] ping - ${greeting}`; + } + } +}); diff --git a/extensions/tracing/src/index.ts b/extensions/tracing/src/index.ts new file mode 100644 index 000000000000..5f2740213425 --- /dev/null +++ b/extensions/tracing/src/index.ts @@ -0,0 +1,9 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/extension-tracing +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +export * from './interceptors/tracing.interceptor'; +export * from './keys'; +export * from './tracing.component'; +export * from './types'; diff --git a/extensions/tracing/src/interceptors/tracing.interceptor.ts b/extensions/tracing/src/interceptors/tracing.interceptor.ts new file mode 100644 index 000000000000..a04b7909303c --- /dev/null +++ b/extensions/tracing/src/interceptors/tracing.interceptor.ts @@ -0,0 +1,73 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/extension-tracing +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + asGlobalInterceptor, + bind, + inject, + Interceptor, + InvocationContext, + Provider, + ValueOrPromise, +} from '@loopback/core'; +import {RequestContext, RestBindings} from '@loopback/rest'; +import * as debugFactory from 'debug'; +import {followsFrom, FORMAT_HTTP_HEADERS, Tracer} from 'opentracing'; +import {TracingBindings} from '../keys'; +const debug = debugFactory('loopback:tracing'); + +@bind(asGlobalInterceptor('tracing')) +export class TracingInterceptor implements Provider { + constructor(@inject(TracingBindings.TRACER) private tracer: Tracer) {} + + value() { + return this.intercept.bind(this); + } + + async intercept( + invocationCtx: InvocationContext, + next: () => ValueOrPromise, + ) { + const reqCtx = invocationCtx.getSync( + RestBindings.Http.CONTEXT, + ); + let reqSpanCtx = this.tracer.extract( + FORMAT_HTTP_HEADERS, + reqCtx.request.headers, + ); + debug('Extracted span context: %s', reqSpanCtx); + let span; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + if (!reqSpanCtx || !(reqSpanCtx as any).traceId) { + span = this.tracer.startSpan(invocationCtx.targetName); + reqSpanCtx = span.context(); + debug('Inject a new span context: %s', reqSpanCtx); + this.tracer.inject( + reqSpanCtx, + FORMAT_HTTP_HEADERS, + reqCtx.request.headers, + ); + } else { + const parentSpan = invocationCtx.getSync(TracingBindings.SPAN, { + optional: true, + }); + span = this.tracer.startSpan(invocationCtx.targetName, { + references: [ + followsFrom((parentSpan && parentSpan.context()) || reqSpanCtx), + ], + }); + if (debug.enabled) debug('A new span is started: %s', span.context()); + } + debug('Span context at %s: %s', invocationCtx.targetName, reqSpanCtx); + + try { + invocationCtx.bind(TracingBindings.SPAN).to(span); + return await next(); + } finally { + span.finish(); + if (debug.enabled) debug('The span is finished: %s', span.context()); + } + } +} diff --git a/extensions/tracing/src/keys.ts b/extensions/tracing/src/keys.ts new file mode 100644 index 000000000000..7718a108498f --- /dev/null +++ b/extensions/tracing/src/keys.ts @@ -0,0 +1,31 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/extension-tracing +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {BindingKey} from '@loopback/core'; +import {Span, Tracer} from 'opentracing'; +import {TracingComponent} from './tracing.component'; +import {TracingConfig} from './types'; + +/** + * Binding keys used by this component. + */ +export namespace TracingBindings { + export const COMPONENT = BindingKey.create( + 'components.TracingComponent', + ); + + export const CONFIG = BindingKey.buildKeyForConfig( + COMPONENT.key, + ); + + export const TRACER = BindingKey.create('tracing.tracer'); + + export const SPAN = BindingKey.create('tracing.span'); +} + +/** + * Binding tags for tracing related services + */ +export namespace TracingTags {} diff --git a/extensions/tracing/src/tracing.component.ts b/extensions/tracing/src/tracing.component.ts new file mode 100644 index 000000000000..94cca349057d --- /dev/null +++ b/extensions/tracing/src/tracing.component.ts @@ -0,0 +1,40 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/extension-tracing +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import { + Application, + bind, + Component, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + config, + ContextTags, + CoreBindings, + createBindingFromClass, + inject, +} from '@loopback/core'; +import {initTracerFromEnv} from 'jaeger-client'; +import {TracingInterceptor} from './interceptors/tracing.interceptor'; +import {TracingBindings} from './keys'; +import {LOOPBACK_TRACE_ID, TracingConfig} from './types'; + +/** + * A component providing tracing status + */ +@bind({tags: {[ContextTags.KEY]: TracingBindings.COMPONENT}}) +export class TracingComponent implements Component { + constructor( + @inject(CoreBindings.APPLICATION_INSTANCE) + private application: Application, + @config() + tracingConfig: TracingConfig = {serviceName: 'loopback'}, + ) { + const tracer = initTracerFromEnv(tracingConfig, { + contextKey: LOOPBACK_TRACE_ID, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any); + this.application.bind(TracingBindings.TRACER).to(tracer); + this.application.add(createBindingFromClass(TracingInterceptor)); + } +} diff --git a/extensions/tracing/src/types.ts b/extensions/tracing/src/types.ts new file mode 100644 index 000000000000..5a13d65080ec --- /dev/null +++ b/extensions/tracing/src/types.ts @@ -0,0 +1,11 @@ +// Copyright IBM Corp. 2019. All Rights Reserved. +// Node module: @loopback/extension-tracing +// This file is licensed under the MIT License. +// License text available at https://opensource.org/licenses/MIT + +import {TracingOptions} from 'jaeger-client'; +export {TracingConfig, TracingOptions} from 'jaeger-client'; + +export const LOOPBACK_TRACE_ID = 'x-loopback-trace-id'; + +export const DEFAULT_TRACING_OPTIONS: TracingOptions = {}; diff --git a/extensions/tracing/tsconfig.build.json b/extensions/tracing/tsconfig.build.json new file mode 100644 index 000000000000..c7b8e49eaca5 --- /dev/null +++ b/extensions/tracing/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "$schema": "http://json.schemastore.org/tsconfig", + "extends": "@loopback/build/config/tsconfig.common.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +}