Skip to content

Commit 4ad6c88

Browse files
Merge pull request #207 from beefancohen/ethan/206
Add otel headers to health checks
2 parents ddbead4 + 4d252a9 commit 4ad6c88

File tree

4 files changed

+124
-9
lines changed

4 files changed

+124
-9
lines changed

.changeset/moody-avocados-allow.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@hyperdx/node-opentelemetry': patch
3+
---
4+
5+
Fix issue where OTEL_EXPORTER_OTLP_HEADERS are not passed to health check endpoints
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { parseOtlpHeaders } from '../utils';
2+
3+
describe('Parse OTLP Headers', () => {
4+
it('should return an empty object when no headers string is provided', () => {
5+
expect(parseOtlpHeaders()).toEqual({});
6+
});
7+
8+
it('should correctly parse a single header', () => {
9+
expect(parseOtlpHeaders('key1=value1')).toEqual({ key1: 'value1' });
10+
});
11+
12+
it('should correctly parse multiple headers', () => {
13+
expect(parseOtlpHeaders('key1=value1,key2=value2')).toEqual({
14+
key1: 'value1',
15+
key2: 'value2',
16+
});
17+
});
18+
19+
it('should handle spaces around keys and values', () => {
20+
expect(parseOtlpHeaders(' key1 = value1 , key2 = value2 ')).toEqual({
21+
key1: 'value1',
22+
key2: 'value2',
23+
});
24+
});
25+
26+
it('should ignore malformed headers without "="', () => {
27+
expect(parseOtlpHeaders('key1=value1,malformedHeader,key2=value2')).toEqual(
28+
{
29+
key1: 'value1',
30+
key2: 'value2',
31+
},
32+
);
33+
});
34+
35+
it('should handle empty values', () => {
36+
expect(parseOtlpHeaders('key1=,key2=value2')).toEqual({
37+
key1: '',
38+
key2: 'value2',
39+
});
40+
});
41+
42+
it('should handle special characters in values', () => {
43+
expect(
44+
parseOtlpHeaders(
45+
'Authorization=Bearer token123,Content-Type=application/json',
46+
),
47+
).toEqual({
48+
Authorization: 'Bearer token123',
49+
'Content-Type': 'application/json',
50+
});
51+
});
52+
53+
it('should handle trailing comma', () => {
54+
expect(parseOtlpHeaders('key1=value1,')).toEqual({
55+
key1: 'value1',
56+
});
57+
});
58+
59+
it('should handle leading comma', () => {
60+
expect(parseOtlpHeaders(',key1=value1')).toEqual({
61+
key1: 'value1',
62+
});
63+
});
64+
65+
it('should handle multiple consecutive commas', () => {
66+
expect(parseOtlpHeaders('key1=value1,,key2=value2')).toEqual({
67+
key1: 'value1',
68+
key2: 'value2',
69+
});
70+
});
71+
});

packages/node-opentelemetry/src/otel.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import { getHyperDXMetricReader } from './metrics';
4949
import { MutableAsyncLocalStorageContextManager } from './MutableAsyncLocalStorageContextManager';
5050
import { Logger as OtelLogger } from './otel-logger';
5151
import HyperDXSpanProcessor from './spanProcessor';
52+
import { parseOtlpHeaders } from './utils';
5253

5354
const UI_LOG_PREFIX = '[⚡HyperDX]';
5455

@@ -215,6 +216,13 @@ export const initSDK = (config: SDKConfig) => {
215216
});
216217
ui.succeed('Set default otel envs');
217218

219+
// Parse OTLP headers from environment variable
220+
const otlpHeaders = parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_HEADERS);
221+
const healthCheckHeaders = {
222+
'Content-Type': 'application/json',
223+
...otlpHeaders,
224+
};
225+
218226
const stopOnTerminationSignals =
219227
config.stopOnTerminationSignals ??
220228
DEFAULT_HDX_NODE_STOP_ON_TERMINATION_SIGNALS; // Stop by default
@@ -244,23 +252,17 @@ export const initSDK = (config: SDKConfig) => {
244252
Promise.all([
245253
healthCheckUrl(ui, DEFAULT_OTEL_TRACES_EXPORTER_URL, {
246254
method: 'POST',
247-
headers: {
248-
'Content-Type': 'application/json',
249-
},
255+
headers: healthCheckHeaders,
250256
body: JSON.stringify({}),
251257
}),
252258
healthCheckUrl(ui, _logger.getExporterUrl(), {
253259
method: 'POST',
254-
headers: {
255-
'Content-Type': 'application/json',
256-
},
260+
headers: healthCheckHeaders,
257261
body: JSON.stringify({}),
258262
}),
259263
healthCheckUrl(ui, DEFAULT_OTEL_METRICS_EXPORTER_URL, {
260264
method: 'POST',
261-
headers: {
262-
'Content-Type': 'application/json',
263-
},
265+
headers: healthCheckHeaders,
264266
body: JSON.stringify({}),
265267
}),
266268
]);

packages/node-opentelemetry/src/utils.ts

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,40 @@ export const stringToBoolean = (stringValue: string | undefined) => {
2626
return undefined;
2727
}
2828
};
29+
30+
/**
31+
* Parses OTEL_EXPORTER_OTLP_HEADERS environment variable into a structured headers object.
32+
* Format: "key1=value1,key2=value2" -> { key1: "value1", key2: "value2" }
33+
*/
34+
export const parseOtlpHeaders = (
35+
headersString?: string,
36+
): Record<string, string> => {
37+
if (!headersString) {
38+
return {};
39+
}
40+
41+
const headers: Record<string, string> = {};
42+
const pairs = headersString.split(',');
43+
44+
for (const pair of pairs) {
45+
const trimmedPair = pair.trim();
46+
if (!trimmedPair) {
47+
continue;
48+
}
49+
50+
const equalIndex = trimmedPair.indexOf('=');
51+
if (equalIndex === -1) {
52+
// Skip malformed pairs without '='
53+
continue;
54+
}
55+
56+
const key = trimmedPair.substring(0, equalIndex).trim();
57+
const value = trimmedPair.substring(equalIndex + 1).trim();
58+
59+
if (key) {
60+
headers[key] = value;
61+
}
62+
}
63+
64+
return headers;
65+
};

0 commit comments

Comments
 (0)