Skip to content

Commit 489481b

Browse files
authored
Merge branch 'main' into fix/upgrade-vulnerable-babel-runtime-dep
2 parents 55c7470 + f3ccd9b commit 489481b

File tree

15 files changed

+539
-52
lines changed

15 files changed

+539
-52
lines changed

.changeset/afraid-grapes-retire.md

Lines changed: 0 additions & 5 deletions
This file was deleted.

packages/browser/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# @hyperdx/browser
22

3+
## 0.22.0
4+
5+
### Minor Changes
6+
7+
- d726266: Added an optional otelResourceAttributes array to the BrowserSDKConfig type.
8+
39
## 0.21.2
410

511
### Patch Changes

packages/browser/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hyperdx/browser",
3-
"version": "0.21.2",
3+
"version": "0.22.0",
44
"license": "Apache-2.0",
55
"main": "build/index.js",
66
"types": "build/index.d.ts",
@@ -15,7 +15,7 @@
1515
"@rollup/plugin-terser": "^0.4.1",
1616
"@rollup/plugin-typescript": "^11.1.1",
1717
"@types/intercom-web": "^2.8.21",
18-
"rollup": "^3.21.0",
18+
"rollup": "^2.79.2",
1919
"rollup-plugin-re": "^1.0.7",
2020
"rollup-plugin-visualizer": "^5.9.0",
2121
"zone.js": "0.11.4"

packages/node-opentelemetry/CHANGELOG.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,17 @@
11
# @hyperdx/node-opentelemetry
22

3+
## 0.10.1
4+
5+
### Patch Changes
6+
7+
- 7038775: fix: setTraceAttributes cross trace attributes leakage issue
8+
9+
## 0.10.0
10+
11+
### Minor Changes
12+
13+
- 88a79d6: Allows setting user defined resource attributes when initializing the SDK.
14+
315
## 0.9.0
416

517
### Minor Changes

packages/node-opentelemetry/README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,12 @@ import { initSDK } from '@hyperdx/node-opentelemetry';
106106
initSDK({
107107
consoleCapture: true, // optional, default: true
108108
additionalInstrumentations: [], // optional, default: []
109+
additionalResourceAttributes: { // optional, default: {}
110+
// Add custom resource attributes to all telemetry data
111+
'environment': 'production',
112+
'deployment.version': '1.0.0',
113+
'custom.attribute': 'value'
114+
},
109115
});
110116

111117
// Other instrumentation code...
@@ -164,6 +170,30 @@ app.use((req, res, next) => {
164170

165171
### (Optional) Advanced Instrumentation Configuration
166172

173+
#### Adding Custom Resource Attributes
174+
175+
Resource attributes are key-value pairs that describe the resource (service/application) producing telemetry data. These attributes are attached to all traces, metrics, and logs exported from your application. Common use cases include adding environment information, deployment versions, or custom metadata for filtering and grouping telemetry data.
176+
177+
When using manual instrumentation with `initSDK`, you can add custom resource attributes using the `additionalResourceAttributes` parameter:
178+
179+
```ts
180+
import { initSDK } from '@hyperdx/node-opentelemetry';
181+
182+
initSDK({
183+
additionalResourceAttributes: {
184+
'deployment.environment': process.env.NODE_ENV || 'development',
185+
'service.version': process.env.APP_VERSION || '0.0.0',
186+
'service.namespace': 'my-namespace',
187+
'cloud.region': process.env.AWS_REGION,
188+
// Add any custom attributes your organization needs
189+
'team.name': 'backend-team',
190+
'feature.flag': 'new-checkout-flow'
191+
},
192+
});
193+
```
194+
195+
These attributes will be included with all telemetry data sent to HyperDX, making it easier to filter and analyze your observability data. For standard attribute names, refer to the [OpenTelemetry Resource Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/resource/).
196+
167197
#### Adding Additional 3rd-Party Instrumentation Packages
168198

169199
When manually instrumenting the SDK, use the `additionalInstrumentations` key to create an array of additional 3rd-party instrumentations. Check [here](https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/metapackages/auto-instrumentations-node#supported-instrumentations) to see the current automatically instrumented packages.

packages/node-opentelemetry/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "@hyperdx/node-opentelemetry",
33
"author": "Warren <[email protected]>",
44
"license": "MIT",
5-
"version": "0.9.0",
5+
"version": "0.10.1",
66
"homepage": "https://www.hyperdx.io",
77
"repository": {
88
"type": "git",

packages/node-opentelemetry/src/MutableAsyncLocalStorageContextManager.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ export class MutableAsyncLocalStorageContextManager extends AbstractAsyncHooksCo
4444
thisArg?: ThisParameterType<F>,
4545
...args: A
4646
): ReturnType<F> {
47-
const mutableContext = this._asyncLocalStorage.getStore()
48-
?.mutableContext ?? {
47+
// Create a fresh mutableContext for each new context to prevent
48+
// cross-contamination between concurrent requests
49+
const mutableContext = {
4950
traceAttributes: new Map(),
5051
};
5152

packages/node-opentelemetry/src/__tests__/otel.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,22 @@ describe('otel', () => {
1818
shutdown();
1919
});
2020

21+
it('should be able to initialize the SDK with initSDK using additional resource attributes', async () => {
22+
initSDK({
23+
apiKey: 'blabla',
24+
advancedNetworkCapture: true,
25+
consoleCapture: true,
26+
additionalResourceAttributes: {
27+
'cloud.region': 'us-east-2',
28+
'cloud.availability_zone': 'us-east-2a',
29+
},
30+
});
31+
32+
await new Promise((resolve) => setTimeout(resolve, 1000));
33+
34+
shutdown();
35+
});
36+
2137
it('should be able to initialize the SDK with init', async () => {
2238
init({
2339
apiKey: 'blabla',
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { ROOT_CONTEXT } from '@opentelemetry/api';
2+
3+
import { MutableAsyncLocalStorageContextManager } from '../MutableAsyncLocalStorageContextManager';
4+
5+
describe('MutableAsyncLocalStorageContextManager - trace attributes isolation', () => {
6+
let contextManager: MutableAsyncLocalStorageContextManager;
7+
8+
beforeEach(() => {
9+
contextManager = new MutableAsyncLocalStorageContextManager();
10+
});
11+
12+
it('should not leak trace attributes between concurrent contexts', async () => {
13+
const results: Array<{ userId: string; requestId: string }> = [];
14+
15+
// Simulate two concurrent requests with different trace attributes
16+
await Promise.all([
17+
new Promise<void>((resolve) => {
18+
contextManager.with(ROOT_CONTEXT, () => {
19+
const ctx = contextManager.getMutableContext();
20+
ctx?.traceAttributes.set('userId', 'user-1');
21+
ctx?.traceAttributes.set('requestId', 'req-1');
22+
23+
// Simulate async work
24+
setTimeout(() => {
25+
const finalCtx = contextManager.getMutableContext();
26+
results.push({
27+
userId: finalCtx?.traceAttributes.get('userId'),
28+
requestId: finalCtx?.traceAttributes.get('requestId'),
29+
});
30+
resolve();
31+
}, 10);
32+
});
33+
}),
34+
35+
new Promise<void>((resolve) => {
36+
contextManager.with(ROOT_CONTEXT, () => {
37+
const ctx = contextManager.getMutableContext();
38+
ctx?.traceAttributes.set('userId', 'user-2');
39+
ctx?.traceAttributes.set('requestId', 'req-2');
40+
41+
// Simulate async work
42+
setTimeout(() => {
43+
const finalCtx = contextManager.getMutableContext();
44+
results.push({
45+
userId: finalCtx?.traceAttributes.get('userId'),
46+
requestId: finalCtx?.traceAttributes.get('requestId'),
47+
});
48+
resolve();
49+
}, 10);
50+
});
51+
}),
52+
]);
53+
54+
// Each context should have its own attributes without cross-contamination
55+
expect(results).toHaveLength(2);
56+
expect(results).toEqual(
57+
expect.arrayContaining([
58+
{ userId: 'user-1', requestId: 'req-1' },
59+
{ userId: 'user-2', requestId: 'req-2' },
60+
]),
61+
);
62+
});
63+
64+
it('should create fresh trace attributes for each new context', () => {
65+
// First context sets some attributes
66+
contextManager.with(ROOT_CONTEXT, () => {
67+
const ctx1 = contextManager.getMutableContext();
68+
ctx1?.traceAttributes.set('userId', 'user-1');
69+
ctx1?.traceAttributes.set('sharedKey', 'parent-value');
70+
71+
expect(ctx1?.traceAttributes.get('userId')).toBe('user-1');
72+
expect(ctx1?.traceAttributes.get('sharedKey')).toBe('parent-value');
73+
74+
// Nested context should have its own fresh trace attributes
75+
contextManager.with(ROOT_CONTEXT, () => {
76+
const ctx2 = contextManager.getMutableContext();
77+
78+
// Should NOT have parent's attributes (fresh Map)
79+
expect(ctx2?.traceAttributes.get('userId')).toBeUndefined();
80+
expect(ctx2?.traceAttributes.get('sharedKey')).toBeUndefined();
81+
82+
// Set new attributes in child context
83+
ctx2?.traceAttributes.set('userId', 'user-2');
84+
ctx2?.traceAttributes.set('sharedKey', 'child-value');
85+
86+
expect(ctx2?.traceAttributes.get('userId')).toBe('user-2');
87+
expect(ctx2?.traceAttributes.get('sharedKey')).toBe('child-value');
88+
});
89+
90+
// After exiting child context, parent should still have original attributes
91+
const ctx1Again = contextManager.getMutableContext();
92+
expect(ctx1Again?.traceAttributes.get('userId')).toBe('user-1');
93+
expect(ctx1Again?.traceAttributes.get('sharedKey')).toBe('parent-value');
94+
});
95+
});
96+
97+
it('should handle rapid sequential context creation without contamination', async () => {
98+
const results: string[] = [];
99+
100+
// Simulate rapid sequential requests
101+
for (let i = 0; i < 5; i++) {
102+
await new Promise<void>((resolve) => {
103+
contextManager.with(ROOT_CONTEXT, () => {
104+
const ctx = contextManager.getMutableContext();
105+
const userId = `user-${i}`;
106+
ctx?.traceAttributes.set('userId', userId);
107+
108+
// Verify the correct userId is set in this context
109+
const actualUserId = ctx?.traceAttributes.get('userId');
110+
results.push(actualUserId as string);
111+
112+
resolve();
113+
});
114+
});
115+
}
116+
117+
// Each request should have had its own userId
118+
expect(results).toEqual(['user-0', 'user-1', 'user-2', 'user-3', 'user-4']);
119+
});
120+
121+
it('should maintain separate mutable contexts across parallel operations', async () => {
122+
const contextSnapshots: Array<Map<string, any>> = [];
123+
124+
await Promise.all(
125+
Array.from(
126+
{ length: 10 },
127+
(_, i) =>
128+
new Promise<void>((resolve) => {
129+
contextManager.with(ROOT_CONTEXT, () => {
130+
const ctx = contextManager.getMutableContext();
131+
ctx?.traceAttributes.set('index', i);
132+
133+
setTimeout(() => {
134+
const finalCtx = contextManager.getMutableContext();
135+
// Clone the Map to preserve its state
136+
contextSnapshots.push(
137+
new Map(finalCtx?.traceAttributes || new Map()),
138+
);
139+
resolve();
140+
}, Math.random() * 10);
141+
});
142+
}),
143+
),
144+
);
145+
146+
// Each context should have its own unique index
147+
const indices = contextSnapshots.map((map) => map.get('index'));
148+
expect(indices.sort()).toEqual([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
149+
});
150+
});

packages/node-opentelemetry/src/otel.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
InstrumentationModuleDefinition,
1313
} from '@opentelemetry/instrumentation';
1414
import { RuntimeNodeInstrumentation } from '@opentelemetry/instrumentation-runtime-node';
15-
import { Resource } from '@opentelemetry/resources';
15+
import { Resource, ResourceAttributes } from '@opentelemetry/resources';
1616
import { MetricReader } from '@opentelemetry/sdk-metrics';
1717
import { NodeSDK } from '@opentelemetry/sdk-node';
1818
import cliSpinners from 'cli-spinners';
@@ -58,6 +58,7 @@ const IS_LOCAL = env.NODE_ENV === 'development' || !env.NODE_ENV;
5858

5959
export type SDKConfig = {
6060
additionalInstrumentations?: InstrumentationBase[];
61+
additionalResourceAttributes?: ResourceAttributes;
6162
advancedNetworkCapture?: boolean;
6263
apiKey?: string;
6364
betaMode?: boolean;
@@ -331,6 +332,7 @@ export const initSDK = (config: SDKConfig) => {
331332

332333
sdk = new NodeSDK({
333334
resource: new Resource({
335+
...config.additionalResourceAttributes,
334336
// https://opentelemetry.io/docs/specs/semconv/resource/#telemetry-sdk-experimental
335337
'telemetry.distro.name': 'hyperdx',
336338
'telemetry.distro.version': PKG_VERSION,

0 commit comments

Comments
 (0)