Skip to content

Commit 01e13aa

Browse files
committed
fix(core): code review comments
gh-0
1 parent 43aa4bf commit 01e13aa

13 files changed

Lines changed: 254 additions & 75 deletions

File tree

packages/observability/src/__tests__/integration/runtime.integration.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,6 @@ describe('observability runtime', () => {
8282
ok(headers.traceparent);
8383
});
8484

85-
strictEqual(initResult.exporterName, 'loadOtlpExporter');
85+
strictEqual(initResult.exporterName, 'InMemorySpanExporter');
8686
});
8787
});
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {strictEqual} from 'assert';
2+
import {shutdownObservability} from '../../bootstrap';
3+
import {DEFAULT_OBSERVABILITY_CONFIG} from '../../config/defaults';
4+
import {
5+
clearRuntimeState,
6+
getRuntimeState,
7+
updateRuntimeState,
8+
} from '../../runtime';
9+
10+
describe('runtime state', () => {
11+
afterEach(() => {
12+
clearRuntimeState();
13+
});
14+
15+
it('returns a snapshot instead of the mutable backing state', () => {
16+
updateRuntimeState({
17+
config: {
18+
...DEFAULT_OBSERVABILITY_CONFIG,
19+
enabled: true,
20+
profile: 'default',
21+
serviceName: 'runtime-state-service',
22+
},
23+
});
24+
25+
const runtimeState = getRuntimeState();
26+
runtimeState.config!.serviceName = 'mutated-from-test';
27+
28+
strictEqual(getRuntimeState().config?.serviceName, 'runtime-state-service');
29+
});
30+
31+
it('swallows shutdown errors and clears runtime state', async () => {
32+
updateRuntimeState({
33+
runtime: {
34+
enabled: true,
35+
profile: 'default',
36+
config: {
37+
...DEFAULT_OBSERVABILITY_CONFIG,
38+
enabled: true,
39+
profile: 'default',
40+
serviceName: 'shutdown-runtime-service',
41+
},
42+
shutdown: async () => {
43+
throw new Error('shutdown-failed');
44+
},
45+
},
46+
});
47+
48+
await shutdownObservability();
49+
50+
strictEqual(getRuntimeState().runtime, undefined);
51+
});
52+
});
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import {strictEqual} from 'assert';
2+
import {SpanStatusCode, trace, Tracer, TracerOptions} from '@opentelemetry/api';
3+
import {withSpan} from '../../tracing';
4+
5+
describe('withSpan', () => {
6+
it('marks failed spans with error status before rethrowing', async () => {
7+
const originalGetTracer = trace.getTracer;
8+
const spanState: {
9+
status?: {code: SpanStatusCode; message?: string};
10+
ended: boolean;
11+
recordedError?: unknown;
12+
} = {
13+
ended: false,
14+
};
15+
16+
const fakeSpan = {
17+
end: () => {
18+
spanState.ended = true;
19+
},
20+
recordException: (error: unknown) => {
21+
spanState.recordedError = error;
22+
},
23+
setAttributes: () => undefined,
24+
setStatus: (status: {code: SpanStatusCode; message?: string}) => {
25+
spanState.status = status;
26+
},
27+
};
28+
29+
const fakeTracer = {
30+
startActiveSpan: (
31+
_name: string,
32+
arg2: unknown,
33+
arg3?: unknown,
34+
arg4?: unknown,
35+
) => {
36+
const callback =
37+
typeof arg2 === 'function'
38+
? arg2
39+
: typeof arg3 === 'function'
40+
? arg3
41+
: arg4;
42+
43+
return (callback as (span: typeof fakeSpan) => unknown)(fakeSpan);
44+
},
45+
} as unknown as Tracer;
46+
47+
trace.getTracer = (
48+
_name: string,
49+
_version?: string,
50+
_options?: TracerOptions,
51+
) => fakeTracer as Tracer;
52+
53+
try {
54+
await withSpan('failing-span', async () => {
55+
throw new Error('boom');
56+
}).catch(() => undefined);
57+
} finally {
58+
trace.getTracer = originalGetTracer;
59+
}
60+
61+
strictEqual(spanState.ended, true);
62+
strictEqual(spanState.recordedError instanceof Error, true);
63+
strictEqual(spanState.status?.code, SpanStatusCode.ERROR);
64+
strictEqual(spanState.status?.message, 'boom');
65+
});
66+
});

packages/observability/src/bootstrap.ts

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ function buildDisabledRuntime(): ObservabilityRuntime {
5656
};
5757
}
5858

59+
function discardProfileState(profile: ObservabilityProfile): void {
60+
profile.shutdown?.().catch(() => undefined);
61+
clearRuntimeState();
62+
}
63+
5964
export function bootstrapObservability(
6065
overrides?: Partial<ObservabilityConfig>,
6166
): ObservabilityRuntime {
@@ -78,9 +83,15 @@ export function bootstrapObservability(
7883
const profileConfig = profile.applyDefaults(resolvedConfig);
7984
validateObservabilityConfig(profileConfig);
8085

81-
const initResult = profile.initialize(profileConfig, {
82-
createExporter: createOtlpExporter,
83-
});
86+
let initResult;
87+
try {
88+
initResult = profile.initialize(profileConfig, {
89+
createExporter: createOtlpExporter,
90+
});
91+
} catch (error) {
92+
discardProfileState(profile);
93+
throw error;
94+
}
8495

8596
updateRuntimeState({
8697
profile,
@@ -93,8 +104,13 @@ export function bootstrapObservability(
93104
config: profileConfig,
94105
tracerProvider: initResult.tracerProvider,
95106
async shutdown() {
96-
await profile.shutdown?.();
97-
clearRuntimeState();
107+
try {
108+
await profile.shutdown?.();
109+
} catch {
110+
return;
111+
} finally {
112+
clearRuntimeState();
113+
}
98114
},
99115
};
100116

@@ -112,7 +128,11 @@ export async function shutdownObservability(): Promise<void> {
112128
return;
113129
}
114130

115-
await runtime.shutdown();
131+
try {
132+
await runtime.shutdown();
133+
} catch {
134+
clearRuntimeState();
135+
}
116136
}
117137

118138
export function isObservabilityEnabled(): boolean {

packages/observability/src/config/resolve-config.ts

Lines changed: 41 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,28 @@ function parseNumber(value: string | undefined): number | undefined {
4141
return Number.isFinite(parsed) ? parsed : undefined;
4242
}
4343

44+
function parseExporterProtocol(
45+
value: string | undefined,
46+
): ExporterProtocol | undefined {
47+
if (value === 'grpc' || value === 'http/protobuf') {
48+
return value;
49+
}
50+
51+
return undefined;
52+
}
53+
54+
function parseSamplerName(value: string | undefined): SamplerName | undefined {
55+
if (
56+
value === 'always_on' ||
57+
value === 'always_off' ||
58+
value === 'traceidratio'
59+
) {
60+
return value;
61+
}
62+
63+
return undefined;
64+
}
65+
4466
function parseHeaders(value: string | undefined): Record<string, string> {
4567
if (!value) {
4668
return {};
@@ -98,13 +120,10 @@ function startupInstrumentationConfig(
98120
DEFAULT_INSTRUMENTATIONS,
99121
) as (keyof InstrumentationToggles)[];
100122

101-
return keys.reduce(
102-
(result, key) => {
103-
result[key] = resolveInstrumentationToggle(key, overrides);
104-
return result;
105-
},
106-
{} as InstrumentationToggles,
107-
);
123+
return keys.reduce((result, key) => {
124+
result[key] = resolveInstrumentationToggle(key, overrides);
125+
return result;
126+
}, {} as InstrumentationToggles);
108127
}
109128

110129
function resolveServiceInfo(
@@ -136,13 +155,15 @@ function resolveExporterConfig(
136155
overrides: Partial<ObservabilityConfig> | undefined,
137156
env: NodeJS.ProcessEnv,
138157
) {
139-
const exporterProtocol = (overrides?.exporterProtocol ??
140-
(env.OTEL_EXPORTER_OTLP_PROTOCOL as ExporterProtocol | undefined) ??
141-
DEFAULT_OBSERVABILITY_CONFIG.exporterProtocol) as ExporterProtocol;
158+
const exporterProtocol =
159+
overrides?.exporterProtocol ??
160+
parseExporterProtocol(env.OTEL_EXPORTER_OTLP_PROTOCOL) ??
161+
DEFAULT_OBSERVABILITY_CONFIG.exporterProtocol;
142162

143-
const sampler = (overrides?.sampler ??
144-
(env.OTEL_TRACES_SAMPLER as SamplerName | undefined) ??
145-
DEFAULT_OBSERVABILITY_CONFIG.sampler) as SamplerName;
163+
const sampler =
164+
overrides?.sampler ??
165+
parseSamplerName(env.OTEL_TRACES_SAMPLER) ??
166+
DEFAULT_OBSERVABILITY_CONFIG.sampler;
146167

147168
const samplerArg =
148169
overrides?.samplerArg ??
@@ -159,7 +180,13 @@ function resolveExporterConfig(
159180
...overrides?.otlpHeaders,
160181
};
161182

162-
return {exporterProtocol, sampler, samplerArg, otlpEndpoint, otlpHeaders};
183+
return {
184+
exporterProtocol,
185+
sampler,
186+
samplerArg,
187+
otlpEndpoint,
188+
otlpHeaders,
189+
};
163190
}
164191

165192
export function resolveBootstrapConfig(

packages/observability/src/config/validate-config.ts

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,15 @@ import {
44
InstrumentationModuleRequirement,
55
InstrumentationName,
66
INSTRUMENTATION_MODULE_REQUIREMENTS,
7+
isModuleInstalled,
78
} from '../profiles/instrumentations';
89

910
function assertDependencyInstalled(moduleName: string, message: string): void {
10-
try {
11-
require.resolve(moduleName);
12-
} catch {
11+
if (!isModuleInstalled(moduleName)) {
1312
throw new Error(message);
1413
}
1514
}
1615

17-
function isDependencyInstalled(moduleName: string): boolean {
18-
try {
19-
require.resolve(moduleName);
20-
return true;
21-
} catch {
22-
return false;
23-
}
24-
}
25-
2616
export function validateObservabilityConfig(
2717
config: ResolvedObservabilityConfig,
2818
): void {
@@ -72,7 +62,7 @@ function validateInstrumentationRequirements(
7262
continue;
7363
}
7464

75-
if (!requirement.modules.some(isDependencyInstalled)) {
65+
if (!requirement.modules.some(isModuleInstalled)) {
7666
throw new Error(
7767
`Install one of the optional peer dependencies "${requirement.modules.join(
7868
'" or "',

packages/observability/src/profiles/datadog.profile.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import {
22
ObservabilityProfileName,
33
ObservabilityProfile,
4-
ProfileBootstrapContext,
5-
ProfileInitResult,
64
ResolvedObservabilityConfig,
75
} from '../types';
86
import {BaseOtlpObservabilityProfile} from './otlp.profile';
@@ -40,11 +38,4 @@ export class DatadogObservabilityProfile
4038
},
4139
};
4240
}
43-
44-
initialize(
45-
config: ResolvedObservabilityConfig,
46-
context: ProfileBootstrapContext,
47-
): ProfileInitResult {
48-
return super.initialize(this.applyDefaults(config), context);
49-
}
5041
}

packages/observability/src/profiles/instrumentations.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export type InstrumentationLoader = (
4242
name: InstrumentationName,
4343
) => ManagedInstrumentation[];
4444

45-
function isModuleInstalled(moduleName: string): boolean {
45+
export function isModuleInstalled(moduleName: string): boolean {
4646
try {
4747
require.resolve(moduleName);
4848
return true;

packages/observability/src/profiles/newrelic.profile.ts

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import {
22
ObservabilityProfileName,
33
ObservabilityProfile,
4-
ProfileBootstrapContext,
5-
ProfileInitResult,
64
ResolvedObservabilityConfig,
75
} from '../types';
86
import {BaseOtlpObservabilityProfile} from './otlp.profile';
@@ -40,11 +38,4 @@ export class NewRelicObservabilityProfile
4038
},
4139
};
4240
}
43-
44-
initialize(
45-
config: ResolvedObservabilityConfig,
46-
context: ProfileBootstrapContext,
47-
): ProfileInitResult {
48-
return super.initialize(this.applyDefaults(config), context);
49-
}
5041
}

0 commit comments

Comments
 (0)