0
# Utility Functions
1
2
Common utility functions including deep object merging, timeout handling, URL pattern matching, and configuration parsing that support various SDK operations.
3
4
## Capabilities
5
6
### Object Utilities
7
8
Deep object merging with circular reference protection and array handling.
9
10
```typescript { .api }
11
/**
12
* Deep merge multiple objects with circular reference protection
13
* @param args - Objects to merge (later objects override earlier ones)
14
* @returns Merged object with all properties combined
15
*/
16
function merge(...args: any[]): any;
17
```
18
19
**Usage Examples:**
20
21
```typescript
22
import { merge } from "@opentelemetry/core";
23
24
// Basic object merging
25
const config1 = {
26
server: {
27
port: 3000,
28
host: "localhost"
29
},
30
features: ["logging", "metrics"]
31
};
32
33
const config2 = {
34
server: {
35
port: 8080,
36
ssl: true
37
},
38
features: ["tracing"],
39
database: {
40
url: "postgresql://localhost:5432/app"
41
}
42
};
43
44
const mergedConfig = merge(config1, config2);
45
console.log(mergedConfig);
46
// Result: {
47
// server: {
48
// port: 8080, // Overridden
49
// host: "localhost", // Preserved
50
// ssl: true // Added
51
// },
52
// features: ["tracing"], // Arrays are replaced, not merged
53
// database: {
54
// url: "postgresql://localhost:5432/app"
55
// }
56
// }
57
58
// Merge with multiple sources
59
const defaults = {
60
timeout: 5000,
61
retries: 3,
62
headers: {
63
"User-Agent": "OpenTelemetry/1.0"
64
}
65
};
66
67
const userConfig = {
68
timeout: 10000,
69
headers: {
70
"Authorization": "Bearer token123"
71
}
72
};
73
74
const envConfig = {
75
retries: 5,
76
headers: {
77
"X-Environment": "production"
78
}
79
};
80
81
const finalConfig = merge(defaults, userConfig, envConfig);
82
console.log(finalConfig);
83
// Result: {
84
// timeout: 10000,
85
// retries: 5,
86
// headers: {
87
// "User-Agent": "OpenTelemetry/1.0",
88
// "Authorization": "Bearer token123",
89
// "X-Environment": "production"
90
// }
91
// }
92
93
// Handle circular references safely
94
const obj1 = { name: "parent" };
95
const obj2 = { child: obj1 };
96
obj1.parent = obj2; // Creates circular reference
97
98
const obj3 = { version: "1.0" };
99
const merged = merge(obj1, obj3); // Won't cause infinite recursion
100
101
// Use in configuration management
102
class ConfigManager {
103
private defaultConfig = {
104
tracing: {
105
enabled: true,
106
samplingRate: 1.0,
107
exporters: ["console"]
108
},
109
metrics: {
110
enabled: false,
111
interval: 30000
112
}
113
};
114
115
loadConfig(userConfig: any, envConfig: any = {}) {
116
return merge(this.defaultConfig, userConfig, envConfig);
117
}
118
}
119
```
120
121
### Timeout Utilities
122
123
Promise timeout handling with custom error types.
124
125
```typescript { .api }
126
/**
127
* Error thrown when operations exceed their timeout
128
*/
129
class TimeoutError extends Error {
130
constructor(message?: string);
131
}
132
133
/**
134
* Add timeout to a promise, rejecting with TimeoutError if exceeded
135
* @param promise - Promise to add timeout to
136
* @param timeout - Timeout in milliseconds
137
* @returns Promise that resolves with original result or rejects with TimeoutError
138
*/
139
function callWithTimeout<T>(promise: Promise<T>, timeout: number): Promise<T>;
140
```
141
142
**Usage Examples:**
143
144
```typescript
145
import { callWithTimeout, TimeoutError } from "@opentelemetry/core";
146
147
// Add timeout to network requests
148
async function fetchWithTimeout(url: string, timeoutMs: number = 5000) {
149
const fetchPromise = fetch(url);
150
151
try {
152
const response = await callWithTimeout(fetchPromise, timeoutMs);
153
return response;
154
} catch (error) {
155
if (error instanceof TimeoutError) {
156
console.error(`Request to ${url} timed out after ${timeoutMs}ms`);
157
throw new Error(`Network timeout: ${url}`);
158
}
159
throw error; // Re-throw other errors
160
}
161
}
162
163
// Use with export operations
164
async function exportWithTimeout<T>(exportFn: () => Promise<T>, timeout: number = 10000): Promise<T> {
165
try {
166
return await callWithTimeout(exportFn(), timeout);
167
} catch (error) {
168
if (error instanceof TimeoutError) {
169
console.error("Export operation timed out");
170
return Promise.reject(new Error("Export timeout"));
171
}
172
throw error;
173
}
174
}
175
176
// Database operations with timeout
177
class DatabaseClient {
178
async query(sql: string, timeout: number = 30000) {
179
const queryPromise = this.executeQuery(sql);
180
181
try {
182
return await callWithTimeout(queryPromise, timeout);
183
} catch (error) {
184
if (error instanceof TimeoutError) {
185
console.error(`Database query timed out: ${sql}`);
186
// Cancel the query if possible
187
this.cancelQuery();
188
throw new Error("Database query timeout");
189
}
190
throw error;
191
}
192
}
193
194
private async executeQuery(sql: string): Promise<any[]> {
195
// Database query implementation
196
return [];
197
}
198
199
private cancelQuery(): void {
200
// Query cancellation implementation
201
}
202
}
203
204
// Batch operations with individual timeouts
205
async function processBatchWithTimeout<T, R>(
206
items: T[],
207
processor: (item: T) => Promise<R>,
208
itemTimeout: number = 5000
209
): Promise<Array<R | Error>> {
210
const results = await Promise.allSettled(
211
items.map(item => callWithTimeout(processor(item), itemTimeout))
212
);
213
214
return results.map((result, index) => {
215
if (result.status === 'fulfilled') {
216
return result.value;
217
} else {
218
const error = result.reason;
219
if (error instanceof TimeoutError) {
220
console.warn(`Item ${index} timed out`);
221
return new Error(`Item ${index} timeout`);
222
}
223
return error;
224
}
225
});
226
}
227
228
// Retry with timeout
229
async function retryWithTimeout<T>(
230
operation: () => Promise<T>,
231
maxRetries: number = 3,
232
timeout: number = 5000,
233
backoff: number = 1000
234
): Promise<T> {
235
let lastError: Error;
236
237
for (let attempt = 1; attempt <= maxRetries; attempt++) {
238
try {
239
return await callWithTimeout(operation(), timeout);
240
} catch (error) {
241
lastError = error instanceof Error ? error : new Error(String(error));
242
243
if (error instanceof TimeoutError) {
244
console.warn(`Attempt ${attempt} timed out`);
245
} else {
246
console.warn(`Attempt ${attempt} failed:`, error.message);
247
}
248
249
if (attempt < maxRetries) {
250
await new Promise(resolve => setTimeout(resolve, backoff * attempt));
251
}
252
}
253
}
254
255
throw lastError!;
256
}
257
```
258
259
### URL Utilities
260
261
URL pattern matching and filtering for instrumentation configuration.
262
263
```typescript { .api }
264
/**
265
* Check if URL matches a string or regex pattern
266
* @param url - URL to test
267
* @param urlToMatch - String or RegExp pattern to match against
268
* @returns True if URL matches the pattern
269
*/
270
function urlMatches(url: string, urlToMatch: string | RegExp): boolean;
271
272
/**
273
* Check if URL should be ignored based on ignore patterns
274
* @param url - URL to test
275
* @param ignoredUrls - Array of string or RegExp patterns to ignore
276
* @returns True if URL should be ignored
277
*/
278
function isUrlIgnored(url: string, ignoredUrls?: Array<string | RegExp>): boolean;
279
```
280
281
**Usage Examples:**
282
283
```typescript
284
import { urlMatches, isUrlIgnored } from "@opentelemetry/core";
285
286
// Basic URL matching
287
console.log(urlMatches("https://api.example.com/users", "https://api.example.com")); // true
288
console.log(urlMatches("https://api.example.com/users", /\/users$/)); // true
289
console.log(urlMatches("https://other.com/users", "https://api.example.com")); // false
290
291
// Configure ignored URLs for instrumentation
292
const ignoredUrls = [
293
"https://internal.monitoring.com",
294
/\/health$/,
295
/\/metrics$/,
296
"http://localhost:3000/debug"
297
];
298
299
// Check if URLs should be ignored
300
console.log(isUrlIgnored("https://api.example.com/users", ignoredUrls)); // false
301
console.log(isUrlIgnored("https://api.example.com/health", ignoredUrls)); // true
302
console.log(isUrlIgnored("https://internal.monitoring.com/status", ignoredUrls)); // true
303
304
// Use in HTTP instrumentation
305
class HttpInstrumentation {
306
private ignoredUrls: Array<string | RegExp>;
307
308
constructor(config: { ignoredUrls?: Array<string | RegExp> } = {}) {
309
this.ignoredUrls = config.ignoredUrls || [];
310
}
311
312
shouldInstrument(url: string): boolean {
313
return !isUrlIgnored(url, this.ignoredUrls);
314
}
315
316
instrumentRequest(url: string, options: any) {
317
if (!this.shouldInstrument(url)) {
318
console.log(`Skipping instrumentation for ignored URL: ${url}`);
319
return this.makeUninstrumentedRequest(url, options);
320
}
321
322
console.log(`Instrumenting request to: ${url}`);
323
return this.makeInstrumentedRequest(url, options);
324
}
325
326
private makeInstrumentedRequest(url: string, options: any) {
327
// Create span and instrument the request
328
}
329
330
private makeUninstrumentedRequest(url: string, options: any) {
331
// Make request without instrumentation
332
}
333
}
334
335
// Advanced pattern matching
336
const complexIgnorePatterns = [
337
// Ignore all internal services
338
/^https?:\/\/.*\.internal\./,
339
340
// Ignore health and monitoring endpoints
341
/\/(health|ping|status|metrics|ready|live)$/,
342
343
// Ignore static assets
344
/\.(css|js|png|jpg|gif|ico|svg|woff|woff2)$/,
345
346
// Ignore specific domains
347
"https://analytics.google.com",
348
"https://www.googletagmanager.com",
349
350
// Ignore development servers
351
/^https?:\/\/localhost/,
352
/^https?:\/\/127\.0\.0\.1/
353
];
354
355
function shouldTraceUrl(url: string): boolean {
356
return !isUrlIgnored(url, complexIgnorePatterns);
357
}
358
359
// URL categorization
360
function categorizeUrl(url: string): string {
361
const categories = [
362
{ pattern: /\/api\//, category: "api" },
363
{ pattern: /\/(health|ping|status)$/, category: "health" },
364
{ pattern: /\/(metrics|telemetry)$/, category: "monitoring" },
365
{ pattern: /\/static\//, category: "static" },
366
{ pattern: /\/admin\//, category: "admin" }
367
];
368
369
for (const { pattern, category } of categories) {
370
if (urlMatches(url, pattern)) {
371
return category;
372
}
373
}
374
375
return "unknown";
376
}
377
378
// Use in middleware
379
function createTracingMiddleware(ignoredUrls: Array<string | RegExp> = []) {
380
return (req: any, res: any, next: any) => {
381
const url = req.url;
382
383
if (isUrlIgnored(url, ignoredUrls)) {
384
// Skip tracing for ignored URLs
385
req.skipTracing = true;
386
} else {
387
// Enable tracing
388
req.urlCategory = categorizeUrl(url);
389
req.shouldTrace = true;
390
}
391
392
next();
393
};
394
}
395
```
396
397
### Callback Utilities
398
399
One-time callback execution with promise-based result handling.
400
401
```typescript { .api }
402
/**
403
* Ensures a callback is only invoked once, with promise-based result handling
404
*/
405
class BindOnceFuture<
406
R,
407
This = unknown,
408
T extends (this: This, ...args: unknown[]) => R = () => R
409
> {
410
/**
411
* Create a new BindOnceFuture
412
* @param callback - Function to bind and call once
413
* @param that - Context ('this' value) for the callback
414
*/
415
constructor(callback: T, that: This);
416
417
/** Whether the callback has been called */
418
readonly isCalled: boolean;
419
420
/** Promise that resolves with the callback result */
421
readonly promise: Promise<R>;
422
423
/**
424
* Call the callback (only the first call will execute)
425
* @param args - Arguments to pass to the callback
426
* @returns Promise resolving to callback result
427
*/
428
call(...args: Parameters<T>): Promise<R>;
429
}
430
```
431
432
**Usage Examples:**
433
434
```typescript
435
import { BindOnceFuture } from "@opentelemetry/core";
436
437
// Create one-time initialization function
438
class ServiceManager {
439
private initFuture: BindOnceFuture<void, this, () => void>;
440
441
constructor() {
442
this.initFuture = new BindOnceFuture(this.doInitialization, this);
443
}
444
445
async initialize(): Promise<void> {
446
// This will only run once, regardless of how many times initialize() is called
447
return this.initFuture.call();
448
}
449
450
private doInitialization(): void {
451
console.log("Initializing service...");
452
// Expensive initialization logic here
453
}
454
455
async getService(): Promise<any> {
456
await this.initialize(); // Safe to call multiple times
457
return this.service;
458
}
459
}
460
461
// One-time resource cleanup
462
class ResourceManager {
463
private cleanupFuture: BindOnceFuture<Promise<void>, this, () => Promise<void>>;
464
465
constructor() {
466
this.cleanupFuture = new BindOnceFuture(this.doCleanup, this);
467
}
468
469
async cleanup(): Promise<void> {
470
return this.cleanupFuture.call();
471
}
472
473
private async doCleanup(): Promise<void> {
474
console.log("Cleaning up resources...");
475
// Cleanup logic that should only run once
476
await this.closeConnections();
477
await this.releaseResources();
478
}
479
480
private async closeConnections(): Promise<void> {
481
// Implementation
482
}
483
484
private async releaseResources(): Promise<void> {
485
// Implementation
486
}
487
}
488
489
// One-time configuration loading
490
class ConfigLoader {
491
private loadConfigFuture: BindOnceFuture<any, this, () => any>;
492
493
constructor() {
494
this.loadConfigFuture = new BindOnceFuture(this.loadConfiguration, this);
495
}
496
497
async getConfig(): Promise<any> {
498
return this.loadConfigFuture.call();
499
}
500
501
private loadConfiguration(): any {
502
console.log("Loading configuration...");
503
// Expensive config loading that should only happen once
504
return {
505
database: { url: "postgresql://localhost:5432/app" },
506
redis: { url: "redis://localhost:6379" },
507
features: ["tracing", "metrics"]
508
};
509
}
510
}
511
512
// Multiple callers, single execution
513
async function demonstrateUsage() {
514
const configLoader = new ConfigLoader();
515
516
// Multiple concurrent calls - only loads once
517
const [config1, config2, config3] = await Promise.all([
518
configLoader.getConfig(),
519
configLoader.getConfig(),
520
configLoader.getConfig()
521
]);
522
523
console.log("All configs are identical:",
524
config1 === config2 && config2 === config3); // true
525
526
// Check if already called
527
console.log("Config was loaded:", configLoader.loadConfigFuture?.isCalled); // true
528
}
529
530
// Error handling with BindOnceFuture
531
class ErrorProneInitializer {
532
private initFuture: BindOnceFuture<string, this, () => string>;
533
534
constructor() {
535
this.initFuture = new BindOnceFuture(this.riskyInitialization, this);
536
}
537
538
async initialize(): Promise<string> {
539
try {
540
return await this.initFuture.call();
541
} catch (error) {
542
console.error("Initialization failed:", error);
543
throw error;
544
}
545
}
546
547
private riskyInitialization(): string {
548
if (Math.random() < 0.5) {
549
throw new Error("Random initialization failure");
550
}
551
return "Successfully initialized";
552
}
553
}
554
555
// Use with different callback signatures
556
function createCounter(): BindOnceFuture<number, object, (start: number) => number> {
557
const context = {};
558
559
return new BindOnceFuture((start: number) => {
560
console.log(`Counter starting at ${start}`);
561
return start;
562
}, context);
563
}
564
565
const counter = createCounter();
566
counter.call(10).then(result => console.log("Counter result:", result)); // 10
567
counter.call(20).then(result => console.log("Counter result:", result)); // Still 10
568
```
569
570
### Configuration Utilities
571
572
Configuration parsing and type conversion utilities.
573
574
```typescript { .api }
575
/**
576
* Convert string value to DiagLogLevel enum value
577
* Returns undefined only for null/undefined input.
578
* For invalid string values, logs a warning and returns DiagLogLevel.INFO as fallback.
579
* @param value - String representation of log level
580
* @returns DiagLogLevel enum value or undefined if input is null/undefined
581
*/
582
function diagLogLevelFromString(value: string | undefined): DiagLogLevel | undefined;
583
```
584
585
**Usage Examples:**
586
587
```typescript
588
import { diagLogLevelFromString } from "@opentelemetry/core";
589
import { DiagLogLevel, diag } from "@opentelemetry/api";
590
591
// Parse log level from environment variable
592
const logLevelStr = process.env.OTEL_LOG_LEVEL || "info";
593
const logLevel = diagLogLevelFromString(logLevelStr);
594
595
if (logLevel !== undefined) {
596
diag.setLogger(console, logLevel);
597
console.log(`Log level set to: ${DiagLogLevel[logLevel]}`);
598
} else {
599
console.warn(`Invalid log level: ${logLevelStr}, using default`);
600
diag.setLogger(console, DiagLogLevel.INFO);
601
}
602
603
// Configuration parser with validation
604
class TelemetryConfig {
605
static parseConfig(env: Record<string, string | undefined>) {
606
return {
607
logLevel: diagLogLevelFromString(env.OTEL_LOG_LEVEL) ?? DiagLogLevel.INFO,
608
serviceName: env.OTEL_SERVICE_NAME ?? "unknown-service",
609
serviceVersion: env.OTEL_SERVICE_VERSION ?? "1.0.0",
610
environment: env.OTEL_ENVIRONMENT ?? "development"
611
};
612
}
613
614
static setDiagnostics(config: ReturnType<typeof TelemetryConfig.parseConfig>) {
615
diag.setLogger(console, config.logLevel);
616
617
// Log configuration at startup
618
diag.info("OpenTelemetry configuration:", {
619
serviceName: config.serviceName,
620
serviceVersion: config.serviceVersion,
621
environment: config.environment,
622
logLevel: DiagLogLevel[config.logLevel]
623
});
624
}
625
}
626
627
// Usage in application startup
628
const config = TelemetryConfig.parseConfig(process.env);
629
TelemetryConfig.setDiagnostics(config);
630
631
// Test different log level formats
632
const testLogLevels = [
633
"debug", "DEBUG", "Debug",
634
"info", "INFO", "Info",
635
"warn", "WARN", "Warn",
636
"error", "ERROR", "Error",
637
"none", "NONE", "None",
638
"all", "ALL", "All",
639
"invalid", "", undefined
640
];
641
642
testLogLevels.forEach(level => {
643
const parsed = diagLogLevelFromString(level);
644
console.log(`"${level}" -> ${parsed !== undefined ? DiagLogLevel[parsed] : 'undefined'}`);
645
});
646
647
// Dynamic log level adjustment
648
function setLogLevel(levelStr: string): boolean {
649
const level = diagLogLevelFromString(levelStr);
650
651
if (level !== undefined) {
652
diag.setLogger(console, level);
653
diag.info(`Log level changed to: ${DiagLogLevel[level]}`);
654
return true;
655
} else {
656
diag.warn(`Invalid log level: ${levelStr}`);
657
return false;
658
}
659
}
660
661
// Runtime configuration updates
662
function updateTelemetryConfig(updates: Record<string, string>) {
663
if (updates.OTEL_LOG_LEVEL) {
664
const success = setLogLevel(updates.OTEL_LOG_LEVEL);
665
if (!success) {
666
delete updates.OTEL_LOG_LEVEL; // Remove invalid value
667
}
668
}
669
670
// Apply other configuration updates
671
return updates;
672
}
673
```
674
675
### Internal Utilities
676
677
Internal utilities namespace for advanced SDK operations.
678
679
```typescript { .api }
680
/**
681
* Internal utilities namespace (use with caution)
682
*/
683
const internal: {
684
/** Internal export utilities */
685
_export: any;
686
};
687
```
688
689
**Usage Examples:**
690
691
```typescript
692
import { internal } from "@opentelemetry/core";
693
694
// Access internal export utilities (advanced usage)
695
// Note: Internal APIs are subject to change without notice
696
const exportUtils = internal._export;
697
698
// These are internal utilities used by other OpenTelemetry packages
699
// Use with caution as they may change without notice
700
console.log("Internal export utilities available:", !!exportUtils);
701
```
702
```