import { Inject, Injectable, OnModuleInit } from "@nestjs/common"; import { Segment, Subsegment, plugins } from "aws-xray-sdk"; import { RequestHandler } from "express"; import { AsyncContext } from "../async-hooks"; import { TracingNotInitializedException, UnknownAsyncContextException, } from "../exceptions"; import { TRACING_ASYNC_CONTEXT_SEGMENT, TRACING_ASYNC_CONTEXT_SUBSEGMENT, XRAY_CLIENT, } from "./constants"; import { TracingConfig, XRayClient } from "./interfaces"; // WORKAROUND: AWSXRay initializes quite early after importing and the // Daemon address can not be changed after that. Our usual initialiation // will happen later, so it can not have an effect. // If you need a custom daemon address, use variable "AWS_XRAY_DAEMON_ADDRESS". if (!process.env.AWS_XRAY_DAEMON_ADDRESS) { process.env.AWS_XRAY_DAEMON_ADDRESS = "172.17.0.1:2000"; } @Injectable() export class TracingService implements OnModuleInit { private readonly config: Required<TracingConfig>; constructor( @Inject(XRAY_CLIENT) private readonly xrayClient: XRayClient, private readonly asyncContext: AsyncContext, options: TracingConfig ) { this.config = { serviceName: options.serviceName || "example-service", rate: options.rate !== undefined ? options.rate : 1, daemonAddress: options.daemonAddress || "172.17.0.1:2000", plugins: options.plugins || [plugins.EC2Plugin, plugins.ECSPlugin], }; // Set defaults } public onModuleInit() { // AWSXRay uses continuation-local-storage for the automatic mode, this // does not work in async/await Scenarios. We will implement our own // "automatic mode" this.xrayClient.enableManualMode(); this.xrayClient.setDaemonAddress(this.config.daemonAddress); this.xrayClient.middleware.setSamplingRules(this.getSamplingRules()); this.xrayClient.config(this.config.plugins); // Disable errors on missing context this.xrayClient.setContextMissingStrategy(() => {}); } public tracingMiddleware(): RequestHandler { return this.xrayClient.express.openSegment(this.config.serviceName); } public setRootSegment(segment: Segment) { try { this.asyncContext.set(TRACING_ASYNC_CONTEXT_SEGMENT, segment); } catch (err) { if (err instanceof UnknownAsyncContextException) { throw new TracingNotInitializedException(); } throw err; } } public getRootSegment(): Segment { try { return this.asyncContext.get<string, Segment>( TRACING_ASYNC_CONTEXT_SEGMENT ); } catch (err) { if (err instanceof UnknownAsyncContextException) { throw new TracingNotInitializedException(); } throw err; } } public setSubSegment(segment: Subsegment) { try { this.asyncContext.set(TRACING_ASYNC_CONTEXT_SUBSEGMENT, segment); } catch (err) { if (err instanceof UnknownAsyncContextException) { throw new TracingNotInitializedException(); } throw err; } } public getSubSegment(): Subsegment { try { return this.asyncContext.get<string, Subsegment>( TRACING_ASYNC_CONTEXT_SUBSEGMENT ); } catch (err) { if (err instanceof UnknownAsyncContextException) { throw new TracingNotInitializedException(); } throw err; } } public getTracingHeader(parentSegment: Subsegment): string { const rootSegment = this.getRootSegment(); return `Root=${rootSegment.trace_id};Parent=${parentSegment.id};Sampled=1`; } public createSubSegment(name: string): Subsegment { const rootSegment = this.getRootSegment(); const subSegment = rootSegment.addNewSubsegment(name); return subSegment; } private getSamplingRules() { return { rules: [ { description: "LoadBalancer HealthCheck", http_method: "GET", host: "*", url_path: "/status", fixed_target: 0, rate: 0, }, ], default: { fixed_target: 0, rate: this.config.rate }, version: 2, }; } }