@inference-net/otel-cf-workers 2.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/LICENSE +28 -0
- package/README.md +426 -0
- package/dist/console-Cy5QdByD.js +38 -0
- package/dist/console-Cy5QdByD.js.map +1 -0
- package/dist/index.d.ts +555 -0
- package/dist/index.js +3664 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023, Erwin van der Koogh
|
|
4
|
+
|
|
5
|
+
Redistribution and use in source and binary forms, with or without
|
|
6
|
+
modification, are permitted provided that the following conditions are met:
|
|
7
|
+
|
|
8
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
9
|
+
list of conditions and the following disclaimer.
|
|
10
|
+
|
|
11
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
12
|
+
this list of conditions and the following disclaimer in the documentation
|
|
13
|
+
and/or other materials provided with the distribution.
|
|
14
|
+
|
|
15
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
16
|
+
contributors may be used to endorse or promote products derived from
|
|
17
|
+
this software without specific prior written permission.
|
|
18
|
+
|
|
19
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
20
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
21
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
22
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
23
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
24
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
25
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
26
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
27
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
28
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/README.md
ADDED
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# otel-cf-workers
|
|
2
|
+
|
|
3
|
+
OpenTelemetry instrumentation for Cloudflare Workers with automatic **tracing** and **logging** for handlers, bindings, and distributed traces.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
yarn add @inference-net/otel-cf-workers @opentelemetry/api
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
Add the `nodejs_compat` compatibility flag to your `wrangler.toml`:
|
|
14
|
+
|
|
15
|
+
```toml
|
|
16
|
+
compatibility_flags = ["nodejs_compat"]
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
### Tracing Only
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { trace } from '@opentelemetry/api'
|
|
25
|
+
import { instrument, ResolveConfigFn } from '@inference-net/otel-cf-workers'
|
|
26
|
+
|
|
27
|
+
export interface Env {
|
|
28
|
+
SIGNOZ_ENDPOINT: string
|
|
29
|
+
SIGNOZ_ACCESS_TOKEN: string
|
|
30
|
+
MY_KV: KVNamespace
|
|
31
|
+
MY_D1: D1Database
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const handler = {
|
|
35
|
+
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
36
|
+
// Auto-instrumented: HTTP handler
|
|
37
|
+
await fetch('https://api.example.com') // Auto-instrumented: outbound fetch
|
|
38
|
+
|
|
39
|
+
await env.MY_KV.get('key') // Auto-instrumented: KV operations
|
|
40
|
+
await env.MY_D1.prepare('SELECT * FROM users').all() // Auto-instrumented: D1 queries
|
|
41
|
+
|
|
42
|
+
// Manual instrumentation: add custom attributes
|
|
43
|
+
trace.getActiveSpan()?.setAttribute('user.id', '123')
|
|
44
|
+
|
|
45
|
+
return new Response('Hello World!')
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const config: ResolveConfigFn = (env: Env, _trigger) => {
|
|
50
|
+
return {
|
|
51
|
+
service: { name: 'my-worker' },
|
|
52
|
+
trace: {
|
|
53
|
+
exporter: {
|
|
54
|
+
url: env.SIGNOZ_ENDPOINT,
|
|
55
|
+
headers: { 'signoz-access-token': env.SIGNOZ_ACCESS_TOKEN },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default instrument(handler, config)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Tracing + Logging
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { trace } from '@opentelemetry/api'
|
|
68
|
+
import { instrument, getLogger, OTLPTransport, ConsoleTransport } from '@inference-net/otel-cf-workers'
|
|
69
|
+
|
|
70
|
+
const handler = {
|
|
71
|
+
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
|
|
72
|
+
const logger = getLogger('my-app')
|
|
73
|
+
|
|
74
|
+
// Logs automatically include trace context (trace ID, span ID)
|
|
75
|
+
logger.info('Processing request', {
|
|
76
|
+
'http.url': request.url,
|
|
77
|
+
'user.id': '123',
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
try {
|
|
81
|
+
await env.MY_KV.get('key')
|
|
82
|
+
logger.debug('KV operation complete')
|
|
83
|
+
|
|
84
|
+
return new Response('OK')
|
|
85
|
+
} catch (error) {
|
|
86
|
+
// Error logs automatically extract exception info
|
|
87
|
+
logger.error(error as Error)
|
|
88
|
+
return new Response('Error', { status: 500 })
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const config: ResolveConfigFn = (env: Env, _trigger) => ({
|
|
94
|
+
service: { name: 'my-worker' },
|
|
95
|
+
trace: {
|
|
96
|
+
exporter: {
|
|
97
|
+
url: `${env.OTEL_ENDPOINT}/v1/traces`,
|
|
98
|
+
headers: { 'x-api-key': env.API_KEY },
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
logs: {
|
|
102
|
+
transports: [
|
|
103
|
+
new OTLPTransport({
|
|
104
|
+
url: `${env.OTEL_ENDPOINT}/v1/logs`,
|
|
105
|
+
headers: { 'x-api-key': env.API_KEY },
|
|
106
|
+
}),
|
|
107
|
+
new ConsoleTransport({ pretty: true }), // Also log to console
|
|
108
|
+
],
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
|
|
112
|
+
export default instrument(handler, config)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Durable Objects
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { instrumentDO, ResolveConfigFn } from '@inference-net/otel-cf-workers'
|
|
119
|
+
|
|
120
|
+
class MyDurableObject implements DurableObject {
|
|
121
|
+
async fetch(request: Request): Promise<Response> {
|
|
122
|
+
// Auto-instrumented: DO fetch handler
|
|
123
|
+
await this.ctx.storage.get('key') // Auto-instrumented: DO storage
|
|
124
|
+
await this.ctx.storage.sql.exec('SELECT * FROM data') // Auto-instrumented: DO SQL
|
|
125
|
+
return new Response('Hello from DO!')
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async alarm(): Promise<void> {
|
|
129
|
+
// Auto-instrumented: DO alarm handler
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const config: ResolveConfigFn = (env, _trigger) => ({
|
|
134
|
+
exporter: { url: env.OTEL_ENDPOINT },
|
|
135
|
+
service: { name: 'my-durable-object' },
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
export const MyDO = instrumentDO(MyDurableObject, config)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## OpenTelemetry Features
|
|
142
|
+
|
|
143
|
+
### ✅ Fully Supported
|
|
144
|
+
|
|
145
|
+
**Tracing:**
|
|
146
|
+
|
|
147
|
+
- **Distributed Tracing**: Automatic W3C Trace Context propagation across services
|
|
148
|
+
- **Semantic Conventions**: Full support for OpenTelemetry semantic conventions (v1.28.0+)
|
|
149
|
+
- `db.query.text` - Database queries and keys
|
|
150
|
+
- `db.system.name` - Database system identification
|
|
151
|
+
- `db.operation.name` - Operation types
|
|
152
|
+
- `db.operation.batch.size` - Batch operation tracking
|
|
153
|
+
- `http.*` - HTTP request/response attributes
|
|
154
|
+
- `faas.*` - FaaS trigger and execution attributes
|
|
155
|
+
- **Custom Spans**: Create manual spans with `trace.getTracer()`
|
|
156
|
+
- **Span Attributes**: Set custom attributes on active spans
|
|
157
|
+
- **Context Propagation**: Async context management across Workers runtime
|
|
158
|
+
- **Sampling**: Both head and tail sampling strategies
|
|
159
|
+
- **Exporters**: OTLP/HTTP (JSON) format
|
|
160
|
+
- **Span Processors**: Custom trace-based batch processing
|
|
161
|
+
|
|
162
|
+
**Logging:**
|
|
163
|
+
|
|
164
|
+
- **Structured Logging**: OpenTelemetry Logs API with convenience methods
|
|
165
|
+
- **Automatic Trace Correlation**: Logs include trace ID and span ID from active spans
|
|
166
|
+
- **Child Loggers**: Inherit attributes from parent loggers for context propagation
|
|
167
|
+
- **Multiple Transports**: Send logs to OTLP backends, console, or custom destinations
|
|
168
|
+
- **Batching Strategies**: Configurable batching (immediate or size-based)
|
|
169
|
+
- **Severity Levels**: Standard OpenTelemetry severity levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL)
|
|
170
|
+
- **Console Instrumentation**: Optional capture of `console.log()`, `console.error()`, etc.
|
|
171
|
+
- **Custom Transports**: Extensible transport interface for custom log destinations
|
|
172
|
+
|
|
173
|
+
📖 **See [LOGS.md](./LOGS.md) for complete logging documentation**
|
|
174
|
+
|
|
175
|
+
### Cloudflare-Specific Attributes
|
|
176
|
+
|
|
177
|
+
In addition to OpenTelemetry standard attributes, we capture Cloudflare-specific metadata:
|
|
178
|
+
|
|
179
|
+
- `cloudflare.*` - Platform-specific attributes (ray ID, colo, script version)
|
|
180
|
+
- `geo.*` - Request geolocation data
|
|
181
|
+
- Response metadata (TTL, cache status, rows read/written)
|
|
182
|
+
- Binding-specific attributes (KV keys, D1 query stats, R2 checksums)
|
|
183
|
+
|
|
184
|
+
## Cloudflare Platform Support
|
|
185
|
+
|
|
186
|
+
### Triggers & Handlers
|
|
187
|
+
|
|
188
|
+
| Feature | Status | Notes |
|
|
189
|
+
| ------------------------------- | ------ | -------------------------------------------------- |
|
|
190
|
+
| HTTP Handler (`fetch`) | ✅ | Full support with geo, headers, user-agent parsing |
|
|
191
|
+
| Scheduled Handler (`scheduled`) | ✅ | Cron trigger instrumentation |
|
|
192
|
+
| Queue Consumer (`queue`) | ✅ | Message batch processing with ack/retry tracking |
|
|
193
|
+
| Email Handler (`email`) | ✅ | Incoming email processing |
|
|
194
|
+
| Durable Object `fetch` | ✅ | DO HTTP requests |
|
|
195
|
+
| Durable Object `alarm` | ✅ | DO alarm triggers |
|
|
196
|
+
| `ctx.waitUntil` | ✅ | Background promise tracking |
|
|
197
|
+
| Tail Handler (`tail`) | ❌ | Not yet supported |
|
|
198
|
+
| DO Hibernated WebSocket | ❌ | Not yet supported |
|
|
199
|
+
|
|
200
|
+
### Bindings
|
|
201
|
+
|
|
202
|
+
| Binding | Status | Operations Instrumented |
|
|
203
|
+
| --------------------- | ------ | ---------------------------------------------------------------------------------------- |
|
|
204
|
+
| **KV Namespace** | ✅ | `get`, `put`, `delete`, `list`, `getWithMetadata` |
|
|
205
|
+
| **R2 Bucket** | ✅ | `head`, `get`, `put`, `delete`, `list`, `createMultipartUpload`, `resumeMultipartUpload` |
|
|
206
|
+
| **D1 Database** | ✅ | `prepare`, `exec`, `batch`, `all`, `run`, `first`, `raw` |
|
|
207
|
+
| **Durable Objects** | ✅ | Stub `fetch` calls |
|
|
208
|
+
| **DO Storage (KV)** | ✅ | `get`, `put`, `delete`, `list`, `getAlarm`, `setAlarm`, `deleteAlarm` |
|
|
209
|
+
| **DO Storage (SQL)** | ✅ | `exec`, `execBatch` |
|
|
210
|
+
| **Queue Producer** | ✅ | `send`, `sendBatch` |
|
|
211
|
+
| **Service Bindings** | ✅ | Worker-to-worker calls |
|
|
212
|
+
| **Analytics Engine** | ✅ | `writeDataPoint` |
|
|
213
|
+
| **Images** | ✅ | `get`, `list`, `delete` |
|
|
214
|
+
| **Rate Limiting** | ✅ | `limit` |
|
|
215
|
+
| **Workers AI** | ❌ | Not yet supported |
|
|
216
|
+
| **Vectorize** | ❌ | Not yet supported |
|
|
217
|
+
| **Hyperdrive** | ❌ | Not yet supported |
|
|
218
|
+
| **Browser Rendering** | ❌ | Not yet supported |
|
|
219
|
+
| **Email Sending** | ❌ | Not yet supported |
|
|
220
|
+
| **mTLS** | ❌ | Not yet supported |
|
|
221
|
+
|
|
222
|
+
### Global APIs
|
|
223
|
+
|
|
224
|
+
| API | Status | Notes |
|
|
225
|
+
| --------- | ------ | ----------------------------------------------- |
|
|
226
|
+
| `fetch()` | ✅ | Global fetch calls with trace context injection |
|
|
227
|
+
| `caches` | ✅ | Cache API operations |
|
|
228
|
+
|
|
229
|
+
### Cloudflare Modules
|
|
230
|
+
|
|
231
|
+
| Module | Status |
|
|
232
|
+
| -------------------- | ------ |
|
|
233
|
+
| `cloudflare:email` | ❌ |
|
|
234
|
+
| `cloudflare:sockets` | ❌ |
|
|
235
|
+
|
|
236
|
+
## Configuration
|
|
237
|
+
|
|
238
|
+
### Basic Configuration
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
const config: ResolveConfigFn = (env: Env, trigger) => ({
|
|
242
|
+
service: {
|
|
243
|
+
name: 'my-service',
|
|
244
|
+
version: '1.0.0', // Optional
|
|
245
|
+
namespace: 'production', // Optional
|
|
246
|
+
},
|
|
247
|
+
trace: {
|
|
248
|
+
exporter: {
|
|
249
|
+
url: env.SIGNOZ_ENDPOINT,
|
|
250
|
+
headers: { 'signoz-access-token': env.SIGNOZ_ACCESS_TOKEN },
|
|
251
|
+
},
|
|
252
|
+
},
|
|
253
|
+
// Logs are optional
|
|
254
|
+
logs: {
|
|
255
|
+
transports: [new OTLPTransport({ url: env.LOGS_ENDPOINT })],
|
|
256
|
+
},
|
|
257
|
+
})
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
**Note:** Both `trace` and `logs` are optional. You can configure:
|
|
261
|
+
|
|
262
|
+
- Tracing only
|
|
263
|
+
- Logging only
|
|
264
|
+
- Both tracing and logging
|
|
265
|
+
- Neither (no telemetry)
|
|
266
|
+
|
|
267
|
+
### Sampling
|
|
268
|
+
|
|
269
|
+
```typescript
|
|
270
|
+
const config: ResolveConfigFn = (env, trigger) => ({
|
|
271
|
+
// ... exporter config
|
|
272
|
+
sampling: {
|
|
273
|
+
// Head sampling: sample 10% of requests at start
|
|
274
|
+
headSampler: {
|
|
275
|
+
ratio: 0.1,
|
|
276
|
+
acceptRemote: true, // Accept parent trace decisions
|
|
277
|
+
},
|
|
278
|
+
// Tail sampling: always keep errors even if not head-sampled
|
|
279
|
+
tailSampler: (trace) => {
|
|
280
|
+
const rootSpan = trace.localRootSpan
|
|
281
|
+
return (
|
|
282
|
+
rootSpan.status.code === SpanStatusCode.ERROR || (rootSpan.spanContext().traceFlags & TraceFlags.SAMPLED) !== 0
|
|
283
|
+
)
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
})
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Trace Context Propagation
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
const config: ResolveConfigFn = (env, trigger) => ({
|
|
293
|
+
// ... exporter config
|
|
294
|
+
|
|
295
|
+
// Control outbound trace context
|
|
296
|
+
fetch: {
|
|
297
|
+
includeTraceContext: (request) => {
|
|
298
|
+
// Only propagate to same-origin requests
|
|
299
|
+
return new URL(request.url).hostname === 'api.example.com'
|
|
300
|
+
},
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
// Control inbound trace context
|
|
304
|
+
handlers: {
|
|
305
|
+
fetch: {
|
|
306
|
+
acceptTraceContext: (request) => {
|
|
307
|
+
// Accept trace context from trusted origins
|
|
308
|
+
return request.headers.get('x-trusted') === 'true'
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
},
|
|
312
|
+
})
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Post-Processing
|
|
316
|
+
|
|
317
|
+
Redact sensitive data before export:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
const config: ResolveConfigFn = (env, trigger) => ({
|
|
321
|
+
// ... exporter config
|
|
322
|
+
postProcessor: (spans) => {
|
|
323
|
+
return spans.map((span) => {
|
|
324
|
+
// Redact URLs with tokens
|
|
325
|
+
if (span.attributes['http.url']) {
|
|
326
|
+
span.attributes['http.url'] = span.attributes['http.url'].replace(/token=[^&]+/, 'token=REDACTED')
|
|
327
|
+
}
|
|
328
|
+
// Remove sensitive headers
|
|
329
|
+
delete span.attributes['http.request.header.authorization']
|
|
330
|
+
return span
|
|
331
|
+
})
|
|
332
|
+
},
|
|
333
|
+
})
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
### Custom Propagator
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
const config: ResolveConfigFn = (env, trigger) => ({
|
|
340
|
+
// ... exporter config
|
|
341
|
+
propagator: new MyCustomPropagator(),
|
|
342
|
+
})
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
## Manual Instrumentation
|
|
346
|
+
|
|
347
|
+
### Adding Attributes
|
|
348
|
+
|
|
349
|
+
```typescript
|
|
350
|
+
import { trace } from '@opentelemetry/api'
|
|
351
|
+
|
|
352
|
+
const handler = {
|
|
353
|
+
async fetch(request: Request, env: Env) {
|
|
354
|
+
const span = trace.getActiveSpan()
|
|
355
|
+
if (span) {
|
|
356
|
+
span.setAttribute('user.id', '123')
|
|
357
|
+
span.setAttribute('user.role', 'admin')
|
|
358
|
+
}
|
|
359
|
+
return new Response('OK')
|
|
360
|
+
},
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Creating Custom Spans
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
import { trace, SpanStatusCode } from '@opentelemetry/api'
|
|
368
|
+
|
|
369
|
+
const handler = {
|
|
370
|
+
async fetch(request: Request, env: Env) {
|
|
371
|
+
const tracer = trace.getTracer('my-app')
|
|
372
|
+
|
|
373
|
+
return await tracer.startActiveSpan('process-request', async (span) => {
|
|
374
|
+
span.setAttribute('request.id', crypto.randomUUID())
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
const result = await doWork()
|
|
378
|
+
span.setStatus({ code: SpanStatusCode.OK })
|
|
379
|
+
return new Response(result)
|
|
380
|
+
} catch (error) {
|
|
381
|
+
span.recordException(error)
|
|
382
|
+
span.setStatus({ code: SpanStatusCode.ERROR })
|
|
383
|
+
throw error
|
|
384
|
+
} finally {
|
|
385
|
+
span.end()
|
|
386
|
+
}
|
|
387
|
+
})
|
|
388
|
+
},
|
|
389
|
+
}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## Limitations
|
|
393
|
+
|
|
394
|
+
- **Timing Accuracy**: The Workers runtime does not expose accurate timing information to protect against Spectre attacks. CPU-bound work may show 0ms duration. The clock only updates on I/O operations.
|
|
395
|
+
- **RPC-Style DO Calls**: Direct RPC method calls to Durable Objects (e.g., `await stub.myMethod()`) are not auto-instrumented. Use fetch-style calls (`await stub.fetch(request)`) for automatic tracing.
|
|
396
|
+
|
|
397
|
+
## Examples
|
|
398
|
+
|
|
399
|
+
See the [examples directory](./examples) for complete working examples:
|
|
400
|
+
|
|
401
|
+
**Tracing:**
|
|
402
|
+
|
|
403
|
+
- [Basic Worker](./examples/worker) - HTTP handler with KV and D1
|
|
404
|
+
- [Quickstart Guide](./examples/quickstart/QUICKSTART_GUIDE.md) - Step-by-step tutorial
|
|
405
|
+
|
|
406
|
+
**Logging:**
|
|
407
|
+
|
|
408
|
+
- [Basic Logging](./examples/logs-basic.ts) - Simple logging setup
|
|
409
|
+
- [Advanced Logging](./examples/logs-advanced.ts) - Traces + logs correlation
|
|
410
|
+
- [Logs Only](./examples/logs-only.ts) - Logging without tracing
|
|
411
|
+
- [Child Loggers](./examples/logs-child-loggers.ts) - Context inheritance with child loggers
|
|
412
|
+
|
|
413
|
+
## Resources
|
|
414
|
+
|
|
415
|
+
- [Logging Documentation](./LOGS.md) - Complete guide to OpenTelemetry Logs
|
|
416
|
+
- [OpenTelemetry Documentation](https://opentelemetry.io/docs/)
|
|
417
|
+
- [Cloudflare Workers Documentation](https://developers.cloudflare.com/workers/)
|
|
418
|
+
- [Semantic Conventions](https://opentelemetry.io/docs/specs/semconv/)
|
|
419
|
+
|
|
420
|
+
## License
|
|
421
|
+
|
|
422
|
+
BSD-3-Clause
|
|
423
|
+
|
|
424
|
+
## Contributing
|
|
425
|
+
|
|
426
|
+
Contributions welcome! This is a fork maintained by [@context-labs](https://github.com/context-labs), originally from [evanderkoogh/otel-cf-workers](https://github.com/evanderkoogh/otel-cf-workers).
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getLogger, SEVERITY_NUMBERS } from './index.js';
|
|
2
|
+
|
|
3
|
+
const severityMap = {
|
|
4
|
+
debug: SEVERITY_NUMBERS.DEBUG,
|
|
5
|
+
log: SEVERITY_NUMBERS.INFO,
|
|
6
|
+
info: SEVERITY_NUMBERS.INFO,
|
|
7
|
+
warn: SEVERITY_NUMBERS.WARN,
|
|
8
|
+
error: SEVERITY_NUMBERS.ERROR
|
|
9
|
+
};
|
|
10
|
+
function instrumentConsole() {
|
|
11
|
+
const logger = getLogger("console");
|
|
12
|
+
for (const [method, severityNumber] of Object.entries(severityMap)) {
|
|
13
|
+
const consoleMethod = method;
|
|
14
|
+
const original = console[consoleMethod];
|
|
15
|
+
if (typeof original !== "function") {
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
console[consoleMethod] = function(...args) {
|
|
19
|
+
try {
|
|
20
|
+
logger.emit({
|
|
21
|
+
severityNumber,
|
|
22
|
+
severityText: method.toUpperCase(),
|
|
23
|
+
body: args.length === 1 ? String(args[0]) : args.map(String).join(" "),
|
|
24
|
+
attributes: {
|
|
25
|
+
"log.source": "console",
|
|
26
|
+
"log.method": method,
|
|
27
|
+
"log.args_count": args.length
|
|
28
|
+
}
|
|
29
|
+
});
|
|
30
|
+
} catch (error) {
|
|
31
|
+
}
|
|
32
|
+
return original.apply(console, args);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export { instrumentConsole };
|
|
38
|
+
//# sourceMappingURL=console-Cy5QdByD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"console-Cy5QdByD.js","sources":["../src/logs/console.ts"],"sourcesContent":["import { getLogger } from './provider.js'\nimport { SEVERITY_NUMBERS } from '../constants.js'\n\nconst severityMap = {\n\tdebug: SEVERITY_NUMBERS.DEBUG,\n\tlog: SEVERITY_NUMBERS.INFO,\n\tinfo: SEVERITY_NUMBERS.INFO,\n\twarn: SEVERITY_NUMBERS.WARN,\n\terror: SEVERITY_NUMBERS.ERROR,\n} as const\n\ntype ConsoleMethod = keyof typeof severityMap\n\n/**\n * Instrument console methods to emit OpenTelemetry log records\n * This is OPT-IN and must be explicitly enabled in configuration\n */\nexport function instrumentConsole() {\n\tconst logger = getLogger('console')\n\n\tfor (const [method, severityNumber] of Object.entries(severityMap)) {\n\t\tconst consoleMethod = method as ConsoleMethod\n\t\tconst original = console[consoleMethod]\n\n\t\tif (typeof original !== 'function') {\n\t\t\tcontinue\n\t\t}\n\n\t\t// Replace console method with instrumented version\n\t\t;(console as any)[consoleMethod] = function (...args: any[]) {\n\t\t\ttry {\n\t\t\t\t// Emit log record\n\t\t\t\tlogger.emit({\n\t\t\t\t\tseverityNumber,\n\t\t\t\t\tseverityText: method.toUpperCase(),\n\t\t\t\t\tbody: args.length === 1 ? String(args[0]) : args.map(String).join(' '),\n\t\t\t\t\tattributes: {\n\t\t\t\t\t\t'log.source': 'console',\n\t\t\t\t\t\t'log.method': method,\n\t\t\t\t\t\t'log.args_count': args.length,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t} catch (error) {\n\t\t\t\t// Don't break console if logging fails - but can't use console.error here!\n\t\t\t}\n\n\t\t\t// Always call original console method\n\t\t\treturn original.apply(console, args)\n\t\t}\n\t}\n}\n"],"names":[],"mappings":";;AAGA,MAAM,WAAA,GAAc;AAAA,EACnB,OAAO,gBAAA,CAAiB,KAAA;AAAA,EACxB,KAAK,gBAAA,CAAiB,IAAA;AAAA,EACtB,MAAM,gBAAA,CAAiB,IAAA;AAAA,EACvB,MAAM,gBAAA,CAAiB,IAAA;AAAA,EACvB,OAAO,gBAAA,CAAiB;AACzB,CAAA;AAQO,SAAS,iBAAA,GAAoB;AACnC,EAAA,MAAM,MAAA,GAAS,UAAU,SAAS,CAAA;AAElC,EAAA,KAAA,MAAW,CAAC,MAAA,EAAQ,cAAc,KAAK,MAAA,CAAO,OAAA,CAAQ,WAAW,CAAA,EAAG;AACnE,IAAA,MAAM,aAAA,GAAgB,MAAA;AACtB,IAAA,MAAM,QAAA,GAAW,QAAQ,aAAa,CAAA;AAEtC,IAAA,IAAI,OAAO,aAAa,UAAA,EAAY;AACnC,MAAA;AAAA,IACD;AAGC,IAAC,OAAA,CAAgB,aAAa,CAAA,GAAI,SAAA,GAAa,IAAA,EAAa;AAC5D,MAAA,IAAI;AAEH,QAAA,MAAA,CAAO,IAAA,CAAK;AAAA,UACX,cAAA;AAAA,UACA,YAAA,EAAc,OAAO,WAAA,EAAY;AAAA,UACjC,IAAA,EAAM,IAAA,CAAK,MAAA,KAAW,CAAA,GAAI,OAAO,IAAA,CAAK,CAAC,CAAC,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,MAAM,CAAA,CAAE,KAAK,GAAG,CAAA;AAAA,UACrE,UAAA,EAAY;AAAA,YACX,YAAA,EAAc,SAAA;AAAA,YACd,YAAA,EAAc,MAAA;AAAA,YACd,kBAAkB,IAAA,CAAK;AAAA;AACxB,SACA,CAAA;AAAA,MACF,SAAS,KAAA,EAAO;AAAA,MAEhB;AAGA,MAAA,OAAO,QAAA,CAAS,KAAA,CAAM,OAAA,EAAS,IAAI,CAAA;AAAA,IACpC,CAAA;AAAA,EACD;AACD;;;;"}
|