diff --git a/CHANGELOG.md b/CHANGELOG.md index 83d7fac7b2..a45390b37b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,5 +83,7 @@ significant modifications will be credited to OpenTelemetry Authors. ([#317](https://github.com/open-telemetry/opentelemetry-demo/pull/317)) * Updated Product Catalog to Match Astronomy Webstore ([#285](https://github.com/open-telemetry/opentelemetry-demo/pull/285)) +* Add Span link for synthetic requests (from load generator) +([#332](https://github.com/open-telemetry/opentelemetry-demo/pull/332)) * Add `synthetic_request=true` baggage to load generator requests ([#331](https://github.com/open-telemetry/opentelemetry-demo/pull/331)) diff --git a/docs/trace_service_features.md b/docs/trace_service_features.md index 96eccc162c..f7675a84d5 100644 --- a/docs/trace_service_features.md +++ b/docs/trace_service_features.md @@ -11,10 +11,10 @@ Emoji Legend | Ad | Java | :100: | :100: | :100: | :construction: | :construction: | :construction: | | Cart | .NET | :100: | :construction: | :100: | :construction: | :construction: | :construction: | | Checkout | Go | :100: | :100: | :100: | :construction: | :construction: | :construction: | -| Currency | C++ | :no_bell: | :100: | :100: | :100: | :construction: | :100: | +| Currency | C++ | :no_bell: | :100: | :100: | :100: | :construction: | :100: | | Email | Ruby | :100: | :100: | :100: | :construction: | :construction: | :construction: | | Feature Flag | Erlang / Elixir | :100: | :construction: | :construction: | :construction: | :construction: | :construction: | -| Frontend | JavaScript | :construction: | :construction: | :construction: | :construction: | :construction: | :construction: | +| Frontend | JavaScript | :100: | :100: | :100: | :construction: | :100: | :100: | | Payment | JavaScript | :100: | :100: | :100: | :construction: | :construction: | :construction: | | Product Catalog | Go | :100: | :construction: | :100: | :construction: | :construction: | :construction: | | Recommendation | Python | :100: | :100: | :100: | :construction: | :construction: | :construction: | diff --git a/src/frontend/utils/telemetry/BackendTracer.ts b/src/frontend/utils/telemetry/BackendTracer.ts index 227853273e..e2416244a7 100644 --- a/src/frontend/utils/telemetry/BackendTracer.ts +++ b/src/frontend/utils/telemetry/BackendTracer.ts @@ -1,8 +1,7 @@ -import { context, trace, Tracer, Span, Context, SpanOptions, SpanStatusCode, Exception } from '@opentelemetry/api'; +import { context, trace, Tracer, Span, SpanStatusCode, Exception } from '@opentelemetry/api'; interface ITracer { getTracer(): Tracer; - createSpanFromContext(name: string, ctx: Context, options?: SpanOptions | undefined): Span; runWithSpan(parentSpan: Span, fn: () => Promise): Promise; } @@ -10,13 +9,6 @@ const BackendTracer = (): ITracer => ({ getTracer() { return trace.getTracer(process.env.OTEL_SERVICE_NAME as string); }, - createSpanFromContext(name, ctx, options) { - const tracer = this.getTracer(); - - if (!ctx) return tracer.startSpan(name, options, context.active()); - - return tracer.startSpan(name, options, ctx); - }, async runWithSpan(parentSpan, fn) { const ctx = trace.setSpan(context.active(), parentSpan); diff --git a/src/frontend/utils/telemetry/Instrumentation.js b/src/frontend/utils/telemetry/Instrumentation.js index c61be5c3c8..8fe36808c2 100644 --- a/src/frontend/utils/telemetry/Instrumentation.js +++ b/src/frontend/utils/telemetry/Instrumentation.js @@ -11,11 +11,7 @@ api.propagation.setGlobalPropagator( const sdk = new NodeSDK({ traceExporter: new OTLPTraceExporter(), - instrumentations: getNodeAutoInstrumentations({ - '@opentelemetry/instrumentation-http': { - enabled: false, - }, - }), + instrumentations: getNodeAutoInstrumentations(), }); sdk.start(); diff --git a/src/frontend/utils/telemetry/InstrumentationMiddleware.ts b/src/frontend/utils/telemetry/InstrumentationMiddleware.ts index 752d4c8831..85ced4f430 100644 --- a/src/frontend/utils/telemetry/InstrumentationMiddleware.ts +++ b/src/frontend/utils/telemetry/InstrumentationMiddleware.ts @@ -1,39 +1,51 @@ -import { NextApiHandler } from 'next'; +import {NextApiHandler} from 'next'; import Tracer from './BackendTracer'; -import { context, propagation, SpanKind, SpanStatusCode, Exception } from '@opentelemetry/api'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; +import {context, Exception, propagation, SpanKind, SpanStatusCode, trace} from '@opentelemetry/api'; +import {SemanticAttributes} from '@opentelemetry/semantic-conventions'; +import {Span} from '@opentelemetry/sdk-trace-base'; const InstrumentationMiddleware = (handler: NextApiHandler): NextApiHandler => { - const wrapper: NextApiHandler = async (request, response) => { - const { headers, method, url = '', httpVersion } = request; - const [target] = url.split('?'); + return async (request, response) => { + const {headers, method, url = '', httpVersion} = request; + const [target] = url.split('?'); - const parentContext = propagation.extract(context.active(), headers); - const span = await Tracer.createSpanFromContext(`API HTTP ${method}`, parentContext, { kind: SpanKind.SERVER }); + let span; + const baggage = propagation.getBaggage(context.active()); + if (baggage?.getEntry("synthetic_request")?.value == "true") { + // if synthetic_request baggage is set, create a new trace linked to the span in context + // this span will look similar to the auto-instrumented HTTP span + const syntheticSpan = trace.getSpan(context.active()) as Span; + span = Tracer.getTracer().startSpan(`HTTP ${method}`, { + root: true, + kind: SpanKind.SERVER, + links: [{context: syntheticSpan.spanContext()}], + attributes: { + "app.synthetic_request": true, + [SemanticAttributes.HTTP_TARGET]: target, + [SemanticAttributes.HTTP_STATUS_CODE]: response.statusCode, + [SemanticAttributes.HTTP_ROUTE]: url, + [SemanticAttributes.HTTP_METHOD]: method, + [SemanticAttributes.HTTP_USER_AGENT]: headers['user-agent'] || '', + [SemanticAttributes.HTTP_URL]: `${headers.host}${url}`, + [SemanticAttributes.HTTP_FLAVOR]: httpVersion, + } + }); - try { - await Tracer.runWithSpan(span, async () => handler(request, response)); - } catch (error) { - span.recordException(error as Exception); - span.setStatus({ code: SpanStatusCode.ERROR }); + } else { + // continue current trace/span + span = trace.getSpan(context.active()) as Span; + } - throw error; - } finally { - span.setAttributes({ - [SemanticAttributes.HTTP_TARGET]: target, - [SemanticAttributes.HTTP_STATUS_CODE]: response.statusCode, - [SemanticAttributes.HTTP_ROUTE]: url, - [SemanticAttributes.HTTP_METHOD]: method, - [SemanticAttributes.HTTP_USER_AGENT]: headers['user-agent'] || '', - [SemanticAttributes.HTTP_URL]: `${headers.host}${url}`, - [SemanticAttributes.HTTP_FLAVOR]: httpVersion, - }); - - span.end(); - } - }; - - return wrapper; + try { + await Tracer.runWithSpan(span, async () => handler(request, response)); + } catch (error) { + span.recordException(error as Exception); + span.setStatus({code: SpanStatusCode.ERROR}); + throw error; + } finally { + span.end(); + } + }; }; export default InstrumentationMiddleware;