-
Notifications
You must be signed in to change notification settings - Fork 1.1k
feat(sdk-trace-base): add util.inspect support for SpanImpl, Tracer, BasicTracerProvider #6690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
a659d84
72ffcf0
b60c455
34539e6
2680da7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -41,6 +41,8 @@ import { ExceptionEventName } from './enums'; | |
| import type { SpanProcessor } from './SpanProcessor'; | ||
| import type { TimedEvent } from './TimedEvent'; | ||
| import type { SpanLimits } from './types'; | ||
| import type { InspectFn, InspectStylizeOptions } from './inspect'; | ||
| import { formatInspect, inspectCustom } from './inspect'; | ||
|
|
||
| /** | ||
| * This type provides the properties of @link{ReadableSpan} at the same time | ||
|
|
@@ -524,4 +526,31 @@ export class SpanImpl implements Span { | |
| // Other types, no need to apply value length limit | ||
| return value; | ||
| } | ||
|
|
||
| [inspectCustom]( | ||
| depth: number, | ||
| options: InspectStylizeOptions | undefined, | ||
| inspect: InspectFn | undefined | ||
| ): unknown { | ||
| const payload = { | ||
| name: this.name, | ||
| kind: this.kind, | ||
| spanContext: this._spanContext, | ||
| parentSpanContext: this.parentSpanContext, | ||
| status: this.status, | ||
| startTime: this.startTime, | ||
| endTime: this.endTime, | ||
| duration: this._duration, | ||
| ended: this._ended, | ||
| attributes: this.attributes, | ||
| events: this.events, | ||
| links: this.links, | ||
| droppedAttributesCount: this._droppedAttributesCount, | ||
| droppedEventsCount: this._droppedEventsCount, | ||
| droppedLinksCount: this._droppedLinksCount, | ||
| instrumentationScope: this.instrumentationScope, | ||
| resource: { attributes: this.resource.attributes }, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @pichlermarc noted (during the OTel JS SIG call earlier today) that accessing const {NodeSDK} = require('@opentelemetry/sdk-node');
const {awsEc2Detector} = require('/Users/trentm/src/opentelemetry-js-contrib/packages/resource-detector-aws');
require('node:util').inspect.defaultOptions.depth = 10;
process.env.OTEL_LOG_LEVEL = 'debug';
const sdk = new NodeSDK({
resourceDetectors: [awsEc2Detector]
});
sdk.start();
process.once('beforeExit', async () => {
await sdk.shutdown();
})
const { diag, trace } = require('@opentelemetry/api');
const tracer = trace.getTracer('default')
tracer.startActiveSpan('a-span', {attributes: {anAttr:'aValue'}}, span => {
span.end();
diag.info('this is my span:', span);
});Running that: This is unfortunate. Possible improvements:
diff --git a/packages/opentelemetry-sdk-trace-base/src/Span.ts b/packages/opentelemetry-sdk-trace-base/src/Span.ts
index b3b9e3b8f..ed76ce745 100644
--- a/packages/opentelemetry-sdk-trace-base/src/Span.ts
+++ b/packages/opentelemetry-sdk-trace-base/src/Span.ts
@@ -532,6 +532,18 @@ export class SpanImpl implements Span {
options: InspectStylizeOptions | undefined,
inspect: InspectFn | undefined
): unknown {
+ // This reproduces ResourceImpl#attributes without any diag logging for
+ // unsettled attributes.
+ const settledAttrs: Attributes = {};
+ for (const [k, v] of this.resource.getRawAttributes()) {
+ if (typeof (v as Partial<PromiseLike<unknown>>)?.then === 'function') {
+ continue;
+ }
+ if (v != null) {
+ settledAttrs[k] ??= v as AttributeValue;
+ }
+ }
+
const payload = {
name: this.name,
kind: this.kind,
@@ -549,7 +561,7 @@ export class SpanImpl implements Span {
droppedEventsCount: this._droppedEventsCount,
droppedLinksCount: this._droppedLinksCount,
instrumentationScope: this.instrumentationScope,
- resource: { attributes: this.resource.attributes },
+ resource: { attributes: settledAttrs },
};
return formatInspect('SpanImpl', payload, depth, options, inspect);
}
Though it could be misleading for those debugging early application behaviour (before all resource attributes are settled), I think it is fine to have the pretty-print representation just skip unsettled attributes -- for the common case.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good catch — addressed in 34539e6. I added a |
||
| }; | ||
| return formatInspect('SpanImpl', payload, depth, options, inspect); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| /** | ||
| * Well-known symbol used by Node.js `util.inspect` (and `console.*`) to | ||
| * render an object via a custom representation. Defined as a global Symbol | ||
| * so it works without importing from `node:util`, keeping this module safe | ||
| * for browser builds (where the symbol is simply never looked up). | ||
| */ | ||
| export const inspectCustom = Symbol.for('nodejs.util.inspect.custom'); | ||
|
|
||
| export type InspectFn = (value: unknown, options: unknown) => string; | ||
|
|
||
| export interface InspectStylizeOptions { | ||
| depth?: number | null; | ||
| stylize?: (text: string, styleType: string) => string; | ||
| } | ||
|
|
||
| /** | ||
| * Build a class-tagged inspect representation. Returns a stub like | ||
| * `[ClassName]` once the recursion budget is exhausted, otherwise returns | ||
| * `ClassName <inspected payload>` so nested fields keep proper coloring, | ||
| * indentation, and depth handling. In environments that don't supply an | ||
| * `inspect` callback (e.g. browsers), falls back to returning the raw | ||
| * payload object. | ||
| */ | ||
| export function formatInspect( | ||
| className: string, | ||
| payload: object, | ||
| depth: number, | ||
| options: InspectStylizeOptions | undefined, | ||
| inspect: InspectFn | undefined | ||
| ): unknown { | ||
| if (typeof depth === 'number' && depth < 0) { | ||
| const tag = `[${className}]`; | ||
| return options?.stylize ? options.stylize(tag, 'special') : tag; | ||
| } | ||
| if (typeof inspect !== 'function' || !options) { | ||
| return payload; | ||
| } | ||
| const childOptions = { | ||
| ...options, | ||
| depth: options.depth == null ? options.depth : options.depth - 1, | ||
| }; | ||
| return `${className} ${inspect(payload, childOptions)}`; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,87 @@ | ||
| /* | ||
| * Copyright The OpenTelemetry Authors | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| */ | ||
|
|
||
| import type { SpanContext } from '@opentelemetry/api'; | ||
| import { ROOT_CONTEXT, SpanKind, TraceFlags } from '@opentelemetry/api'; | ||
| import * as assert from 'assert'; | ||
| import * as util from 'util'; | ||
| import { BasicTracerProvider } from '../../src'; | ||
| import { SpanImpl } from '../../src/Span'; | ||
| import type { Tracer } from '../../src/Tracer'; | ||
|
|
||
| describe('util.inspect', () => { | ||
| const tracerProvider = new BasicTracerProvider(); | ||
| const tracer = tracerProvider.getTracer('default') as Tracer; | ||
| const spanContext: SpanContext = { | ||
| traceId: 'd4cda95b652f4a1592b449d5929fda1b', | ||
| spanId: '6e0c63257de34c92', | ||
| traceFlags: TraceFlags.SAMPLED, | ||
| }; | ||
|
|
||
| describe('SpanImpl', () => { | ||
| it('should render with class tag and key fields', () => { | ||
| const span = new SpanImpl({ | ||
| scope: tracer.instrumentationScope, | ||
| resource: tracer['_resource'], | ||
| context: ROOT_CONTEXT, | ||
| spanContext, | ||
| name: 'span1', | ||
| kind: SpanKind.CLIENT, | ||
| spanLimits: tracer.getSpanLimits(), | ||
| spanProcessor: tracer['_spanProcessor'], | ||
| attributes: { foo: 'bar' }, | ||
| }); | ||
|
|
||
| const out = util.inspect(span, { depth: 5, colors: false }); | ||
| assert.ok(out.startsWith('SpanImpl '), `unexpected prefix: ${out}`); | ||
| assert.ok(out.includes("name: 'span1'")); | ||
| assert.ok(out.includes(spanContext.traceId)); | ||
| assert.ok(out.includes(spanContext.spanId)); | ||
| assert.ok(out.includes("foo: 'bar'")); | ||
| }); | ||
|
|
||
| it('should collapse to a stub when depth budget is exhausted', () => { | ||
| const span = new SpanImpl({ | ||
| scope: tracer.instrumentationScope, | ||
| resource: tracer['_resource'], | ||
| context: ROOT_CONTEXT, | ||
| spanContext, | ||
| name: 'span1', | ||
| kind: SpanKind.CLIENT, | ||
| spanLimits: tracer.getSpanLimits(), | ||
| spanProcessor: tracer['_spanProcessor'], | ||
| }); | ||
|
|
||
| const out = util.inspect({ span }, { depth: 0, colors: false }); | ||
| assert.ok(out.includes('[SpanImpl]'), `unexpected output: ${out}`); | ||
| }); | ||
| }); | ||
|
|
||
| describe('Tracer', () => { | ||
| it('should render with scope and resource', () => { | ||
| const t = tracerProvider.getTracer('debug-scope', '1.2.3'); | ||
| const out = util.inspect(t, { depth: 4, colors: false }); | ||
| assert.ok(out.startsWith('Tracer '), `unexpected prefix: ${out}`); | ||
| assert.ok(out.includes("name: 'debug-scope'")); | ||
| assert.ok(out.includes("version: '1.2.3'")); | ||
| assert.ok(out.includes('spanLimits')); | ||
| }); | ||
| }); | ||
|
|
||
| describe('BasicTracerProvider', () => { | ||
| it('should render with tracer keys', () => { | ||
| const provider = new BasicTracerProvider(); | ||
| provider.getTracer('a'); | ||
| provider.getTracer('b', '0.0.1'); | ||
| const out = util.inspect(provider, { depth: 4, colors: false }); | ||
| assert.ok( | ||
| out.startsWith('BasicTracerProvider '), | ||
| `unexpected prefix: ${out}` | ||
| ); | ||
| assert.ok(out.includes("'a@:'")); | ||
| assert.ok(out.includes("'b@0.0.1:'")); | ||
| }); | ||
| }); | ||
| }); |
Uh oh!
There was an error while loading. Please reload this page.