Skip to content

Commit 0132ab9

Browse files
Merge master into feature/emr
2 parents 9126814 + 3b44bcb commit 0132ab9

File tree

3 files changed

+116
-17
lines changed

3 files changed

+116
-17
lines changed

docs/telemetry.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,3 +293,38 @@ traceId: 'aaaaa-aaaaa-aaaaa-aaaaa-aaaaa'
293293
allowing us to look up `traceId=aaaaa-aaaaa-aaaaa-aaaaa-aaaaa` in our telemetry instance and find all the related events.
294294
295295
For more information visit the OpenTelemetry documentation on traces: https://opentelemetry.io/docs/concepts/signals/traces/
296+
297+
### Manual Trace ID Instrumentation
298+
299+
In certain scenarios you may need to manually instrument disjoint flows to track how a `traceId` propagates through them. e.g.
300+
301+
1. Measuring the time it takes for a message to travel from Amazon Q chat, through VS Code, and back to the customer.
302+
2. Determining the duration for Amazon Q inline to display a message to the user.
303+
304+
In these cases, where there isn't a direct hierarchy of function calls, manual instrumentation of the `traceId` is necessary.
305+
306+
#### Implementation Options
307+
308+
#### 1. When not currently running in a span
309+
310+
If you're not within an active span and you know the `traceId` you want to use:
311+
312+
```javascript
313+
telemetry.withTraceId(() => {
314+
// Code to be executed within this trace
315+
}, 'myTraceId')
316+
```
317+
318+
This method wraps the provided function with the specified traceId
319+
320+
#### 2. When currently running in a span
321+
322+
If you're already executing within a span (e.g., vscode_executeCommand) and you know the traceId you want to use:
323+
324+
```javascript
325+
telemetry.record({
326+
traceId: 'myTraceId',
327+
})
328+
```
329+
330+
This approach records the traceId for the current span and all future spans within the same execution context.

packages/core/src/shared/telemetry/spans.ts

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -353,22 +353,7 @@ export class TelemetryTracer extends TelemetryBase {
353353
* See docs/telemetry.md
354354
*/
355355
public run<T, U extends MetricName>(name: U, fn: (span: Metric<MetricShapes[U]>) => T, options?: SpanOptions): T {
356-
const initTraceId = (callback: () => T): T => {
357-
/**
358-
* Generate a new traceId if one doesn't exist.
359-
* This ensures the traceId is created before the span,
360-
* allowing it to propagate to all child telemetry metrics.
361-
*/
362-
if (!this.attributes?.traceId) {
363-
return this.runRoot(() => {
364-
this.record({ traceId: randomUUID() })
365-
return callback()
366-
})
367-
}
368-
return callback()
369-
}
370-
371-
return initTraceId(() => {
356+
return this.withTraceId(() => {
372357
const span = this.createSpan(name, options).start()
373358
const frame = this.switchContext(span)
374359

@@ -396,7 +381,34 @@ export class TelemetryTracer extends TelemetryBase {
396381
span.stop(e)
397382
throw e
398383
}
399-
})
384+
}, this.attributes?.traceId ?? randomUUID())
385+
}
386+
387+
/**
388+
* **Use {@link run} for most scenarios. Only use this method when you have a specific, pre-existing trace ID that you need to instrument.**
389+
*
390+
* Associates a known trace ID with subsequent telemetry events in the provided callback,
391+
* enabling correlation of events from multiple disjoint sources (e.g., webview, VSCode, partner team code).
392+
*
393+
* The difference between this and using telemetry.record({traceId: 'foo'}) directly is that this will create an active
394+
* span if one doesn't already exist. If you already know you're operating in a span (like vscode_executeCommand) then use
395+
* telemetry.record directly.
396+
*
397+
* Records traceId iff this metric is not already associated with a trace
398+
*/
399+
withTraceId<T>(callback: () => T, traceId: string): T {
400+
/**
401+
* Generate a new traceId if one doesn't exist.
402+
* This ensures the traceId is created before the span,
403+
* allowing it to propagate to all child telemetry metrics.
404+
*/
405+
if (!this.attributes?.traceId) {
406+
return this.runRoot(() => {
407+
this.record({ traceId })
408+
return callback()
409+
})
410+
}
411+
return callback()
400412
}
401413

402414
/**

packages/core/src/test/shared/telemetry/spans.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,4 +630,56 @@ describe('TelemetryTracer', function () {
630630
})
631631
})
632632
})
633+
634+
describe('withTraceId()', function () {
635+
const expectedId = 'foo-foo-foo-foo-foo'
636+
637+
beforeEach(() => {
638+
sandbox.stub(crypto, 'randomUUID').returns(expectedId)
639+
})
640+
641+
it('uses trace id', function () {
642+
telemetry.withTraceId(() => {
643+
telemetry.vscode_viewLogs.emit({
644+
result: 'Succeeded',
645+
})
646+
}, expectedId)
647+
648+
const viewLogsEvent = getMetrics('vscode_viewLogs')[0]
649+
assert.deepStrictEqual(viewLogsEvent.traceId, expectedId)
650+
})
651+
652+
it('does not use traceId when in a span', function () {
653+
telemetry.vscode_executeCommand.run(() => {
654+
telemetry.withTraceId(() => {
655+
telemetry.vscode_viewLogs.emit({
656+
result: 'Succeeded',
657+
})
658+
}, 'testTraceId')
659+
})
660+
661+
const executeCommandEvent = getMetrics('vscode_executeCommand')[0]
662+
assert.deepStrictEqual(executeCommandEvent.traceId, expectedId)
663+
664+
const viewLogsEvent = getMetrics('vscode_viewLogs')[0]
665+
assert.deepStrictEqual(viewLogsEvent.traceId, expectedId)
666+
})
667+
668+
it('passes traceId through regular functions', function () {
669+
telemetry.withTraceId(() => {
670+
function foo() {
671+
function fi() {
672+
telemetry.vscode_viewLogs.emit({
673+
result: 'Succeeded',
674+
})
675+
}
676+
fi()
677+
}
678+
foo()
679+
}, expectedId)
680+
681+
const viewLogsEvent = getMetrics('vscode_viewLogs')[0]
682+
assert.deepStrictEqual(viewLogsEvent.traceId, expectedId)
683+
})
684+
})
633685
})

0 commit comments

Comments
 (0)