@mastra/observability 1.0.0-beta.10 → 1.0.0-beta.11
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/CHANGELOG.md +95 -0
- package/README.md +34 -79
- package/dist/exporters/base.d.ts +3 -2
- package/dist/exporters/base.d.ts.map +1 -1
- package/dist/exporters/index.d.ts +2 -2
- package/dist/exporters/index.d.ts.map +1 -1
- package/dist/exporters/tracking.d.ts +450 -0
- package/dist/exporters/tracking.d.ts.map +1 -0
- package/dist/index.cjs +1028 -25
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1027 -26
- package/dist/index.js.map +1 -1
- package/dist/instances/base.d.ts +15 -1
- package/dist/instances/base.d.ts.map +1 -1
- package/dist/model-tracing.d.ts.map +1 -1
- package/dist/spans/base.d.ts.map +1 -1
- package/dist/spans/default.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -4162,7 +4162,11 @@ var BaseExporter = class {
|
|
|
4162
4162
|
/** Mastra logger instance */
|
|
4163
4163
|
logger;
|
|
4164
4164
|
/** Whether this exporter is disabled */
|
|
4165
|
-
|
|
4165
|
+
#disabled = false;
|
|
4166
|
+
/** Public getter for disabled state */
|
|
4167
|
+
get isDisabled() {
|
|
4168
|
+
return this.#disabled;
|
|
4169
|
+
}
|
|
4166
4170
|
/**
|
|
4167
4171
|
* Initialize the base exporter with logger
|
|
4168
4172
|
*/
|
|
@@ -4201,7 +4205,7 @@ var BaseExporter = class {
|
|
|
4201
4205
|
* @param reason - Reason why the exporter is disabled
|
|
4202
4206
|
*/
|
|
4203
4207
|
setDisabled(reason) {
|
|
4204
|
-
this
|
|
4208
|
+
this.#disabled = true;
|
|
4205
4209
|
this.logger.warn(`${this.name} disabled: ${reason}`);
|
|
4206
4210
|
}
|
|
4207
4211
|
/**
|
|
@@ -4225,6 +4229,939 @@ var BaseExporter = class {
|
|
|
4225
4229
|
this.logger.info(`${this.name} shutdown complete`);
|
|
4226
4230
|
}
|
|
4227
4231
|
};
|
|
4232
|
+
var TraceData = class {
|
|
4233
|
+
/** The vendor-specific root/trace object */
|
|
4234
|
+
#rootSpan;
|
|
4235
|
+
/** The span ID of the root span */
|
|
4236
|
+
#rootSpanId;
|
|
4237
|
+
/** Whether a span with isRootSpan=true has been successfully processed */
|
|
4238
|
+
#rootSpanProcessed;
|
|
4239
|
+
/** Maps eventId to vendor-specific event objects */
|
|
4240
|
+
#events;
|
|
4241
|
+
/** Maps spanId to vendor-specific span objects */
|
|
4242
|
+
#spans;
|
|
4243
|
+
/** Maps spanId to parentSpanId, representing the span hierarchy */
|
|
4244
|
+
#tree;
|
|
4245
|
+
/** Set of span IDs that have started but not yet ended */
|
|
4246
|
+
#activeSpanIds;
|
|
4247
|
+
/** Maps spanId to vendor-specific metadata */
|
|
4248
|
+
#metadata;
|
|
4249
|
+
/** Arbitrary key-value storage for per-trace data */
|
|
4250
|
+
#extraData;
|
|
4251
|
+
/** Events waiting for the root span to be processed */
|
|
4252
|
+
#waitingForRoot;
|
|
4253
|
+
/** Events waiting for specific parent spans, keyed by parentSpanId */
|
|
4254
|
+
#waitingForParent;
|
|
4255
|
+
/** When this trace data was created, used for cap enforcement */
|
|
4256
|
+
createdAt;
|
|
4257
|
+
constructor() {
|
|
4258
|
+
this.#events = /* @__PURE__ */ new Map();
|
|
4259
|
+
this.#spans = /* @__PURE__ */ new Map();
|
|
4260
|
+
this.#activeSpanIds = /* @__PURE__ */ new Set();
|
|
4261
|
+
this.#tree = /* @__PURE__ */ new Map();
|
|
4262
|
+
this.#metadata = /* @__PURE__ */ new Map();
|
|
4263
|
+
this.#extraData = /* @__PURE__ */ new Map();
|
|
4264
|
+
this.#rootSpanProcessed = false;
|
|
4265
|
+
this.#waitingForRoot = [];
|
|
4266
|
+
this.#waitingForParent = /* @__PURE__ */ new Map();
|
|
4267
|
+
this.createdAt = /* @__PURE__ */ new Date();
|
|
4268
|
+
}
|
|
4269
|
+
/**
|
|
4270
|
+
* Check if this trace has a root span registered.
|
|
4271
|
+
* @returns True if addRoot() has been called
|
|
4272
|
+
*/
|
|
4273
|
+
hasRoot() {
|
|
4274
|
+
return !!this.#rootSpanId;
|
|
4275
|
+
}
|
|
4276
|
+
/**
|
|
4277
|
+
* Register the root span for this trace.
|
|
4278
|
+
* @param args.rootId - The span ID of the root span
|
|
4279
|
+
* @param args.rootData - The vendor-specific root object
|
|
4280
|
+
*/
|
|
4281
|
+
addRoot(args) {
|
|
4282
|
+
this.#rootSpanId = args.rootId;
|
|
4283
|
+
this.#rootSpan = args.rootData;
|
|
4284
|
+
this.#rootSpanProcessed = true;
|
|
4285
|
+
}
|
|
4286
|
+
/**
|
|
4287
|
+
* Get the vendor-specific root object.
|
|
4288
|
+
* @returns The root object, or undefined if not yet set
|
|
4289
|
+
*/
|
|
4290
|
+
getRoot() {
|
|
4291
|
+
return this.#rootSpan;
|
|
4292
|
+
}
|
|
4293
|
+
/**
|
|
4294
|
+
* Check if a span with isRootSpan=true has been successfully processed.
|
|
4295
|
+
* Set via addRoot() or markRootSpanProcessed().
|
|
4296
|
+
* @returns True if the root span has been processed
|
|
4297
|
+
*/
|
|
4298
|
+
isRootProcessed() {
|
|
4299
|
+
return this.#rootSpanProcessed;
|
|
4300
|
+
}
|
|
4301
|
+
/**
|
|
4302
|
+
* Mark that the root span has been processed.
|
|
4303
|
+
* Used by exporters with skipBuildRootTask=true where root goes through _buildSpan
|
|
4304
|
+
* instead of _buildRoot.
|
|
4305
|
+
*/
|
|
4306
|
+
markRootSpanProcessed() {
|
|
4307
|
+
this.#rootSpanProcessed = true;
|
|
4308
|
+
}
|
|
4309
|
+
/**
|
|
4310
|
+
* Store an arbitrary value in per-trace storage.
|
|
4311
|
+
* @param key - Storage key
|
|
4312
|
+
* @param value - Value to store
|
|
4313
|
+
*/
|
|
4314
|
+
setExtraValue(key, value) {
|
|
4315
|
+
this.#extraData.set(key, value);
|
|
4316
|
+
}
|
|
4317
|
+
/**
|
|
4318
|
+
* Check if a key exists in per-trace storage.
|
|
4319
|
+
* @param key - Storage key
|
|
4320
|
+
* @returns True if the key exists
|
|
4321
|
+
*/
|
|
4322
|
+
hasExtraValue(key) {
|
|
4323
|
+
return this.#extraData.has(key);
|
|
4324
|
+
}
|
|
4325
|
+
/**
|
|
4326
|
+
* Get a value from per-trace storage.
|
|
4327
|
+
* @param key - Storage key
|
|
4328
|
+
* @returns The stored value, or undefined if not found
|
|
4329
|
+
*/
|
|
4330
|
+
getExtraValue(key) {
|
|
4331
|
+
return this.#extraData.get(key);
|
|
4332
|
+
}
|
|
4333
|
+
// ============================================================================
|
|
4334
|
+
// Early Queue Methods
|
|
4335
|
+
// ============================================================================
|
|
4336
|
+
/**
|
|
4337
|
+
* Add an event to the waiting queue.
|
|
4338
|
+
* @param args.event - The tracing event to queue
|
|
4339
|
+
* @param args.waitingFor - 'root' or a specific parentSpanId
|
|
4340
|
+
* @param args.attempts - Optional: preserve attempts count when re-queuing
|
|
4341
|
+
* @param args.queuedAt - Optional: preserve original queue time when re-queuing
|
|
4342
|
+
*/
|
|
4343
|
+
addToWaitingQueue(args) {
|
|
4344
|
+
const queuedEvent = {
|
|
4345
|
+
event: args.event,
|
|
4346
|
+
waitingFor: args.waitingFor,
|
|
4347
|
+
attempts: args.attempts ?? 0,
|
|
4348
|
+
queuedAt: args.queuedAt ?? /* @__PURE__ */ new Date()
|
|
4349
|
+
};
|
|
4350
|
+
if (args.waitingFor === "root") {
|
|
4351
|
+
this.#waitingForRoot.push(queuedEvent);
|
|
4352
|
+
} else {
|
|
4353
|
+
const queue = this.#waitingForParent.get(args.waitingFor) ?? [];
|
|
4354
|
+
queue.push(queuedEvent);
|
|
4355
|
+
this.#waitingForParent.set(args.waitingFor, queue);
|
|
4356
|
+
}
|
|
4357
|
+
}
|
|
4358
|
+
/**
|
|
4359
|
+
* Get all events waiting for the root span.
|
|
4360
|
+
* Returns a copy of the internal array.
|
|
4361
|
+
*/
|
|
4362
|
+
getEventsWaitingForRoot() {
|
|
4363
|
+
return [...this.#waitingForRoot];
|
|
4364
|
+
}
|
|
4365
|
+
/**
|
|
4366
|
+
* Get all events waiting for a specific parent span.
|
|
4367
|
+
* Returns a copy of the internal array.
|
|
4368
|
+
*/
|
|
4369
|
+
getEventsWaitingFor(args) {
|
|
4370
|
+
return [...this.#waitingForParent.get(args.spanId) ?? []];
|
|
4371
|
+
}
|
|
4372
|
+
/**
|
|
4373
|
+
* Clear the waiting-for-root queue.
|
|
4374
|
+
*/
|
|
4375
|
+
clearWaitingForRoot() {
|
|
4376
|
+
this.#waitingForRoot = [];
|
|
4377
|
+
}
|
|
4378
|
+
/**
|
|
4379
|
+
* Clear the waiting queue for a specific parent span.
|
|
4380
|
+
*/
|
|
4381
|
+
clearWaitingFor(args) {
|
|
4382
|
+
this.#waitingForParent.delete(args.spanId);
|
|
4383
|
+
}
|
|
4384
|
+
/**
|
|
4385
|
+
* Get total count of events in all waiting queues.
|
|
4386
|
+
*/
|
|
4387
|
+
waitingQueueSize() {
|
|
4388
|
+
let count = this.#waitingForRoot.length;
|
|
4389
|
+
for (const queue of this.#waitingForParent.values()) {
|
|
4390
|
+
count += queue.length;
|
|
4391
|
+
}
|
|
4392
|
+
return count;
|
|
4393
|
+
}
|
|
4394
|
+
/**
|
|
4395
|
+
* Get all queued events across all waiting queues.
|
|
4396
|
+
* Used for cleanup and logging orphaned events.
|
|
4397
|
+
* @returns Array of all queued events
|
|
4398
|
+
*/
|
|
4399
|
+
getAllQueuedEvents() {
|
|
4400
|
+
const all = [...this.#waitingForRoot];
|
|
4401
|
+
for (const queue of this.#waitingForParent.values()) {
|
|
4402
|
+
all.push(...queue);
|
|
4403
|
+
}
|
|
4404
|
+
return all;
|
|
4405
|
+
}
|
|
4406
|
+
// ============================================================================
|
|
4407
|
+
// Span Tree Methods
|
|
4408
|
+
// ============================================================================
|
|
4409
|
+
/**
|
|
4410
|
+
* Record the parent-child relationship for a span.
|
|
4411
|
+
* @param args.spanId - The child span ID
|
|
4412
|
+
* @param args.parentSpanId - The parent span ID, or undefined for root spans
|
|
4413
|
+
*/
|
|
4414
|
+
addBranch(args) {
|
|
4415
|
+
this.#tree.set(args.spanId, args.parentSpanId);
|
|
4416
|
+
}
|
|
4417
|
+
/**
|
|
4418
|
+
* Get the parent span ID for a given span.
|
|
4419
|
+
* @param args.spanId - The span ID to look up
|
|
4420
|
+
* @returns The parent span ID, or undefined if root or not found
|
|
4421
|
+
*/
|
|
4422
|
+
getParentId(args) {
|
|
4423
|
+
return this.#tree.get(args.spanId);
|
|
4424
|
+
}
|
|
4425
|
+
// ============================================================================
|
|
4426
|
+
// Span Management Methods
|
|
4427
|
+
// ============================================================================
|
|
4428
|
+
/**
|
|
4429
|
+
* Register a span and mark it as active.
|
|
4430
|
+
* @param args.spanId - The span ID
|
|
4431
|
+
* @param args.spanData - The vendor-specific span object
|
|
4432
|
+
*/
|
|
4433
|
+
addSpan(args) {
|
|
4434
|
+
this.#spans.set(args.spanId, args.spanData);
|
|
4435
|
+
this.#activeSpanIds.add(args.spanId);
|
|
4436
|
+
}
|
|
4437
|
+
/**
|
|
4438
|
+
* Check if a span exists (regardless of active state).
|
|
4439
|
+
* @param args.spanId - The span ID to check
|
|
4440
|
+
* @returns True if the span exists
|
|
4441
|
+
*/
|
|
4442
|
+
hasSpan(args) {
|
|
4443
|
+
return this.#spans.has(args.spanId);
|
|
4444
|
+
}
|
|
4445
|
+
/**
|
|
4446
|
+
* Get a span by ID.
|
|
4447
|
+
* @param args.spanId - The span ID to look up
|
|
4448
|
+
* @returns The vendor-specific span object, or undefined if not found
|
|
4449
|
+
*/
|
|
4450
|
+
getSpan(args) {
|
|
4451
|
+
return this.#spans.get(args.spanId);
|
|
4452
|
+
}
|
|
4453
|
+
/**
|
|
4454
|
+
* Mark a span as ended (no longer active).
|
|
4455
|
+
* @param args.spanId - The span ID to mark as ended
|
|
4456
|
+
*/
|
|
4457
|
+
endSpan(args) {
|
|
4458
|
+
this.#activeSpanIds.delete(args.spanId);
|
|
4459
|
+
}
|
|
4460
|
+
/**
|
|
4461
|
+
* Check if a span is currently active (started but not ended).
|
|
4462
|
+
* @param args.spanId - The span ID to check
|
|
4463
|
+
* @returns True if the span is active
|
|
4464
|
+
*/
|
|
4465
|
+
isActiveSpan(args) {
|
|
4466
|
+
return this.#activeSpanIds.has(args.spanId);
|
|
4467
|
+
}
|
|
4468
|
+
/**
|
|
4469
|
+
* Get the count of currently active spans.
|
|
4470
|
+
* @returns Number of active spans
|
|
4471
|
+
*/
|
|
4472
|
+
activeSpanCount() {
|
|
4473
|
+
return this.#activeSpanIds.size;
|
|
4474
|
+
}
|
|
4475
|
+
/**
|
|
4476
|
+
* Get all active span IDs.
|
|
4477
|
+
* @returns Array of active span IDs
|
|
4478
|
+
*/
|
|
4479
|
+
get activeSpanIds() {
|
|
4480
|
+
return [...this.#activeSpanIds];
|
|
4481
|
+
}
|
|
4482
|
+
// ============================================================================
|
|
4483
|
+
// Event Management Methods
|
|
4484
|
+
// ============================================================================
|
|
4485
|
+
/**
|
|
4486
|
+
* Register an event.
|
|
4487
|
+
* @param args.eventId - The event ID
|
|
4488
|
+
* @param args.eventData - The vendor-specific event object
|
|
4489
|
+
*/
|
|
4490
|
+
addEvent(args) {
|
|
4491
|
+
this.#events.set(args.eventId, args.eventData);
|
|
4492
|
+
}
|
|
4493
|
+
// ============================================================================
|
|
4494
|
+
// Metadata Methods
|
|
4495
|
+
// ============================================================================
|
|
4496
|
+
/**
|
|
4497
|
+
* Store vendor-specific metadata for a span.
|
|
4498
|
+
* Note: This overwrites any existing metadata for the span.
|
|
4499
|
+
* @param args.spanId - The span ID
|
|
4500
|
+
* @param args.metadata - The vendor-specific metadata
|
|
4501
|
+
*/
|
|
4502
|
+
addMetadata(args) {
|
|
4503
|
+
this.#metadata.set(args.spanId, args.metadata);
|
|
4504
|
+
}
|
|
4505
|
+
/**
|
|
4506
|
+
* Get vendor-specific metadata for a span.
|
|
4507
|
+
* @param args.spanId - The span ID
|
|
4508
|
+
* @returns The metadata, or undefined if not found
|
|
4509
|
+
*/
|
|
4510
|
+
getMetadata(args) {
|
|
4511
|
+
return this.#metadata.get(args.spanId);
|
|
4512
|
+
}
|
|
4513
|
+
// ============================================================================
|
|
4514
|
+
// Parent Lookup Methods
|
|
4515
|
+
// ============================================================================
|
|
4516
|
+
/**
|
|
4517
|
+
* Get the parent span or event for a given span.
|
|
4518
|
+
* Looks up in both spans and events maps.
|
|
4519
|
+
* @param args.span - The span to find the parent for
|
|
4520
|
+
* @returns The parent span/event object, or undefined if root or not found
|
|
4521
|
+
*/
|
|
4522
|
+
getParent(args) {
|
|
4523
|
+
const parentId = args.span.parentSpanId;
|
|
4524
|
+
if (parentId) {
|
|
4525
|
+
if (this.#spans.has(parentId)) {
|
|
4526
|
+
return this.#spans.get(parentId);
|
|
4527
|
+
}
|
|
4528
|
+
if (this.#events.has(parentId)) {
|
|
4529
|
+
return this.#events.get(parentId);
|
|
4530
|
+
}
|
|
4531
|
+
}
|
|
4532
|
+
return void 0;
|
|
4533
|
+
}
|
|
4534
|
+
/**
|
|
4535
|
+
* Get the parent span/event or fall back to the root object.
|
|
4536
|
+
* Useful for vendors that attach child spans to either parent spans or the trace root.
|
|
4537
|
+
* @param args.span - The span to find the parent for
|
|
4538
|
+
* @returns The parent span/event, the root object, or undefined
|
|
4539
|
+
*/
|
|
4540
|
+
getParentOrRoot(args) {
|
|
4541
|
+
return this.getParent(args) ?? this.getRoot();
|
|
4542
|
+
}
|
|
4543
|
+
};
|
|
4544
|
+
var DEFAULT_EARLY_QUEUE_MAX_ATTEMPTS = 5;
|
|
4545
|
+
var DEFAULT_EARLY_QUEUE_TTL_MS = 3e4;
|
|
4546
|
+
var DEFAULT_TRACE_CLEANUP_DELAY_MS = 3e4;
|
|
4547
|
+
var DEFAULT_MAX_PENDING_CLEANUP_TRACES = 100;
|
|
4548
|
+
var DEFAULT_MAX_TOTAL_TRACES = 500;
|
|
4549
|
+
var TrackingExporter = class extends BaseExporter {
|
|
4550
|
+
/** Map of traceId to per-trace data container */
|
|
4551
|
+
#traceMap = /* @__PURE__ */ new Map();
|
|
4552
|
+
/** Flag to prevent processing during shutdown */
|
|
4553
|
+
#shutdownStarted = false;
|
|
4554
|
+
/** Flag to prevent concurrent hard cap enforcement */
|
|
4555
|
+
#hardCapEnforcementInProgress = false;
|
|
4556
|
+
/** Map of traceId to scheduled cleanup timeout */
|
|
4557
|
+
#pendingCleanups = /* @__PURE__ */ new Map();
|
|
4558
|
+
// Note: #traceMap maintains insertion order (JS Map spec), so we use
|
|
4559
|
+
// #traceMap.keys() to iterate traces oldest-first for cap enforcement.
|
|
4560
|
+
/** Subclass configuration with resolved values */
|
|
4561
|
+
config;
|
|
4562
|
+
/** Maximum attempts to process a queued event before dropping */
|
|
4563
|
+
#earlyQueueMaxAttempts;
|
|
4564
|
+
/** TTL in milliseconds for queued events */
|
|
4565
|
+
#earlyQueueTTLMs;
|
|
4566
|
+
/** Delay before cleaning up completed traces */
|
|
4567
|
+
#traceCleanupDelayMs;
|
|
4568
|
+
/** Soft cap on traces awaiting cleanup */
|
|
4569
|
+
#maxPendingCleanupTraces;
|
|
4570
|
+
/** Hard cap on total traces (will abort active spans if exceeded) */
|
|
4571
|
+
#maxTotalTraces;
|
|
4572
|
+
constructor(config) {
|
|
4573
|
+
super(config);
|
|
4574
|
+
this.config = config;
|
|
4575
|
+
this.#earlyQueueMaxAttempts = config.earlyQueueMaxAttempts ?? DEFAULT_EARLY_QUEUE_MAX_ATTEMPTS;
|
|
4576
|
+
this.#earlyQueueTTLMs = config.earlyQueueTTLMs ?? DEFAULT_EARLY_QUEUE_TTL_MS;
|
|
4577
|
+
this.#traceCleanupDelayMs = config.traceCleanupDelayMs ?? DEFAULT_TRACE_CLEANUP_DELAY_MS;
|
|
4578
|
+
this.#maxPendingCleanupTraces = config.maxPendingCleanupTraces ?? DEFAULT_MAX_PENDING_CLEANUP_TRACES;
|
|
4579
|
+
this.#maxTotalTraces = config.maxTotalTraces ?? DEFAULT_MAX_TOTAL_TRACES;
|
|
4580
|
+
}
|
|
4581
|
+
// ============================================================================
|
|
4582
|
+
// Early Queue Processing
|
|
4583
|
+
// ============================================================================
|
|
4584
|
+
/**
|
|
4585
|
+
* Schedule async processing of events waiting for root span.
|
|
4586
|
+
* Called after root span is successfully processed.
|
|
4587
|
+
*/
|
|
4588
|
+
#scheduleProcessWaitingForRoot(traceId) {
|
|
4589
|
+
setImmediate(() => {
|
|
4590
|
+
this.#processWaitingForRoot(traceId).catch((error) => {
|
|
4591
|
+
this.logger.error(`${this.name}: Error processing waiting-for-root queue`, { error, traceId });
|
|
4592
|
+
});
|
|
4593
|
+
});
|
|
4594
|
+
}
|
|
4595
|
+
/**
|
|
4596
|
+
* Schedule async processing of events waiting for a specific parent span.
|
|
4597
|
+
* Called after a span/event is successfully created.
|
|
4598
|
+
*/
|
|
4599
|
+
#scheduleProcessWaitingFor(traceId, spanId) {
|
|
4600
|
+
setImmediate(() => {
|
|
4601
|
+
this.#processWaitingFor(traceId, spanId).catch((error) => {
|
|
4602
|
+
this.logger.error(`${this.name}: Error processing waiting queue`, { error, traceId, spanId });
|
|
4603
|
+
});
|
|
4604
|
+
});
|
|
4605
|
+
}
|
|
4606
|
+
/**
|
|
4607
|
+
* Process all events waiting for root span.
|
|
4608
|
+
*/
|
|
4609
|
+
async #processWaitingForRoot(traceId) {
|
|
4610
|
+
if (this.#shutdownStarted) return;
|
|
4611
|
+
const traceData = this.#traceMap.get(traceId);
|
|
4612
|
+
if (!traceData) return;
|
|
4613
|
+
const queue = traceData.getEventsWaitingForRoot();
|
|
4614
|
+
if (queue.length === 0) return;
|
|
4615
|
+
this.logger.debug(`${this.name}: Processing ${queue.length} events waiting for root`, { traceId });
|
|
4616
|
+
const toKeep = [];
|
|
4617
|
+
const now = Date.now();
|
|
4618
|
+
for (const queuedEvent of queue) {
|
|
4619
|
+
if (now - queuedEvent.queuedAt.getTime() > this.#earlyQueueTTLMs) {
|
|
4620
|
+
this.logger.warn(`${this.name}: Dropping event due to TTL expiry`, {
|
|
4621
|
+
traceId,
|
|
4622
|
+
spanId: queuedEvent.event.exportedSpan.id,
|
|
4623
|
+
waitingFor: queuedEvent.waitingFor,
|
|
4624
|
+
queuedAt: queuedEvent.queuedAt,
|
|
4625
|
+
attempts: queuedEvent.attempts
|
|
4626
|
+
});
|
|
4627
|
+
continue;
|
|
4628
|
+
}
|
|
4629
|
+
if (queuedEvent.attempts >= this.#earlyQueueMaxAttempts) {
|
|
4630
|
+
this.logger.warn(`${this.name}: Dropping event due to max attempts`, {
|
|
4631
|
+
traceId,
|
|
4632
|
+
spanId: queuedEvent.event.exportedSpan.id,
|
|
4633
|
+
waitingFor: queuedEvent.waitingFor,
|
|
4634
|
+
attempts: queuedEvent.attempts
|
|
4635
|
+
});
|
|
4636
|
+
continue;
|
|
4637
|
+
}
|
|
4638
|
+
queuedEvent.attempts++;
|
|
4639
|
+
const processed = await this.#tryProcessQueuedEvent(queuedEvent, traceData);
|
|
4640
|
+
if (!processed) {
|
|
4641
|
+
const parentId = queuedEvent.event.exportedSpan.parentSpanId;
|
|
4642
|
+
if (parentId && traceData.isRootProcessed()) {
|
|
4643
|
+
traceData.addToWaitingQueue({
|
|
4644
|
+
event: queuedEvent.event,
|
|
4645
|
+
waitingFor: parentId,
|
|
4646
|
+
attempts: queuedEvent.attempts,
|
|
4647
|
+
queuedAt: queuedEvent.queuedAt
|
|
4648
|
+
});
|
|
4649
|
+
} else {
|
|
4650
|
+
toKeep.push(queuedEvent);
|
|
4651
|
+
}
|
|
4652
|
+
}
|
|
4653
|
+
}
|
|
4654
|
+
traceData.clearWaitingForRoot();
|
|
4655
|
+
for (const event of toKeep) {
|
|
4656
|
+
traceData.addToWaitingQueue({
|
|
4657
|
+
event: event.event,
|
|
4658
|
+
waitingFor: "root",
|
|
4659
|
+
attempts: event.attempts,
|
|
4660
|
+
queuedAt: event.queuedAt
|
|
4661
|
+
});
|
|
4662
|
+
}
|
|
4663
|
+
}
|
|
4664
|
+
/**
|
|
4665
|
+
* Process events waiting for a specific parent span.
|
|
4666
|
+
*/
|
|
4667
|
+
async #processWaitingFor(traceId, spanId) {
|
|
4668
|
+
if (this.#shutdownStarted) return;
|
|
4669
|
+
const traceData = this.#traceMap.get(traceId);
|
|
4670
|
+
if (!traceData) return;
|
|
4671
|
+
const queue = traceData.getEventsWaitingFor({ spanId });
|
|
4672
|
+
if (queue.length === 0) return;
|
|
4673
|
+
this.logger.debug(`${this.name}: Processing ${queue.length} events waiting for span`, { traceId, spanId });
|
|
4674
|
+
const toKeep = [];
|
|
4675
|
+
const now = Date.now();
|
|
4676
|
+
for (const queuedEvent of queue) {
|
|
4677
|
+
if (now - queuedEvent.queuedAt.getTime() > this.#earlyQueueTTLMs) {
|
|
4678
|
+
this.logger.warn(`${this.name}: Dropping event due to TTL expiry`, {
|
|
4679
|
+
traceId,
|
|
4680
|
+
spanId: queuedEvent.event.exportedSpan.id,
|
|
4681
|
+
waitingFor: queuedEvent.waitingFor,
|
|
4682
|
+
queuedAt: queuedEvent.queuedAt,
|
|
4683
|
+
attempts: queuedEvent.attempts
|
|
4684
|
+
});
|
|
4685
|
+
continue;
|
|
4686
|
+
}
|
|
4687
|
+
if (queuedEvent.attempts >= this.#earlyQueueMaxAttempts) {
|
|
4688
|
+
this.logger.warn(`${this.name}: Dropping event due to max attempts`, {
|
|
4689
|
+
traceId,
|
|
4690
|
+
spanId: queuedEvent.event.exportedSpan.id,
|
|
4691
|
+
waitingFor: queuedEvent.waitingFor,
|
|
4692
|
+
attempts: queuedEvent.attempts
|
|
4693
|
+
});
|
|
4694
|
+
continue;
|
|
4695
|
+
}
|
|
4696
|
+
queuedEvent.attempts++;
|
|
4697
|
+
const processed = await this.#tryProcessQueuedEvent(queuedEvent, traceData);
|
|
4698
|
+
if (!processed) {
|
|
4699
|
+
toKeep.push(queuedEvent);
|
|
4700
|
+
}
|
|
4701
|
+
}
|
|
4702
|
+
traceData.clearWaitingFor({ spanId });
|
|
4703
|
+
for (const event of toKeep) {
|
|
4704
|
+
traceData.addToWaitingQueue({
|
|
4705
|
+
event: event.event,
|
|
4706
|
+
waitingFor: spanId,
|
|
4707
|
+
attempts: event.attempts,
|
|
4708
|
+
queuedAt: event.queuedAt
|
|
4709
|
+
});
|
|
4710
|
+
}
|
|
4711
|
+
}
|
|
4712
|
+
/**
|
|
4713
|
+
* Try to process a queued event.
|
|
4714
|
+
* Returns true if successfully processed, false if still waiting for dependencies.
|
|
4715
|
+
*/
|
|
4716
|
+
async #tryProcessQueuedEvent(queuedEvent, traceData) {
|
|
4717
|
+
const { event } = queuedEvent;
|
|
4718
|
+
const { exportedSpan } = event;
|
|
4719
|
+
const method = this.getMethod(event);
|
|
4720
|
+
try {
|
|
4721
|
+
switch (method) {
|
|
4722
|
+
case "handleEventSpan": {
|
|
4723
|
+
traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
|
|
4724
|
+
const eventData = await this._buildEvent({ span: exportedSpan, traceData });
|
|
4725
|
+
if (eventData) {
|
|
4726
|
+
if (!this.skipCachingEventSpans) {
|
|
4727
|
+
traceData.addEvent({ eventId: exportedSpan.id, eventData });
|
|
4728
|
+
}
|
|
4729
|
+
this.#scheduleProcessWaitingFor(exportedSpan.traceId, exportedSpan.id);
|
|
4730
|
+
return true;
|
|
4731
|
+
}
|
|
4732
|
+
return false;
|
|
4733
|
+
}
|
|
4734
|
+
case "handleSpanStart": {
|
|
4735
|
+
traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
|
|
4736
|
+
const spanData = await this._buildSpan({ span: exportedSpan, traceData });
|
|
4737
|
+
if (spanData) {
|
|
4738
|
+
traceData.addSpan({ spanId: exportedSpan.id, spanData });
|
|
4739
|
+
if (exportedSpan.isRootSpan) {
|
|
4740
|
+
traceData.markRootSpanProcessed();
|
|
4741
|
+
}
|
|
4742
|
+
this.#scheduleProcessWaitingFor(exportedSpan.traceId, exportedSpan.id);
|
|
4743
|
+
return true;
|
|
4744
|
+
}
|
|
4745
|
+
return false;
|
|
4746
|
+
}
|
|
4747
|
+
case "handleSpanUpdate": {
|
|
4748
|
+
await this._updateSpan({ span: exportedSpan, traceData });
|
|
4749
|
+
return true;
|
|
4750
|
+
}
|
|
4751
|
+
case "handleSpanEnd": {
|
|
4752
|
+
traceData.endSpan({ spanId: exportedSpan.id });
|
|
4753
|
+
await this._finishSpan({ span: exportedSpan, traceData });
|
|
4754
|
+
if (traceData.activeSpanCount() === 0) {
|
|
4755
|
+
this.#scheduleCleanup(exportedSpan.traceId);
|
|
4756
|
+
}
|
|
4757
|
+
return true;
|
|
4758
|
+
}
|
|
4759
|
+
default:
|
|
4760
|
+
return false;
|
|
4761
|
+
}
|
|
4762
|
+
} catch (error) {
|
|
4763
|
+
this.logger.error(`${this.name}: Error processing queued event`, { error, event, method });
|
|
4764
|
+
return false;
|
|
4765
|
+
}
|
|
4766
|
+
}
|
|
4767
|
+
// ============================================================================
|
|
4768
|
+
// Delayed Cleanup
|
|
4769
|
+
// ============================================================================
|
|
4770
|
+
/**
|
|
4771
|
+
* Schedule cleanup of trace data after a delay.
|
|
4772
|
+
* Allows late-arriving data to still be processed.
|
|
4773
|
+
*/
|
|
4774
|
+
#scheduleCleanup(traceId) {
|
|
4775
|
+
this.#cancelScheduledCleanup(traceId);
|
|
4776
|
+
this.logger.debug(`${this.name}: Scheduling cleanup in ${this.#traceCleanupDelayMs}ms`, { traceId });
|
|
4777
|
+
const timeout = setTimeout(() => {
|
|
4778
|
+
this.#pendingCleanups.delete(traceId);
|
|
4779
|
+
this.#performCleanup(traceId);
|
|
4780
|
+
}, this.#traceCleanupDelayMs);
|
|
4781
|
+
this.#pendingCleanups.set(traceId, timeout);
|
|
4782
|
+
this.#enforcePendingCleanupCap();
|
|
4783
|
+
}
|
|
4784
|
+
/**
|
|
4785
|
+
* Cancel a scheduled cleanup for a trace.
|
|
4786
|
+
*/
|
|
4787
|
+
#cancelScheduledCleanup(traceId) {
|
|
4788
|
+
const existingTimeout = this.#pendingCleanups.get(traceId);
|
|
4789
|
+
if (existingTimeout) {
|
|
4790
|
+
clearTimeout(existingTimeout);
|
|
4791
|
+
this.#pendingCleanups.delete(traceId);
|
|
4792
|
+
this.logger.debug(`${this.name}: Cancelled scheduled cleanup`, { traceId });
|
|
4793
|
+
}
|
|
4794
|
+
}
|
|
4795
|
+
/**
|
|
4796
|
+
* Perform the actual cleanup of trace data.
|
|
4797
|
+
*/
|
|
4798
|
+
#performCleanup(traceId) {
|
|
4799
|
+
const traceData = this.#traceMap.get(traceId);
|
|
4800
|
+
if (!traceData) return;
|
|
4801
|
+
const orphanedEvents = traceData.getAllQueuedEvents();
|
|
4802
|
+
if (orphanedEvents.length > 0) {
|
|
4803
|
+
this.logger.warn(`${this.name}: Dropping ${orphanedEvents.length} orphaned events on cleanup`, {
|
|
4804
|
+
traceId,
|
|
4805
|
+
orphanedEvents: orphanedEvents.map((e) => ({
|
|
4806
|
+
spanId: e.event.exportedSpan.id,
|
|
4807
|
+
waitingFor: e.waitingFor,
|
|
4808
|
+
attempts: e.attempts,
|
|
4809
|
+
queuedAt: e.queuedAt
|
|
4810
|
+
}))
|
|
4811
|
+
});
|
|
4812
|
+
}
|
|
4813
|
+
this.#traceMap.delete(traceId);
|
|
4814
|
+
this.logger.debug(`${this.name}: Cleaned up trace data`, { traceId });
|
|
4815
|
+
}
|
|
4816
|
+
// ============================================================================
|
|
4817
|
+
// Cap Enforcement
|
|
4818
|
+
// ============================================================================
|
|
4819
|
+
/**
|
|
4820
|
+
* Enforce soft cap on pending cleanup traces.
|
|
4821
|
+
* Only removes traces with activeSpanCount == 0.
|
|
4822
|
+
*/
|
|
4823
|
+
#enforcePendingCleanupCap() {
|
|
4824
|
+
if (this.#pendingCleanups.size <= this.#maxPendingCleanupTraces) {
|
|
4825
|
+
return;
|
|
4826
|
+
}
|
|
4827
|
+
const toRemove = this.#pendingCleanups.size - this.#maxPendingCleanupTraces;
|
|
4828
|
+
this.logger.warn(`${this.name}: Pending cleanup cap exceeded, force-cleaning ${toRemove} traces`, {
|
|
4829
|
+
pendingCount: this.#pendingCleanups.size,
|
|
4830
|
+
cap: this.#maxPendingCleanupTraces
|
|
4831
|
+
});
|
|
4832
|
+
let removed = 0;
|
|
4833
|
+
for (const traceId of this.#traceMap.keys()) {
|
|
4834
|
+
if (removed >= toRemove) break;
|
|
4835
|
+
if (this.#pendingCleanups.has(traceId)) {
|
|
4836
|
+
this.#cancelScheduledCleanup(traceId);
|
|
4837
|
+
this.#performCleanup(traceId);
|
|
4838
|
+
removed++;
|
|
4839
|
+
}
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4842
|
+
/**
|
|
4843
|
+
* Enforce hard cap on total traces.
|
|
4844
|
+
* Will kill even active traces if necessary.
|
|
4845
|
+
* Uses a flag to prevent concurrent executions when called fire-and-forget.
|
|
4846
|
+
*/
|
|
4847
|
+
async #enforceHardCap() {
|
|
4848
|
+
if (this.#traceMap.size <= this.#maxTotalTraces || this.#hardCapEnforcementInProgress) {
|
|
4849
|
+
return;
|
|
4850
|
+
}
|
|
4851
|
+
this.#hardCapEnforcementInProgress = true;
|
|
4852
|
+
try {
|
|
4853
|
+
if (this.#traceMap.size <= this.#maxTotalTraces) {
|
|
4854
|
+
return;
|
|
4855
|
+
}
|
|
4856
|
+
const toRemove = this.#traceMap.size - this.#maxTotalTraces;
|
|
4857
|
+
this.logger.warn(`${this.name}: Total trace cap exceeded, killing ${toRemove} oldest traces`, {
|
|
4858
|
+
traceCount: this.#traceMap.size,
|
|
4859
|
+
cap: this.#maxTotalTraces
|
|
4860
|
+
});
|
|
4861
|
+
const reason = {
|
|
4862
|
+
id: "TRACE_CAP_EXCEEDED",
|
|
4863
|
+
message: "Trace killed due to memory cap enforcement.",
|
|
4864
|
+
domain: "MASTRA_OBSERVABILITY",
|
|
4865
|
+
category: "SYSTEM"
|
|
4866
|
+
};
|
|
4867
|
+
let removed = 0;
|
|
4868
|
+
for (const traceId of [...this.#traceMap.keys()]) {
|
|
4869
|
+
if (removed >= toRemove) break;
|
|
4870
|
+
const traceData = this.#traceMap.get(traceId);
|
|
4871
|
+
if (traceData) {
|
|
4872
|
+
for (const spanId of traceData.activeSpanIds) {
|
|
4873
|
+
const span = traceData.getSpan({ spanId });
|
|
4874
|
+
if (span) {
|
|
4875
|
+
await this._abortSpan({ span, traceData, reason });
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
this.#cancelScheduledCleanup(traceId);
|
|
4879
|
+
this.#performCleanup(traceId);
|
|
4880
|
+
removed++;
|
|
4881
|
+
}
|
|
4882
|
+
}
|
|
4883
|
+
} finally {
|
|
4884
|
+
this.#hardCapEnforcementInProgress = false;
|
|
4885
|
+
}
|
|
4886
|
+
}
|
|
4887
|
+
// ============================================================================
|
|
4888
|
+
// Lifecycle Hooks (Override in subclass)
|
|
4889
|
+
// ============================================================================
|
|
4890
|
+
/**
|
|
4891
|
+
* Hook called before processing each tracing event.
|
|
4892
|
+
* Override to transform or enrich the event before processing.
|
|
4893
|
+
* @param event - The incoming tracing event
|
|
4894
|
+
* @returns The (possibly modified) event to process
|
|
4895
|
+
*/
|
|
4896
|
+
async _preExportTracingEvent(event) {
|
|
4897
|
+
return event;
|
|
4898
|
+
}
|
|
4899
|
+
/**
|
|
4900
|
+
* Hook called after processing each tracing event.
|
|
4901
|
+
* Override to perform post-processing actions like flushing.
|
|
4902
|
+
*/
|
|
4903
|
+
async _postExportTracingEvent() {
|
|
4904
|
+
}
|
|
4905
|
+
// ============================================================================
|
|
4906
|
+
// Behavior Flags (Override in subclass as needed)
|
|
4907
|
+
// ============================================================================
|
|
4908
|
+
/**
|
|
4909
|
+
* If true, skip calling _buildRoot and let root spans go through _buildSpan.
|
|
4910
|
+
* Use when the vendor doesn't have a separate trace/root concept.
|
|
4911
|
+
* @default false
|
|
4912
|
+
*/
|
|
4913
|
+
skipBuildRootTask = false;
|
|
4914
|
+
/**
|
|
4915
|
+
* If true, skip processing span_updated events entirely.
|
|
4916
|
+
* Use when the vendor doesn't support incremental span updates.
|
|
4917
|
+
* @default false
|
|
4918
|
+
*/
|
|
4919
|
+
skipSpanUpdateEvents = false;
|
|
4920
|
+
/**
|
|
4921
|
+
* If true, don't cache event spans in TraceData.
|
|
4922
|
+
* Use when events can't be parents of other spans.
|
|
4923
|
+
* @default false
|
|
4924
|
+
*/
|
|
4925
|
+
skipCachingEventSpans = false;
|
|
4926
|
+
getMethod(event) {
|
|
4927
|
+
if (event.exportedSpan.isEvent) {
|
|
4928
|
+
return "handleEventSpan";
|
|
4929
|
+
}
|
|
4930
|
+
const eventType = event.type;
|
|
4931
|
+
switch (eventType) {
|
|
4932
|
+
case TracingEventType.SPAN_STARTED:
|
|
4933
|
+
return "handleSpanStart";
|
|
4934
|
+
case TracingEventType.SPAN_UPDATED:
|
|
4935
|
+
return "handleSpanUpdate";
|
|
4936
|
+
case TracingEventType.SPAN_ENDED:
|
|
4937
|
+
return "handleSpanEnd";
|
|
4938
|
+
default: {
|
|
4939
|
+
const _exhaustiveCheck = eventType;
|
|
4940
|
+
throw new Error(`Unhandled event type: ${_exhaustiveCheck}`);
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4943
|
+
}
|
|
4944
|
+
async _exportTracingEvent(event) {
|
|
4945
|
+
if (this.#shutdownStarted) {
|
|
4946
|
+
return;
|
|
4947
|
+
}
|
|
4948
|
+
const method = this.getMethod(event);
|
|
4949
|
+
if (method == "handleSpanUpdate" && this.skipSpanUpdateEvents) {
|
|
4950
|
+
return;
|
|
4951
|
+
}
|
|
4952
|
+
const traceId = event.exportedSpan.traceId;
|
|
4953
|
+
const traceData = this.getTraceData({ traceId, method });
|
|
4954
|
+
const { exportedSpan } = await this._preExportTracingEvent(event);
|
|
4955
|
+
if (!this.skipBuildRootTask && !traceData.hasRoot()) {
|
|
4956
|
+
if (exportedSpan.isRootSpan) {
|
|
4957
|
+
this.logger.debug(`${this.name}: Building root`, {
|
|
4958
|
+
traceId: exportedSpan.traceId,
|
|
4959
|
+
spanId: exportedSpan.id
|
|
4960
|
+
});
|
|
4961
|
+
const rootData = await this._buildRoot({ span: exportedSpan, traceData });
|
|
4962
|
+
if (rootData) {
|
|
4963
|
+
this.logger.debug(`${this.name}: Adding root`, {
|
|
4964
|
+
traceId: exportedSpan.traceId,
|
|
4965
|
+
spanId: exportedSpan.id
|
|
4966
|
+
});
|
|
4967
|
+
traceData.addRoot({ rootId: exportedSpan.id, rootData });
|
|
4968
|
+
this.#scheduleProcessWaitingForRoot(traceId);
|
|
4969
|
+
}
|
|
4970
|
+
} else {
|
|
4971
|
+
this.logger.debug(`${this.name}: Root does not exist, adding span to waiting queue.`, {
|
|
4972
|
+
traceId: exportedSpan.traceId,
|
|
4973
|
+
spanId: exportedSpan.id
|
|
4974
|
+
});
|
|
4975
|
+
traceData.addToWaitingQueue({ event, waitingFor: "root" });
|
|
4976
|
+
return;
|
|
4977
|
+
}
|
|
4978
|
+
}
|
|
4979
|
+
if (exportedSpan.metadata && this.name in exportedSpan.metadata) {
|
|
4980
|
+
const metadata = exportedSpan.metadata[this.name];
|
|
4981
|
+
this.logger.debug(`${this.name}: Found provider metadata in span`, {
|
|
4982
|
+
traceId: exportedSpan.traceId,
|
|
4983
|
+
spanId: exportedSpan.id,
|
|
4984
|
+
metadata
|
|
4985
|
+
});
|
|
4986
|
+
traceData.addMetadata({ spanId: exportedSpan.id, metadata });
|
|
4987
|
+
}
|
|
4988
|
+
try {
|
|
4989
|
+
switch (method) {
|
|
4990
|
+
case "handleEventSpan": {
|
|
4991
|
+
this.logger.debug(`${this.name}: handling event`, {
|
|
4992
|
+
traceId: exportedSpan.traceId,
|
|
4993
|
+
spanId: exportedSpan.id
|
|
4994
|
+
});
|
|
4995
|
+
traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
|
|
4996
|
+
const eventData = await this._buildEvent({ span: exportedSpan, traceData });
|
|
4997
|
+
if (eventData) {
|
|
4998
|
+
if (!this.skipCachingEventSpans) {
|
|
4999
|
+
this.logger.debug(`${this.name}: adding event to traceData`, {
|
|
5000
|
+
traceId: exportedSpan.traceId,
|
|
5001
|
+
spanId: exportedSpan.id
|
|
5002
|
+
});
|
|
5003
|
+
traceData.addEvent({ eventId: exportedSpan.id, eventData });
|
|
5004
|
+
}
|
|
5005
|
+
this.#scheduleProcessWaitingFor(traceId, exportedSpan.id);
|
|
5006
|
+
} else {
|
|
5007
|
+
const parentId = exportedSpan.parentSpanId;
|
|
5008
|
+
this.logger.debug(`${this.name}: adding event to waiting queue`, {
|
|
5009
|
+
traceId: exportedSpan.traceId,
|
|
5010
|
+
spanId: exportedSpan.id,
|
|
5011
|
+
waitingFor: parentId ?? "root"
|
|
5012
|
+
});
|
|
5013
|
+
traceData.addToWaitingQueue({ event, waitingFor: parentId ?? "root" });
|
|
5014
|
+
}
|
|
5015
|
+
break;
|
|
5016
|
+
}
|
|
5017
|
+
case "handleSpanStart": {
|
|
5018
|
+
this.logger.debug(`${this.name}: handling span start`, {
|
|
5019
|
+
traceId: exportedSpan.traceId,
|
|
5020
|
+
spanId: exportedSpan.id
|
|
5021
|
+
});
|
|
5022
|
+
traceData.addBranch({ spanId: exportedSpan.id, parentSpanId: exportedSpan.parentSpanId });
|
|
5023
|
+
const spanData = await this._buildSpan({ span: exportedSpan, traceData });
|
|
5024
|
+
if (spanData) {
|
|
5025
|
+
this.logger.debug(`${this.name}: adding span to traceData`, {
|
|
5026
|
+
traceId: exportedSpan.traceId,
|
|
5027
|
+
spanId: exportedSpan.id
|
|
5028
|
+
});
|
|
5029
|
+
traceData.addSpan({ spanId: exportedSpan.id, spanData });
|
|
5030
|
+
if (exportedSpan.isRootSpan) {
|
|
5031
|
+
traceData.markRootSpanProcessed();
|
|
5032
|
+
this.#scheduleProcessWaitingForRoot(traceId);
|
|
5033
|
+
}
|
|
5034
|
+
this.#scheduleProcessWaitingFor(traceId, exportedSpan.id);
|
|
5035
|
+
} else {
|
|
5036
|
+
const parentId = exportedSpan.parentSpanId;
|
|
5037
|
+
this.logger.debug(`${this.name}: adding span to waiting queue`, {
|
|
5038
|
+
traceId: exportedSpan.traceId,
|
|
5039
|
+
waitingFor: parentId ?? "root"
|
|
5040
|
+
});
|
|
5041
|
+
traceData.addToWaitingQueue({ event, waitingFor: parentId ?? "root" });
|
|
5042
|
+
}
|
|
5043
|
+
break;
|
|
5044
|
+
}
|
|
5045
|
+
case "handleSpanUpdate":
|
|
5046
|
+
this.logger.debug(`${this.name}: handling span update`, {
|
|
5047
|
+
traceId: exportedSpan.traceId,
|
|
5048
|
+
spanId: exportedSpan.id
|
|
5049
|
+
});
|
|
5050
|
+
await this._updateSpan({ span: exportedSpan, traceData });
|
|
5051
|
+
break;
|
|
5052
|
+
case "handleSpanEnd":
|
|
5053
|
+
this.logger.debug(`${this.name}: handling span end`, {
|
|
5054
|
+
traceId: exportedSpan.traceId,
|
|
5055
|
+
spanId: exportedSpan.id
|
|
5056
|
+
});
|
|
5057
|
+
traceData.endSpan({ spanId: exportedSpan.id });
|
|
5058
|
+
await this._finishSpan({ span: exportedSpan, traceData });
|
|
5059
|
+
if (traceData.activeSpanCount() === 0) {
|
|
5060
|
+
this.#scheduleCleanup(traceId);
|
|
5061
|
+
}
|
|
5062
|
+
break;
|
|
5063
|
+
}
|
|
5064
|
+
} catch (error) {
|
|
5065
|
+
this.logger.error(`${this.name}: exporter error`, { error, event, method });
|
|
5066
|
+
}
|
|
5067
|
+
if (traceData.activeSpanCount() === 0) {
|
|
5068
|
+
this.#scheduleCleanup(traceId);
|
|
5069
|
+
}
|
|
5070
|
+
await this._postExportTracingEvent();
|
|
5071
|
+
}
|
|
5072
|
+
// ============================================================================
|
|
5073
|
+
// Protected Helpers
|
|
5074
|
+
// ============================================================================
|
|
5075
|
+
/**
|
|
5076
|
+
* Get or create the TraceData container for a trace.
|
|
5077
|
+
* Also cancels any pending cleanup since new data has arrived.
|
|
5078
|
+
*
|
|
5079
|
+
* @param args.traceId - The trace ID
|
|
5080
|
+
* @param args.method - The calling method name (for logging)
|
|
5081
|
+
* @returns The TraceData container for this trace
|
|
5082
|
+
*/
|
|
5083
|
+
getTraceData(args) {
|
|
5084
|
+
const { traceId, method } = args;
|
|
5085
|
+
this.#cancelScheduledCleanup(traceId);
|
|
5086
|
+
if (!this.#traceMap.has(traceId)) {
|
|
5087
|
+
this.#traceMap.set(traceId, new TraceData());
|
|
5088
|
+
this.logger.debug(`${this.name}: Created new trace data cache`, {
|
|
5089
|
+
traceId,
|
|
5090
|
+
method
|
|
5091
|
+
});
|
|
5092
|
+
this.#enforceHardCap().catch((error) => {
|
|
5093
|
+
this.logger.error(`${this.name}: Error enforcing hard cap`, { error });
|
|
5094
|
+
});
|
|
5095
|
+
}
|
|
5096
|
+
return this.#traceMap.get(traceId);
|
|
5097
|
+
}
|
|
5098
|
+
/**
|
|
5099
|
+
* Get the current number of traces being tracked.
|
|
5100
|
+
* @returns The trace count
|
|
5101
|
+
*/
|
|
5102
|
+
traceMapSize() {
|
|
5103
|
+
return this.#traceMap.size;
|
|
5104
|
+
}
|
|
5105
|
+
// ============================================================================
|
|
5106
|
+
// Shutdown Hooks (Override in subclass as needed)
|
|
5107
|
+
// ============================================================================
|
|
5108
|
+
/**
|
|
5109
|
+
* Hook called at the start of shutdown, before cancelling timers and aborting spans.
|
|
5110
|
+
* Override to perform vendor-specific pre-shutdown tasks.
|
|
5111
|
+
*/
|
|
5112
|
+
async _preShutdown() {
|
|
5113
|
+
}
|
|
5114
|
+
/**
|
|
5115
|
+
* Hook called at the end of shutdown, after all spans are aborted.
|
|
5116
|
+
* Override to perform vendor-specific cleanup (e.g., flushing).
|
|
5117
|
+
*/
|
|
5118
|
+
async _postShutdown() {
|
|
5119
|
+
}
|
|
5120
|
+
/**
|
|
5121
|
+
* Gracefully shut down the exporter.
|
|
5122
|
+
* Cancels all pending cleanup timers, aborts all active spans, and clears state.
|
|
5123
|
+
*/
|
|
5124
|
+
async shutdown() {
|
|
5125
|
+
if (this.isDisabled) {
|
|
5126
|
+
return;
|
|
5127
|
+
}
|
|
5128
|
+
this.#shutdownStarted = true;
|
|
5129
|
+
await this._preShutdown();
|
|
5130
|
+
for (const [traceId, timeout] of this.#pendingCleanups) {
|
|
5131
|
+
clearTimeout(timeout);
|
|
5132
|
+
this.logger.debug(`${this.name}: Cancelled pending cleanup on shutdown`, { traceId });
|
|
5133
|
+
}
|
|
5134
|
+
this.#pendingCleanups.clear();
|
|
5135
|
+
const reason = {
|
|
5136
|
+
id: "SHUTDOWN",
|
|
5137
|
+
message: "Observability is shutting down.",
|
|
5138
|
+
domain: "MASTRA_OBSERVABILITY",
|
|
5139
|
+
category: "SYSTEM"
|
|
5140
|
+
};
|
|
5141
|
+
for (const [traceId, traceData] of this.#traceMap) {
|
|
5142
|
+
const orphanedEvents = traceData.getAllQueuedEvents();
|
|
5143
|
+
if (orphanedEvents.length > 0) {
|
|
5144
|
+
this.logger.warn(`${this.name}: Dropping ${orphanedEvents.length} orphaned events on shutdown`, {
|
|
5145
|
+
traceId,
|
|
5146
|
+
orphanedEvents: orphanedEvents.map((e) => ({
|
|
5147
|
+
spanId: e.event.exportedSpan.id,
|
|
5148
|
+
waitingFor: e.waitingFor,
|
|
5149
|
+
attempts: e.attempts
|
|
5150
|
+
}))
|
|
5151
|
+
});
|
|
5152
|
+
}
|
|
5153
|
+
for (const spanId of traceData.activeSpanIds) {
|
|
5154
|
+
const span = traceData.getSpan({ spanId });
|
|
5155
|
+
if (span) {
|
|
5156
|
+
await this._abortSpan({ span, traceData, reason });
|
|
5157
|
+
}
|
|
5158
|
+
}
|
|
5159
|
+
}
|
|
5160
|
+
this.#traceMap.clear();
|
|
5161
|
+
await this._postShutdown();
|
|
5162
|
+
await super.shutdown();
|
|
5163
|
+
}
|
|
5164
|
+
};
|
|
4228
5165
|
var CloudExporter = class extends BaseExporter {
|
|
4229
5166
|
name = "mastra-cloud-observability-exporter";
|
|
4230
5167
|
config;
|
|
@@ -5478,10 +6415,39 @@ var ModelSpanTracker = class {
|
|
|
5478
6415
|
case "step-finish":
|
|
5479
6416
|
this.#endStepSpan(chunk.payload);
|
|
5480
6417
|
break;
|
|
6418
|
+
// Infrastructure chunks - skip creating spans for these
|
|
6419
|
+
// They are either redundant, metadata-only, or error/control flow
|
|
5481
6420
|
case "raw":
|
|
5482
|
-
//
|
|
6421
|
+
// Redundant raw data
|
|
5483
6422
|
case "start":
|
|
6423
|
+
// Stream start marker
|
|
5484
6424
|
case "finish":
|
|
6425
|
+
// Stream finish marker (step-finish already captures this)
|
|
6426
|
+
case "response-metadata":
|
|
6427
|
+
// Response metadata (not semantic content)
|
|
6428
|
+
case "source":
|
|
6429
|
+
// Source references (metadata)
|
|
6430
|
+
case "file":
|
|
6431
|
+
// Binary file data (too large/not semantic)
|
|
6432
|
+
case "error":
|
|
6433
|
+
// Error handling
|
|
6434
|
+
case "abort":
|
|
6435
|
+
// Abort signal
|
|
6436
|
+
case "tripwire":
|
|
6437
|
+
// Processor rejection
|
|
6438
|
+
case "watch":
|
|
6439
|
+
// Internal watch event
|
|
6440
|
+
case "tool-error":
|
|
6441
|
+
// Tool error handling
|
|
6442
|
+
case "tool-call-approval":
|
|
6443
|
+
// Approval request (not content)
|
|
6444
|
+
case "tool-call-suspended":
|
|
6445
|
+
// Suspension (not content)
|
|
6446
|
+
case "reasoning-signature":
|
|
6447
|
+
// Signature metadata
|
|
6448
|
+
case "redacted-reasoning":
|
|
6449
|
+
// Redacted content metadata
|
|
6450
|
+
case "step-output":
|
|
5485
6451
|
break;
|
|
5486
6452
|
case "tool-output":
|
|
5487
6453
|
this.#handleToolOutputChunk(chunk);
|
|
@@ -5496,20 +6462,6 @@ var ModelSpanTracker = class {
|
|
|
5496
6462
|
this.#createEventSpan(chunk.type, cleanPayload);
|
|
5497
6463
|
break;
|
|
5498
6464
|
}
|
|
5499
|
-
// Default: auto-create event span for all other chunk types
|
|
5500
|
-
default: {
|
|
5501
|
-
let outputPayload = chunk.payload;
|
|
5502
|
-
if (outputPayload && typeof outputPayload === "object" && "data" in outputPayload) {
|
|
5503
|
-
const typedPayload = outputPayload;
|
|
5504
|
-
outputPayload = { ...typedPayload };
|
|
5505
|
-
if (typedPayload.data) {
|
|
5506
|
-
outputPayload.size = typeof typedPayload.data === "string" ? typedPayload.data.length : typedPayload.data instanceof Uint8Array ? typedPayload.data.length : void 0;
|
|
5507
|
-
delete outputPayload.data;
|
|
5508
|
-
}
|
|
5509
|
-
}
|
|
5510
|
-
this.#createEventSpan(chunk.type, outputPayload);
|
|
5511
|
-
break;
|
|
5512
|
-
}
|
|
5513
6465
|
}
|
|
5514
6466
|
}
|
|
5515
6467
|
})
|
|
@@ -5710,15 +6662,15 @@ var BaseSpan = class {
|
|
|
5710
6662
|
this.attributes = deepClean(options.attributes, this.deepCleanOptions) || {};
|
|
5711
6663
|
this.metadata = deepClean(options.metadata, this.deepCleanOptions);
|
|
5712
6664
|
this.parent = options.parent;
|
|
5713
|
-
this.startTime = /* @__PURE__ */ new Date();
|
|
6665
|
+
this.startTime = options.startTime ?? /* @__PURE__ */ new Date();
|
|
5714
6666
|
this.observabilityInstance = observabilityInstance;
|
|
5715
6667
|
this.isEvent = options.isEvent ?? false;
|
|
5716
6668
|
this.isInternal = isSpanInternal(this.type, options.tracingPolicy?.internal);
|
|
5717
6669
|
this.traceState = options.traceState;
|
|
5718
6670
|
this.tags = !options.parent && options.tags?.length ? options.tags : void 0;
|
|
5719
|
-
this.entityType = options.entityType;
|
|
5720
|
-
this.entityId = options.entityId;
|
|
5721
|
-
this.entityName = options.entityName;
|
|
6671
|
+
this.entityType = options.entityType ?? options.parent?.entityType;
|
|
6672
|
+
this.entityId = options.entityId ?? options.parent?.entityId;
|
|
6673
|
+
this.entityName = options.entityName ?? options.parent?.entityName;
|
|
5722
6674
|
if (this.isEvent) {
|
|
5723
6675
|
this.output = deepClean(options.output, this.deepCleanOptions);
|
|
5724
6676
|
} else {
|
|
@@ -5767,6 +6719,8 @@ var BaseSpan = class {
|
|
|
5767
6719
|
}
|
|
5768
6720
|
/** Returns a lightweight span ready for export */
|
|
5769
6721
|
exportSpan(includeInternalSpans) {
|
|
6722
|
+
const hideInput = this.traceState?.hideInput ?? false;
|
|
6723
|
+
const hideOutput = this.traceState?.hideOutput ?? false;
|
|
5770
6724
|
return {
|
|
5771
6725
|
id: this.id,
|
|
5772
6726
|
traceId: this.traceId,
|
|
@@ -5779,8 +6733,8 @@ var BaseSpan = class {
|
|
|
5779
6733
|
metadata: this.metadata,
|
|
5780
6734
|
startTime: this.startTime,
|
|
5781
6735
|
endTime: this.endTime,
|
|
5782
|
-
input: this.input,
|
|
5783
|
-
output: this.output,
|
|
6736
|
+
input: hideInput ? void 0 : this.input,
|
|
6737
|
+
output: hideOutput ? void 0 : this.output,
|
|
5784
6738
|
errorInfo: this.errorInfo,
|
|
5785
6739
|
isEvent: this.isEvent,
|
|
5786
6740
|
isRootSpan: this.isRootSpan,
|
|
@@ -5820,6 +6774,14 @@ var DefaultSpan = class extends BaseSpan {
|
|
|
5820
6774
|
traceId;
|
|
5821
6775
|
constructor(options, observabilityInstance) {
|
|
5822
6776
|
super(options, observabilityInstance);
|
|
6777
|
+
if (options.spanId && options.traceId) {
|
|
6778
|
+
this.id = options.spanId;
|
|
6779
|
+
this.traceId = options.traceId;
|
|
6780
|
+
if (options.parentSpanId) {
|
|
6781
|
+
this.parentSpanId = options.parentSpanId;
|
|
6782
|
+
}
|
|
6783
|
+
return;
|
|
6784
|
+
}
|
|
5823
6785
|
const bridge = observabilityInstance.getBridge();
|
|
5824
6786
|
if (bridge && !this.isInternal) {
|
|
5825
6787
|
const bridgeIds = bridge.createSpan(options);
|
|
@@ -6064,8 +7026,12 @@ var BaseObservabilityInstance = class extends MastraBase {
|
|
|
6064
7026
|
const mergedMetadata = metadata || tracingMetadata ? { ...metadata, ...tracingMetadata } : void 0;
|
|
6065
7027
|
const enrichedMetadata = this.extractMetadataFromRequestContext(requestContext, mergedMetadata, traceState);
|
|
6066
7028
|
const tags = !options.parent ? tracingOptions?.tags : void 0;
|
|
7029
|
+
const traceId = !options.parent ? options.traceId ?? tracingOptions?.traceId : options.traceId;
|
|
7030
|
+
const parentSpanId = !options.parent ? options.parentSpanId ?? tracingOptions?.parentSpanId : options.parentSpanId;
|
|
6067
7031
|
const span = this.createSpan({
|
|
6068
7032
|
...rest,
|
|
7033
|
+
traceId,
|
|
7034
|
+
parentSpanId,
|
|
6069
7035
|
metadata: enrichedMetadata,
|
|
6070
7036
|
traceState,
|
|
6071
7037
|
tags
|
|
@@ -6078,6 +7044,37 @@ var BaseObservabilityInstance = class extends MastraBase {
|
|
|
6078
7044
|
}
|
|
6079
7045
|
return span;
|
|
6080
7046
|
}
|
|
7047
|
+
/**
|
|
7048
|
+
* Rebuild a span from exported data for lifecycle operations.
|
|
7049
|
+
* Used by durable execution engines (e.g., Inngest) to end/update spans
|
|
7050
|
+
* that were created in a previous durable operation.
|
|
7051
|
+
*
|
|
7052
|
+
* The rebuilt span:
|
|
7053
|
+
* - Does NOT emit SPAN_STARTED (assumes original span already did)
|
|
7054
|
+
* - Can have end(), update(), error() called on it
|
|
7055
|
+
* - Will emit SPAN_ENDED or SPAN_UPDATED when those methods are called
|
|
7056
|
+
*
|
|
7057
|
+
* @param cached - The exported span data to rebuild from
|
|
7058
|
+
* @returns A span that can have lifecycle methods called on it
|
|
7059
|
+
*/
|
|
7060
|
+
rebuildSpan(cached) {
|
|
7061
|
+
const span = this.createSpan({
|
|
7062
|
+
name: cached.name,
|
|
7063
|
+
type: cached.type,
|
|
7064
|
+
traceId: cached.traceId,
|
|
7065
|
+
spanId: cached.id,
|
|
7066
|
+
parentSpanId: cached.parentSpanId,
|
|
7067
|
+
startTime: cached.startTime instanceof Date ? cached.startTime : new Date(cached.startTime),
|
|
7068
|
+
input: cached.input,
|
|
7069
|
+
attributes: cached.attributes,
|
|
7070
|
+
metadata: cached.metadata,
|
|
7071
|
+
entityType: cached.entityType,
|
|
7072
|
+
entityId: cached.entityId,
|
|
7073
|
+
entityName: cached.entityName
|
|
7074
|
+
});
|
|
7075
|
+
this.wireSpanLifecycle(span);
|
|
7076
|
+
return span;
|
|
7077
|
+
}
|
|
6081
7078
|
// ============================================================================
|
|
6082
7079
|
// Configuration Management
|
|
6083
7080
|
// ============================================================================
|
|
@@ -6180,11 +7177,15 @@ var BaseObservabilityInstance = class extends MastraBase {
|
|
|
6180
7177
|
const configuredKeys = this.config.requestContextKeys ?? [];
|
|
6181
7178
|
const additionalKeys = tracingOptions?.requestContextKeys ?? [];
|
|
6182
7179
|
const allKeys = [...configuredKeys, ...additionalKeys];
|
|
6183
|
-
|
|
7180
|
+
const hideInput = tracingOptions?.hideInput;
|
|
7181
|
+
const hideOutput = tracingOptions?.hideOutput;
|
|
7182
|
+
if (allKeys.length === 0 && !hideInput && !hideOutput) {
|
|
6184
7183
|
return void 0;
|
|
6185
7184
|
}
|
|
6186
7185
|
return {
|
|
6187
|
-
requestContextKeys: allKeys
|
|
7186
|
+
requestContextKeys: allKeys,
|
|
7187
|
+
...hideInput !== void 0 && { hideInput },
|
|
7188
|
+
...hideOutput !== void 0 && { hideOutput }
|
|
6188
7189
|
};
|
|
6189
7190
|
}
|
|
6190
7191
|
/**
|
|
@@ -6714,6 +7715,6 @@ function buildTracingOptions(...updaters) {
|
|
|
6714
7715
|
return updaters.reduce((opts, updater) => updater(opts), {});
|
|
6715
7716
|
}
|
|
6716
7717
|
|
|
6717
|
-
export { BaseExporter, BaseObservabilityInstance, BaseSpan, CloudExporter, ConsoleExporter, DEFAULT_DEEP_CLEAN_OPTIONS, DEFAULT_KEYS_TO_STRIP, DefaultExporter, DefaultObservabilityInstance, DefaultSpan, ModelSpanTracker, NoOpSpan, Observability, SamplingStrategyType, SensitiveDataFilter, TestExporter, buildTracingOptions, deepClean, getExternalParentId, mergeSerializationOptions, observabilityConfigValueSchema, observabilityInstanceConfigSchema, observabilityRegistryConfigSchema, samplingStrategySchema, serializationOptionsSchema, truncateString };
|
|
7718
|
+
export { BaseExporter, BaseObservabilityInstance, BaseSpan, CloudExporter, ConsoleExporter, DEFAULT_DEEP_CLEAN_OPTIONS, DEFAULT_KEYS_TO_STRIP, DefaultExporter, DefaultObservabilityInstance, DefaultSpan, ModelSpanTracker, NoOpSpan, Observability, SamplingStrategyType, SensitiveDataFilter, TestExporter, TraceData, TrackingExporter, buildTracingOptions, deepClean, getExternalParentId, mergeSerializationOptions, observabilityConfigValueSchema, observabilityInstanceConfigSchema, observabilityRegistryConfigSchema, samplingStrategySchema, serializationOptionsSchema, truncateString };
|
|
6718
7719
|
//# sourceMappingURL=index.js.map
|
|
6719
7720
|
//# sourceMappingURL=index.js.map
|