@littlebearapps/platform-consumer-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +306 -0
- package/package.json +53 -0
- package/src/ai-gateway.ts +305 -0
- package/src/constants.ts +147 -0
- package/src/costs.ts +590 -0
- package/src/do-heartbeat.ts +249 -0
- package/src/dynamic-patterns.ts +273 -0
- package/src/errors.ts +285 -0
- package/src/features.ts +149 -0
- package/src/heartbeat.ts +27 -0
- package/src/index.ts +950 -0
- package/src/logging.ts +543 -0
- package/src/middleware.ts +447 -0
- package/src/patterns.ts +156 -0
- package/src/proxy.ts +732 -0
- package/src/retry.ts +19 -0
- package/src/service-client.ts +291 -0
- package/src/telemetry.ts +342 -0
- package/src/timeout.ts +212 -0
- package/src/tracing.ts +403 -0
- package/src/types.ts +465 -0
package/src/logging.ts
ADDED
|
@@ -0,0 +1,543 @@
|
|
|
1
|
+
/// <reference types="@cloudflare/workers-types" />
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Platform SDK Logging
|
|
5
|
+
*
|
|
6
|
+
* Structured JSON logging for Workers Observability.
|
|
7
|
+
* Provides correlation IDs, error categorisation, and timed operations.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* import { createLoggerFromEnv } from './lib/platform-sdk';
|
|
12
|
+
*
|
|
13
|
+
* const log = createLoggerFromEnv(env, 'stripe-connector', 'platform:connector:stripe');
|
|
14
|
+
* log.info('Starting sync', { customerId: 'cus_123' });
|
|
15
|
+
*
|
|
16
|
+
* try {
|
|
17
|
+
* const duration = await log.timed('fetch_customers', async () => {
|
|
18
|
+
* return await stripe.customers.list();
|
|
19
|
+
* });
|
|
20
|
+
* } catch (error) {
|
|
21
|
+
* log.error('Sync failed', error);
|
|
22
|
+
* }
|
|
23
|
+
* ```
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { CircuitBreakerError } from './types';
|
|
27
|
+
import { extractTraceContext, setTraceContext } from './tracing';
|
|
28
|
+
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// TYPES
|
|
31
|
+
// =============================================================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Log severity levels.
|
|
35
|
+
* Maps to Workers Observability log levels.
|
|
36
|
+
*/
|
|
37
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Error category for classification.
|
|
41
|
+
* Used for alerting priority and deduplication.
|
|
42
|
+
*/
|
|
43
|
+
export type ErrorCategory =
|
|
44
|
+
| 'VALIDATION' // Input/schema validation errors
|
|
45
|
+
| 'NETWORK' // Network/timeout errors
|
|
46
|
+
| 'CIRCUIT_BREAKER' // Circuit breaker tripped
|
|
47
|
+
| 'INTERNAL' // Internal/unexpected errors
|
|
48
|
+
| 'AUTH' // Authentication/authorisation errors
|
|
49
|
+
| 'RATE_LIMIT' // Rate limiting errors
|
|
50
|
+
| 'D1_ERROR' // D1 database errors
|
|
51
|
+
| 'KV_ERROR' // KV namespace errors
|
|
52
|
+
| 'QUEUE_ERROR' // Queue errors
|
|
53
|
+
| 'EXTERNAL_API'; // External API errors
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Structured log entry.
|
|
57
|
+
* JSON format for Workers Observability.
|
|
58
|
+
*/
|
|
59
|
+
export interface StructuredLog {
|
|
60
|
+
/** Log level */
|
|
61
|
+
level: LogLevel;
|
|
62
|
+
/** Human-readable message */
|
|
63
|
+
message: string;
|
|
64
|
+
/** ISO 8601 timestamp */
|
|
65
|
+
timestamp: string;
|
|
66
|
+
/** Correlation ID for request tracing */
|
|
67
|
+
correlationId?: string;
|
|
68
|
+
/** W3C Trace ID (32 hex chars) for distributed tracing */
|
|
69
|
+
traceId?: string;
|
|
70
|
+
/** W3C Span ID (16 hex chars) for distributed tracing */
|
|
71
|
+
spanId?: string;
|
|
72
|
+
/** Feature ID (project:category:feature) */
|
|
73
|
+
featureId?: string;
|
|
74
|
+
/** Worker name */
|
|
75
|
+
worker: string;
|
|
76
|
+
/** Error category (for error/warn levels) */
|
|
77
|
+
category?: ErrorCategory;
|
|
78
|
+
/** Error details */
|
|
79
|
+
error?: {
|
|
80
|
+
name: string;
|
|
81
|
+
message: string;
|
|
82
|
+
stack?: string;
|
|
83
|
+
code?: string;
|
|
84
|
+
};
|
|
85
|
+
/** Additional context */
|
|
86
|
+
context?: Record<string, unknown>;
|
|
87
|
+
/** Operation duration in milliseconds */
|
|
88
|
+
durationMs?: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Logger configuration options.
|
|
93
|
+
*/
|
|
94
|
+
export interface LoggerOptions {
|
|
95
|
+
/** Worker name for identification */
|
|
96
|
+
worker: string;
|
|
97
|
+
/** Feature ID for budget tracking */
|
|
98
|
+
featureId?: string;
|
|
99
|
+
/** Correlation ID for request tracing */
|
|
100
|
+
correlationId?: string;
|
|
101
|
+
/** W3C Trace ID for distributed tracing */
|
|
102
|
+
traceId?: string;
|
|
103
|
+
/** W3C Span ID for distributed tracing */
|
|
104
|
+
spanId?: string;
|
|
105
|
+
/** Minimum log level (default: 'info') */
|
|
106
|
+
minLevel?: LogLevel;
|
|
107
|
+
/** Additional default context */
|
|
108
|
+
defaultContext?: Record<string, unknown>;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Logger interface.
|
|
113
|
+
*/
|
|
114
|
+
export interface Logger {
|
|
115
|
+
/** Log debug message */
|
|
116
|
+
debug(message: string, context?: Record<string, unknown>): void;
|
|
117
|
+
/** Log info message */
|
|
118
|
+
info(message: string, context?: Record<string, unknown>): void;
|
|
119
|
+
/** Log warning message (with optional error) */
|
|
120
|
+
warn(message: string, error?: unknown, context?: Record<string, unknown>): void;
|
|
121
|
+
/** Log error message */
|
|
122
|
+
error(message: string, error?: unknown, context?: Record<string, unknown>): void;
|
|
123
|
+
/** Time an async operation and log its duration */
|
|
124
|
+
timed<T>(operation: string, fn: () => Promise<T>, context?: Record<string, unknown>): Promise<T>;
|
|
125
|
+
/** Create a child logger with additional context */
|
|
126
|
+
child(context: Record<string, unknown>): Logger;
|
|
127
|
+
/** Get the correlation ID */
|
|
128
|
+
readonly correlationId: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// =============================================================================
|
|
132
|
+
// CORRELATION ID MANAGEMENT
|
|
133
|
+
// =============================================================================
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* WeakMap to store correlation IDs per environment.
|
|
137
|
+
* Same pattern as telemetry context.
|
|
138
|
+
*/
|
|
139
|
+
const correlationIds = new WeakMap<object, string>();
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Generate a new correlation ID.
|
|
143
|
+
* Uses crypto.randomUUID() for uniqueness.
|
|
144
|
+
*/
|
|
145
|
+
export function generateCorrelationId(): string {
|
|
146
|
+
return crypto.randomUUID();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get or create a correlation ID for an environment.
|
|
151
|
+
*/
|
|
152
|
+
export function getCorrelationId(env: object): string {
|
|
153
|
+
let id = correlationIds.get(env);
|
|
154
|
+
if (!id) {
|
|
155
|
+
id = generateCorrelationId();
|
|
156
|
+
correlationIds.set(env, id);
|
|
157
|
+
}
|
|
158
|
+
return id;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Set a specific correlation ID for an environment.
|
|
163
|
+
* Useful for propagating IDs from incoming requests.
|
|
164
|
+
*/
|
|
165
|
+
export function setCorrelationId(env: object, correlationId: string): void {
|
|
166
|
+
correlationIds.set(env, correlationId);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// =============================================================================
|
|
170
|
+
// ERROR CATEGORISATION
|
|
171
|
+
// =============================================================================
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Categorise an error based on its type and message.
|
|
175
|
+
* Uses error name, message patterns, and known error types.
|
|
176
|
+
*/
|
|
177
|
+
export function categoriseError(error: unknown): ErrorCategory {
|
|
178
|
+
if (error instanceof CircuitBreakerError) {
|
|
179
|
+
return 'CIRCUIT_BREAKER';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (error instanceof Error) {
|
|
183
|
+
const name = error.name.toLowerCase();
|
|
184
|
+
const message = error.message.toLowerCase();
|
|
185
|
+
|
|
186
|
+
// Auth errors
|
|
187
|
+
if (
|
|
188
|
+
name.includes('auth') ||
|
|
189
|
+
name.includes('unauthorized') ||
|
|
190
|
+
name.includes('forbidden') ||
|
|
191
|
+
message.includes('unauthorized') ||
|
|
192
|
+
message.includes('forbidden') ||
|
|
193
|
+
message.includes('401') ||
|
|
194
|
+
message.includes('403')
|
|
195
|
+
) {
|
|
196
|
+
return 'AUTH';
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Rate limit errors
|
|
200
|
+
if (
|
|
201
|
+
name.includes('ratelimit') ||
|
|
202
|
+
message.includes('rate limit') ||
|
|
203
|
+
message.includes('too many requests') ||
|
|
204
|
+
message.includes('429')
|
|
205
|
+
) {
|
|
206
|
+
return 'RATE_LIMIT';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Network/timeout errors
|
|
210
|
+
if (
|
|
211
|
+
name.includes('timeout') ||
|
|
212
|
+
name.includes('network') ||
|
|
213
|
+
name.includes('fetch') ||
|
|
214
|
+
message.includes('timeout') ||
|
|
215
|
+
message.includes('network') ||
|
|
216
|
+
message.includes('econnrefused') ||
|
|
217
|
+
message.includes('enotfound') ||
|
|
218
|
+
message.includes('socket hang up')
|
|
219
|
+
) {
|
|
220
|
+
return 'NETWORK';
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Validation errors
|
|
224
|
+
if (
|
|
225
|
+
name.includes('validation') ||
|
|
226
|
+
name.includes('schema') ||
|
|
227
|
+
name.includes('parse') ||
|
|
228
|
+
message.includes('invalid') ||
|
|
229
|
+
message.includes('required') ||
|
|
230
|
+
message.includes('expected')
|
|
231
|
+
) {
|
|
232
|
+
return 'VALIDATION';
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// D1 errors
|
|
236
|
+
if (name.includes('d1') || message.includes('d1_error') || message.includes('sqlite')) {
|
|
237
|
+
return 'D1_ERROR';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// KV errors
|
|
241
|
+
if (name.includes('kv') || message.includes('kv_error') || message.includes('namespace')) {
|
|
242
|
+
return 'KV_ERROR';
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Queue errors
|
|
246
|
+
if (name.includes('queue') || message.includes('queue_error')) {
|
|
247
|
+
return 'QUEUE_ERROR';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// External API errors (by common status codes)
|
|
251
|
+
if (
|
|
252
|
+
message.includes('500') ||
|
|
253
|
+
message.includes('502') ||
|
|
254
|
+
message.includes('503') ||
|
|
255
|
+
message.includes('504')
|
|
256
|
+
) {
|
|
257
|
+
return 'EXTERNAL_API';
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return 'INTERNAL';
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Extract error code from an error if available.
|
|
266
|
+
*/
|
|
267
|
+
export function extractErrorCode(error: unknown): string | undefined {
|
|
268
|
+
if (error && typeof error === 'object') {
|
|
269
|
+
const errorObj = error as Record<string, unknown>;
|
|
270
|
+
if (typeof errorObj.code === 'string') {
|
|
271
|
+
return errorObj.code;
|
|
272
|
+
}
|
|
273
|
+
if (typeof errorObj.errno === 'string') {
|
|
274
|
+
return errorObj.errno;
|
|
275
|
+
}
|
|
276
|
+
if (typeof errorObj.status === 'number') {
|
|
277
|
+
return `HTTP_${errorObj.status}`;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return undefined;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// =============================================================================
|
|
284
|
+
// LOG LEVEL FILTERING
|
|
285
|
+
// =============================================================================
|
|
286
|
+
|
|
287
|
+
const LOG_LEVELS: Record<LogLevel, number> = {
|
|
288
|
+
debug: 0,
|
|
289
|
+
info: 1,
|
|
290
|
+
warn: 2,
|
|
291
|
+
error: 3,
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Check if a log level should be output based on minimum level.
|
|
296
|
+
*/
|
|
297
|
+
function shouldLog(level: LogLevel, minLevel: LogLevel): boolean {
|
|
298
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[minLevel];
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// =============================================================================
|
|
302
|
+
// LOGGER IMPLEMENTATION
|
|
303
|
+
// =============================================================================
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Create a structured logger.
|
|
307
|
+
*/
|
|
308
|
+
export function createLogger(options: LoggerOptions): Logger {
|
|
309
|
+
const {
|
|
310
|
+
worker,
|
|
311
|
+
featureId,
|
|
312
|
+
correlationId,
|
|
313
|
+
traceId,
|
|
314
|
+
spanId,
|
|
315
|
+
minLevel = 'info',
|
|
316
|
+
defaultContext = {},
|
|
317
|
+
} = options;
|
|
318
|
+
|
|
319
|
+
const logCorrelationId = correlationId ?? generateCorrelationId();
|
|
320
|
+
|
|
321
|
+
function formatLog(
|
|
322
|
+
level: LogLevel,
|
|
323
|
+
message: string,
|
|
324
|
+
error?: unknown,
|
|
325
|
+
context?: Record<string, unknown>,
|
|
326
|
+
durationMs?: number
|
|
327
|
+
): StructuredLog {
|
|
328
|
+
const log: StructuredLog = {
|
|
329
|
+
level,
|
|
330
|
+
message,
|
|
331
|
+
timestamp: new Date().toISOString(),
|
|
332
|
+
worker,
|
|
333
|
+
};
|
|
334
|
+
|
|
335
|
+
if (logCorrelationId) {
|
|
336
|
+
log.correlationId = logCorrelationId;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Add distributed tracing context
|
|
340
|
+
if (traceId) {
|
|
341
|
+
log.traceId = traceId;
|
|
342
|
+
}
|
|
343
|
+
if (spanId) {
|
|
344
|
+
log.spanId = spanId;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (featureId) {
|
|
348
|
+
log.featureId = featureId;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (error) {
|
|
352
|
+
log.category = categoriseError(error);
|
|
353
|
+
log.error = {
|
|
354
|
+
name: error instanceof Error ? error.name : 'Error',
|
|
355
|
+
message: error instanceof Error ? error.message : String(error),
|
|
356
|
+
code: extractErrorCode(error),
|
|
357
|
+
};
|
|
358
|
+
// Only include stack in debug mode or for errors
|
|
359
|
+
if (error instanceof Error && error.stack && level === 'error') {
|
|
360
|
+
log.error.stack = error.stack;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (durationMs !== undefined) {
|
|
365
|
+
log.durationMs = durationMs;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Merge default context with provided context
|
|
369
|
+
const mergedContext = { ...defaultContext, ...context };
|
|
370
|
+
if (Object.keys(mergedContext).length > 0) {
|
|
371
|
+
log.context = mergedContext;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return log;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function output(log: StructuredLog): void {
|
|
378
|
+
// Output as JSON for Workers Observability
|
|
379
|
+
const json = JSON.stringify(log);
|
|
380
|
+
|
|
381
|
+
switch (log.level) {
|
|
382
|
+
case 'debug':
|
|
383
|
+
console.debug(json);
|
|
384
|
+
break;
|
|
385
|
+
case 'info':
|
|
386
|
+
console.log(json);
|
|
387
|
+
break;
|
|
388
|
+
case 'warn':
|
|
389
|
+
console.warn(json);
|
|
390
|
+
break;
|
|
391
|
+
case 'error':
|
|
392
|
+
console.error(json);
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const logger: Logger = {
|
|
398
|
+
get correlationId() {
|
|
399
|
+
return logCorrelationId;
|
|
400
|
+
},
|
|
401
|
+
|
|
402
|
+
debug(message: string, context?: Record<string, unknown>): void {
|
|
403
|
+
if (shouldLog('debug', minLevel)) {
|
|
404
|
+
output(formatLog('debug', message, undefined, context));
|
|
405
|
+
}
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
info(message: string, context?: Record<string, unknown>): void {
|
|
409
|
+
if (shouldLog('info', minLevel)) {
|
|
410
|
+
output(formatLog('info', message, undefined, context));
|
|
411
|
+
}
|
|
412
|
+
},
|
|
413
|
+
|
|
414
|
+
warn(message: string, error?: unknown, context?: Record<string, unknown>): void {
|
|
415
|
+
if (shouldLog('warn', minLevel)) {
|
|
416
|
+
output(formatLog('warn', message, error, context));
|
|
417
|
+
}
|
|
418
|
+
},
|
|
419
|
+
|
|
420
|
+
error(message: string, error?: unknown, context?: Record<string, unknown>): void {
|
|
421
|
+
if (shouldLog('error', minLevel)) {
|
|
422
|
+
output(formatLog('error', message, error, context));
|
|
423
|
+
}
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
async timed<T>(
|
|
427
|
+
operation: string,
|
|
428
|
+
fn: () => Promise<T>,
|
|
429
|
+
context?: Record<string, unknown>
|
|
430
|
+
): Promise<T> {
|
|
431
|
+
const start = Date.now();
|
|
432
|
+
try {
|
|
433
|
+
const result = await fn();
|
|
434
|
+
const durationMs = Date.now() - start;
|
|
435
|
+
if (shouldLog('info', minLevel)) {
|
|
436
|
+
output(formatLog('info', `${operation} completed`, undefined, context, durationMs));
|
|
437
|
+
}
|
|
438
|
+
return result;
|
|
439
|
+
} catch (error) {
|
|
440
|
+
const durationMs = Date.now() - start;
|
|
441
|
+
if (shouldLog('error', minLevel)) {
|
|
442
|
+
output(formatLog('error', `${operation} failed`, error, context, durationMs));
|
|
443
|
+
}
|
|
444
|
+
throw error;
|
|
445
|
+
}
|
|
446
|
+
},
|
|
447
|
+
|
|
448
|
+
child(context: Record<string, unknown>): Logger {
|
|
449
|
+
return createLogger({
|
|
450
|
+
worker,
|
|
451
|
+
featureId,
|
|
452
|
+
correlationId: logCorrelationId,
|
|
453
|
+
traceId,
|
|
454
|
+
spanId,
|
|
455
|
+
minLevel,
|
|
456
|
+
defaultContext: { ...defaultContext, ...context },
|
|
457
|
+
});
|
|
458
|
+
},
|
|
459
|
+
};
|
|
460
|
+
|
|
461
|
+
return logger;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Create a logger from a tracked environment.
|
|
466
|
+
* Automatically extracts or creates correlation ID.
|
|
467
|
+
*
|
|
468
|
+
* @param env - Worker environment (tracked or raw)
|
|
469
|
+
* @param worker - Worker name
|
|
470
|
+
* @param featureId - Feature ID for budget tracking
|
|
471
|
+
* @param minLevel - Minimum log level (default: 'info')
|
|
472
|
+
*/
|
|
473
|
+
export function createLoggerFromEnv(
|
|
474
|
+
env: object,
|
|
475
|
+
worker: string,
|
|
476
|
+
featureId?: string,
|
|
477
|
+
minLevel: LogLevel = 'info'
|
|
478
|
+
): Logger {
|
|
479
|
+
const correlationId = getCorrelationId(env);
|
|
480
|
+
|
|
481
|
+
return createLogger({
|
|
482
|
+
worker,
|
|
483
|
+
featureId,
|
|
484
|
+
correlationId,
|
|
485
|
+
minLevel,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// =============================================================================
|
|
490
|
+
// REQUEST CONTEXT HELPERS
|
|
491
|
+
// =============================================================================
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Extract correlation ID from request headers.
|
|
495
|
+
* Looks for common correlation ID headers.
|
|
496
|
+
*/
|
|
497
|
+
export function extractCorrelationIdFromRequest(request: Request): string | undefined {
|
|
498
|
+
const headers = [
|
|
499
|
+
'x-correlation-id',
|
|
500
|
+
'x-request-id',
|
|
501
|
+
'x-trace-id',
|
|
502
|
+
'cf-ray', // Cloudflare Ray ID as fallback
|
|
503
|
+
];
|
|
504
|
+
|
|
505
|
+
for (const header of headers) {
|
|
506
|
+
const value = request.headers.get(header);
|
|
507
|
+
if (value) {
|
|
508
|
+
return value;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return undefined;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Create a logger from an incoming request.
|
|
517
|
+
* Extracts correlation ID and trace context from headers if present.
|
|
518
|
+
*/
|
|
519
|
+
export function createLoggerFromRequest(
|
|
520
|
+
request: Request,
|
|
521
|
+
env: object,
|
|
522
|
+
worker: string,
|
|
523
|
+
featureId?: string,
|
|
524
|
+
minLevel: LogLevel = 'info'
|
|
525
|
+
): Logger {
|
|
526
|
+
const correlationId = extractCorrelationIdFromRequest(request) ?? getCorrelationId(env);
|
|
527
|
+
|
|
528
|
+
// Also set on env for downstream use
|
|
529
|
+
setCorrelationId(env, correlationId);
|
|
530
|
+
|
|
531
|
+
// Extract W3C Trace Context from request
|
|
532
|
+
const traceContext = extractTraceContext(request);
|
|
533
|
+
setTraceContext(env, traceContext);
|
|
534
|
+
|
|
535
|
+
return createLogger({
|
|
536
|
+
worker,
|
|
537
|
+
featureId,
|
|
538
|
+
correlationId,
|
|
539
|
+
traceId: traceContext.traceId,
|
|
540
|
+
spanId: traceContext.spanId,
|
|
541
|
+
minLevel,
|
|
542
|
+
});
|
|
543
|
+
}
|