duron 0.3.0-beta.6 → 0.3.0-beta.8
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/dist/action-job.js +1 -1
- package/dist/action.d.ts.map +1 -1
- package/dist/action.js +2 -2
- package/dist/adapters/postgres/schema.d.ts.map +1 -1
- package/dist/adapters/postgres/schema.js +3 -1
- package/dist/step-manager.d.ts.map +1 -1
- package/dist/step-manager.js +39 -0
- package/dist/telemetry/adapter.d.ts +22 -0
- package/dist/telemetry/adapter.d.ts.map +1 -1
- package/dist/telemetry/adapter.js +6 -0
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/telemetry/index.d.ts.map +1 -1
- package/dist/telemetry/local.d.ts +2 -1
- package/dist/telemetry/local.d.ts.map +1 -1
- package/dist/telemetry/local.js +63 -0
- package/dist/telemetry/noop.d.ts +2 -1
- package/dist/telemetry/noop.d.ts.map +1 -1
- package/dist/telemetry/noop.js +27 -0
- package/dist/telemetry/opentelemetry.d.ts +2 -1
- package/dist/telemetry/opentelemetry.d.ts.map +1 -1
- package/dist/telemetry/opentelemetry.js +110 -0
- package/migrations/postgres/20260119153838_flimsy_thor_girl/snapshot.json +12 -36
- package/package.json +1 -1
- package/src/action-job.ts +1 -1
- package/src/action.ts +8 -14
- package/src/adapters/postgres/schema.ts +3 -1
- package/src/step-manager.ts +58 -1
- package/src/telemetry/adapter.ts +174 -0
- package/src/telemetry/index.ts +3 -0
- package/src/telemetry/local.ts +93 -0
- package/src/telemetry/noop.ts +46 -0
- package/src/telemetry/opentelemetry.ts +145 -2
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
"version": "8",
|
|
3
3
|
"dialect": "postgres",
|
|
4
4
|
"id": "4e93c0bf-b135-40b4-b130-0cf0968bffe3",
|
|
5
|
-
"prevIds": [
|
|
6
|
-
"00000000-0000-0000-0000-000000000000"
|
|
7
|
-
],
|
|
5
|
+
"prevIds": ["00000000-0000-0000-0000-000000000000"],
|
|
8
6
|
"ddl": [
|
|
9
7
|
{
|
|
10
8
|
"name": "duron",
|
|
@@ -1268,14 +1266,10 @@
|
|
|
1268
1266
|
},
|
|
1269
1267
|
{
|
|
1270
1268
|
"nameExplicit": false,
|
|
1271
|
-
"columns": [
|
|
1272
|
-
"job_id"
|
|
1273
|
-
],
|
|
1269
|
+
"columns": ["job_id"],
|
|
1274
1270
|
"schemaTo": "duron",
|
|
1275
1271
|
"tableTo": "jobs",
|
|
1276
|
-
"columnsTo": [
|
|
1277
|
-
"id"
|
|
1278
|
-
],
|
|
1272
|
+
"columnsTo": ["id"],
|
|
1279
1273
|
"onUpdate": "NO ACTION",
|
|
1280
1274
|
"onDelete": "CASCADE",
|
|
1281
1275
|
"name": "job_steps_job_id_jobs_id_fkey",
|
|
@@ -1285,14 +1279,10 @@
|
|
|
1285
1279
|
},
|
|
1286
1280
|
{
|
|
1287
1281
|
"nameExplicit": false,
|
|
1288
|
-
"columns": [
|
|
1289
|
-
"job_id"
|
|
1290
|
-
],
|
|
1282
|
+
"columns": ["job_id"],
|
|
1291
1283
|
"schemaTo": "duron",
|
|
1292
1284
|
"tableTo": "jobs",
|
|
1293
|
-
"columnsTo": [
|
|
1294
|
-
"id"
|
|
1295
|
-
],
|
|
1285
|
+
"columnsTo": ["id"],
|
|
1296
1286
|
"onUpdate": "NO ACTION",
|
|
1297
1287
|
"onDelete": "CASCADE",
|
|
1298
1288
|
"name": "metrics_job_id_jobs_id_fkey",
|
|
@@ -1302,14 +1292,10 @@
|
|
|
1302
1292
|
},
|
|
1303
1293
|
{
|
|
1304
1294
|
"nameExplicit": false,
|
|
1305
|
-
"columns": [
|
|
1306
|
-
"step_id"
|
|
1307
|
-
],
|
|
1295
|
+
"columns": ["step_id"],
|
|
1308
1296
|
"schemaTo": "duron",
|
|
1309
1297
|
"tableTo": "job_steps",
|
|
1310
|
-
"columnsTo": [
|
|
1311
|
-
"id"
|
|
1312
|
-
],
|
|
1298
|
+
"columnsTo": ["id"],
|
|
1313
1299
|
"onUpdate": "NO ACTION",
|
|
1314
1300
|
"onDelete": "CASCADE",
|
|
1315
1301
|
"name": "metrics_step_id_job_steps_id_fkey",
|
|
@@ -1318,9 +1304,7 @@
|
|
|
1318
1304
|
"table": "metrics"
|
|
1319
1305
|
},
|
|
1320
1306
|
{
|
|
1321
|
-
"columns": [
|
|
1322
|
-
"id"
|
|
1323
|
-
],
|
|
1307
|
+
"columns": ["id"],
|
|
1324
1308
|
"nameExplicit": false,
|
|
1325
1309
|
"name": "job_steps_pkey",
|
|
1326
1310
|
"schema": "duron",
|
|
@@ -1328,9 +1312,7 @@
|
|
|
1328
1312
|
"entityType": "pks"
|
|
1329
1313
|
},
|
|
1330
1314
|
{
|
|
1331
|
-
"columns": [
|
|
1332
|
-
"id"
|
|
1333
|
-
],
|
|
1315
|
+
"columns": ["id"],
|
|
1334
1316
|
"nameExplicit": false,
|
|
1335
1317
|
"name": "jobs_pkey",
|
|
1336
1318
|
"schema": "duron",
|
|
@@ -1338,9 +1320,7 @@
|
|
|
1338
1320
|
"entityType": "pks"
|
|
1339
1321
|
},
|
|
1340
1322
|
{
|
|
1341
|
-
"columns": [
|
|
1342
|
-
"id"
|
|
1343
|
-
],
|
|
1323
|
+
"columns": ["id"],
|
|
1344
1324
|
"nameExplicit": false,
|
|
1345
1325
|
"name": "metrics_pkey",
|
|
1346
1326
|
"schema": "duron",
|
|
@@ -1349,11 +1329,7 @@
|
|
|
1349
1329
|
},
|
|
1350
1330
|
{
|
|
1351
1331
|
"nameExplicit": true,
|
|
1352
|
-
"columns": [
|
|
1353
|
-
"job_id",
|
|
1354
|
-
"name",
|
|
1355
|
-
"parent_step_id"
|
|
1356
|
-
],
|
|
1332
|
+
"columns": ["job_id", "name", "parent_step_id"],
|
|
1357
1333
|
"nullsNotDistinct": true,
|
|
1358
1334
|
"name": "unique_job_step_name_parent",
|
|
1359
1335
|
"entityType": "uniques",
|
|
@@ -1383,4 +1359,4 @@
|
|
|
1383
1359
|
}
|
|
1384
1360
|
],
|
|
1385
1361
|
"renames": []
|
|
1386
|
-
}
|
|
1362
|
+
}
|
package/package.json
CHANGED
package/src/action-job.ts
CHANGED
|
@@ -61,7 +61,7 @@ export class ActionJob<TAction extends Action<any, any, any>> {
|
|
|
61
61
|
adapter: options.database,
|
|
62
62
|
telemetry: options.telemetry,
|
|
63
63
|
logger: options.logger,
|
|
64
|
-
concurrencyLimit: options.action.concurrency,
|
|
64
|
+
concurrencyLimit: options.action.steps.concurrency,
|
|
65
65
|
})
|
|
66
66
|
|
|
67
67
|
this.#done = new Promise((resolve) => {
|
package/src/action.ts
CHANGED
|
@@ -133,18 +133,13 @@ export interface StepDefinitionHandlerContext<TInput extends z.ZodObject, TVaria
|
|
|
133
133
|
* The job ID this step belongs to.
|
|
134
134
|
*/
|
|
135
135
|
jobId: string
|
|
136
|
-
|
|
137
136
|
}
|
|
138
137
|
|
|
139
138
|
/**
|
|
140
139
|
* A reusable step definition created with createStep().
|
|
141
140
|
* Can be executed within an action handler using ctx.run().
|
|
142
141
|
*/
|
|
143
|
-
export interface StepDefinition<
|
|
144
|
-
TInput extends z.ZodObject,
|
|
145
|
-
TResult,
|
|
146
|
-
TVariables = Record<string, unknown>,
|
|
147
|
-
> {
|
|
142
|
+
export interface StepDefinition<TInput extends z.ZodObject, TResult, TVariables = Record<string, unknown>> {
|
|
148
143
|
/**
|
|
149
144
|
* The name of the step.
|
|
150
145
|
* Can be a static string or a function that generates the name from the input.
|
|
@@ -358,9 +353,9 @@ export function createActionDefinitionSchema<
|
|
|
358
353
|
* Function to determine the concurrency limit for a step.
|
|
359
354
|
* The concurrency limit is stored with each step and used during fetch operations.
|
|
360
355
|
* When fetching steps, the latest step's concurrency limit is used for each stepKey.
|
|
361
|
-
* If not provided, defaults to
|
|
356
|
+
* If not provided, defaults to 100.
|
|
362
357
|
*/
|
|
363
|
-
concurrency: z.number().default(
|
|
358
|
+
concurrency: z.number().default(100).describe('How many steps can run concurrently for this action'),
|
|
364
359
|
retry: RetryOptionsSchema.describe('How to retry on failure for the steps of this action'),
|
|
365
360
|
expire: z
|
|
366
361
|
.number()
|
|
@@ -368,7 +363,7 @@ export function createActionDefinitionSchema<
|
|
|
368
363
|
.describe('How long a step can run for (milliseconds)'),
|
|
369
364
|
})
|
|
370
365
|
.default({
|
|
371
|
-
concurrency:
|
|
366
|
+
concurrency: 100,
|
|
372
367
|
retry: { limit: 4, factor: 2, minTimeout: 1000, maxTimeout: 30000 },
|
|
373
368
|
expire: 5 * 60 * 1000,
|
|
374
369
|
}),
|
|
@@ -417,11 +412,10 @@ export const defineAction = <TVariables = Record<string, unknown>>() => {
|
|
|
417
412
|
/**
|
|
418
413
|
* Input type for createStep() - the definition object before transformation.
|
|
419
414
|
*/
|
|
420
|
-
export type StepDefinitionInput<
|
|
421
|
-
TInput
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
> = Omit<StepDefinition<TInput, TResult, TVariables>, '__stepDefinition'>
|
|
415
|
+
export type StepDefinitionInput<TInput extends z.ZodObject, TResult, TVariables = Record<string, unknown>> = Omit<
|
|
416
|
+
StepDefinition<TInput, TResult, TVariables>,
|
|
417
|
+
'__stepDefinition'
|
|
418
|
+
>
|
|
425
419
|
|
|
426
420
|
/**
|
|
427
421
|
* Creates a reusable step definition that can be executed within action handlers.
|
|
@@ -119,7 +119,9 @@ export default function createSchema(schemaName: string) {
|
|
|
119
119
|
index('idx_job_steps_output_fts').using('gin', sql`to_tsvector('english', ${table.output}::text)`),
|
|
120
120
|
// Unique constraint - step name is unique within a parent (name + parentStepId)
|
|
121
121
|
// nullsNotDistinct ensures NULL parent_step_id values are treated as equal for uniqueness
|
|
122
|
-
unique('unique_job_step_name_parent')
|
|
122
|
+
unique('unique_job_step_name_parent')
|
|
123
|
+
.on(table.job_id, table.name, table.parent_step_id)
|
|
124
|
+
.nullsNotDistinct(),
|
|
123
125
|
check(
|
|
124
126
|
'job_steps_status_check',
|
|
125
127
|
sql`${table.status} IN ${sql.raw(`(${STEP_STATUSES.map((s) => `'${s}'`).join(',')})`)}`,
|
package/src/step-manager.ts
CHANGED
|
@@ -23,7 +23,46 @@ import {
|
|
|
23
23
|
serializeError,
|
|
24
24
|
UnhandledChildStepsError,
|
|
25
25
|
} from './errors.js'
|
|
26
|
-
import type { ObserveContext, Span, TelemetryAdapter } from './telemetry/adapter.js'
|
|
26
|
+
import type { ObserveContext, Span, TelemetryAdapter, Tracer, TracerSpan } from './telemetry/adapter.js'
|
|
27
|
+
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Noop Tracer (for fallback observe context)
|
|
30
|
+
// ============================================================================
|
|
31
|
+
|
|
32
|
+
const noopTracerSpan: TracerSpan = {
|
|
33
|
+
setAttribute() {
|
|
34
|
+
// No-op
|
|
35
|
+
},
|
|
36
|
+
setAttributes() {
|
|
37
|
+
// No-op
|
|
38
|
+
},
|
|
39
|
+
addEvent() {
|
|
40
|
+
// No-op
|
|
41
|
+
},
|
|
42
|
+
recordException() {
|
|
43
|
+
// No-op
|
|
44
|
+
},
|
|
45
|
+
setStatusOk() {
|
|
46
|
+
// No-op
|
|
47
|
+
},
|
|
48
|
+
setStatusError() {
|
|
49
|
+
// No-op
|
|
50
|
+
},
|
|
51
|
+
end() {
|
|
52
|
+
// No-op
|
|
53
|
+
},
|
|
54
|
+
isRecording() {
|
|
55
|
+
return false
|
|
56
|
+
},
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const createNoopTracer = (name: string): Tracer => ({
|
|
60
|
+
name,
|
|
61
|
+
startSpan(): TracerSpan {
|
|
62
|
+
return noopTracerSpan
|
|
63
|
+
},
|
|
64
|
+
})
|
|
65
|
+
|
|
27
66
|
import pRetry from './utils/p-retry.js'
|
|
28
67
|
import waitForAbort from './utils/wait-for-abort.js'
|
|
29
68
|
|
|
@@ -251,6 +290,9 @@ export class StepManager {
|
|
|
251
290
|
addSpanEvent: () => {
|
|
252
291
|
// No-op
|
|
253
292
|
},
|
|
293
|
+
getTracer: (name: string) => {
|
|
294
|
+
return createNoopTracer(name)
|
|
295
|
+
},
|
|
254
296
|
}
|
|
255
297
|
}
|
|
256
298
|
|
|
@@ -261,6 +303,21 @@ export class StepManager {
|
|
|
261
303
|
* @returns Promise resolving to the step result
|
|
262
304
|
*/
|
|
263
305
|
async push(task: TaskStep): Promise<any> {
|
|
306
|
+
// Warn about potential starvation when child steps are queued and all slots are occupied
|
|
307
|
+
if (task.parentStepId !== null && this.#queue.running() >= this.#queue.concurrency) {
|
|
308
|
+
this.#logger.warn(
|
|
309
|
+
{
|
|
310
|
+
jobId: this.#jobId,
|
|
311
|
+
actionName: this.#actionName,
|
|
312
|
+
stepName: task.name,
|
|
313
|
+
parentStepId: task.parentStepId,
|
|
314
|
+
running: this.#queue.running(),
|
|
315
|
+
waiting: this.#queue.length(),
|
|
316
|
+
concurrency: this.#queue.concurrency,
|
|
317
|
+
},
|
|
318
|
+
'[StepManager] Potential starvation: child step queued while all concurrency slots are occupied by parent steps. Consider increasing steps.concurrency.',
|
|
319
|
+
)
|
|
320
|
+
}
|
|
264
321
|
return this.#queue.push(task)
|
|
265
322
|
}
|
|
266
323
|
|
package/src/telemetry/adapter.ts
CHANGED
|
@@ -108,6 +108,106 @@ export interface AddSpanAttributeOptions {
|
|
|
108
108
|
value: string | number | boolean
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Options for starting a custom span with the tracer.
|
|
113
|
+
*/
|
|
114
|
+
export interface StartSpanOptions {
|
|
115
|
+
/**
|
|
116
|
+
* Span kind (internal, client, server, producer, consumer).
|
|
117
|
+
* @default 'internal'
|
|
118
|
+
*/
|
|
119
|
+
kind?: 'internal' | 'client' | 'server' | 'producer' | 'consumer'
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Initial attributes for the span.
|
|
123
|
+
*/
|
|
124
|
+
attributes?: Record<string, string | number | boolean>
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Parent span to use for context propagation.
|
|
128
|
+
* If not provided, uses the current active context.
|
|
129
|
+
*/
|
|
130
|
+
parentSpan?: TracerSpan
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* A span created by the Tracer for manual instrumentation.
|
|
135
|
+
*/
|
|
136
|
+
export interface TracerSpan {
|
|
137
|
+
/**
|
|
138
|
+
* Set an attribute on the span.
|
|
139
|
+
*
|
|
140
|
+
* @param key - The attribute key
|
|
141
|
+
* @param value - The attribute value
|
|
142
|
+
*/
|
|
143
|
+
setAttribute(key: string, value: string | number | boolean): void
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Set multiple attributes on the span.
|
|
147
|
+
*
|
|
148
|
+
* @param attributes - The attributes to set
|
|
149
|
+
*/
|
|
150
|
+
setAttributes(attributes: Record<string, string | number | boolean>): void
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Add an event to the span.
|
|
154
|
+
*
|
|
155
|
+
* @param name - The event name
|
|
156
|
+
* @param attributes - Optional event attributes
|
|
157
|
+
*/
|
|
158
|
+
addEvent(name: string, attributes?: Record<string, string | number | boolean>): void
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Record an exception on the span.
|
|
162
|
+
*
|
|
163
|
+
* @param error - The error to record
|
|
164
|
+
*/
|
|
165
|
+
recordException(error: Error): void
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Set the span status to OK.
|
|
169
|
+
*/
|
|
170
|
+
setStatusOk(): void
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Set the span status to error.
|
|
174
|
+
*
|
|
175
|
+
* @param message - Optional error message
|
|
176
|
+
*/
|
|
177
|
+
setStatusError(message?: string): void
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* End the span.
|
|
181
|
+
* After calling this, no more operations can be performed on the span.
|
|
182
|
+
*/
|
|
183
|
+
end(): void
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Check if this span is recording.
|
|
187
|
+
*/
|
|
188
|
+
isRecording(): boolean
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* A Tracer provides methods for creating spans.
|
|
193
|
+
* Similar to OpenTelemetry's Tracer interface.
|
|
194
|
+
*/
|
|
195
|
+
export interface Tracer {
|
|
196
|
+
/**
|
|
197
|
+
* The name of this tracer.
|
|
198
|
+
*/
|
|
199
|
+
readonly name: string
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Start a new span.
|
|
203
|
+
*
|
|
204
|
+
* @param name - The name of the span
|
|
205
|
+
* @param options - Optional span configuration
|
|
206
|
+
* @returns A TracerSpan for manual instrumentation
|
|
207
|
+
*/
|
|
208
|
+
startSpan(name: string, options?: StartSpanOptions): TracerSpan
|
|
209
|
+
}
|
|
210
|
+
|
|
111
211
|
/**
|
|
112
212
|
* Observe context provided to action and step handlers.
|
|
113
213
|
*/
|
|
@@ -136,6 +236,37 @@ export interface ObserveContext {
|
|
|
136
236
|
* @param attributes - Optional event attributes
|
|
137
237
|
*/
|
|
138
238
|
addSpanEvent(name: string, attributes?: Record<string, any>): void
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Get a tracer for manual instrumentation.
|
|
242
|
+
* Similar to OpenTelemetry's `trace.getTracer()` method.
|
|
243
|
+
*
|
|
244
|
+
* @param name - The name of the tracer (typically your service or library name)
|
|
245
|
+
* @returns A Tracer for creating custom spans
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* const tracer = ctx.observe.getTracer('my-service')
|
|
250
|
+
*
|
|
251
|
+
* const span = tracer.startSpan('external-api-call', {
|
|
252
|
+
* kind: 'client',
|
|
253
|
+
* attributes: { 'api.endpoint': '/users' }
|
|
254
|
+
* })
|
|
255
|
+
*
|
|
256
|
+
* try {
|
|
257
|
+
* const result = await fetch('https://api.example.com/users')
|
|
258
|
+
* span.setStatusOk()
|
|
259
|
+
* return result
|
|
260
|
+
* } catch (error) {
|
|
261
|
+
* span.recordException(error)
|
|
262
|
+
* span.setStatusError(error.message)
|
|
263
|
+
* throw error
|
|
264
|
+
* } finally {
|
|
265
|
+
* span.end()
|
|
266
|
+
* }
|
|
267
|
+
* ```
|
|
268
|
+
*/
|
|
269
|
+
getTracer(name: string): Tracer
|
|
139
270
|
}
|
|
140
271
|
|
|
141
272
|
// ============================================================================
|
|
@@ -369,6 +500,41 @@ export abstract class TelemetryAdapter {
|
|
|
369
500
|
return this._addSpanAttribute(options)
|
|
370
501
|
}
|
|
371
502
|
|
|
503
|
+
// ============================================================================
|
|
504
|
+
// Tracer Methods
|
|
505
|
+
// ============================================================================
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* Get a tracer for manual instrumentation.
|
|
509
|
+
* Similar to OpenTelemetry's `trace.getTracer()` method.
|
|
510
|
+
*
|
|
511
|
+
* @param name - The name of the tracer (typically your service or library name)
|
|
512
|
+
* @returns A Tracer for creating custom spans
|
|
513
|
+
*
|
|
514
|
+
* @example
|
|
515
|
+
* ```typescript
|
|
516
|
+
* const tracer = telemetry.getTracer('my-service')
|
|
517
|
+
*
|
|
518
|
+
* const span = tracer.startSpan('process-order', {
|
|
519
|
+
* attributes: { 'order.id': orderId }
|
|
520
|
+
* })
|
|
521
|
+
*
|
|
522
|
+
* try {
|
|
523
|
+
* // Do some work
|
|
524
|
+
* span.addEvent('order.validated')
|
|
525
|
+
* span.setStatusOk()
|
|
526
|
+
* } catch (error) {
|
|
527
|
+
* span.recordException(error)
|
|
528
|
+
* span.setStatusError(error.message)
|
|
529
|
+
* } finally {
|
|
530
|
+
* span.end()
|
|
531
|
+
* }
|
|
532
|
+
* ```
|
|
533
|
+
*/
|
|
534
|
+
getTracer(name: string): Tracer {
|
|
535
|
+
return this._getTracer(name)
|
|
536
|
+
}
|
|
537
|
+
|
|
372
538
|
// ============================================================================
|
|
373
539
|
// Context Methods
|
|
374
540
|
// ============================================================================
|
|
@@ -404,6 +570,9 @@ export abstract class TelemetryAdapter {
|
|
|
404
570
|
this.#logger?.error(err, 'Error adding span event')
|
|
405
571
|
})
|
|
406
572
|
},
|
|
573
|
+
getTracer: (name: string) => {
|
|
574
|
+
return this.getTracer(name)
|
|
575
|
+
},
|
|
407
576
|
}
|
|
408
577
|
}
|
|
409
578
|
|
|
@@ -465,4 +634,9 @@ export abstract class TelemetryAdapter {
|
|
|
465
634
|
* Internal method to add a span attribute.
|
|
466
635
|
*/
|
|
467
636
|
protected abstract _addSpanAttribute(options: AddSpanAttributeOptions): Promise<void>
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Internal method to get a tracer for manual instrumentation.
|
|
640
|
+
*/
|
|
641
|
+
protected abstract _getTracer(name: string): Tracer
|
|
468
642
|
}
|
package/src/telemetry/index.ts
CHANGED
|
@@ -9,8 +9,11 @@ export {
|
|
|
9
9
|
type Span,
|
|
10
10
|
type StartDatabaseSpanOptions,
|
|
11
11
|
type StartJobSpanOptions,
|
|
12
|
+
type StartSpanOptions,
|
|
12
13
|
type StartStepSpanOptions,
|
|
13
14
|
TelemetryAdapter,
|
|
15
|
+
type Tracer,
|
|
16
|
+
type TracerSpan,
|
|
14
17
|
} from './adapter.js'
|
|
15
18
|
export { LocalTelemetryAdapter, type LocalTelemetryAdapterOptions, localTelemetryAdapter } from './local.js'
|
|
16
19
|
export { NoopTelemetryAdapter, noopTelemetryAdapter } from './noop.js'
|
package/src/telemetry/local.ts
CHANGED
|
@@ -7,8 +7,11 @@ import {
|
|
|
7
7
|
type Span,
|
|
8
8
|
type StartDatabaseSpanOptions,
|
|
9
9
|
type StartJobSpanOptions,
|
|
10
|
+
type StartSpanOptions,
|
|
10
11
|
type StartStepSpanOptions,
|
|
11
12
|
TelemetryAdapter,
|
|
13
|
+
type Tracer,
|
|
14
|
+
type TracerSpan,
|
|
12
15
|
} from './adapter.js'
|
|
13
16
|
|
|
14
17
|
// ============================================================================
|
|
@@ -304,6 +307,96 @@ export class LocalTelemetryAdapter extends TelemetryAdapter {
|
|
|
304
307
|
},
|
|
305
308
|
})
|
|
306
309
|
}
|
|
310
|
+
|
|
311
|
+
// ============================================================================
|
|
312
|
+
// Tracer Methods
|
|
313
|
+
// ============================================================================
|
|
314
|
+
|
|
315
|
+
protected _getTracer(name: string): Tracer {
|
|
316
|
+
const adapter = this
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
name,
|
|
320
|
+
|
|
321
|
+
startSpan(spanName: string, options?: StartSpanOptions): TracerSpan {
|
|
322
|
+
const spanId = `tracer:${name}:${globalThis.crypto.randomUUID()}`
|
|
323
|
+
const startTime = Date.now()
|
|
324
|
+
let ended = false
|
|
325
|
+
const attributes: Record<string, string | number | boolean> = {
|
|
326
|
+
...options?.attributes,
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Note: Local adapter tracer spans don't have a jobId context,
|
|
330
|
+
// so they can't be stored in the database. They're essentially no-ops
|
|
331
|
+
// but provide a consistent API for code that needs a tracer.
|
|
332
|
+
// For actual metrics storage, use ctx.observe within action/step handlers.
|
|
333
|
+
|
|
334
|
+
const tracerSpan: TracerSpan = {
|
|
335
|
+
setAttribute(key: string, value: string | number | boolean): void {
|
|
336
|
+
if (!ended) {
|
|
337
|
+
attributes[key] = value
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
setAttributes(attrs: Record<string, string | number | boolean>): void {
|
|
342
|
+
if (!ended) {
|
|
343
|
+
Object.assign(attributes, attrs)
|
|
344
|
+
}
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
addEvent(eventName: string, eventAttrs?: Record<string, string | number | boolean>): void {
|
|
348
|
+
if (!ended) {
|
|
349
|
+
adapter.logger?.debug({ spanId, event: eventName, attributes: eventAttrs }, 'Tracer span event')
|
|
350
|
+
}
|
|
351
|
+
},
|
|
352
|
+
|
|
353
|
+
recordException(error: Error): void {
|
|
354
|
+
if (!ended) {
|
|
355
|
+
attributes['error.message'] = error.message
|
|
356
|
+
attributes['error.name'] = error.name
|
|
357
|
+
adapter.logger?.debug({ spanId, error: error.message }, 'Tracer span exception')
|
|
358
|
+
}
|
|
359
|
+
},
|
|
360
|
+
|
|
361
|
+
setStatusOk(): void {
|
|
362
|
+
if (!ended) {
|
|
363
|
+
// biome-ignore lint/complexity/useLiteralKeys: Index signature requires bracket notation
|
|
364
|
+
attributes['status'] = 'ok'
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
|
|
368
|
+
setStatusError(message?: string): void {
|
|
369
|
+
if (!ended) {
|
|
370
|
+
// biome-ignore lint/complexity/useLiteralKeys: Index signature requires bracket notation
|
|
371
|
+
attributes['status'] = 'error'
|
|
372
|
+
if (message) {
|
|
373
|
+
attributes['status.message'] = message
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
|
|
378
|
+
end(): void {
|
|
379
|
+
if (!ended) {
|
|
380
|
+
ended = true
|
|
381
|
+
const duration = Date.now() - startTime
|
|
382
|
+
adapter.logger?.debug(
|
|
383
|
+
{ spanId, spanName, tracerName: name, durationMs: duration, attributes },
|
|
384
|
+
'Tracer span ended',
|
|
385
|
+
)
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
|
|
389
|
+
isRecording(): boolean {
|
|
390
|
+
return !ended
|
|
391
|
+
},
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
adapter.logger?.debug({ spanId, spanName, tracerName: name }, 'Tracer span started')
|
|
395
|
+
|
|
396
|
+
return tracerSpan
|
|
397
|
+
},
|
|
398
|
+
}
|
|
399
|
+
}
|
|
307
400
|
}
|
|
308
401
|
|
|
309
402
|
/**
|
package/src/telemetry/noop.ts
CHANGED
|
@@ -8,8 +8,41 @@ import {
|
|
|
8
8
|
type StartJobSpanOptions,
|
|
9
9
|
type StartStepSpanOptions,
|
|
10
10
|
TelemetryAdapter,
|
|
11
|
+
type Tracer,
|
|
12
|
+
type TracerSpan,
|
|
11
13
|
} from './adapter.js'
|
|
12
14
|
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Noop Tracer Span
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
const noopTracerSpan: TracerSpan = {
|
|
20
|
+
setAttribute() {
|
|
21
|
+
// No-op
|
|
22
|
+
},
|
|
23
|
+
setAttributes() {
|
|
24
|
+
// No-op
|
|
25
|
+
},
|
|
26
|
+
addEvent() {
|
|
27
|
+
// No-op
|
|
28
|
+
},
|
|
29
|
+
recordException() {
|
|
30
|
+
// No-op
|
|
31
|
+
},
|
|
32
|
+
setStatusOk() {
|
|
33
|
+
// No-op
|
|
34
|
+
},
|
|
35
|
+
setStatusError() {
|
|
36
|
+
// No-op
|
|
37
|
+
},
|
|
38
|
+
end() {
|
|
39
|
+
// No-op
|
|
40
|
+
},
|
|
41
|
+
isRecording() {
|
|
42
|
+
return false
|
|
43
|
+
},
|
|
44
|
+
}
|
|
45
|
+
|
|
13
46
|
// ============================================================================
|
|
14
47
|
// Noop Telemetry Adapter
|
|
15
48
|
// ============================================================================
|
|
@@ -84,6 +117,19 @@ export class NoopTelemetryAdapter extends TelemetryAdapter {
|
|
|
84
117
|
protected async _addSpanAttribute(_options: AddSpanAttributeOptions): Promise<void> {
|
|
85
118
|
// No-op
|
|
86
119
|
}
|
|
120
|
+
|
|
121
|
+
// ============================================================================
|
|
122
|
+
// Tracer Methods
|
|
123
|
+
// ============================================================================
|
|
124
|
+
|
|
125
|
+
protected _getTracer(name: string): Tracer {
|
|
126
|
+
return {
|
|
127
|
+
name,
|
|
128
|
+
startSpan(): TracerSpan {
|
|
129
|
+
return noopTracerSpan
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
}
|
|
87
133
|
}
|
|
88
134
|
|
|
89
135
|
/**
|