@mastra/observability 0.0.0-fix-9244-clickhouse-metadata-20251105010900 → 0.0.0-main-test-05-11-2025-2-20251105214713
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 +33 -3
- package/dist/config.d.ts +75 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/default.d.ts +29 -0
- package/dist/default.d.ts.map +1 -0
- package/dist/exporters/base.d.ts +10 -10
- package/dist/exporters/base.d.ts.map +1 -1
- package/dist/exporters/cloud.d.ts +2 -2
- package/dist/exporters/cloud.d.ts.map +1 -1
- package/dist/exporters/console.d.ts +2 -2
- package/dist/exporters/console.d.ts.map +1 -1
- package/dist/exporters/default.d.ts +10 -14
- package/dist/exporters/default.d.ts.map +1 -1
- package/dist/exporters/index.d.ts +1 -1
- package/dist/index.cjs +1691 -1645
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1688 -1633
- package/dist/index.js.map +1 -1
- package/dist/{tracers → instances}/base.d.ts +26 -25
- package/dist/instances/base.d.ts.map +1 -0
- package/dist/instances/default.d.ts +8 -0
- package/dist/instances/default.d.ts.map +1 -0
- package/dist/instances/index.d.ts.map +1 -0
- package/dist/model-tracing.d.ts +6 -6
- package/dist/model-tracing.d.ts.map +1 -1
- package/dist/registry.d.ts +46 -48
- package/dist/registry.d.ts.map +1 -1
- package/dist/span_processors/index.d.ts +1 -1
- package/dist/span_processors/sensitive-data-filter.d.ts +4 -4
- package/dist/span_processors/sensitive-data-filter.d.ts.map +1 -1
- package/dist/spans/base.d.ts +11 -11
- package/dist/spans/base.d.ts.map +1 -1
- package/dist/spans/default.d.ts +4 -4
- package/dist/spans/default.d.ts.map +1 -1
- package/dist/spans/index.d.ts +1 -1
- package/dist/spans/no-op.d.ts +4 -4
- package/dist/spans/no-op.d.ts.map +1 -1
- package/package.json +5 -5
- package/dist/default-entrypoint.d.ts +0 -16
- package/dist/default-entrypoint.d.ts.map +0 -1
- package/dist/init.d.ts +0 -2
- package/dist/init.d.ts.map +0 -1
- package/dist/tracers/base.d.ts.map +0 -1
- package/dist/tracers/default.d.ts +0 -7
- package/dist/tracers/default.d.ts.map +0 -1
- package/dist/tracers/index.d.ts.map +0 -1
- /package/dist/{tracers → instances}/index.d.ts +0 -0
package/dist/index.cjs
CHANGED
|
@@ -2,1764 +2,1861 @@
|
|
|
2
2
|
|
|
3
3
|
var base = require('@mastra/core/base');
|
|
4
4
|
var logger = require('@mastra/core/logger');
|
|
5
|
+
var error = require('@mastra/core/error');
|
|
5
6
|
var observability = require('@mastra/core/observability');
|
|
6
7
|
var utils = require('@mastra/core/utils');
|
|
7
8
|
var web = require('stream/web');
|
|
8
|
-
var error = require('@mastra/core/error');
|
|
9
9
|
|
|
10
|
-
// src/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
getTracingContext() {
|
|
26
|
-
return {
|
|
27
|
-
currentSpan: this.#currentStepSpan ?? this.#modelSpan
|
|
28
|
-
};
|
|
29
|
-
}
|
|
10
|
+
// src/default.ts
|
|
11
|
+
|
|
12
|
+
// src/config.ts
|
|
13
|
+
var SamplingStrategyType = /* @__PURE__ */ ((SamplingStrategyType2) => {
|
|
14
|
+
SamplingStrategyType2["ALWAYS"] = "always";
|
|
15
|
+
SamplingStrategyType2["NEVER"] = "never";
|
|
16
|
+
SamplingStrategyType2["RATIO"] = "ratio";
|
|
17
|
+
SamplingStrategyType2["CUSTOM"] = "custom";
|
|
18
|
+
return SamplingStrategyType2;
|
|
19
|
+
})(SamplingStrategyType || {});
|
|
20
|
+
var BaseExporter = class {
|
|
21
|
+
/** Mastra logger instance */
|
|
22
|
+
logger;
|
|
23
|
+
/** Whether this exporter is disabled */
|
|
24
|
+
isDisabled = false;
|
|
30
25
|
/**
|
|
31
|
-
*
|
|
26
|
+
* Initialize the base exporter with logger
|
|
32
27
|
*/
|
|
33
|
-
|
|
34
|
-
this
|
|
28
|
+
constructor(config = {}) {
|
|
29
|
+
const logLevel = this.resolveLogLevel(config.logLevel);
|
|
30
|
+
this.logger = config.logger ?? new logger.ConsoleLogger({ level: logLevel, name: this.constructor.name });
|
|
35
31
|
}
|
|
36
32
|
/**
|
|
37
|
-
*
|
|
33
|
+
* Set the logger for the exporter (called by Mastra/ObservabilityInstance during initialization)
|
|
38
34
|
*/
|
|
39
|
-
|
|
40
|
-
this
|
|
35
|
+
__setLogger(logger) {
|
|
36
|
+
this.logger = logger;
|
|
37
|
+
this.logger.debug(`Logger updated for exporter [name=${this.name}]`);
|
|
41
38
|
}
|
|
42
39
|
/**
|
|
43
|
-
*
|
|
40
|
+
* Convert string log level to LogLevel enum
|
|
44
41
|
*/
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
resolveLogLevel(logLevel) {
|
|
43
|
+
if (!logLevel) {
|
|
44
|
+
return logger.LogLevel.INFO;
|
|
45
|
+
}
|
|
46
|
+
if (typeof logLevel === "number") {
|
|
47
|
+
return logLevel;
|
|
48
|
+
}
|
|
49
|
+
const logLevelMap = {
|
|
50
|
+
debug: logger.LogLevel.DEBUG,
|
|
51
|
+
info: logger.LogLevel.INFO,
|
|
52
|
+
warn: logger.LogLevel.WARN,
|
|
53
|
+
error: logger.LogLevel.ERROR
|
|
54
|
+
};
|
|
55
|
+
return logLevelMap[logLevel] ?? logger.LogLevel.INFO;
|
|
47
56
|
}
|
|
48
57
|
/**
|
|
49
|
-
*
|
|
58
|
+
* Mark the exporter as disabled and log a message
|
|
59
|
+
*
|
|
60
|
+
* @param reason - Reason why the exporter is disabled
|
|
50
61
|
*/
|
|
51
|
-
|
|
52
|
-
this
|
|
53
|
-
|
|
54
|
-
type: observability.AISpanType.MODEL_STEP,
|
|
55
|
-
attributes: {
|
|
56
|
-
stepIndex: this.#stepIndex,
|
|
57
|
-
...payload?.messageId ? { messageId: payload.messageId } : {},
|
|
58
|
-
...payload?.warnings?.length ? { warnings: payload.warnings } : {}
|
|
59
|
-
},
|
|
60
|
-
input: payload?.request
|
|
61
|
-
});
|
|
62
|
-
this.#chunkSequence = 0;
|
|
62
|
+
setDisabled(reason) {
|
|
63
|
+
this.isDisabled = true;
|
|
64
|
+
this.logger.warn(`${this.name} disabled: ${reason}`);
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
|
-
*
|
|
67
|
+
* Export a tracing event
|
|
68
|
+
*
|
|
69
|
+
* This method checks if the exporter is disabled before calling _exportEvent.
|
|
70
|
+
* Subclasses should implement _exportEvent instead of overriding this method.
|
|
66
71
|
*/
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
const { usage, ...otherOutput } = output;
|
|
71
|
-
const stepResult = payload.stepResult;
|
|
72
|
-
const metadata = payload.metadata;
|
|
73
|
-
const cleanMetadata = metadata ? { ...metadata } : void 0;
|
|
74
|
-
if (cleanMetadata?.request) {
|
|
75
|
-
delete cleanMetadata.request;
|
|
72
|
+
async exportTracingEvent(event) {
|
|
73
|
+
if (this.isDisabled) {
|
|
74
|
+
return;
|
|
76
75
|
}
|
|
77
|
-
this
|
|
78
|
-
output: otherOutput,
|
|
79
|
-
attributes: {
|
|
80
|
-
usage,
|
|
81
|
-
isContinued: stepResult.isContinued,
|
|
82
|
-
finishReason: stepResult.reason,
|
|
83
|
-
warnings: stepResult.warnings
|
|
84
|
-
},
|
|
85
|
-
metadata: {
|
|
86
|
-
...cleanMetadata
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
this.#currentStepSpan = void 0;
|
|
90
|
-
this.#stepIndex++;
|
|
76
|
+
await this._exportTracingEvent(event);
|
|
91
77
|
}
|
|
92
78
|
/**
|
|
93
|
-
*
|
|
79
|
+
* Shutdown the exporter and clean up resources
|
|
80
|
+
*
|
|
81
|
+
* Default implementation just logs. Override to add custom cleanup.
|
|
94
82
|
*/
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
this.#startStepSpan();
|
|
98
|
-
}
|
|
99
|
-
this.#currentChunkSpan = this.#currentStepSpan?.createChildSpan({
|
|
100
|
-
name: `chunk: '${chunkType}'`,
|
|
101
|
-
type: observability.AISpanType.MODEL_CHUNK,
|
|
102
|
-
attributes: {
|
|
103
|
-
chunkType,
|
|
104
|
-
sequenceNumber: this.#chunkSequence
|
|
105
|
-
}
|
|
106
|
-
});
|
|
107
|
-
this.#accumulator = initialData || {};
|
|
83
|
+
async shutdown() {
|
|
84
|
+
this.logger.info(`${this.name} shutdown complete`);
|
|
108
85
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
86
|
+
};
|
|
87
|
+
var CloudExporter = class extends BaseExporter {
|
|
88
|
+
name = "mastra-cloud-observability-exporter";
|
|
89
|
+
config;
|
|
90
|
+
buffer;
|
|
91
|
+
flushTimer = null;
|
|
92
|
+
constructor(config = {}) {
|
|
93
|
+
super(config);
|
|
94
|
+
const accessToken = config.accessToken ?? process.env.MASTRA_CLOUD_ACCESS_TOKEN;
|
|
95
|
+
if (!accessToken) {
|
|
96
|
+
this.setDisabled(
|
|
97
|
+
"MASTRA_CLOUD_ACCESS_TOKEN environment variable not set. \u{1F680} Sign up for Mastra Cloud at https://cloud.mastra.ai to see your traces online and obtain your access token."
|
|
98
|
+
);
|
|
117
99
|
}
|
|
100
|
+
const endpoint = config.endpoint ?? process.env.MASTRA_CLOUD_AI_TRACES_ENDPOINT ?? "https://api.mastra.ai/ai/spans/publish";
|
|
101
|
+
this.config = {
|
|
102
|
+
logger: this.logger,
|
|
103
|
+
logLevel: config.logLevel ?? logger.LogLevel.INFO,
|
|
104
|
+
maxBatchSize: config.maxBatchSize ?? 1e3,
|
|
105
|
+
maxBatchWaitMs: config.maxBatchWaitMs ?? 5e3,
|
|
106
|
+
maxRetries: config.maxRetries ?? 3,
|
|
107
|
+
accessToken: accessToken || "",
|
|
108
|
+
endpoint
|
|
109
|
+
};
|
|
110
|
+
this.buffer = {
|
|
111
|
+
spans: [],
|
|
112
|
+
totalSize: 0
|
|
113
|
+
};
|
|
118
114
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
*/
|
|
123
|
-
#endChunkSpan(output) {
|
|
124
|
-
if (!this.#currentChunkSpan) return;
|
|
125
|
-
this.#currentChunkSpan.end({
|
|
126
|
-
output: output !== void 0 ? output : this.#accumulator
|
|
127
|
-
});
|
|
128
|
-
this.#currentChunkSpan = void 0;
|
|
129
|
-
this.#accumulator = {};
|
|
130
|
-
this.#chunkSequence++;
|
|
131
|
-
}
|
|
132
|
-
/**
|
|
133
|
-
* Create an event span (for single chunks like tool-call)
|
|
134
|
-
*/
|
|
135
|
-
#createEventSpan(chunkType, output) {
|
|
136
|
-
if (!this.#currentStepSpan) {
|
|
137
|
-
this.#startStepSpan();
|
|
115
|
+
async _exportTracingEvent(event) {
|
|
116
|
+
if (event.type !== observability.TracingEventType.SPAN_ENDED) {
|
|
117
|
+
return;
|
|
138
118
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (span) {
|
|
149
|
-
this.#chunkSequence++;
|
|
119
|
+
this.addToBuffer(event);
|
|
120
|
+
if (this.shouldFlush()) {
|
|
121
|
+
this.flush().catch((error) => {
|
|
122
|
+
this.logger.error("Batch flush failed", {
|
|
123
|
+
error: error instanceof Error ? error.message : String(error)
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
} else if (this.buffer.totalSize === 1) {
|
|
127
|
+
this.scheduleFlush();
|
|
150
128
|
}
|
|
151
129
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
130
|
+
addToBuffer(event) {
|
|
131
|
+
if (this.buffer.totalSize === 0) {
|
|
132
|
+
this.buffer.firstEventTime = /* @__PURE__ */ new Date();
|
|
133
|
+
}
|
|
134
|
+
const spanRecord = this.formatSpan(event.exportedSpan);
|
|
135
|
+
this.buffer.spans.push(spanRecord);
|
|
136
|
+
this.buffer.totalSize++;
|
|
157
137
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
138
|
+
formatSpan(span) {
|
|
139
|
+
const spanRecord = {
|
|
140
|
+
traceId: span.traceId,
|
|
141
|
+
spanId: span.id,
|
|
142
|
+
parentSpanId: span.parentSpanId ?? null,
|
|
143
|
+
name: span.name,
|
|
144
|
+
spanType: span.type,
|
|
145
|
+
attributes: span.attributes ?? null,
|
|
146
|
+
metadata: span.metadata ?? null,
|
|
147
|
+
startedAt: span.startTime,
|
|
148
|
+
endedAt: span.endTime ?? null,
|
|
149
|
+
input: span.input ?? null,
|
|
150
|
+
output: span.output ?? null,
|
|
151
|
+
error: span.errorInfo,
|
|
152
|
+
isEvent: span.isEvent,
|
|
153
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
154
|
+
updatedAt: null
|
|
155
|
+
};
|
|
156
|
+
return spanRecord;
|
|
163
157
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
case "text-delta":
|
|
173
|
-
this.#appendToAccumulator("text", chunk.payload.text);
|
|
174
|
-
break;
|
|
175
|
-
case "text-end": {
|
|
176
|
-
this.#endChunkSpan();
|
|
177
|
-
break;
|
|
158
|
+
shouldFlush() {
|
|
159
|
+
if (this.buffer.totalSize >= this.config.maxBatchSize) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
if (this.buffer.firstEventTime && this.buffer.totalSize > 0) {
|
|
163
|
+
const elapsed = Date.now() - this.buffer.firstEventTime.getTime();
|
|
164
|
+
if (elapsed >= this.config.maxBatchWaitMs) {
|
|
165
|
+
return true;
|
|
178
166
|
}
|
|
179
167
|
}
|
|
168
|
+
return false;
|
|
180
169
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
170
|
+
scheduleFlush() {
|
|
171
|
+
if (this.flushTimer) {
|
|
172
|
+
clearTimeout(this.flushTimer);
|
|
173
|
+
}
|
|
174
|
+
this.flushTimer = setTimeout(() => {
|
|
175
|
+
this.flush().catch((error$1) => {
|
|
176
|
+
const mastraError = new error.MastraError(
|
|
177
|
+
{
|
|
178
|
+
id: `CLOUD_EXPORTER_FAILED_TO_SCHEDULE_FLUSH`,
|
|
179
|
+
domain: error.ErrorDomain.MASTRA_OBSERVABILITY,
|
|
180
|
+
category: error.ErrorCategory.USER
|
|
181
|
+
},
|
|
182
|
+
error$1
|
|
183
|
+
);
|
|
184
|
+
this.logger.trackException(mastraError);
|
|
185
|
+
this.logger.error("Scheduled flush failed", mastraError);
|
|
186
|
+
});
|
|
187
|
+
}, this.config.maxBatchWaitMs);
|
|
188
|
+
}
|
|
189
|
+
async flush() {
|
|
190
|
+
if (this.flushTimer) {
|
|
191
|
+
clearTimeout(this.flushTimer);
|
|
192
|
+
this.flushTimer = null;
|
|
193
|
+
}
|
|
194
|
+
if (this.buffer.totalSize === 0) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
const startTime = Date.now();
|
|
198
|
+
const spansCopy = [...this.buffer.spans];
|
|
199
|
+
const flushReason = this.buffer.totalSize >= this.config.maxBatchSize ? "size" : "time";
|
|
200
|
+
this.resetBuffer();
|
|
201
|
+
try {
|
|
202
|
+
await this.batchUpload(spansCopy);
|
|
203
|
+
const elapsed = Date.now() - startTime;
|
|
204
|
+
this.logger.debug("Batch flushed successfully", {
|
|
205
|
+
batchSize: spansCopy.length,
|
|
206
|
+
flushReason,
|
|
207
|
+
durationMs: elapsed
|
|
208
|
+
});
|
|
209
|
+
} catch (error$1) {
|
|
210
|
+
const mastraError = new error.MastraError(
|
|
211
|
+
{
|
|
212
|
+
id: `CLOUD_EXPORTER_FAILED_TO_BATCH_UPLOAD`,
|
|
213
|
+
domain: error.ErrorDomain.MASTRA_OBSERVABILITY,
|
|
214
|
+
category: error.ErrorCategory.USER,
|
|
215
|
+
details: {
|
|
216
|
+
droppedBatchSize: spansCopy.length
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
error$1
|
|
220
|
+
);
|
|
221
|
+
this.logger.trackException(mastraError);
|
|
222
|
+
this.logger.error("Batch upload failed after all retries, dropping batch", mastraError);
|
|
196
223
|
}
|
|
197
224
|
}
|
|
198
225
|
/**
|
|
199
|
-
*
|
|
226
|
+
* Uploads spans to cloud API using fetchWithRetry for all retry logic
|
|
200
227
|
*/
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
228
|
+
async batchUpload(spans) {
|
|
229
|
+
const headers = {
|
|
230
|
+
Authorization: `Bearer ${this.config.accessToken}`,
|
|
231
|
+
"Content-Type": "application/json"
|
|
232
|
+
};
|
|
233
|
+
const options = {
|
|
234
|
+
method: "POST",
|
|
235
|
+
headers,
|
|
236
|
+
body: JSON.stringify({ spans })
|
|
237
|
+
};
|
|
238
|
+
await utils.fetchWithRetry(this.config.endpoint, options, this.config.maxRetries);
|
|
239
|
+
}
|
|
240
|
+
resetBuffer() {
|
|
241
|
+
this.buffer.spans = [];
|
|
242
|
+
this.buffer.firstEventTime = void 0;
|
|
243
|
+
this.buffer.totalSize = 0;
|
|
244
|
+
}
|
|
245
|
+
async shutdown() {
|
|
246
|
+
if (this.isDisabled) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
if (this.flushTimer) {
|
|
250
|
+
clearTimeout(this.flushTimer);
|
|
251
|
+
this.flushTimer = null;
|
|
252
|
+
}
|
|
253
|
+
if (this.buffer.totalSize > 0) {
|
|
254
|
+
this.logger.info("Flushing remaining events on shutdown", {
|
|
255
|
+
remainingEvents: this.buffer.totalSize
|
|
256
|
+
});
|
|
257
|
+
try {
|
|
258
|
+
await this.flush();
|
|
259
|
+
} catch (error$1) {
|
|
260
|
+
const mastraError = new error.MastraError(
|
|
261
|
+
{
|
|
262
|
+
id: `CLOUD_EXPORTER_FAILED_TO_FLUSH_REMAINING_EVENTS_DURING_SHUTDOWN`,
|
|
263
|
+
domain: error.ErrorDomain.MASTRA_OBSERVABILITY,
|
|
264
|
+
category: error.ErrorCategory.USER,
|
|
265
|
+
details: {
|
|
266
|
+
remainingEvents: this.buffer.totalSize
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
error$1
|
|
270
|
+
);
|
|
271
|
+
this.logger.trackException(mastraError);
|
|
272
|
+
this.logger.error("Failed to flush remaining events during shutdown", mastraError);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
this.logger.info("CloudExporter shutdown complete");
|
|
276
|
+
}
|
|
277
|
+
};
|
|
278
|
+
var ConsoleExporter = class extends BaseExporter {
|
|
279
|
+
name = "tracing-console-exporter";
|
|
280
|
+
constructor(config = {}) {
|
|
281
|
+
super(config);
|
|
282
|
+
}
|
|
283
|
+
async _exportTracingEvent(event) {
|
|
284
|
+
const span = event.exportedSpan;
|
|
285
|
+
const formatAttributes = (attributes) => {
|
|
286
|
+
try {
|
|
287
|
+
return JSON.stringify(attributes, null, 2);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
const errMsg = error instanceof Error ? error.message : "Unknown formatting error";
|
|
290
|
+
return `[Unable to serialize attributes: ${errMsg}]`;
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
const formatDuration = (startTime, endTime) => {
|
|
294
|
+
if (!endTime) return "N/A";
|
|
295
|
+
const duration = endTime.getTime() - startTime.getTime();
|
|
296
|
+
return `${duration}ms`;
|
|
297
|
+
};
|
|
298
|
+
switch (event.type) {
|
|
299
|
+
case observability.TracingEventType.SPAN_STARTED:
|
|
300
|
+
this.logger.info(`\u{1F680} SPAN_STARTED`);
|
|
301
|
+
this.logger.info(` Type: ${span.type}`);
|
|
302
|
+
this.logger.info(` Name: ${span.name}`);
|
|
303
|
+
this.logger.info(` ID: ${span.id}`);
|
|
304
|
+
this.logger.info(` Trace ID: ${span.traceId}`);
|
|
305
|
+
if (span.input !== void 0) {
|
|
306
|
+
this.logger.info(` Input: ${formatAttributes(span.input)}`);
|
|
307
|
+
}
|
|
308
|
+
this.logger.info(` Attributes: ${formatAttributes(span.attributes)}`);
|
|
309
|
+
this.logger.info("\u2500".repeat(80));
|
|
208
310
|
break;
|
|
209
|
-
case
|
|
210
|
-
|
|
311
|
+
case observability.TracingEventType.SPAN_ENDED:
|
|
312
|
+
const duration = formatDuration(span.startTime, span.endTime);
|
|
313
|
+
this.logger.info(`\u2705 SPAN_ENDED`);
|
|
314
|
+
this.logger.info(` Type: ${span.type}`);
|
|
315
|
+
this.logger.info(` Name: ${span.name}`);
|
|
316
|
+
this.logger.info(` ID: ${span.id}`);
|
|
317
|
+
this.logger.info(` Duration: ${duration}`);
|
|
318
|
+
this.logger.info(` Trace ID: ${span.traceId}`);
|
|
319
|
+
if (span.input !== void 0) {
|
|
320
|
+
this.logger.info(` Input: ${formatAttributes(span.input)}`);
|
|
321
|
+
}
|
|
322
|
+
if (span.output !== void 0) {
|
|
323
|
+
this.logger.info(` Output: ${formatAttributes(span.output)}`);
|
|
324
|
+
}
|
|
325
|
+
if (span.errorInfo) {
|
|
326
|
+
this.logger.info(` Error: ${formatAttributes(span.errorInfo)}`);
|
|
327
|
+
}
|
|
328
|
+
this.logger.info(` Attributes: ${formatAttributes(span.attributes)}`);
|
|
329
|
+
this.logger.info("\u2500".repeat(80));
|
|
211
330
|
break;
|
|
212
|
-
case
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
331
|
+
case observability.TracingEventType.SPAN_UPDATED:
|
|
332
|
+
this.logger.info(`\u{1F4DD} SPAN_UPDATED`);
|
|
333
|
+
this.logger.info(` Type: ${span.type}`);
|
|
334
|
+
this.logger.info(` Name: ${span.name}`);
|
|
335
|
+
this.logger.info(` ID: ${span.id}`);
|
|
336
|
+
this.logger.info(` Trace ID: ${span.traceId}`);
|
|
337
|
+
if (span.input !== void 0) {
|
|
338
|
+
this.logger.info(` Input: ${formatAttributes(span.input)}`);
|
|
220
339
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
340
|
+
if (span.output !== void 0) {
|
|
341
|
+
this.logger.info(` Output: ${formatAttributes(span.output)}`);
|
|
342
|
+
}
|
|
343
|
+
if (span.errorInfo) {
|
|
344
|
+
this.logger.info(` Error: ${formatAttributes(span.errorInfo)}`);
|
|
345
|
+
}
|
|
346
|
+
this.logger.info(` Updated Attributes: ${formatAttributes(span.attributes)}`);
|
|
347
|
+
this.logger.info("\u2500".repeat(80));
|
|
226
348
|
break;
|
|
227
|
-
|
|
349
|
+
default:
|
|
350
|
+
this.logger.warn(`Tracing event type not implemented: ${event.type}`);
|
|
228
351
|
}
|
|
229
352
|
}
|
|
353
|
+
async shutdown() {
|
|
354
|
+
this.logger.info("ConsoleExporter shutdown");
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
function resolveTracingStorageStrategy(config, storage, logger) {
|
|
358
|
+
if (config.strategy && config.strategy !== "auto") {
|
|
359
|
+
const hints = storage.tracingStrategy;
|
|
360
|
+
if (hints.supported.includes(config.strategy)) {
|
|
361
|
+
return config.strategy;
|
|
362
|
+
}
|
|
363
|
+
logger.warn("User-specified tracing strategy not supported by storage adapter, falling back to auto-selection", {
|
|
364
|
+
userStrategy: config.strategy,
|
|
365
|
+
storageAdapter: storage.constructor.name,
|
|
366
|
+
supportedStrategies: hints.supported,
|
|
367
|
+
fallbackStrategy: hints.preferred
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return storage.tracingStrategy.preferred;
|
|
371
|
+
}
|
|
372
|
+
var DefaultExporter = class extends BaseExporter {
|
|
373
|
+
name = "mastra-default-observability-exporter";
|
|
374
|
+
#storage;
|
|
375
|
+
#config;
|
|
376
|
+
#resolvedStrategy;
|
|
377
|
+
buffer;
|
|
378
|
+
#flushTimer = null;
|
|
379
|
+
// Track all spans that have been created, persists across flushes
|
|
380
|
+
allCreatedSpans = /* @__PURE__ */ new Set();
|
|
381
|
+
constructor(config = {}) {
|
|
382
|
+
super(config);
|
|
383
|
+
if (config === void 0) {
|
|
384
|
+
config = {};
|
|
385
|
+
}
|
|
386
|
+
this.#config = {
|
|
387
|
+
...config,
|
|
388
|
+
maxBatchSize: config.maxBatchSize ?? 1e3,
|
|
389
|
+
maxBufferSize: config.maxBufferSize ?? 1e4,
|
|
390
|
+
maxBatchWaitMs: config.maxBatchWaitMs ?? 5e3,
|
|
391
|
+
maxRetries: config.maxRetries ?? 4,
|
|
392
|
+
retryDelayMs: config.retryDelayMs ?? 500,
|
|
393
|
+
strategy: config.strategy ?? "auto"
|
|
394
|
+
};
|
|
395
|
+
this.buffer = {
|
|
396
|
+
creates: [],
|
|
397
|
+
updates: [],
|
|
398
|
+
insertOnly: [],
|
|
399
|
+
seenSpans: /* @__PURE__ */ new Set(),
|
|
400
|
+
spanSequences: /* @__PURE__ */ new Map(),
|
|
401
|
+
completedSpans: /* @__PURE__ */ new Set(),
|
|
402
|
+
outOfOrderCount: 0,
|
|
403
|
+
totalSize: 0
|
|
404
|
+
};
|
|
405
|
+
this.#resolvedStrategy = "batch-with-updates";
|
|
406
|
+
}
|
|
407
|
+
#strategyInitialized = false;
|
|
230
408
|
/**
|
|
231
|
-
*
|
|
409
|
+
* Initialize the exporter (called after all dependencies are ready)
|
|
232
410
|
*/
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
}
|
|
239
|
-
break;
|
|
240
|
-
case "object-result":
|
|
241
|
-
this.#endChunkSpan(chunk.object);
|
|
242
|
-
break;
|
|
411
|
+
init(options) {
|
|
412
|
+
this.#storage = options.mastra?.getStorage();
|
|
413
|
+
if (!this.#storage) {
|
|
414
|
+
this.logger.warn("DefaultExporter disabled: Storage not available. Traces will not be persisted.");
|
|
415
|
+
return;
|
|
243
416
|
}
|
|
417
|
+
this.initializeStrategy(this.#storage);
|
|
244
418
|
}
|
|
245
419
|
/**
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
* This should be added to the stream pipeline to automatically
|
|
249
|
-
* create MODEL_STEP and MODEL_CHUNK spans for each semantic unit in the stream.
|
|
420
|
+
* Initialize the resolved strategy once storage is available
|
|
250
421
|
*/
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
422
|
+
initializeStrategy(storage) {
|
|
423
|
+
if (this.#strategyInitialized) return;
|
|
424
|
+
this.#resolvedStrategy = resolveTracingStorageStrategy(this.#config, storage, this.logger);
|
|
425
|
+
this.#strategyInitialized = true;
|
|
426
|
+
this.logger.debug("tracing storage exporter initialized", {
|
|
427
|
+
strategy: this.#resolvedStrategy,
|
|
428
|
+
source: this.#config.strategy !== "auto" ? "user" : "auto",
|
|
429
|
+
storageAdapter: storage.constructor.name,
|
|
430
|
+
maxBatchSize: this.#config.maxBatchSize,
|
|
431
|
+
maxBatchWaitMs: this.#config.maxBatchWaitMs
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
/**
|
|
435
|
+
* Builds a unique span key for tracking
|
|
436
|
+
*/
|
|
437
|
+
buildSpanKey(traceId, spanId) {
|
|
438
|
+
return `${traceId}:${spanId}`;
|
|
439
|
+
}
|
|
440
|
+
/**
|
|
441
|
+
* Gets the next sequence number for a span
|
|
442
|
+
*/
|
|
443
|
+
getNextSequence(spanKey) {
|
|
444
|
+
const current = this.buffer.spanSequences.get(spanKey) || 0;
|
|
445
|
+
const next = current + 1;
|
|
446
|
+
this.buffer.spanSequences.set(spanKey, next);
|
|
447
|
+
return next;
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Handles out-of-order span updates by logging and skipping
|
|
451
|
+
*/
|
|
452
|
+
handleOutOfOrderUpdate(event) {
|
|
453
|
+
this.logger.warn("Out-of-order span update detected - skipping event", {
|
|
454
|
+
spanId: event.exportedSpan.id,
|
|
455
|
+
traceId: event.exportedSpan.traceId,
|
|
456
|
+
spanName: event.exportedSpan.name,
|
|
457
|
+
eventType: event.type
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Adds an event to the appropriate buffer based on strategy
|
|
462
|
+
*/
|
|
463
|
+
addToBuffer(event) {
|
|
464
|
+
const spanKey = this.buildSpanKey(event.exportedSpan.traceId, event.exportedSpan.id);
|
|
465
|
+
if (this.buffer.totalSize === 0) {
|
|
466
|
+
this.buffer.firstEventTime = /* @__PURE__ */ new Date();
|
|
467
|
+
}
|
|
468
|
+
switch (event.type) {
|
|
469
|
+
case observability.TracingEventType.SPAN_STARTED:
|
|
470
|
+
if (this.#resolvedStrategy === "batch-with-updates") {
|
|
471
|
+
const createRecord = this.buildCreateRecord(event.exportedSpan);
|
|
472
|
+
this.buffer.creates.push(createRecord);
|
|
473
|
+
this.buffer.seenSpans.add(spanKey);
|
|
474
|
+
this.allCreatedSpans.add(spanKey);
|
|
475
|
+
}
|
|
476
|
+
break;
|
|
477
|
+
case observability.TracingEventType.SPAN_UPDATED:
|
|
478
|
+
if (this.#resolvedStrategy === "batch-with-updates") {
|
|
479
|
+
if (this.allCreatedSpans.has(spanKey)) {
|
|
480
|
+
this.buffer.updates.push({
|
|
481
|
+
traceId: event.exportedSpan.traceId,
|
|
482
|
+
spanId: event.exportedSpan.id,
|
|
483
|
+
updates: this.buildUpdateRecord(event.exportedSpan),
|
|
484
|
+
sequenceNumber: this.getNextSequence(spanKey)
|
|
485
|
+
});
|
|
486
|
+
} else {
|
|
487
|
+
this.handleOutOfOrderUpdate(event);
|
|
488
|
+
this.buffer.outOfOrderCount++;
|
|
302
489
|
}
|
|
303
490
|
}
|
|
304
|
-
|
|
305
|
-
|
|
491
|
+
break;
|
|
492
|
+
case observability.TracingEventType.SPAN_ENDED:
|
|
493
|
+
if (this.#resolvedStrategy === "batch-with-updates") {
|
|
494
|
+
if (this.allCreatedSpans.has(spanKey)) {
|
|
495
|
+
this.buffer.updates.push({
|
|
496
|
+
traceId: event.exportedSpan.traceId,
|
|
497
|
+
spanId: event.exportedSpan.id,
|
|
498
|
+
updates: this.buildUpdateRecord(event.exportedSpan),
|
|
499
|
+
sequenceNumber: this.getNextSequence(spanKey)
|
|
500
|
+
});
|
|
501
|
+
this.buffer.completedSpans.add(spanKey);
|
|
502
|
+
} else if (event.exportedSpan.isEvent) {
|
|
503
|
+
const createRecord = this.buildCreateRecord(event.exportedSpan);
|
|
504
|
+
this.buffer.creates.push(createRecord);
|
|
505
|
+
this.buffer.seenSpans.add(spanKey);
|
|
506
|
+
this.allCreatedSpans.add(spanKey);
|
|
507
|
+
this.buffer.completedSpans.add(spanKey);
|
|
508
|
+
} else {
|
|
509
|
+
this.handleOutOfOrderUpdate(event);
|
|
510
|
+
this.buffer.outOfOrderCount++;
|
|
511
|
+
}
|
|
512
|
+
} else if (this.#resolvedStrategy === "insert-only") {
|
|
513
|
+
const createRecord = this.buildCreateRecord(event.exportedSpan);
|
|
514
|
+
this.buffer.insertOnly.push(createRecord);
|
|
515
|
+
this.buffer.completedSpans.add(spanKey);
|
|
516
|
+
this.allCreatedSpans.add(spanKey);
|
|
517
|
+
}
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
this.buffer.totalSize = this.buffer.creates.length + this.buffer.updates.length + this.buffer.insertOnly.length;
|
|
306
521
|
}
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
522
|
+
/**
|
|
523
|
+
* Checks if buffer should be flushed based on size or time triggers
|
|
524
|
+
*/
|
|
525
|
+
shouldFlush() {
|
|
526
|
+
if (this.buffer.totalSize >= this.#config.maxBufferSize) {
|
|
527
|
+
return true;
|
|
528
|
+
}
|
|
529
|
+
if (this.buffer.totalSize >= this.#config.maxBatchSize) {
|
|
530
|
+
return true;
|
|
531
|
+
}
|
|
532
|
+
if (this.buffer.firstEventTime && this.buffer.totalSize > 0) {
|
|
533
|
+
const elapsed = Date.now() - this.buffer.firstEventTime.getTime();
|
|
534
|
+
if (elapsed >= this.#config.maxBatchWaitMs) {
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
312
538
|
return false;
|
|
313
539
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
case observability.AISpanType.MCP_TOOL_CALL:
|
|
331
|
-
return (flags & observability.InternalSpans.TOOL) !== 0;
|
|
332
|
-
// Model-related spans
|
|
333
|
-
case observability.AISpanType.MODEL_GENERATION:
|
|
334
|
-
case observability.AISpanType.MODEL_STEP:
|
|
335
|
-
case observability.AISpanType.MODEL_CHUNK:
|
|
336
|
-
return (flags & observability.InternalSpans.MODEL) !== 0;
|
|
337
|
-
// Default: never internal
|
|
338
|
-
default:
|
|
339
|
-
return false;
|
|
540
|
+
/**
|
|
541
|
+
* Resets the buffer after successful flush
|
|
542
|
+
*/
|
|
543
|
+
resetBuffer(completedSpansToCleanup = /* @__PURE__ */ new Set()) {
|
|
544
|
+
this.buffer.creates = [];
|
|
545
|
+
this.buffer.updates = [];
|
|
546
|
+
this.buffer.insertOnly = [];
|
|
547
|
+
this.buffer.seenSpans.clear();
|
|
548
|
+
this.buffer.spanSequences.clear();
|
|
549
|
+
this.buffer.completedSpans.clear();
|
|
550
|
+
this.buffer.outOfOrderCount = 0;
|
|
551
|
+
this.buffer.firstEventTime = void 0;
|
|
552
|
+
this.buffer.totalSize = 0;
|
|
553
|
+
for (const spanKey of completedSpansToCleanup) {
|
|
554
|
+
this.allCreatedSpans.delete(spanKey);
|
|
555
|
+
}
|
|
340
556
|
}
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
557
|
+
/**
|
|
558
|
+
* Schedules a flush using setTimeout
|
|
559
|
+
*/
|
|
560
|
+
scheduleFlush() {
|
|
561
|
+
if (this.#flushTimer) {
|
|
562
|
+
clearTimeout(this.#flushTimer);
|
|
563
|
+
}
|
|
564
|
+
this.#flushTimer = setTimeout(() => {
|
|
565
|
+
this.flush().catch((error) => {
|
|
566
|
+
this.logger.error("Scheduled flush failed", {
|
|
567
|
+
error: error instanceof Error ? error.message : String(error)
|
|
568
|
+
});
|
|
569
|
+
});
|
|
570
|
+
}, this.#config.maxBatchWaitMs);
|
|
571
|
+
}
|
|
572
|
+
/**
|
|
573
|
+
* Serializes span attributes to storage record format
|
|
574
|
+
* Handles all Span types and their specific attributes
|
|
575
|
+
*/
|
|
576
|
+
serializeAttributes(span) {
|
|
577
|
+
if (!span.attributes) {
|
|
578
|
+
return null;
|
|
579
|
+
}
|
|
580
|
+
try {
|
|
581
|
+
return JSON.parse(
|
|
582
|
+
JSON.stringify(span.attributes, (_key, value) => {
|
|
583
|
+
if (value instanceof Date) {
|
|
584
|
+
return value.toISOString();
|
|
585
|
+
}
|
|
586
|
+
if (typeof value === "object" && value !== null) {
|
|
587
|
+
return value;
|
|
588
|
+
}
|
|
589
|
+
return value;
|
|
590
|
+
})
|
|
591
|
+
);
|
|
592
|
+
} catch (error) {
|
|
593
|
+
this.logger.warn("Failed to serialize span attributes, storing as null", {
|
|
594
|
+
spanId: span.id,
|
|
595
|
+
spanType: span.type,
|
|
596
|
+
error: error instanceof Error ? error.message : String(error)
|
|
597
|
+
});
|
|
598
|
+
return null;
|
|
374
599
|
}
|
|
375
600
|
}
|
|
376
|
-
|
|
377
|
-
return
|
|
601
|
+
buildCreateRecord(span) {
|
|
602
|
+
return {
|
|
603
|
+
traceId: span.traceId,
|
|
604
|
+
spanId: span.id,
|
|
605
|
+
parentSpanId: span.parentSpanId ?? null,
|
|
606
|
+
name: span.name,
|
|
607
|
+
scope: null,
|
|
608
|
+
spanType: span.type,
|
|
609
|
+
attributes: this.serializeAttributes(span),
|
|
610
|
+
metadata: span.metadata ?? null,
|
|
611
|
+
links: null,
|
|
612
|
+
startedAt: span.startTime,
|
|
613
|
+
endedAt: span.endTime ?? null,
|
|
614
|
+
input: span.input,
|
|
615
|
+
output: span.output,
|
|
616
|
+
error: span.errorInfo,
|
|
617
|
+
isEvent: span.isEvent
|
|
618
|
+
};
|
|
378
619
|
}
|
|
379
|
-
|
|
380
|
-
return
|
|
620
|
+
buildUpdateRecord(span) {
|
|
621
|
+
return {
|
|
622
|
+
name: span.name,
|
|
623
|
+
scope: null,
|
|
624
|
+
attributes: this.serializeAttributes(span),
|
|
625
|
+
metadata: span.metadata ?? null,
|
|
626
|
+
links: null,
|
|
627
|
+
endedAt: span.endTime ?? null,
|
|
628
|
+
input: span.input,
|
|
629
|
+
output: span.output,
|
|
630
|
+
error: span.errorInfo
|
|
631
|
+
};
|
|
381
632
|
}
|
|
382
633
|
/**
|
|
383
|
-
*
|
|
384
|
-
* Returns undefined for non-MODEL_GENERATION spans
|
|
634
|
+
* Handles realtime strategy - processes each event immediately
|
|
385
635
|
*/
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
636
|
+
async handleRealtimeEvent(event, storage) {
|
|
637
|
+
const span = event.exportedSpan;
|
|
638
|
+
const spanKey = this.buildSpanKey(span.traceId, span.id);
|
|
639
|
+
if (span.isEvent) {
|
|
640
|
+
if (event.type === observability.TracingEventType.SPAN_ENDED) {
|
|
641
|
+
await storage.createSpan(this.buildCreateRecord(event.exportedSpan));
|
|
642
|
+
} else {
|
|
643
|
+
this.logger.warn(`Tracing event type not implemented for event spans: ${event.type}`);
|
|
644
|
+
}
|
|
645
|
+
} else {
|
|
646
|
+
switch (event.type) {
|
|
647
|
+
case observability.TracingEventType.SPAN_STARTED:
|
|
648
|
+
await storage.createSpan(this.buildCreateRecord(event.exportedSpan));
|
|
649
|
+
this.allCreatedSpans.add(spanKey);
|
|
650
|
+
break;
|
|
651
|
+
case observability.TracingEventType.SPAN_UPDATED:
|
|
652
|
+
await storage.updateSpan({
|
|
653
|
+
traceId: span.traceId,
|
|
654
|
+
spanId: span.id,
|
|
655
|
+
updates: this.buildUpdateRecord(span)
|
|
656
|
+
});
|
|
657
|
+
break;
|
|
658
|
+
case observability.TracingEventType.SPAN_ENDED:
|
|
659
|
+
await storage.updateSpan({
|
|
660
|
+
traceId: span.traceId,
|
|
661
|
+
spanId: span.id,
|
|
662
|
+
updates: this.buildUpdateRecord(span)
|
|
663
|
+
});
|
|
664
|
+
this.allCreatedSpans.delete(spanKey);
|
|
665
|
+
break;
|
|
666
|
+
default:
|
|
667
|
+
this.logger.warn(`Tracing event type not implemented for span spans: ${event.type}`);
|
|
668
|
+
}
|
|
389
669
|
}
|
|
390
|
-
return new ModelSpanTracker(this);
|
|
391
|
-
}
|
|
392
|
-
/** Returns `TRUE` if the span is the root span of a trace */
|
|
393
|
-
get isRootSpan() {
|
|
394
|
-
return !this.parent;
|
|
395
670
|
}
|
|
396
|
-
/**
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
671
|
+
/**
|
|
672
|
+
* Handles batch-with-updates strategy - buffers events and processes in batches
|
|
673
|
+
*/
|
|
674
|
+
handleBatchWithUpdatesEvent(event) {
|
|
675
|
+
this.addToBuffer(event);
|
|
676
|
+
if (this.shouldFlush()) {
|
|
677
|
+
this.flush().catch((error) => {
|
|
678
|
+
this.logger.error("Batch flush failed", {
|
|
679
|
+
error: error instanceof Error ? error.message : String(error)
|
|
680
|
+
});
|
|
681
|
+
});
|
|
682
|
+
} else if (this.buffer.totalSize === 1) {
|
|
683
|
+
this.scheduleFlush();
|
|
400
684
|
}
|
|
401
|
-
if (includeInternalSpans) return this.parent.id;
|
|
402
|
-
if (this.parent.isInternal) return this.parent.getParentSpanId(includeInternalSpans);
|
|
403
|
-
return this.parent.id;
|
|
404
685
|
}
|
|
405
|
-
/**
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
686
|
+
/**
|
|
687
|
+
* Handles insert-only strategy - only processes SPAN_ENDED events in batches
|
|
688
|
+
*/
|
|
689
|
+
handleInsertOnlyEvent(event) {
|
|
690
|
+
if (event.type === observability.TracingEventType.SPAN_ENDED) {
|
|
691
|
+
this.addToBuffer(event);
|
|
692
|
+
if (this.shouldFlush()) {
|
|
693
|
+
this.flush().catch((error) => {
|
|
694
|
+
this.logger.error("Batch flush failed", {
|
|
695
|
+
error: error instanceof Error ? error.message : String(error)
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
} else if (this.buffer.totalSize === 1) {
|
|
699
|
+
this.scheduleFlush();
|
|
411
700
|
}
|
|
412
|
-
current = current.parent;
|
|
413
701
|
}
|
|
414
|
-
return void 0;
|
|
415
|
-
}
|
|
416
|
-
/** Returns a lightweight span ready for export */
|
|
417
|
-
exportSpan(includeInternalSpans) {
|
|
418
|
-
return {
|
|
419
|
-
id: this.id,
|
|
420
|
-
traceId: this.traceId,
|
|
421
|
-
name: this.name,
|
|
422
|
-
type: this.type,
|
|
423
|
-
attributes: this.attributes,
|
|
424
|
-
metadata: this.metadata,
|
|
425
|
-
startTime: this.startTime,
|
|
426
|
-
endTime: this.endTime,
|
|
427
|
-
input: this.input,
|
|
428
|
-
output: this.output,
|
|
429
|
-
errorInfo: this.errorInfo,
|
|
430
|
-
isEvent: this.isEvent,
|
|
431
|
-
isRootSpan: this.isRootSpan,
|
|
432
|
-
parentSpanId: this.getParentSpanId(includeInternalSpans)
|
|
433
|
-
};
|
|
434
|
-
}
|
|
435
|
-
get externalTraceId() {
|
|
436
|
-
return this.isValid ? this.traceId : void 0;
|
|
437
702
|
}
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
"steps",
|
|
444
|
-
"tracingContext"
|
|
445
|
-
]);
|
|
446
|
-
function deepClean(value, options = {}, _seen = /* @__PURE__ */ new WeakSet(), _depth = 0) {
|
|
447
|
-
const { keysToStrip = DEFAULT_KEYS_TO_STRIP, maxDepth = 10 } = options;
|
|
448
|
-
if (_depth > maxDepth) {
|
|
449
|
-
return "[MaxDepth]";
|
|
703
|
+
/**
|
|
704
|
+
* Calculates retry delay using exponential backoff
|
|
705
|
+
*/
|
|
706
|
+
calculateRetryDelay(attempt) {
|
|
707
|
+
return this.#config.retryDelayMs * Math.pow(2, attempt);
|
|
450
708
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
709
|
+
/**
|
|
710
|
+
* Flushes the current buffer to storage with retry logic
|
|
711
|
+
*/
|
|
712
|
+
async flush() {
|
|
713
|
+
if (!this.#storage) {
|
|
714
|
+
this.logger.debug("Cannot flush traces. Mastra storage is not initialized");
|
|
715
|
+
return;
|
|
457
716
|
}
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
}
|
|
462
|
-
_seen.add(value);
|
|
463
|
-
if (Array.isArray(value)) {
|
|
464
|
-
return value.map((item) => deepClean(item, options, _seen, _depth + 1));
|
|
465
|
-
}
|
|
466
|
-
const cleaned = {};
|
|
467
|
-
for (const [key, val] of Object.entries(value)) {
|
|
468
|
-
if (keysToStrip.has(key)) {
|
|
469
|
-
continue;
|
|
717
|
+
if (this.#flushTimer) {
|
|
718
|
+
clearTimeout(this.#flushTimer);
|
|
719
|
+
this.#flushTimer = null;
|
|
470
720
|
}
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
} catch (error) {
|
|
474
|
-
cleaned[key] = `[${error instanceof Error ? error.message : String(error)}]`;
|
|
721
|
+
if (this.buffer.totalSize === 0) {
|
|
722
|
+
return;
|
|
475
723
|
}
|
|
724
|
+
const startTime = Date.now();
|
|
725
|
+
const flushReason = this.buffer.totalSize >= this.#config.maxBufferSize ? "overflow" : this.buffer.totalSize >= this.#config.maxBatchSize ? "size" : "time";
|
|
726
|
+
const bufferCopy = {
|
|
727
|
+
creates: [...this.buffer.creates],
|
|
728
|
+
updates: [...this.buffer.updates],
|
|
729
|
+
insertOnly: [...this.buffer.insertOnly],
|
|
730
|
+
seenSpans: new Set(this.buffer.seenSpans),
|
|
731
|
+
spanSequences: new Map(this.buffer.spanSequences),
|
|
732
|
+
completedSpans: new Set(this.buffer.completedSpans),
|
|
733
|
+
outOfOrderCount: this.buffer.outOfOrderCount,
|
|
734
|
+
firstEventTime: this.buffer.firstEventTime,
|
|
735
|
+
totalSize: this.buffer.totalSize
|
|
736
|
+
};
|
|
737
|
+
this.resetBuffer();
|
|
738
|
+
await this.flushWithRetries(this.#storage, bufferCopy, 0);
|
|
739
|
+
const elapsed = Date.now() - startTime;
|
|
740
|
+
this.logger.debug("Batch flushed", {
|
|
741
|
+
strategy: this.#resolvedStrategy,
|
|
742
|
+
batchSize: bufferCopy.totalSize,
|
|
743
|
+
flushReason,
|
|
744
|
+
durationMs: elapsed,
|
|
745
|
+
outOfOrderCount: bufferCopy.outOfOrderCount > 0 ? bufferCopy.outOfOrderCount : void 0
|
|
746
|
+
});
|
|
476
747
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
748
|
+
/**
|
|
749
|
+
* Attempts to flush with exponential backoff retry logic
|
|
750
|
+
*/
|
|
751
|
+
async flushWithRetries(storage, buffer, attempt) {
|
|
752
|
+
try {
|
|
753
|
+
if (this.#resolvedStrategy === "batch-with-updates") {
|
|
754
|
+
if (buffer.creates.length > 0) {
|
|
755
|
+
await storage.batchCreateSpans({ records: buffer.creates });
|
|
756
|
+
}
|
|
757
|
+
if (buffer.updates.length > 0) {
|
|
758
|
+
const sortedUpdates = buffer.updates.sort((a, b) => {
|
|
759
|
+
const spanCompare = this.buildSpanKey(a.traceId, a.spanId).localeCompare(
|
|
760
|
+
this.buildSpanKey(b.traceId, b.spanId)
|
|
761
|
+
);
|
|
762
|
+
if (spanCompare !== 0) return spanCompare;
|
|
763
|
+
return a.sequenceNumber - b.sequenceNumber;
|
|
764
|
+
});
|
|
765
|
+
await storage.batchUpdateSpans({ records: sortedUpdates });
|
|
766
|
+
}
|
|
767
|
+
} else if (this.#resolvedStrategy === "insert-only") {
|
|
768
|
+
if (buffer.insertOnly.length > 0) {
|
|
769
|
+
await storage.batchCreateSpans({ records: buffer.insertOnly });
|
|
770
|
+
}
|
|
495
771
|
}
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
if (
|
|
501
|
-
|
|
772
|
+
for (const spanKey of buffer.completedSpans) {
|
|
773
|
+
this.allCreatedSpans.delete(spanKey);
|
|
774
|
+
}
|
|
775
|
+
} catch (error) {
|
|
776
|
+
if (attempt < this.#config.maxRetries) {
|
|
777
|
+
const retryDelay = this.calculateRetryDelay(attempt);
|
|
778
|
+
this.logger.warn("Batch flush failed, retrying", {
|
|
779
|
+
attempt: attempt + 1,
|
|
780
|
+
maxRetries: this.#config.maxRetries,
|
|
781
|
+
nextRetryInMs: retryDelay,
|
|
782
|
+
error: error instanceof Error ? error.message : String(error)
|
|
783
|
+
});
|
|
784
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
785
|
+
return this.flushWithRetries(storage, buffer, attempt + 1);
|
|
502
786
|
} else {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
787
|
+
this.logger.error("Batch flush failed after all retries, dropping batch", {
|
|
788
|
+
finalAttempt: attempt + 1,
|
|
789
|
+
maxRetries: this.#config.maxRetries,
|
|
790
|
+
droppedBatchSize: buffer.totalSize,
|
|
791
|
+
error: error instanceof Error ? error.message : String(error)
|
|
792
|
+
});
|
|
793
|
+
for (const spanKey of buffer.completedSpans) {
|
|
794
|
+
this.allCreatedSpans.delete(spanKey);
|
|
795
|
+
}
|
|
506
796
|
}
|
|
507
797
|
}
|
|
508
798
|
}
|
|
509
|
-
|
|
510
|
-
if (this
|
|
511
|
-
|
|
512
|
-
}
|
|
513
|
-
this.endTime = /* @__PURE__ */ new Date();
|
|
514
|
-
if (options?.output !== void 0) {
|
|
515
|
-
this.output = deepClean(options.output);
|
|
516
|
-
}
|
|
517
|
-
if (options?.attributes) {
|
|
518
|
-
this.attributes = { ...this.attributes, ...deepClean(options.attributes) };
|
|
519
|
-
}
|
|
520
|
-
if (options?.metadata) {
|
|
521
|
-
this.metadata = { ...this.metadata, ...deepClean(options.metadata) };
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
error(options) {
|
|
525
|
-
if (this.isEvent) {
|
|
799
|
+
async _exportTracingEvent(event) {
|
|
800
|
+
if (!this.#storage) {
|
|
801
|
+
this.logger.debug("Cannot store traces. Mastra storage is not initialized");
|
|
526
802
|
return;
|
|
527
803
|
}
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
id: error$1.id,
|
|
531
|
-
details: error$1.details,
|
|
532
|
-
category: error$1.category,
|
|
533
|
-
domain: error$1.domain,
|
|
534
|
-
message: error$1.message
|
|
535
|
-
} : {
|
|
536
|
-
message: error$1.message
|
|
537
|
-
};
|
|
538
|
-
if (attributes) {
|
|
539
|
-
this.attributes = { ...this.attributes, ...deepClean(attributes) };
|
|
540
|
-
}
|
|
541
|
-
if (metadata) {
|
|
542
|
-
this.metadata = { ...this.metadata, ...deepClean(metadata) };
|
|
804
|
+
if (!this.#strategyInitialized) {
|
|
805
|
+
this.initializeStrategy(this.#storage);
|
|
543
806
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
807
|
+
switch (this.#resolvedStrategy) {
|
|
808
|
+
case "realtime":
|
|
809
|
+
await this.handleRealtimeEvent(event, this.#storage);
|
|
810
|
+
break;
|
|
811
|
+
case "batch-with-updates":
|
|
812
|
+
this.handleBatchWithUpdatesEvent(event);
|
|
813
|
+
break;
|
|
814
|
+
case "insert-only":
|
|
815
|
+
this.handleInsertOnlyEvent(event);
|
|
816
|
+
break;
|
|
548
817
|
}
|
|
549
818
|
}
|
|
550
|
-
|
|
551
|
-
if (this
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
if (options.input !== void 0) {
|
|
555
|
-
this.input = deepClean(options.input);
|
|
556
|
-
}
|
|
557
|
-
if (options.output !== void 0) {
|
|
558
|
-
this.output = deepClean(options.output);
|
|
559
|
-
}
|
|
560
|
-
if (options.attributes) {
|
|
561
|
-
this.attributes = { ...this.attributes, ...deepClean(options.attributes) };
|
|
819
|
+
async shutdown() {
|
|
820
|
+
if (this.#flushTimer) {
|
|
821
|
+
clearTimeout(this.#flushTimer);
|
|
822
|
+
this.#flushTimer = null;
|
|
562
823
|
}
|
|
563
|
-
if (
|
|
564
|
-
this.
|
|
824
|
+
if (this.buffer.totalSize > 0) {
|
|
825
|
+
this.logger.info("Flushing remaining events on shutdown", {
|
|
826
|
+
remainingEvents: this.buffer.totalSize
|
|
827
|
+
});
|
|
828
|
+
try {
|
|
829
|
+
await this.flush();
|
|
830
|
+
} catch (error) {
|
|
831
|
+
this.logger.error("Failed to flush remaining events during shutdown", {
|
|
832
|
+
error: error instanceof Error ? error.message : String(error)
|
|
833
|
+
});
|
|
834
|
+
}
|
|
565
835
|
}
|
|
566
|
-
|
|
567
|
-
get isValid() {
|
|
568
|
-
return true;
|
|
569
|
-
}
|
|
570
|
-
async export() {
|
|
571
|
-
return JSON.stringify({
|
|
572
|
-
spanId: this.id,
|
|
573
|
-
traceId: this.traceId,
|
|
574
|
-
startTime: this.startTime,
|
|
575
|
-
endTime: this.endTime,
|
|
576
|
-
attributes: this.attributes,
|
|
577
|
-
metadata: this.metadata
|
|
578
|
-
});
|
|
836
|
+
this.logger.info("DefaultExporter shutdown complete");
|
|
579
837
|
}
|
|
580
838
|
};
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
591
|
-
}
|
|
592
|
-
function generateTraceId() {
|
|
593
|
-
const bytes = new Uint8Array(16);
|
|
594
|
-
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
595
|
-
crypto.getRandomValues(bytes);
|
|
596
|
-
} else {
|
|
597
|
-
for (let i = 0; i < 16; i++) {
|
|
598
|
-
bytes[i] = Math.floor(Math.random() * 256);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
602
|
-
}
|
|
603
|
-
function isValidTraceId(traceId) {
|
|
604
|
-
return /^[0-9a-f]{1,32}$/i.test(traceId);
|
|
605
|
-
}
|
|
606
|
-
function isValidSpanId(spanId) {
|
|
607
|
-
return /^[0-9a-f]{1,16}$/i.test(spanId);
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
// src/spans/no-op.ts
|
|
611
|
-
var NoOpAISpan = class extends BaseAISpan {
|
|
612
|
-
id;
|
|
613
|
-
traceId;
|
|
614
|
-
constructor(options, aiTracing) {
|
|
615
|
-
super(options, aiTracing);
|
|
616
|
-
this.id = "no-op";
|
|
617
|
-
this.traceId = "no-op-trace";
|
|
839
|
+
var ModelSpanTracker = class {
|
|
840
|
+
#modelSpan;
|
|
841
|
+
#currentStepSpan;
|
|
842
|
+
#currentChunkSpan;
|
|
843
|
+
#accumulator = {};
|
|
844
|
+
#stepIndex = 0;
|
|
845
|
+
#chunkSequence = 0;
|
|
846
|
+
constructor(modelSpan) {
|
|
847
|
+
this.#modelSpan = modelSpan;
|
|
618
848
|
}
|
|
619
|
-
|
|
849
|
+
/**
|
|
850
|
+
* Get the tracing context for creating child spans.
|
|
851
|
+
* Returns the current step span if active, otherwise the model span.
|
|
852
|
+
*/
|
|
853
|
+
getTracingContext() {
|
|
854
|
+
return {
|
|
855
|
+
currentSpan: this.#currentStepSpan ?? this.#modelSpan
|
|
856
|
+
};
|
|
620
857
|
}
|
|
621
|
-
|
|
858
|
+
/**
|
|
859
|
+
* Report an error on the generation span
|
|
860
|
+
*/
|
|
861
|
+
reportGenerationError(options) {
|
|
862
|
+
this.#modelSpan?.error(options);
|
|
622
863
|
}
|
|
623
|
-
|
|
864
|
+
/**
|
|
865
|
+
* End the generation span
|
|
866
|
+
*/
|
|
867
|
+
endGeneration(options) {
|
|
868
|
+
this.#modelSpan?.end(options);
|
|
624
869
|
}
|
|
625
|
-
|
|
626
|
-
|
|
870
|
+
/**
|
|
871
|
+
* Update the generation span
|
|
872
|
+
*/
|
|
873
|
+
updateGeneration(options) {
|
|
874
|
+
this.#modelSpan?.update(options);
|
|
627
875
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
};
|
|
876
|
+
/**
|
|
877
|
+
* Start a new Model execution step
|
|
878
|
+
*/
|
|
879
|
+
#startStepSpan(payload) {
|
|
880
|
+
this.#currentStepSpan = this.#modelSpan?.createChildSpan({
|
|
881
|
+
name: `step: ${this.#stepIndex}`,
|
|
882
|
+
type: observability.SpanType.MODEL_STEP,
|
|
883
|
+
attributes: {
|
|
884
|
+
stepIndex: this.#stepIndex,
|
|
885
|
+
...payload?.messageId ? { messageId: payload.messageId } : {},
|
|
886
|
+
...payload?.warnings?.length ? { warnings: payload.warnings } : {}
|
|
887
|
+
},
|
|
888
|
+
input: payload?.request
|
|
889
|
+
});
|
|
890
|
+
this.#chunkSequence = 0;
|
|
644
891
|
}
|
|
645
892
|
/**
|
|
646
|
-
*
|
|
647
|
-
* and propagate logger to exporters
|
|
893
|
+
* End the current Model execution step with token usage, finish reason, output, and metadata
|
|
648
894
|
*/
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
895
|
+
#endStepSpan(payload) {
|
|
896
|
+
if (!this.#currentStepSpan) return;
|
|
897
|
+
const output = payload.output;
|
|
898
|
+
const { usage, ...otherOutput } = output;
|
|
899
|
+
const stepResult = payload.stepResult;
|
|
900
|
+
const metadata = payload.metadata;
|
|
901
|
+
const cleanMetadata = metadata ? { ...metadata } : void 0;
|
|
902
|
+
if (cleanMetadata?.request) {
|
|
903
|
+
delete cleanMetadata.request;
|
|
904
|
+
}
|
|
905
|
+
this.#currentStepSpan.end({
|
|
906
|
+
output: otherOutput,
|
|
907
|
+
attributes: {
|
|
908
|
+
usage,
|
|
909
|
+
isContinued: stepResult.isContinued,
|
|
910
|
+
finishReason: stepResult.reason,
|
|
911
|
+
warnings: stepResult.warnings
|
|
912
|
+
},
|
|
913
|
+
metadata: {
|
|
914
|
+
...cleanMetadata
|
|
654
915
|
}
|
|
655
916
|
});
|
|
656
|
-
this
|
|
657
|
-
|
|
658
|
-
);
|
|
659
|
-
}
|
|
660
|
-
// ============================================================================
|
|
661
|
-
// Protected getters for clean config access
|
|
662
|
-
// ============================================================================
|
|
663
|
-
get exporters() {
|
|
664
|
-
return this.config.exporters || [];
|
|
665
|
-
}
|
|
666
|
-
get processors() {
|
|
667
|
-
return this.config.processors || [];
|
|
917
|
+
this.#currentStepSpan = void 0;
|
|
918
|
+
this.#stepIndex++;
|
|
668
919
|
}
|
|
669
|
-
// ============================================================================
|
|
670
|
-
// Public API - Single type-safe span creation method
|
|
671
|
-
// ============================================================================
|
|
672
920
|
/**
|
|
673
|
-
*
|
|
921
|
+
* Create a new chunk span (for multi-part chunks like text-start/delta/end)
|
|
674
922
|
*/
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
return new NoOpAISpan({ ...rest, metadata }, this);
|
|
679
|
-
}
|
|
680
|
-
let traceState;
|
|
681
|
-
if (options.parent) {
|
|
682
|
-
traceState = options.parent.traceState;
|
|
683
|
-
} else {
|
|
684
|
-
traceState = this.computeTraceState(tracingOptions);
|
|
923
|
+
#startChunkSpan(chunkType, initialData) {
|
|
924
|
+
if (!this.#currentStepSpan) {
|
|
925
|
+
this.#startStepSpan();
|
|
685
926
|
}
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
927
|
+
this.#currentChunkSpan = this.#currentStepSpan?.createChildSpan({
|
|
928
|
+
name: `chunk: '${chunkType}'`,
|
|
929
|
+
type: observability.SpanType.MODEL_CHUNK,
|
|
930
|
+
attributes: {
|
|
931
|
+
chunkType,
|
|
932
|
+
sequenceNumber: this.#chunkSequence
|
|
933
|
+
}
|
|
691
934
|
});
|
|
692
|
-
|
|
693
|
-
|
|
935
|
+
this.#accumulator = initialData || {};
|
|
936
|
+
}
|
|
937
|
+
/**
|
|
938
|
+
* Append string content to a specific field in the accumulator
|
|
939
|
+
*/
|
|
940
|
+
#appendToAccumulator(field, text) {
|
|
941
|
+
if (this.#accumulator[field] === void 0) {
|
|
942
|
+
this.#accumulator[field] = text;
|
|
694
943
|
} else {
|
|
695
|
-
this
|
|
696
|
-
this.emitSpanStarted(span);
|
|
944
|
+
this.#accumulator[field] += text;
|
|
697
945
|
}
|
|
698
|
-
return span;
|
|
699
946
|
}
|
|
700
|
-
// ============================================================================
|
|
701
|
-
// Configuration Management
|
|
702
|
-
// ============================================================================
|
|
703
947
|
/**
|
|
704
|
-
*
|
|
948
|
+
* End the current chunk span.
|
|
949
|
+
* Safe to call multiple times - will no-op if span already ended.
|
|
705
950
|
*/
|
|
706
|
-
|
|
707
|
-
|
|
951
|
+
#endChunkSpan(output) {
|
|
952
|
+
if (!this.#currentChunkSpan) return;
|
|
953
|
+
this.#currentChunkSpan.end({
|
|
954
|
+
output: output !== void 0 ? output : this.#accumulator
|
|
955
|
+
});
|
|
956
|
+
this.#currentChunkSpan = void 0;
|
|
957
|
+
this.#accumulator = {};
|
|
958
|
+
this.#chunkSequence++;
|
|
708
959
|
}
|
|
709
|
-
// ============================================================================
|
|
710
|
-
// Plugin Access
|
|
711
|
-
// ============================================================================
|
|
712
960
|
/**
|
|
713
|
-
*
|
|
961
|
+
* Create an event span (for single chunks like tool-call)
|
|
714
962
|
*/
|
|
715
|
-
|
|
716
|
-
|
|
963
|
+
#createEventSpan(chunkType, output) {
|
|
964
|
+
if (!this.#currentStepSpan) {
|
|
965
|
+
this.#startStepSpan();
|
|
966
|
+
}
|
|
967
|
+
const span = this.#currentStepSpan?.createEventSpan({
|
|
968
|
+
name: `chunk: '${chunkType}'`,
|
|
969
|
+
type: observability.SpanType.MODEL_CHUNK,
|
|
970
|
+
attributes: {
|
|
971
|
+
chunkType,
|
|
972
|
+
sequenceNumber: this.#chunkSequence
|
|
973
|
+
},
|
|
974
|
+
output
|
|
975
|
+
});
|
|
976
|
+
if (span) {
|
|
977
|
+
this.#chunkSequence++;
|
|
978
|
+
}
|
|
717
979
|
}
|
|
718
980
|
/**
|
|
719
|
-
*
|
|
981
|
+
* Check if there is currently an active chunk span
|
|
720
982
|
*/
|
|
721
|
-
|
|
722
|
-
return
|
|
983
|
+
#hasActiveChunkSpan() {
|
|
984
|
+
return !!this.#currentChunkSpan;
|
|
723
985
|
}
|
|
724
986
|
/**
|
|
725
|
-
* Get the
|
|
987
|
+
* Get the current accumulator value
|
|
726
988
|
*/
|
|
727
|
-
|
|
728
|
-
return this
|
|
989
|
+
#getAccumulator() {
|
|
990
|
+
return this.#accumulator;
|
|
729
991
|
}
|
|
730
|
-
// ============================================================================
|
|
731
|
-
// Span Lifecycle Management
|
|
732
|
-
// ============================================================================
|
|
733
992
|
/**
|
|
734
|
-
*
|
|
735
|
-
* This ensures all spans emit events regardless of implementation
|
|
993
|
+
* Handle text chunk spans (text-start/delta/end)
|
|
736
994
|
*/
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
originalEnd(options);
|
|
749
|
-
this.emitSpanEnded(span);
|
|
750
|
-
};
|
|
751
|
-
span.update = (options) => {
|
|
752
|
-
if (span.isEvent) {
|
|
753
|
-
this.logger.warn(`Update() is not available on event spans`);
|
|
754
|
-
return;
|
|
995
|
+
#handleTextChunk(chunk) {
|
|
996
|
+
switch (chunk.type) {
|
|
997
|
+
case "text-start":
|
|
998
|
+
this.#startChunkSpan("text");
|
|
999
|
+
break;
|
|
1000
|
+
case "text-delta":
|
|
1001
|
+
this.#appendToAccumulator("text", chunk.payload.text);
|
|
1002
|
+
break;
|
|
1003
|
+
case "text-end": {
|
|
1004
|
+
this.#endChunkSpan();
|
|
1005
|
+
break;
|
|
755
1006
|
}
|
|
756
|
-
|
|
757
|
-
this.emitSpanUpdated(span);
|
|
758
|
-
};
|
|
1007
|
+
}
|
|
759
1008
|
}
|
|
760
|
-
// ============================================================================
|
|
761
|
-
// Utility Methods
|
|
762
|
-
// ============================================================================
|
|
763
1009
|
/**
|
|
764
|
-
*
|
|
1010
|
+
* Handle reasoning chunk spans (reasoning-start/delta/end)
|
|
765
1011
|
*/
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
case
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
return false;
|
|
779
|
-
}
|
|
780
|
-
return Math.random() < sampling.probability;
|
|
781
|
-
case observability.SamplingStrategyType.CUSTOM:
|
|
782
|
-
return sampling.sampler(options);
|
|
783
|
-
default:
|
|
784
|
-
throw new Error(`Sampling strategy type not implemented: ${sampling.type}`);
|
|
1012
|
+
#handleReasoningChunk(chunk) {
|
|
1013
|
+
switch (chunk.type) {
|
|
1014
|
+
case "reasoning-start":
|
|
1015
|
+
this.#startChunkSpan("reasoning");
|
|
1016
|
+
break;
|
|
1017
|
+
case "reasoning-delta":
|
|
1018
|
+
this.#appendToAccumulator("text", chunk.payload.text);
|
|
1019
|
+
break;
|
|
1020
|
+
case "reasoning-end": {
|
|
1021
|
+
this.#endChunkSpan();
|
|
1022
|
+
break;
|
|
1023
|
+
}
|
|
785
1024
|
}
|
|
786
1025
|
}
|
|
787
1026
|
/**
|
|
788
|
-
*
|
|
1027
|
+
* Handle tool call chunk spans (tool-call-input-streaming-start/delta/end, tool-call)
|
|
789
1028
|
*/
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
1029
|
+
#handleToolCallChunk(chunk) {
|
|
1030
|
+
switch (chunk.type) {
|
|
1031
|
+
case "tool-call-input-streaming-start":
|
|
1032
|
+
this.#startChunkSpan("tool-call", {
|
|
1033
|
+
toolName: chunk.payload.toolName,
|
|
1034
|
+
toolCallId: chunk.payload.toolCallId
|
|
1035
|
+
});
|
|
1036
|
+
break;
|
|
1037
|
+
case "tool-call-delta":
|
|
1038
|
+
this.#appendToAccumulator("toolInput", chunk.payload.argsTextDelta);
|
|
1039
|
+
break;
|
|
1040
|
+
case "tool-call-input-streaming-end":
|
|
1041
|
+
case "tool-call": {
|
|
1042
|
+
const acc = this.#getAccumulator();
|
|
1043
|
+
let toolInput;
|
|
1044
|
+
try {
|
|
1045
|
+
toolInput = acc.toolInput ? JSON.parse(acc.toolInput) : {};
|
|
1046
|
+
} catch {
|
|
1047
|
+
toolInput = acc.toolInput;
|
|
1048
|
+
}
|
|
1049
|
+
this.#endChunkSpan({
|
|
1050
|
+
toolName: acc.toolName,
|
|
1051
|
+
toolCallId: acc.toolCallId,
|
|
1052
|
+
toolInput
|
|
1053
|
+
});
|
|
1054
|
+
break;
|
|
1055
|
+
}
|
|
796
1056
|
}
|
|
797
|
-
return {
|
|
798
|
-
requestContextKeys: allKeys
|
|
799
|
-
};
|
|
800
1057
|
}
|
|
801
1058
|
/**
|
|
802
|
-
*
|
|
1059
|
+
* Handle object chunk spans (object, object-result)
|
|
803
1060
|
*/
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
1061
|
+
#handleObjectChunk(chunk) {
|
|
1062
|
+
switch (chunk.type) {
|
|
1063
|
+
case "object":
|
|
1064
|
+
if (!this.#hasActiveChunkSpan()) {
|
|
1065
|
+
this.#startChunkSpan("object");
|
|
1066
|
+
}
|
|
1067
|
+
break;
|
|
1068
|
+
case "object-result":
|
|
1069
|
+
this.#endChunkSpan(chunk.object);
|
|
1070
|
+
break;
|
|
811
1071
|
}
|
|
812
|
-
return {
|
|
813
|
-
...extracted,
|
|
814
|
-
...explicitMetadata
|
|
815
|
-
// Explicit metadata always wins
|
|
816
|
-
};
|
|
817
1072
|
}
|
|
818
1073
|
/**
|
|
819
|
-
*
|
|
1074
|
+
* Wraps a stream with model tracing transform to track MODEL_STEP and MODEL_CHUNK spans.
|
|
1075
|
+
*
|
|
1076
|
+
* This should be added to the stream pipeline to automatically
|
|
1077
|
+
* create MODEL_STEP and MODEL_CHUNK spans for each semantic unit in the stream.
|
|
820
1078
|
*/
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1079
|
+
wrapStream(stream) {
|
|
1080
|
+
return stream.pipeThrough(
|
|
1081
|
+
new web.TransformStream({
|
|
1082
|
+
transform: (chunk, controller) => {
|
|
1083
|
+
controller.enqueue(chunk);
|
|
1084
|
+
switch (chunk.type) {
|
|
1085
|
+
case "text-start":
|
|
1086
|
+
case "text-delta":
|
|
1087
|
+
case "text-end":
|
|
1088
|
+
this.#handleTextChunk(chunk);
|
|
1089
|
+
break;
|
|
1090
|
+
case "tool-call-input-streaming-start":
|
|
1091
|
+
case "tool-call-delta":
|
|
1092
|
+
case "tool-call-input-streaming-end":
|
|
1093
|
+
case "tool-call":
|
|
1094
|
+
this.#handleToolCallChunk(chunk);
|
|
1095
|
+
break;
|
|
1096
|
+
case "reasoning-start":
|
|
1097
|
+
case "reasoning-delta":
|
|
1098
|
+
case "reasoning-end":
|
|
1099
|
+
this.#handleReasoningChunk(chunk);
|
|
1100
|
+
break;
|
|
1101
|
+
case "object":
|
|
1102
|
+
case "object-result":
|
|
1103
|
+
this.#handleObjectChunk(chunk);
|
|
1104
|
+
break;
|
|
1105
|
+
case "step-start":
|
|
1106
|
+
this.#startStepSpan(chunk.payload);
|
|
1107
|
+
break;
|
|
1108
|
+
case "step-finish":
|
|
1109
|
+
this.#endStepSpan(chunk.payload);
|
|
1110
|
+
break;
|
|
1111
|
+
case "raw":
|
|
1112
|
+
// Skip raw chunks as they're redundant
|
|
1113
|
+
case "start":
|
|
1114
|
+
case "finish":
|
|
1115
|
+
break;
|
|
1116
|
+
// Default: auto-create event span for all other chunk types
|
|
1117
|
+
default: {
|
|
1118
|
+
let outputPayload = chunk.payload;
|
|
1119
|
+
if (outputPayload && typeof outputPayload === "object" && "data" in outputPayload) {
|
|
1120
|
+
const typedPayload = outputPayload;
|
|
1121
|
+
outputPayload = { ...typedPayload };
|
|
1122
|
+
if (typedPayload.data) {
|
|
1123
|
+
outputPayload.size = typeof typedPayload.data === "string" ? typedPayload.data.length : typedPayload.data instanceof Uint8Array ? typedPayload.data.length : void 0;
|
|
1124
|
+
delete outputPayload.data;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
this.#createEventSpan(chunk.type, outputPayload);
|
|
1128
|
+
break;
|
|
1129
|
+
}
|
|
833
1130
|
}
|
|
834
|
-
} else {
|
|
835
|
-
utils.setNestedValue(result, key, value);
|
|
836
1131
|
}
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
return result;
|
|
1132
|
+
})
|
|
1133
|
+
);
|
|
840
1134
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
break;
|
|
848
|
-
}
|
|
849
|
-
try {
|
|
850
|
-
span = processor.process(span);
|
|
851
|
-
} catch (error) {
|
|
852
|
-
this.logger.error(`[AI Tracing] Processor error [name=${processor.name}]`, error);
|
|
853
|
-
}
|
|
854
|
-
}
|
|
855
|
-
return span;
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
// src/spans/base.ts
|
|
1138
|
+
function isSpanInternal(spanType, flags) {
|
|
1139
|
+
if (flags === void 0 || flags === observability.InternalSpans.NONE) {
|
|
1140
|
+
return false;
|
|
856
1141
|
}
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
1142
|
+
switch (spanType) {
|
|
1143
|
+
// Workflow-related spans
|
|
1144
|
+
case observability.SpanType.WORKFLOW_RUN:
|
|
1145
|
+
case observability.SpanType.WORKFLOW_STEP:
|
|
1146
|
+
case observability.SpanType.WORKFLOW_CONDITIONAL:
|
|
1147
|
+
case observability.SpanType.WORKFLOW_CONDITIONAL_EVAL:
|
|
1148
|
+
case observability.SpanType.WORKFLOW_PARALLEL:
|
|
1149
|
+
case observability.SpanType.WORKFLOW_LOOP:
|
|
1150
|
+
case observability.SpanType.WORKFLOW_SLEEP:
|
|
1151
|
+
case observability.SpanType.WORKFLOW_WAIT_EVENT:
|
|
1152
|
+
return (flags & observability.InternalSpans.WORKFLOW) !== 0;
|
|
1153
|
+
// Agent-related spans
|
|
1154
|
+
case observability.SpanType.AGENT_RUN:
|
|
1155
|
+
return (flags & observability.InternalSpans.AGENT) !== 0;
|
|
1156
|
+
// Tool-related spans
|
|
1157
|
+
case observability.SpanType.TOOL_CALL:
|
|
1158
|
+
case observability.SpanType.MCP_TOOL_CALL:
|
|
1159
|
+
return (flags & observability.InternalSpans.TOOL) !== 0;
|
|
1160
|
+
// Model-related spans
|
|
1161
|
+
case observability.SpanType.MODEL_GENERATION:
|
|
1162
|
+
case observability.SpanType.MODEL_STEP:
|
|
1163
|
+
case observability.SpanType.MODEL_CHUNK:
|
|
1164
|
+
return (flags & observability.InternalSpans.MODEL) !== 0;
|
|
1165
|
+
// Default: never internal
|
|
1166
|
+
default:
|
|
1167
|
+
return false;
|
|
865
1168
|
}
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1169
|
+
}
|
|
1170
|
+
var BaseSpan = class {
|
|
1171
|
+
name;
|
|
1172
|
+
type;
|
|
1173
|
+
attributes;
|
|
1174
|
+
parent;
|
|
1175
|
+
startTime;
|
|
1176
|
+
endTime;
|
|
1177
|
+
isEvent;
|
|
1178
|
+
isInternal;
|
|
1179
|
+
observabilityInstance;
|
|
1180
|
+
input;
|
|
1181
|
+
output;
|
|
1182
|
+
errorInfo;
|
|
1183
|
+
metadata;
|
|
1184
|
+
traceState;
|
|
1185
|
+
/** Parent span ID (for root spans that are children of external spans) */
|
|
1186
|
+
parentSpanId;
|
|
1187
|
+
constructor(options, observabilityInstance) {
|
|
1188
|
+
this.name = options.name;
|
|
1189
|
+
this.type = options.type;
|
|
1190
|
+
this.attributes = deepClean(options.attributes) || {};
|
|
1191
|
+
this.metadata = deepClean(options.metadata);
|
|
1192
|
+
this.parent = options.parent;
|
|
1193
|
+
this.startTime = /* @__PURE__ */ new Date();
|
|
1194
|
+
this.observabilityInstance = observabilityInstance;
|
|
1195
|
+
this.isEvent = options.isEvent ?? false;
|
|
1196
|
+
this.isInternal = isSpanInternal(this.type, options.tracingPolicy?.internal);
|
|
1197
|
+
this.traceState = options.traceState;
|
|
1198
|
+
if (this.isEvent) {
|
|
1199
|
+
this.output = deepClean(options.output);
|
|
1200
|
+
} else {
|
|
1201
|
+
this.input = deepClean(options.input);
|
|
875
1202
|
}
|
|
876
1203
|
}
|
|
1204
|
+
createChildSpan(options) {
|
|
1205
|
+
return this.observabilityInstance.startSpan({ ...options, parent: this, isEvent: false });
|
|
1206
|
+
}
|
|
1207
|
+
createEventSpan(options) {
|
|
1208
|
+
return this.observabilityInstance.startSpan({ ...options, parent: this, isEvent: true });
|
|
1209
|
+
}
|
|
877
1210
|
/**
|
|
878
|
-
*
|
|
1211
|
+
* Create a ModelSpanTracker for this span (only works if this is a MODEL_GENERATION span)
|
|
1212
|
+
* Returns undefined for non-MODEL_GENERATION spans
|
|
879
1213
|
*/
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
this.exportEvent({ type: observability.AITracingEventType.SPAN_ENDED, exportedSpan }).catch((error) => {
|
|
884
|
-
this.logger.error("[AI Tracing] Failed to export span_ended event", error);
|
|
885
|
-
});
|
|
1214
|
+
createTracker() {
|
|
1215
|
+
if (this.type !== observability.SpanType.MODEL_GENERATION) {
|
|
1216
|
+
return void 0;
|
|
886
1217
|
}
|
|
1218
|
+
return new ModelSpanTracker(this);
|
|
887
1219
|
}
|
|
888
|
-
/**
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
});
|
|
1220
|
+
/** Returns `TRUE` if the span is the root span of a trace */
|
|
1221
|
+
get isRootSpan() {
|
|
1222
|
+
return !this.parent;
|
|
1223
|
+
}
|
|
1224
|
+
/** Get the closest parent spanId that isn't an internal span */
|
|
1225
|
+
getParentSpanId(includeInternalSpans) {
|
|
1226
|
+
if (!this.parent) {
|
|
1227
|
+
return this.parentSpanId;
|
|
897
1228
|
}
|
|
1229
|
+
if (includeInternalSpans) return this.parent.id;
|
|
1230
|
+
if (this.parent.isInternal) return this.parent.getParentSpanId(includeInternalSpans);
|
|
1231
|
+
return this.parent.id;
|
|
898
1232
|
}
|
|
899
|
-
/**
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
if (exporter.exportEvent) {
|
|
906
|
-
await exporter.exportEvent(event);
|
|
907
|
-
this.logger.debug(`[AI Tracing] Event exported [exporter=${exporter.name}] [type=${event.type}]`);
|
|
908
|
-
}
|
|
909
|
-
} catch (error) {
|
|
910
|
-
this.logger.error(`[AI Tracing] Export error [exporter=${exporter.name}]`, error);
|
|
1233
|
+
/** Find the closest parent span of a specific type by walking up the parent chain */
|
|
1234
|
+
findParent(spanType) {
|
|
1235
|
+
let current = this.parent;
|
|
1236
|
+
while (current) {
|
|
1237
|
+
if (current.type === spanType) {
|
|
1238
|
+
return current;
|
|
911
1239
|
}
|
|
912
|
-
|
|
913
|
-
|
|
1240
|
+
current = current.parent;
|
|
1241
|
+
}
|
|
1242
|
+
return void 0;
|
|
914
1243
|
}
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1244
|
+
/** Returns a lightweight span ready for export */
|
|
1245
|
+
exportSpan(includeInternalSpans) {
|
|
1246
|
+
return {
|
|
1247
|
+
id: this.id,
|
|
1248
|
+
traceId: this.traceId,
|
|
1249
|
+
name: this.name,
|
|
1250
|
+
type: this.type,
|
|
1251
|
+
attributes: this.attributes,
|
|
1252
|
+
metadata: this.metadata,
|
|
1253
|
+
startTime: this.startTime,
|
|
1254
|
+
endTime: this.endTime,
|
|
1255
|
+
input: this.input,
|
|
1256
|
+
output: this.output,
|
|
1257
|
+
errorInfo: this.errorInfo,
|
|
1258
|
+
isEvent: this.isEvent,
|
|
1259
|
+
isRootSpan: this.isRootSpan,
|
|
1260
|
+
parentSpanId: this.getParentSpanId(includeInternalSpans)
|
|
1261
|
+
};
|
|
924
1262
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
*/
|
|
928
|
-
async shutdown() {
|
|
929
|
-
this.logger.debug(`[AI Tracing] Shutdown started [name=${this.name}]`);
|
|
930
|
-
const shutdownPromises = [...this.exporters.map((e) => e.shutdown()), ...this.processors.map((p) => p.shutdown())];
|
|
931
|
-
await Promise.allSettled(shutdownPromises);
|
|
932
|
-
this.logger.info(`[AI Tracing] Shutdown completed [name=${this.name}]`);
|
|
1263
|
+
get externalTraceId() {
|
|
1264
|
+
return this.isValid ? this.traceId : void 0;
|
|
933
1265
|
}
|
|
934
1266
|
};
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1267
|
+
var DEFAULT_KEYS_TO_STRIP = /* @__PURE__ */ new Set([
|
|
1268
|
+
"logger",
|
|
1269
|
+
"experimental_providerMetadata",
|
|
1270
|
+
"providerMetadata",
|
|
1271
|
+
"steps",
|
|
1272
|
+
"tracingContext"
|
|
1273
|
+
]);
|
|
1274
|
+
function deepClean(value, options = {}, _seen = /* @__PURE__ */ new WeakSet(), _depth = 0) {
|
|
1275
|
+
const { keysToStrip = DEFAULT_KEYS_TO_STRIP, maxDepth = 10 } = options;
|
|
1276
|
+
if (_depth > maxDepth) {
|
|
1277
|
+
return "[MaxDepth]";
|
|
940
1278
|
}
|
|
941
|
-
|
|
942
|
-
|
|
1279
|
+
if (value === null || typeof value !== "object") {
|
|
1280
|
+
try {
|
|
1281
|
+
JSON.stringify(value);
|
|
1282
|
+
return value;
|
|
1283
|
+
} catch (error) {
|
|
1284
|
+
return `[${error instanceof Error ? error.message : String(error)}]`;
|
|
1285
|
+
}
|
|
943
1286
|
}
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
/** Mastra logger instance */
|
|
947
|
-
logger;
|
|
948
|
-
/** Whether this exporter is disabled */
|
|
949
|
-
isDisabled = false;
|
|
950
|
-
/**
|
|
951
|
-
* Initialize the base exporter with logger
|
|
952
|
-
*/
|
|
953
|
-
constructor(config = {}) {
|
|
954
|
-
const logLevel = this.resolveLogLevel(config.logLevel);
|
|
955
|
-
this.logger = config.logger ?? new logger.ConsoleLogger({ level: logLevel, name: this.constructor.name });
|
|
1287
|
+
if (_seen.has(value)) {
|
|
1288
|
+
return "[Circular]";
|
|
956
1289
|
}
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
__setLogger(logger) {
|
|
961
|
-
this.logger = logger;
|
|
962
|
-
this.logger.debug(`Logger updated for exporter [name=${this.name}]`);
|
|
1290
|
+
_seen.add(value);
|
|
1291
|
+
if (Array.isArray(value)) {
|
|
1292
|
+
return value.map((item) => deepClean(item, options, _seen, _depth + 1));
|
|
963
1293
|
}
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
if (!logLevel) {
|
|
969
|
-
return logger.LogLevel.INFO;
|
|
1294
|
+
const cleaned = {};
|
|
1295
|
+
for (const [key, val] of Object.entries(value)) {
|
|
1296
|
+
if (keysToStrip.has(key)) {
|
|
1297
|
+
continue;
|
|
970
1298
|
}
|
|
971
|
-
|
|
972
|
-
|
|
1299
|
+
try {
|
|
1300
|
+
cleaned[key] = deepClean(val, options, _seen, _depth + 1);
|
|
1301
|
+
} catch (error) {
|
|
1302
|
+
cleaned[key] = `[${error instanceof Error ? error.message : String(error)}]`;
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
return cleaned;
|
|
1306
|
+
}
|
|
1307
|
+
var DefaultSpan = class extends BaseSpan {
|
|
1308
|
+
id;
|
|
1309
|
+
traceId;
|
|
1310
|
+
constructor(options, observabilityInstance) {
|
|
1311
|
+
super(options, observabilityInstance);
|
|
1312
|
+
this.id = generateSpanId();
|
|
1313
|
+
if (options.parent) {
|
|
1314
|
+
this.traceId = options.parent.traceId;
|
|
1315
|
+
} else if (options.traceId) {
|
|
1316
|
+
if (isValidTraceId(options.traceId)) {
|
|
1317
|
+
this.traceId = options.traceId;
|
|
1318
|
+
} else {
|
|
1319
|
+
console.error(
|
|
1320
|
+
`[Mastra Tracing] Invalid traceId: must be 1-32 hexadecimal characters, got "${options.traceId}". Generating new trace ID.`
|
|
1321
|
+
);
|
|
1322
|
+
this.traceId = generateTraceId();
|
|
1323
|
+
}
|
|
1324
|
+
} else {
|
|
1325
|
+
this.traceId = generateTraceId();
|
|
1326
|
+
}
|
|
1327
|
+
if (!options.parent && options.parentSpanId) {
|
|
1328
|
+
if (isValidSpanId(options.parentSpanId)) {
|
|
1329
|
+
this.parentSpanId = options.parentSpanId;
|
|
1330
|
+
} else {
|
|
1331
|
+
console.error(
|
|
1332
|
+
`[Mastra Tracing] Invalid parentSpanId: must be 1-16 hexadecimal characters, got "${options.parentSpanId}". Ignoring parent span ID.`
|
|
1333
|
+
);
|
|
1334
|
+
}
|
|
973
1335
|
}
|
|
974
|
-
const logLevelMap = {
|
|
975
|
-
debug: logger.LogLevel.DEBUG,
|
|
976
|
-
info: logger.LogLevel.INFO,
|
|
977
|
-
warn: logger.LogLevel.WARN,
|
|
978
|
-
error: logger.LogLevel.ERROR
|
|
979
|
-
};
|
|
980
|
-
return logLevelMap[logLevel] ?? logger.LogLevel.INFO;
|
|
981
|
-
}
|
|
982
|
-
/**
|
|
983
|
-
* Mark the exporter as disabled and log a message
|
|
984
|
-
*
|
|
985
|
-
* @param reason - Reason why the exporter is disabled
|
|
986
|
-
*/
|
|
987
|
-
setDisabled(reason) {
|
|
988
|
-
this.isDisabled = true;
|
|
989
|
-
this.logger.warn(`${this.name} disabled: ${reason}`);
|
|
990
1336
|
}
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
*
|
|
994
|
-
* This method checks if the exporter is disabled before calling _exportEvent.
|
|
995
|
-
* Subclasses should implement _exportEvent instead of overriding this method.
|
|
996
|
-
*/
|
|
997
|
-
async exportEvent(event) {
|
|
998
|
-
if (this.isDisabled) {
|
|
1337
|
+
end(options) {
|
|
1338
|
+
if (this.isEvent) {
|
|
999
1339
|
return;
|
|
1000
1340
|
}
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1341
|
+
this.endTime = /* @__PURE__ */ new Date();
|
|
1342
|
+
if (options?.output !== void 0) {
|
|
1343
|
+
this.output = deepClean(options.output);
|
|
1344
|
+
}
|
|
1345
|
+
if (options?.attributes) {
|
|
1346
|
+
this.attributes = { ...this.attributes, ...deepClean(options.attributes) };
|
|
1347
|
+
}
|
|
1348
|
+
if (options?.metadata) {
|
|
1349
|
+
this.metadata = { ...this.metadata, ...deepClean(options.metadata) };
|
|
1350
|
+
}
|
|
1010
1351
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
config;
|
|
1015
|
-
buffer;
|
|
1016
|
-
flushTimer = null;
|
|
1017
|
-
constructor(config = {}) {
|
|
1018
|
-
super(config);
|
|
1019
|
-
const accessToken = config.accessToken ?? process.env.MASTRA_CLOUD_ACCESS_TOKEN;
|
|
1020
|
-
if (!accessToken) {
|
|
1021
|
-
this.setDisabled(
|
|
1022
|
-
"MASTRA_CLOUD_ACCESS_TOKEN environment variable not set. \u{1F680} Sign up for Mastra Cloud at https://cloud.mastra.ai to see your AI traces online and obtain your access token."
|
|
1023
|
-
);
|
|
1352
|
+
error(options) {
|
|
1353
|
+
if (this.isEvent) {
|
|
1354
|
+
return;
|
|
1024
1355
|
}
|
|
1025
|
-
const
|
|
1026
|
-
this.
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
};
|
|
1035
|
-
this.buffer = {
|
|
1036
|
-
spans: [],
|
|
1037
|
-
totalSize: 0
|
|
1356
|
+
const { error: error$1, endSpan = true, attributes, metadata } = options;
|
|
1357
|
+
this.errorInfo = error$1 instanceof error.MastraError ? {
|
|
1358
|
+
id: error$1.id,
|
|
1359
|
+
details: error$1.details,
|
|
1360
|
+
category: error$1.category,
|
|
1361
|
+
domain: error$1.domain,
|
|
1362
|
+
message: error$1.message
|
|
1363
|
+
} : {
|
|
1364
|
+
message: error$1.message
|
|
1038
1365
|
};
|
|
1366
|
+
if (attributes) {
|
|
1367
|
+
this.attributes = { ...this.attributes, ...deepClean(attributes) };
|
|
1368
|
+
}
|
|
1369
|
+
if (metadata) {
|
|
1370
|
+
this.metadata = { ...this.metadata, ...deepClean(metadata) };
|
|
1371
|
+
}
|
|
1372
|
+
if (endSpan) {
|
|
1373
|
+
this.end();
|
|
1374
|
+
} else {
|
|
1375
|
+
this.update({});
|
|
1376
|
+
}
|
|
1039
1377
|
}
|
|
1040
|
-
|
|
1041
|
-
if (
|
|
1378
|
+
update(options) {
|
|
1379
|
+
if (this.isEvent) {
|
|
1042
1380
|
return;
|
|
1043
1381
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
this.flush().catch((error) => {
|
|
1047
|
-
this.logger.error("Batch flush failed", {
|
|
1048
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1049
|
-
});
|
|
1050
|
-
});
|
|
1051
|
-
} else if (this.buffer.totalSize === 1) {
|
|
1052
|
-
this.scheduleFlush();
|
|
1382
|
+
if (options.input !== void 0) {
|
|
1383
|
+
this.input = deepClean(options.input);
|
|
1053
1384
|
}
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
if (this.buffer.totalSize === 0) {
|
|
1057
|
-
this.buffer.firstEventTime = /* @__PURE__ */ new Date();
|
|
1385
|
+
if (options.output !== void 0) {
|
|
1386
|
+
this.output = deepClean(options.output);
|
|
1058
1387
|
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
this.buffer.totalSize++;
|
|
1062
|
-
}
|
|
1063
|
-
formatSpan(span) {
|
|
1064
|
-
const spanRecord = {
|
|
1065
|
-
traceId: span.traceId,
|
|
1066
|
-
spanId: span.id,
|
|
1067
|
-
parentSpanId: span.parentSpanId ?? null,
|
|
1068
|
-
name: span.name,
|
|
1069
|
-
spanType: span.type,
|
|
1070
|
-
attributes: span.attributes ?? null,
|
|
1071
|
-
metadata: span.metadata ?? null,
|
|
1072
|
-
startedAt: span.startTime,
|
|
1073
|
-
endedAt: span.endTime ?? null,
|
|
1074
|
-
input: span.input ?? null,
|
|
1075
|
-
output: span.output ?? null,
|
|
1076
|
-
error: span.errorInfo,
|
|
1077
|
-
isEvent: span.isEvent,
|
|
1078
|
-
createdAt: /* @__PURE__ */ new Date(),
|
|
1079
|
-
updatedAt: null
|
|
1080
|
-
};
|
|
1081
|
-
return spanRecord;
|
|
1082
|
-
}
|
|
1083
|
-
shouldFlush() {
|
|
1084
|
-
if (this.buffer.totalSize >= this.config.maxBatchSize) {
|
|
1085
|
-
return true;
|
|
1388
|
+
if (options.attributes) {
|
|
1389
|
+
this.attributes = { ...this.attributes, ...deepClean(options.attributes) };
|
|
1086
1390
|
}
|
|
1087
|
-
if (
|
|
1088
|
-
|
|
1089
|
-
if (elapsed >= this.config.maxBatchWaitMs) {
|
|
1090
|
-
return true;
|
|
1091
|
-
}
|
|
1391
|
+
if (options.metadata) {
|
|
1392
|
+
this.metadata = { ...this.metadata, ...deepClean(options.metadata) };
|
|
1092
1393
|
}
|
|
1093
|
-
return false;
|
|
1094
1394
|
}
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
clearTimeout(this.flushTimer);
|
|
1098
|
-
}
|
|
1099
|
-
this.flushTimer = setTimeout(() => {
|
|
1100
|
-
this.flush().catch((error$1) => {
|
|
1101
|
-
const mastraError = new error.MastraError(
|
|
1102
|
-
{
|
|
1103
|
-
id: `CLOUD_AI_TRACING_FAILED_TO_SCHEDULE_FLUSH`,
|
|
1104
|
-
domain: error.ErrorDomain.MASTRA_OBSERVABILITY,
|
|
1105
|
-
category: error.ErrorCategory.USER
|
|
1106
|
-
},
|
|
1107
|
-
error$1
|
|
1108
|
-
);
|
|
1109
|
-
this.logger.trackException(mastraError);
|
|
1110
|
-
this.logger.error("Scheduled flush failed", mastraError);
|
|
1111
|
-
});
|
|
1112
|
-
}, this.config.maxBatchWaitMs);
|
|
1395
|
+
get isValid() {
|
|
1396
|
+
return true;
|
|
1113
1397
|
}
|
|
1114
|
-
async
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
this.
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1398
|
+
async export() {
|
|
1399
|
+
return JSON.stringify({
|
|
1400
|
+
spanId: this.id,
|
|
1401
|
+
traceId: this.traceId,
|
|
1402
|
+
startTime: this.startTime,
|
|
1403
|
+
endTime: this.endTime,
|
|
1404
|
+
attributes: this.attributes,
|
|
1405
|
+
metadata: this.metadata
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
function generateSpanId() {
|
|
1410
|
+
const bytes = new Uint8Array(8);
|
|
1411
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
1412
|
+
crypto.getRandomValues(bytes);
|
|
1413
|
+
} else {
|
|
1414
|
+
for (let i = 0; i < 8; i++) {
|
|
1415
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
1121
1416
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
durationMs: elapsed
|
|
1133
|
-
});
|
|
1134
|
-
} catch (error$1) {
|
|
1135
|
-
const mastraError = new error.MastraError(
|
|
1136
|
-
{
|
|
1137
|
-
id: `CLOUD_AI_TRACING_FAILED_TO_BATCH_UPLOAD`,
|
|
1138
|
-
domain: error.ErrorDomain.MASTRA_OBSERVABILITY,
|
|
1139
|
-
category: error.ErrorCategory.USER,
|
|
1140
|
-
details: {
|
|
1141
|
-
droppedBatchSize: spansCopy.length
|
|
1142
|
-
}
|
|
1143
|
-
},
|
|
1144
|
-
error$1
|
|
1145
|
-
);
|
|
1146
|
-
this.logger.trackException(mastraError);
|
|
1147
|
-
this.logger.error("Batch upload failed after all retries, dropping batch", mastraError);
|
|
1417
|
+
}
|
|
1418
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1419
|
+
}
|
|
1420
|
+
function generateTraceId() {
|
|
1421
|
+
const bytes = new Uint8Array(16);
|
|
1422
|
+
if (typeof crypto !== "undefined" && crypto.getRandomValues) {
|
|
1423
|
+
crypto.getRandomValues(bytes);
|
|
1424
|
+
} else {
|
|
1425
|
+
for (let i = 0; i < 16; i++) {
|
|
1426
|
+
bytes[i] = Math.floor(Math.random() * 256);
|
|
1148
1427
|
}
|
|
1149
1428
|
}
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1429
|
+
return Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
1430
|
+
}
|
|
1431
|
+
function isValidTraceId(traceId) {
|
|
1432
|
+
return /^[0-9a-f]{1,32}$/i.test(traceId);
|
|
1433
|
+
}
|
|
1434
|
+
function isValidSpanId(spanId) {
|
|
1435
|
+
return /^[0-9a-f]{1,16}$/i.test(spanId);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
// src/spans/no-op.ts
|
|
1439
|
+
var NoOpSpan = class extends BaseSpan {
|
|
1440
|
+
id;
|
|
1441
|
+
traceId;
|
|
1442
|
+
constructor(options, observabilityInstance) {
|
|
1443
|
+
super(options, observabilityInstance);
|
|
1444
|
+
this.id = "no-op";
|
|
1445
|
+
this.traceId = "no-op-trace";
|
|
1446
|
+
}
|
|
1447
|
+
end(_options) {
|
|
1164
1448
|
}
|
|
1165
|
-
|
|
1166
|
-
this.buffer.spans = [];
|
|
1167
|
-
this.buffer.firstEventTime = void 0;
|
|
1168
|
-
this.buffer.totalSize = 0;
|
|
1449
|
+
error(_options) {
|
|
1169
1450
|
}
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
if (this.flushTimer) {
|
|
1175
|
-
clearTimeout(this.flushTimer);
|
|
1176
|
-
this.flushTimer = null;
|
|
1177
|
-
}
|
|
1178
|
-
if (this.buffer.totalSize > 0) {
|
|
1179
|
-
this.logger.info("Flushing remaining events on shutdown", {
|
|
1180
|
-
remainingEvents: this.buffer.totalSize
|
|
1181
|
-
});
|
|
1182
|
-
try {
|
|
1183
|
-
await this.flush();
|
|
1184
|
-
} catch (error$1) {
|
|
1185
|
-
const mastraError = new error.MastraError(
|
|
1186
|
-
{
|
|
1187
|
-
id: `CLOUD_AI_TRACING_FAILED_TO_FLUSH_REMAINING_EVENTS_DURING_SHUTDOWN`,
|
|
1188
|
-
domain: error.ErrorDomain.MASTRA_OBSERVABILITY,
|
|
1189
|
-
category: error.ErrorCategory.USER,
|
|
1190
|
-
details: {
|
|
1191
|
-
remainingEvents: this.buffer.totalSize
|
|
1192
|
-
}
|
|
1193
|
-
},
|
|
1194
|
-
error$1
|
|
1195
|
-
);
|
|
1196
|
-
this.logger.trackException(mastraError);
|
|
1197
|
-
this.logger.error("Failed to flush remaining events during shutdown", mastraError);
|
|
1198
|
-
}
|
|
1199
|
-
}
|
|
1200
|
-
this.logger.info("CloudExporter shutdown complete");
|
|
1451
|
+
update(_options) {
|
|
1452
|
+
}
|
|
1453
|
+
get isValid() {
|
|
1454
|
+
return false;
|
|
1201
1455
|
}
|
|
1202
1456
|
};
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1457
|
+
|
|
1458
|
+
// src/instances/base.ts
|
|
1459
|
+
var BaseObservabilityInstance = class extends base.MastraBase {
|
|
1460
|
+
config;
|
|
1461
|
+
constructor(config) {
|
|
1462
|
+
super({ component: logger.RegisteredLogger.OBSERVABILITY, name: config.serviceName });
|
|
1463
|
+
this.config = {
|
|
1464
|
+
serviceName: config.serviceName,
|
|
1465
|
+
name: config.name,
|
|
1466
|
+
sampling: config.sampling ?? { type: "always" /* ALWAYS */ },
|
|
1467
|
+
exporters: config.exporters ?? [],
|
|
1468
|
+
spanOutputProcessors: config.spanOutputProcessors ?? [],
|
|
1469
|
+
includeInternalSpans: config.includeInternalSpans ?? false,
|
|
1470
|
+
requestContextKeys: config.requestContextKeys ?? []
|
|
1471
|
+
};
|
|
1207
1472
|
}
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1473
|
+
/**
|
|
1474
|
+
* Override setLogger to add Observability specific initialization log
|
|
1475
|
+
* and propagate logger to exporters
|
|
1476
|
+
*/
|
|
1477
|
+
__setLogger(logger) {
|
|
1478
|
+
super.__setLogger(logger);
|
|
1479
|
+
this.exporters.forEach((exporter) => {
|
|
1480
|
+
if (typeof exporter.__setLogger === "function") {
|
|
1481
|
+
exporter.__setLogger(logger);
|
|
1216
1482
|
}
|
|
1217
|
-
};
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
return `${duration}ms`;
|
|
1222
|
-
};
|
|
1223
|
-
switch (event.type) {
|
|
1224
|
-
case observability.AITracingEventType.SPAN_STARTED:
|
|
1225
|
-
this.logger.info(`\u{1F680} SPAN_STARTED`);
|
|
1226
|
-
this.logger.info(` Type: ${span.type}`);
|
|
1227
|
-
this.logger.info(` Name: ${span.name}`);
|
|
1228
|
-
this.logger.info(` ID: ${span.id}`);
|
|
1229
|
-
this.logger.info(` Trace ID: ${span.traceId}`);
|
|
1230
|
-
if (span.input !== void 0) {
|
|
1231
|
-
this.logger.info(` Input: ${formatAttributes(span.input)}`);
|
|
1232
|
-
}
|
|
1233
|
-
this.logger.info(` Attributes: ${formatAttributes(span.attributes)}`);
|
|
1234
|
-
this.logger.info("\u2500".repeat(80));
|
|
1235
|
-
break;
|
|
1236
|
-
case observability.AITracingEventType.SPAN_ENDED:
|
|
1237
|
-
const duration = formatDuration(span.startTime, span.endTime);
|
|
1238
|
-
this.logger.info(`\u2705 SPAN_ENDED`);
|
|
1239
|
-
this.logger.info(` Type: ${span.type}`);
|
|
1240
|
-
this.logger.info(` Name: ${span.name}`);
|
|
1241
|
-
this.logger.info(` ID: ${span.id}`);
|
|
1242
|
-
this.logger.info(` Duration: ${duration}`);
|
|
1243
|
-
this.logger.info(` Trace ID: ${span.traceId}`);
|
|
1244
|
-
if (span.input !== void 0) {
|
|
1245
|
-
this.logger.info(` Input: ${formatAttributes(span.input)}`);
|
|
1246
|
-
}
|
|
1247
|
-
if (span.output !== void 0) {
|
|
1248
|
-
this.logger.info(` Output: ${formatAttributes(span.output)}`);
|
|
1249
|
-
}
|
|
1250
|
-
if (span.errorInfo) {
|
|
1251
|
-
this.logger.info(` Error: ${formatAttributes(span.errorInfo)}`);
|
|
1252
|
-
}
|
|
1253
|
-
this.logger.info(` Attributes: ${formatAttributes(span.attributes)}`);
|
|
1254
|
-
this.logger.info("\u2500".repeat(80));
|
|
1255
|
-
break;
|
|
1256
|
-
case observability.AITracingEventType.SPAN_UPDATED:
|
|
1257
|
-
this.logger.info(`\u{1F4DD} SPAN_UPDATED`);
|
|
1258
|
-
this.logger.info(` Type: ${span.type}`);
|
|
1259
|
-
this.logger.info(` Name: ${span.name}`);
|
|
1260
|
-
this.logger.info(` ID: ${span.id}`);
|
|
1261
|
-
this.logger.info(` Trace ID: ${span.traceId}`);
|
|
1262
|
-
if (span.input !== void 0) {
|
|
1263
|
-
this.logger.info(` Input: ${formatAttributes(span.input)}`);
|
|
1264
|
-
}
|
|
1265
|
-
if (span.output !== void 0) {
|
|
1266
|
-
this.logger.info(` Output: ${formatAttributes(span.output)}`);
|
|
1267
|
-
}
|
|
1268
|
-
if (span.errorInfo) {
|
|
1269
|
-
this.logger.info(` Error: ${formatAttributes(span.errorInfo)}`);
|
|
1270
|
-
}
|
|
1271
|
-
this.logger.info(` Updated Attributes: ${formatAttributes(span.attributes)}`);
|
|
1272
|
-
this.logger.info("\u2500".repeat(80));
|
|
1273
|
-
break;
|
|
1274
|
-
default:
|
|
1275
|
-
this.logger.warn(`Tracing event type not implemented: ${event.type}`);
|
|
1276
|
-
}
|
|
1483
|
+
});
|
|
1484
|
+
this.logger.debug(
|
|
1485
|
+
`[Observability] Initialized [service=${this.config.serviceName}] [instance=${this.config.name}] [sampling=${this.config.sampling.type}]`
|
|
1486
|
+
);
|
|
1277
1487
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1488
|
+
// ============================================================================
|
|
1489
|
+
// Protected getters for clean config access
|
|
1490
|
+
// ============================================================================
|
|
1491
|
+
get exporters() {
|
|
1492
|
+
return this.config.exporters || [];
|
|
1280
1493
|
}
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
if (userConfig.strategy && userConfig.strategy !== "auto") {
|
|
1284
|
-
const hints = storage.aiTracingStrategy;
|
|
1285
|
-
if (hints.supported.includes(userConfig.strategy)) {
|
|
1286
|
-
return userConfig.strategy;
|
|
1287
|
-
}
|
|
1288
|
-
logger.warn("User-specified AI tracing strategy not supported by storage adapter, falling back to auto-selection", {
|
|
1289
|
-
userStrategy: userConfig.strategy,
|
|
1290
|
-
storageAdapter: storage.constructor.name,
|
|
1291
|
-
supportedStrategies: hints.supported,
|
|
1292
|
-
fallbackStrategy: hints.preferred
|
|
1293
|
-
});
|
|
1494
|
+
get spanOutputProcessors() {
|
|
1495
|
+
return this.config.spanOutputProcessors || [];
|
|
1294
1496
|
}
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
this.logger = logger$1;
|
|
1497
|
+
// ============================================================================
|
|
1498
|
+
// Public API - Single type-safe span creation method
|
|
1499
|
+
// ============================================================================
|
|
1500
|
+
/**
|
|
1501
|
+
* Start a new span of a specific SpanType
|
|
1502
|
+
*/
|
|
1503
|
+
startSpan(options) {
|
|
1504
|
+
const { customSamplerOptions, requestContext, metadata, tracingOptions, ...rest } = options;
|
|
1505
|
+
if (!this.shouldSample(customSamplerOptions)) {
|
|
1506
|
+
return new NoOpSpan({ ...rest, metadata }, this);
|
|
1507
|
+
}
|
|
1508
|
+
let traceState;
|
|
1509
|
+
if (options.parent) {
|
|
1510
|
+
traceState = options.parent.traceState;
|
|
1310
1511
|
} else {
|
|
1311
|
-
|
|
1512
|
+
traceState = this.computeTraceState(tracingOptions);
|
|
1312
1513
|
}
|
|
1313
|
-
this.
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
spanSequences: /* @__PURE__ */ new Map(),
|
|
1327
|
-
completedSpans: /* @__PURE__ */ new Set(),
|
|
1328
|
-
outOfOrderCount: 0,
|
|
1329
|
-
totalSize: 0
|
|
1330
|
-
};
|
|
1331
|
-
this.resolvedStrategy = "batch-with-updates";
|
|
1514
|
+
const enrichedMetadata = this.extractMetadataFromRequestContext(requestContext, metadata, traceState);
|
|
1515
|
+
const span = this.createSpan({
|
|
1516
|
+
...rest,
|
|
1517
|
+
metadata: enrichedMetadata,
|
|
1518
|
+
traceState
|
|
1519
|
+
});
|
|
1520
|
+
if (span.isEvent) {
|
|
1521
|
+
this.emitSpanEnded(span);
|
|
1522
|
+
} else {
|
|
1523
|
+
this.wireSpanLifecycle(span);
|
|
1524
|
+
this.emitSpanStarted(span);
|
|
1525
|
+
}
|
|
1526
|
+
return span;
|
|
1332
1527
|
}
|
|
1333
|
-
|
|
1528
|
+
// ============================================================================
|
|
1529
|
+
// Configuration Management
|
|
1530
|
+
// ============================================================================
|
|
1334
1531
|
/**
|
|
1335
|
-
*
|
|
1532
|
+
* Get current configuration
|
|
1336
1533
|
*/
|
|
1337
|
-
|
|
1338
|
-
this.
|
|
1339
|
-
if (!this.storage) {
|
|
1340
|
-
this.logger.warn("DefaultExporter disabled: Storage not available. Traces will not be persisted.");
|
|
1341
|
-
return;
|
|
1342
|
-
}
|
|
1343
|
-
this.initializeStrategy(this.storage);
|
|
1534
|
+
getConfig() {
|
|
1535
|
+
return { ...this.config };
|
|
1344
1536
|
}
|
|
1537
|
+
// ============================================================================
|
|
1538
|
+
// Plugin Access
|
|
1539
|
+
// ============================================================================
|
|
1345
1540
|
/**
|
|
1346
|
-
*
|
|
1541
|
+
* Get all exporters
|
|
1347
1542
|
*/
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
this.resolvedStrategy = resolveStrategy(this.config, storage, this.logger);
|
|
1351
|
-
this.strategyInitialized = true;
|
|
1352
|
-
this.logger.debug("AI tracing exporter initialized", {
|
|
1353
|
-
strategy: this.resolvedStrategy,
|
|
1354
|
-
source: this.config.strategy !== "auto" ? "user" : "auto",
|
|
1355
|
-
storageAdapter: storage.constructor.name,
|
|
1356
|
-
maxBatchSize: this.config.maxBatchSize,
|
|
1357
|
-
maxBatchWaitMs: this.config.maxBatchWaitMs
|
|
1358
|
-
});
|
|
1543
|
+
getExporters() {
|
|
1544
|
+
return [...this.exporters];
|
|
1359
1545
|
}
|
|
1360
1546
|
/**
|
|
1361
|
-
*
|
|
1547
|
+
* Get all span output processors
|
|
1362
1548
|
*/
|
|
1363
|
-
|
|
1364
|
-
return
|
|
1549
|
+
getSpanOutputProcessors() {
|
|
1550
|
+
return [...this.spanOutputProcessors];
|
|
1365
1551
|
}
|
|
1366
1552
|
/**
|
|
1367
|
-
*
|
|
1553
|
+
* Get the logger instance (for exporters and other components)
|
|
1368
1554
|
*/
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
const next = current + 1;
|
|
1372
|
-
this.buffer.spanSequences.set(spanKey, next);
|
|
1373
|
-
return next;
|
|
1555
|
+
getLogger() {
|
|
1556
|
+
return this.logger;
|
|
1374
1557
|
}
|
|
1558
|
+
// ============================================================================
|
|
1559
|
+
// Span Lifecycle Management
|
|
1560
|
+
// ============================================================================
|
|
1375
1561
|
/**
|
|
1376
|
-
*
|
|
1562
|
+
* Automatically wires up Observability lifecycle events for any span
|
|
1563
|
+
* This ensures all spans emit events regardless of implementation
|
|
1377
1564
|
*/
|
|
1378
|
-
|
|
1379
|
-
this.
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1565
|
+
wireSpanLifecycle(span) {
|
|
1566
|
+
if (!this.config.includeInternalSpans && span.isInternal) {
|
|
1567
|
+
return;
|
|
1568
|
+
}
|
|
1569
|
+
const originalEnd = span.end.bind(span);
|
|
1570
|
+
const originalUpdate = span.update.bind(span);
|
|
1571
|
+
span.end = (options) => {
|
|
1572
|
+
if (span.isEvent) {
|
|
1573
|
+
this.logger.warn(`End event is not available on event spans`);
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
originalEnd(options);
|
|
1577
|
+
this.emitSpanEnded(span);
|
|
1578
|
+
};
|
|
1579
|
+
span.update = (options) => {
|
|
1580
|
+
if (span.isEvent) {
|
|
1581
|
+
this.logger.warn(`Update() is not available on event spans`);
|
|
1582
|
+
return;
|
|
1583
|
+
}
|
|
1584
|
+
originalUpdate(options);
|
|
1585
|
+
this.emitSpanUpdated(span);
|
|
1586
|
+
};
|
|
1385
1587
|
}
|
|
1588
|
+
// ============================================================================
|
|
1589
|
+
// Utility Methods
|
|
1590
|
+
// ============================================================================
|
|
1386
1591
|
/**
|
|
1387
|
-
*
|
|
1592
|
+
* Check if an AI trace should be sampled
|
|
1388
1593
|
*/
|
|
1389
|
-
|
|
1390
|
-
const
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
this.
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
break;
|
|
1403
|
-
case observability.AITracingEventType.SPAN_UPDATED:
|
|
1404
|
-
if (this.resolvedStrategy === "batch-with-updates") {
|
|
1405
|
-
if (this.allCreatedSpans.has(spanKey)) {
|
|
1406
|
-
this.buffer.updates.push({
|
|
1407
|
-
traceId: event.exportedSpan.traceId,
|
|
1408
|
-
spanId: event.exportedSpan.id,
|
|
1409
|
-
updates: this.buildUpdateRecord(event.exportedSpan),
|
|
1410
|
-
sequenceNumber: this.getNextSequence(spanKey)
|
|
1411
|
-
});
|
|
1412
|
-
} else {
|
|
1413
|
-
this.handleOutOfOrderUpdate(event);
|
|
1414
|
-
this.buffer.outOfOrderCount++;
|
|
1415
|
-
}
|
|
1416
|
-
}
|
|
1417
|
-
break;
|
|
1418
|
-
case observability.AITracingEventType.SPAN_ENDED:
|
|
1419
|
-
if (this.resolvedStrategy === "batch-with-updates") {
|
|
1420
|
-
if (this.allCreatedSpans.has(spanKey)) {
|
|
1421
|
-
this.buffer.updates.push({
|
|
1422
|
-
traceId: event.exportedSpan.traceId,
|
|
1423
|
-
spanId: event.exportedSpan.id,
|
|
1424
|
-
updates: this.buildUpdateRecord(event.exportedSpan),
|
|
1425
|
-
sequenceNumber: this.getNextSequence(spanKey)
|
|
1426
|
-
});
|
|
1427
|
-
this.buffer.completedSpans.add(spanKey);
|
|
1428
|
-
} else if (event.exportedSpan.isEvent) {
|
|
1429
|
-
const createRecord = this.buildCreateRecord(event.exportedSpan);
|
|
1430
|
-
this.buffer.creates.push(createRecord);
|
|
1431
|
-
this.buffer.seenSpans.add(spanKey);
|
|
1432
|
-
this.allCreatedSpans.add(spanKey);
|
|
1433
|
-
this.buffer.completedSpans.add(spanKey);
|
|
1434
|
-
} else {
|
|
1435
|
-
this.handleOutOfOrderUpdate(event);
|
|
1436
|
-
this.buffer.outOfOrderCount++;
|
|
1437
|
-
}
|
|
1438
|
-
} else if (this.resolvedStrategy === "insert-only") {
|
|
1439
|
-
const createRecord = this.buildCreateRecord(event.exportedSpan);
|
|
1440
|
-
this.buffer.insertOnly.push(createRecord);
|
|
1441
|
-
this.buffer.completedSpans.add(spanKey);
|
|
1442
|
-
this.allCreatedSpans.add(spanKey);
|
|
1594
|
+
shouldSample(options) {
|
|
1595
|
+
const { sampling } = this.config;
|
|
1596
|
+
switch (sampling.type) {
|
|
1597
|
+
case "always" /* ALWAYS */:
|
|
1598
|
+
return true;
|
|
1599
|
+
case "never" /* NEVER */:
|
|
1600
|
+
return false;
|
|
1601
|
+
case "ratio" /* RATIO */:
|
|
1602
|
+
if (sampling.probability === void 0 || sampling.probability < 0 || sampling.probability > 1) {
|
|
1603
|
+
this.logger.warn(
|
|
1604
|
+
`Invalid sampling probability: ${sampling.probability}. Expected value between 0 and 1. Defaulting to no sampling.`
|
|
1605
|
+
);
|
|
1606
|
+
return false;
|
|
1443
1607
|
}
|
|
1444
|
-
|
|
1608
|
+
return Math.random() < sampling.probability;
|
|
1609
|
+
case "custom" /* CUSTOM */:
|
|
1610
|
+
return sampling.sampler(options);
|
|
1611
|
+
default:
|
|
1612
|
+
throw new Error(`Sampling strategy type not implemented: ${sampling.type}`);
|
|
1445
1613
|
}
|
|
1446
|
-
this.buffer.totalSize = this.buffer.creates.length + this.buffer.updates.length + this.buffer.insertOnly.length;
|
|
1447
1614
|
}
|
|
1448
1615
|
/**
|
|
1449
|
-
*
|
|
1616
|
+
* Compute TraceState for a new trace based on configured and per-request keys
|
|
1450
1617
|
*/
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1618
|
+
computeTraceState(tracingOptions) {
|
|
1619
|
+
const configuredKeys = this.config.requestContextKeys ?? [];
|
|
1620
|
+
const additionalKeys = tracingOptions?.requestContextKeys ?? [];
|
|
1621
|
+
const allKeys = [...configuredKeys, ...additionalKeys];
|
|
1622
|
+
if (allKeys.length === 0) {
|
|
1623
|
+
return void 0;
|
|
1454
1624
|
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1625
|
+
return {
|
|
1626
|
+
requestContextKeys: allKeys
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
/**
|
|
1630
|
+
* Extract metadata from RequestContext using TraceState
|
|
1631
|
+
*/
|
|
1632
|
+
extractMetadataFromRequestContext(requestContext, explicitMetadata, traceState) {
|
|
1633
|
+
if (!requestContext || !traceState || traceState.requestContextKeys.length === 0) {
|
|
1634
|
+
return explicitMetadata;
|
|
1457
1635
|
}
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
return true;
|
|
1462
|
-
}
|
|
1636
|
+
const extracted = this.extractKeys(requestContext, traceState.requestContextKeys);
|
|
1637
|
+
if (Object.keys(extracted).length === 0 && !explicitMetadata) {
|
|
1638
|
+
return void 0;
|
|
1463
1639
|
}
|
|
1464
|
-
return
|
|
1640
|
+
return {
|
|
1641
|
+
...extracted,
|
|
1642
|
+
...explicitMetadata
|
|
1643
|
+
// Explicit metadata always wins
|
|
1644
|
+
};
|
|
1465
1645
|
}
|
|
1466
1646
|
/**
|
|
1467
|
-
*
|
|
1647
|
+
* Extract specific keys from RequestContext
|
|
1468
1648
|
*/
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1649
|
+
extractKeys(requestContext, keys) {
|
|
1650
|
+
const result = {};
|
|
1651
|
+
for (const key of keys) {
|
|
1652
|
+
const parts = key.split(".");
|
|
1653
|
+
const rootKey = parts[0];
|
|
1654
|
+
const value = requestContext.get(rootKey);
|
|
1655
|
+
if (value !== void 0) {
|
|
1656
|
+
if (parts.length > 1) {
|
|
1657
|
+
const nestedPath = parts.slice(1).join(".");
|
|
1658
|
+
const nestedValue = utils.getNestedValue(value, nestedPath);
|
|
1659
|
+
if (nestedValue !== void 0) {
|
|
1660
|
+
utils.setNestedValue(result, key, nestedValue);
|
|
1661
|
+
}
|
|
1662
|
+
} else {
|
|
1663
|
+
utils.setNestedValue(result, key, value);
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1481
1666
|
}
|
|
1667
|
+
return result;
|
|
1482
1668
|
}
|
|
1483
1669
|
/**
|
|
1484
|
-
*
|
|
1670
|
+
* Process a span through all output processors
|
|
1485
1671
|
*/
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1672
|
+
processSpan(span) {
|
|
1673
|
+
for (const processor of this.spanOutputProcessors) {
|
|
1674
|
+
if (!span) {
|
|
1675
|
+
break;
|
|
1676
|
+
}
|
|
1677
|
+
try {
|
|
1678
|
+
span = processor.process(span);
|
|
1679
|
+
} catch (error) {
|
|
1680
|
+
this.logger.error(`[Observability] Processor error [name=${processor.name}]`, error);
|
|
1681
|
+
}
|
|
1489
1682
|
}
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1683
|
+
return span;
|
|
1684
|
+
}
|
|
1685
|
+
// ============================================================================
|
|
1686
|
+
// Event-driven Export Methods
|
|
1687
|
+
// ============================================================================
|
|
1688
|
+
getSpanForExport(span) {
|
|
1689
|
+
if (!span.isValid) return void 0;
|
|
1690
|
+
if (span.isInternal && !this.config.includeInternalSpans) return void 0;
|
|
1691
|
+
const processedSpan = this.processSpan(span);
|
|
1692
|
+
return processedSpan?.exportSpan(this.config.includeInternalSpans);
|
|
1497
1693
|
}
|
|
1498
1694
|
/**
|
|
1499
|
-
*
|
|
1500
|
-
* Handles all AI span types and their specific attributes
|
|
1695
|
+
* Emit a span started event
|
|
1501
1696
|
*/
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
return JSON.parse(
|
|
1508
|
-
JSON.stringify(span.attributes, (_key, value) => {
|
|
1509
|
-
if (value instanceof Date) {
|
|
1510
|
-
return value.toISOString();
|
|
1511
|
-
}
|
|
1512
|
-
if (typeof value === "object" && value !== null) {
|
|
1513
|
-
return value;
|
|
1514
|
-
}
|
|
1515
|
-
return value;
|
|
1516
|
-
})
|
|
1517
|
-
);
|
|
1518
|
-
} catch (error) {
|
|
1519
|
-
this.logger.warn("Failed to serialize span attributes, storing as null", {
|
|
1520
|
-
spanId: span.id,
|
|
1521
|
-
spanType: span.type,
|
|
1522
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1697
|
+
emitSpanStarted(span) {
|
|
1698
|
+
const exportedSpan = this.getSpanForExport(span);
|
|
1699
|
+
if (exportedSpan) {
|
|
1700
|
+
this.exportTracingEvent({ type: observability.TracingEventType.SPAN_STARTED, exportedSpan }).catch((error) => {
|
|
1701
|
+
this.logger.error("[Observability] Failed to export span_started event", error);
|
|
1523
1702
|
});
|
|
1524
|
-
return null;
|
|
1525
1703
|
}
|
|
1526
1704
|
}
|
|
1527
|
-
buildCreateRecord(span) {
|
|
1528
|
-
return {
|
|
1529
|
-
traceId: span.traceId,
|
|
1530
|
-
spanId: span.id,
|
|
1531
|
-
parentSpanId: span.parentSpanId ?? null,
|
|
1532
|
-
name: span.name,
|
|
1533
|
-
scope: null,
|
|
1534
|
-
spanType: span.type,
|
|
1535
|
-
attributes: this.serializeAttributes(span),
|
|
1536
|
-
metadata: span.metadata ?? null,
|
|
1537
|
-
links: null,
|
|
1538
|
-
startedAt: span.startTime,
|
|
1539
|
-
endedAt: span.endTime ?? null,
|
|
1540
|
-
input: span.input,
|
|
1541
|
-
output: span.output,
|
|
1542
|
-
error: span.errorInfo,
|
|
1543
|
-
isEvent: span.isEvent
|
|
1544
|
-
};
|
|
1545
|
-
}
|
|
1546
|
-
buildUpdateRecord(span) {
|
|
1547
|
-
return {
|
|
1548
|
-
name: span.name,
|
|
1549
|
-
scope: null,
|
|
1550
|
-
attributes: this.serializeAttributes(span),
|
|
1551
|
-
metadata: span.metadata ?? null,
|
|
1552
|
-
links: null,
|
|
1553
|
-
endedAt: span.endTime ?? null,
|
|
1554
|
-
input: span.input,
|
|
1555
|
-
output: span.output,
|
|
1556
|
-
error: span.errorInfo
|
|
1557
|
-
};
|
|
1558
|
-
}
|
|
1559
1705
|
/**
|
|
1560
|
-
*
|
|
1706
|
+
* Emit a span ended event (called automatically when spans end)
|
|
1561
1707
|
*/
|
|
1562
|
-
|
|
1563
|
-
const
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
} else {
|
|
1569
|
-
this.logger.warn(`Tracing event type not implemented for event spans: ${event.type}`);
|
|
1570
|
-
}
|
|
1571
|
-
} else {
|
|
1572
|
-
switch (event.type) {
|
|
1573
|
-
case observability.AITracingEventType.SPAN_STARTED:
|
|
1574
|
-
await storage.createAISpan(this.buildCreateRecord(event.exportedSpan));
|
|
1575
|
-
this.allCreatedSpans.add(spanKey);
|
|
1576
|
-
break;
|
|
1577
|
-
case observability.AITracingEventType.SPAN_UPDATED:
|
|
1578
|
-
await storage.updateAISpan({
|
|
1579
|
-
traceId: span.traceId,
|
|
1580
|
-
spanId: span.id,
|
|
1581
|
-
updates: this.buildUpdateRecord(span)
|
|
1582
|
-
});
|
|
1583
|
-
break;
|
|
1584
|
-
case observability.AITracingEventType.SPAN_ENDED:
|
|
1585
|
-
await storage.updateAISpan({
|
|
1586
|
-
traceId: span.traceId,
|
|
1587
|
-
spanId: span.id,
|
|
1588
|
-
updates: this.buildUpdateRecord(span)
|
|
1589
|
-
});
|
|
1590
|
-
this.allCreatedSpans.delete(spanKey);
|
|
1591
|
-
break;
|
|
1592
|
-
default:
|
|
1593
|
-
this.logger.warn(`Tracing event type not implemented for span spans: ${event.type}`);
|
|
1594
|
-
}
|
|
1708
|
+
emitSpanEnded(span) {
|
|
1709
|
+
const exportedSpan = this.getSpanForExport(span);
|
|
1710
|
+
if (exportedSpan) {
|
|
1711
|
+
this.exportTracingEvent({ type: observability.TracingEventType.SPAN_ENDED, exportedSpan }).catch((error) => {
|
|
1712
|
+
this.logger.error("[Observability] Failed to export span_ended event", error);
|
|
1713
|
+
});
|
|
1595
1714
|
}
|
|
1596
1715
|
}
|
|
1597
1716
|
/**
|
|
1598
|
-
*
|
|
1717
|
+
* Emit a span updated event
|
|
1599
1718
|
*/
|
|
1600
|
-
|
|
1601
|
-
this.
|
|
1602
|
-
if (
|
|
1603
|
-
this.
|
|
1604
|
-
this.logger.error("
|
|
1605
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1606
|
-
});
|
|
1719
|
+
emitSpanUpdated(span) {
|
|
1720
|
+
const exportedSpan = this.getSpanForExport(span);
|
|
1721
|
+
if (exportedSpan) {
|
|
1722
|
+
this.exportTracingEvent({ type: observability.TracingEventType.SPAN_UPDATED, exportedSpan }).catch((error) => {
|
|
1723
|
+
this.logger.error("[Observability] Failed to export span_updated event", error);
|
|
1607
1724
|
});
|
|
1608
|
-
} else if (this.buffer.totalSize === 1) {
|
|
1609
|
-
this.scheduleFlush();
|
|
1610
1725
|
}
|
|
1611
1726
|
}
|
|
1612
1727
|
/**
|
|
1613
|
-
*
|
|
1728
|
+
* Export tracing event through all exporters (realtime mode)
|
|
1614
1729
|
*/
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
this.logger.
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
});
|
|
1624
|
-
} else if (this.buffer.totalSize === 1) {
|
|
1625
|
-
this.scheduleFlush();
|
|
1730
|
+
async exportTracingEvent(event) {
|
|
1731
|
+
const exportPromises = this.exporters.map(async (exporter) => {
|
|
1732
|
+
try {
|
|
1733
|
+
if (exporter.exportTracingEvent) {
|
|
1734
|
+
await exporter.exportTracingEvent(event);
|
|
1735
|
+
this.logger.debug(`[Observability] Event exported [exporter=${exporter.name}] [type=${event.type}]`);
|
|
1736
|
+
}
|
|
1737
|
+
} catch (error) {
|
|
1738
|
+
this.logger.error(`[Observability] Export error [exporter=${exporter.name}]`, error);
|
|
1626
1739
|
}
|
|
1627
|
-
}
|
|
1740
|
+
});
|
|
1741
|
+
await Promise.allSettled(exportPromises);
|
|
1628
1742
|
}
|
|
1743
|
+
// ============================================================================
|
|
1744
|
+
// Lifecycle Management
|
|
1745
|
+
// ============================================================================
|
|
1629
1746
|
/**
|
|
1630
|
-
*
|
|
1747
|
+
* Initialize Observability (called by Mastra during component registration)
|
|
1631
1748
|
*/
|
|
1632
|
-
|
|
1633
|
-
|
|
1749
|
+
init() {
|
|
1750
|
+
this.logger.debug(`[Observability] Initialization started [name=${this.name}]`);
|
|
1751
|
+
this.logger.info(`[Observability] Initialized successfully [name=${this.name}]`);
|
|
1634
1752
|
}
|
|
1635
1753
|
/**
|
|
1636
|
-
*
|
|
1754
|
+
* Shutdown Observability and clean up resources
|
|
1637
1755
|
*/
|
|
1638
|
-
async
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1756
|
+
async shutdown() {
|
|
1757
|
+
this.logger.debug(`[Observability] Shutdown started [name=${this.name}]`);
|
|
1758
|
+
const shutdownPromises = [
|
|
1759
|
+
...this.exporters.map((e) => e.shutdown()),
|
|
1760
|
+
...this.spanOutputProcessors.map((p) => p.shutdown())
|
|
1761
|
+
];
|
|
1762
|
+
await Promise.allSettled(shutdownPromises);
|
|
1763
|
+
this.logger.info(`[Observability] Shutdown completed [name=${this.name}]`);
|
|
1764
|
+
}
|
|
1765
|
+
};
|
|
1766
|
+
|
|
1767
|
+
// src/instances/default.ts
|
|
1768
|
+
var DefaultObservabilityInstance = class extends BaseObservabilityInstance {
|
|
1769
|
+
constructor(config) {
|
|
1770
|
+
super(config);
|
|
1771
|
+
}
|
|
1772
|
+
createSpan(options) {
|
|
1773
|
+
return new DefaultSpan(options, this);
|
|
1774
|
+
}
|
|
1775
|
+
};
|
|
1776
|
+
|
|
1777
|
+
// src/registry.ts
|
|
1778
|
+
var ObservabilityRegistry = class {
|
|
1779
|
+
#instances = /* @__PURE__ */ new Map();
|
|
1780
|
+
#defaultInstance;
|
|
1781
|
+
#configSelector;
|
|
1782
|
+
/**
|
|
1783
|
+
* Register a tracing instance
|
|
1784
|
+
*/
|
|
1785
|
+
register(name, instance, isDefault = false) {
|
|
1786
|
+
if (this.#instances.has(name)) {
|
|
1787
|
+
throw new Error(`Tracing instance '${name}' already registered`);
|
|
1646
1788
|
}
|
|
1647
|
-
|
|
1648
|
-
|
|
1789
|
+
this.#instances.set(name, instance);
|
|
1790
|
+
if (isDefault || !this.#defaultInstance) {
|
|
1791
|
+
this.#defaultInstance = instance;
|
|
1649
1792
|
}
|
|
1650
|
-
const startTime = Date.now();
|
|
1651
|
-
const flushReason = this.buffer.totalSize >= this.config.maxBufferSize ? "overflow" : this.buffer.totalSize >= this.config.maxBatchSize ? "size" : "time";
|
|
1652
|
-
const bufferCopy = {
|
|
1653
|
-
creates: [...this.buffer.creates],
|
|
1654
|
-
updates: [...this.buffer.updates],
|
|
1655
|
-
insertOnly: [...this.buffer.insertOnly],
|
|
1656
|
-
seenSpans: new Set(this.buffer.seenSpans),
|
|
1657
|
-
spanSequences: new Map(this.buffer.spanSequences),
|
|
1658
|
-
completedSpans: new Set(this.buffer.completedSpans),
|
|
1659
|
-
outOfOrderCount: this.buffer.outOfOrderCount,
|
|
1660
|
-
firstEventTime: this.buffer.firstEventTime,
|
|
1661
|
-
totalSize: this.buffer.totalSize
|
|
1662
|
-
};
|
|
1663
|
-
this.resetBuffer();
|
|
1664
|
-
await this.flushWithRetries(this.storage, bufferCopy, 0);
|
|
1665
|
-
const elapsed = Date.now() - startTime;
|
|
1666
|
-
this.logger.debug("Batch flushed", {
|
|
1667
|
-
strategy: this.resolvedStrategy,
|
|
1668
|
-
batchSize: bufferCopy.totalSize,
|
|
1669
|
-
flushReason,
|
|
1670
|
-
durationMs: elapsed,
|
|
1671
|
-
outOfOrderCount: bufferCopy.outOfOrderCount > 0 ? bufferCopy.outOfOrderCount : void 0
|
|
1672
|
-
});
|
|
1673
1793
|
}
|
|
1674
1794
|
/**
|
|
1675
|
-
*
|
|
1795
|
+
* Get a tracing instance by name
|
|
1676
1796
|
*/
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
this.
|
|
1700
|
-
}
|
|
1701
|
-
} catch (error) {
|
|
1702
|
-
if (attempt < this.config.maxRetries) {
|
|
1703
|
-
const retryDelay = this.calculateRetryDelay(attempt);
|
|
1704
|
-
this.logger.warn("Batch flush failed, retrying", {
|
|
1705
|
-
attempt: attempt + 1,
|
|
1706
|
-
maxRetries: this.config.maxRetries,
|
|
1707
|
-
nextRetryInMs: retryDelay,
|
|
1708
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1709
|
-
});
|
|
1710
|
-
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
|
1711
|
-
return this.flushWithRetries(storage, buffer, attempt + 1);
|
|
1712
|
-
} else {
|
|
1713
|
-
this.logger.error("Batch flush failed after all retries, dropping batch", {
|
|
1714
|
-
finalAttempt: attempt + 1,
|
|
1715
|
-
maxRetries: this.config.maxRetries,
|
|
1716
|
-
droppedBatchSize: buffer.totalSize,
|
|
1717
|
-
error: error instanceof Error ? error.message : String(error)
|
|
1718
|
-
});
|
|
1719
|
-
for (const spanKey of buffer.completedSpans) {
|
|
1720
|
-
this.allCreatedSpans.delete(spanKey);
|
|
1721
|
-
}
|
|
1797
|
+
get(name) {
|
|
1798
|
+
return this.#instances.get(name);
|
|
1799
|
+
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Get the default tracing instance
|
|
1802
|
+
*/
|
|
1803
|
+
getDefault() {
|
|
1804
|
+
return this.#defaultInstance;
|
|
1805
|
+
}
|
|
1806
|
+
/**
|
|
1807
|
+
* Set the tracing selector function
|
|
1808
|
+
*/
|
|
1809
|
+
setSelector(selector) {
|
|
1810
|
+
this.#configSelector = selector;
|
|
1811
|
+
}
|
|
1812
|
+
/**
|
|
1813
|
+
* Get the selected tracing instance based on context
|
|
1814
|
+
*/
|
|
1815
|
+
getSelected(options) {
|
|
1816
|
+
if (this.#configSelector) {
|
|
1817
|
+
const selected = this.#configSelector(options, this.#instances);
|
|
1818
|
+
if (selected && this.#instances.has(selected)) {
|
|
1819
|
+
return this.#instances.get(selected);
|
|
1722
1820
|
}
|
|
1723
1821
|
}
|
|
1822
|
+
return this.#defaultInstance;
|
|
1724
1823
|
}
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
case "realtime":
|
|
1735
|
-
await this.handleRealtimeEvent(event, this.storage);
|
|
1736
|
-
break;
|
|
1737
|
-
case "batch-with-updates":
|
|
1738
|
-
this.handleBatchWithUpdatesEvent(event);
|
|
1739
|
-
break;
|
|
1740
|
-
case "insert-only":
|
|
1741
|
-
this.handleInsertOnlyEvent(event);
|
|
1742
|
-
break;
|
|
1824
|
+
/**
|
|
1825
|
+
* Unregister a tracing instance
|
|
1826
|
+
*/
|
|
1827
|
+
unregister(name) {
|
|
1828
|
+
const instance = this.#instances.get(name);
|
|
1829
|
+
const deleted = this.#instances.delete(name);
|
|
1830
|
+
if (deleted && instance === this.#defaultInstance) {
|
|
1831
|
+
const next = this.#instances.values().next();
|
|
1832
|
+
this.#defaultInstance = next.done ? void 0 : next.value;
|
|
1743
1833
|
}
|
|
1834
|
+
return deleted;
|
|
1744
1835
|
}
|
|
1836
|
+
/**
|
|
1837
|
+
* Shutdown all instances and clear the registry
|
|
1838
|
+
*/
|
|
1745
1839
|
async shutdown() {
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1840
|
+
const shutdownPromises = Array.from(this.#instances.values()).map((instance) => instance.shutdown());
|
|
1841
|
+
await Promise.allSettled(shutdownPromises);
|
|
1842
|
+
this.#instances.clear();
|
|
1843
|
+
this.#instances.clear();
|
|
1844
|
+
this.#defaultInstance = void 0;
|
|
1845
|
+
this.#configSelector = void 0;
|
|
1846
|
+
}
|
|
1847
|
+
/**
|
|
1848
|
+
* Clear all instances without shutdown
|
|
1849
|
+
*/
|
|
1850
|
+
clear() {
|
|
1851
|
+
this.#instances.clear();
|
|
1852
|
+
this.#defaultInstance = void 0;
|
|
1853
|
+
this.#configSelector = void 0;
|
|
1854
|
+
}
|
|
1855
|
+
/**
|
|
1856
|
+
* list all registered instances
|
|
1857
|
+
*/
|
|
1858
|
+
list() {
|
|
1859
|
+
return new Map(this.#instances);
|
|
1763
1860
|
}
|
|
1764
1861
|
};
|
|
1765
1862
|
|
|
@@ -1884,173 +1981,122 @@ var SensitiveDataFilter = class {
|
|
|
1884
1981
|
async shutdown() {
|
|
1885
1982
|
}
|
|
1886
1983
|
};
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1984
|
+
|
|
1985
|
+
// src/default.ts
|
|
1986
|
+
function isInstance(obj) {
|
|
1987
|
+
return obj instanceof BaseObservabilityInstance;
|
|
1988
|
+
}
|
|
1989
|
+
var Observability = class extends base.MastraBase {
|
|
1990
|
+
#registry = new ObservabilityRegistry();
|
|
1991
|
+
constructor(config) {
|
|
1992
|
+
super({
|
|
1993
|
+
component: logger.RegisteredLogger.OBSERVABILITY,
|
|
1994
|
+
name: "Observability"
|
|
1995
|
+
});
|
|
1996
|
+
if (config === void 0) {
|
|
1997
|
+
config = {};
|
|
1897
1998
|
}
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1999
|
+
if (config.default?.enabled && config.configs?.["default"]) {
|
|
2000
|
+
throw new Error(
|
|
2001
|
+
"Cannot use 'default' as a custom config name when default tracing is enabled. Please rename your custom config to avoid conflicts."
|
|
2002
|
+
);
|
|
1901
2003
|
}
|
|
2004
|
+
if (config.default?.enabled) {
|
|
2005
|
+
const defaultInstance = new DefaultObservabilityInstance({
|
|
2006
|
+
serviceName: "mastra",
|
|
2007
|
+
name: "default",
|
|
2008
|
+
sampling: { type: "always" /* ALWAYS */ },
|
|
2009
|
+
exporters: [new DefaultExporter(), new CloudExporter()],
|
|
2010
|
+
spanOutputProcessors: [new SensitiveDataFilter()]
|
|
2011
|
+
});
|
|
2012
|
+
this.#registry.register("default", defaultInstance, true);
|
|
2013
|
+
}
|
|
2014
|
+
if (config.configs) {
|
|
2015
|
+
const instances = Object.entries(config.configs);
|
|
2016
|
+
instances.forEach(([name, tracingDef], index) => {
|
|
2017
|
+
const instance = isInstance(tracingDef) ? tracingDef : new DefaultObservabilityInstance({ ...tracingDef, name });
|
|
2018
|
+
const isDefault = !config.default?.enabled && index === 0;
|
|
2019
|
+
this.#registry.register(name, instance, isDefault);
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
if (config.configSelector) {
|
|
2023
|
+
this.#registry.setSelector(config.configSelector);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
setMastraContext(options) {
|
|
2027
|
+
const instances = this.listInstances();
|
|
2028
|
+
const { mastra } = options;
|
|
2029
|
+
instances.forEach((instance) => {
|
|
2030
|
+
const config = instance.getConfig();
|
|
2031
|
+
const exporters = instance.getExporters();
|
|
2032
|
+
exporters.forEach((exporter) => {
|
|
2033
|
+
if ("init" in exporter && typeof exporter.init === "function") {
|
|
2034
|
+
try {
|
|
2035
|
+
exporter.init({ mastra, config });
|
|
2036
|
+
} catch (error) {
|
|
2037
|
+
this.logger?.warn("Failed to initialize observability exporter", {
|
|
2038
|
+
exporterName: exporter.name,
|
|
2039
|
+
error: error instanceof Error ? error.message : String(error)
|
|
2040
|
+
});
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
});
|
|
2044
|
+
});
|
|
1902
2045
|
}
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
}
|
|
1909
|
-
/**
|
|
1910
|
-
* Get the default tracing instance
|
|
1911
|
-
*/
|
|
1912
|
-
getDefault() {
|
|
1913
|
-
return this.defaultInstance;
|
|
2046
|
+
setLogger(options) {
|
|
2047
|
+
super.__setLogger(options.logger);
|
|
2048
|
+
this.listInstances().forEach((instance) => {
|
|
2049
|
+
instance.__setLogger(options.logger);
|
|
2050
|
+
});
|
|
1914
2051
|
}
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
*/
|
|
1918
|
-
setSelector(selector) {
|
|
1919
|
-
this.configSelector = selector;
|
|
2052
|
+
getSelectedInstance(options) {
|
|
2053
|
+
return this.#registry.getSelected(options);
|
|
1920
2054
|
}
|
|
1921
2055
|
/**
|
|
1922
|
-
*
|
|
2056
|
+
* Registry management methods
|
|
1923
2057
|
*/
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
const selected = this.configSelector(options, this.instances);
|
|
1927
|
-
if (selected && this.instances.has(selected)) {
|
|
1928
|
-
return this.instances.get(selected);
|
|
1929
|
-
}
|
|
1930
|
-
}
|
|
1931
|
-
return this.defaultInstance;
|
|
2058
|
+
registerInstance(name, instance, isDefault = false) {
|
|
2059
|
+
this.#registry.register(name, instance, isDefault);
|
|
1932
2060
|
}
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
*/
|
|
1936
|
-
unregister(name) {
|
|
1937
|
-
return this.instances.delete(name);
|
|
2061
|
+
getInstance(name) {
|
|
2062
|
+
return this.#registry.get(name);
|
|
1938
2063
|
}
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
*/
|
|
1942
|
-
async shutdown() {
|
|
1943
|
-
const shutdownPromises = Array.from(this.instances.values()).map((instance) => instance.shutdown());
|
|
1944
|
-
await Promise.allSettled(shutdownPromises);
|
|
1945
|
-
this.instances.clear();
|
|
2064
|
+
getDefaultInstance() {
|
|
2065
|
+
return this.#registry.getDefault();
|
|
1946
2066
|
}
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
*/
|
|
1950
|
-
clear() {
|
|
1951
|
-
this.instances.clear();
|
|
1952
|
-
this.defaultInstance = void 0;
|
|
1953
|
-
this.configSelector = void 0;
|
|
2067
|
+
listInstances() {
|
|
2068
|
+
return this.#registry.list();
|
|
1954
2069
|
}
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
*/
|
|
1958
|
-
getAll() {
|
|
1959
|
-
return new Map(this.instances);
|
|
2070
|
+
unregisterInstance(name) {
|
|
2071
|
+
return this.#registry.unregister(name);
|
|
1960
2072
|
}
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
function registerAITracing(name, instance, isDefault = false) {
|
|
1964
|
-
aiTracingRegistry.register(name, instance, isDefault);
|
|
1965
|
-
}
|
|
1966
|
-
function getAITracing(name) {
|
|
1967
|
-
return aiTracingRegistry.get(name);
|
|
1968
|
-
}
|
|
1969
|
-
function getDefaultAITracing() {
|
|
1970
|
-
return aiTracingRegistry.getDefault();
|
|
1971
|
-
}
|
|
1972
|
-
function setSelector(selector) {
|
|
1973
|
-
aiTracingRegistry.setSelector(selector);
|
|
1974
|
-
}
|
|
1975
|
-
function getSelectedAITracing(options) {
|
|
1976
|
-
return aiTracingRegistry.getSelected(options);
|
|
1977
|
-
}
|
|
1978
|
-
function unregisterAITracing(name) {
|
|
1979
|
-
return aiTracingRegistry.unregister(name);
|
|
1980
|
-
}
|
|
1981
|
-
async function shutdownAITracingRegistry() {
|
|
1982
|
-
await aiTracingRegistry.shutdown();
|
|
1983
|
-
}
|
|
1984
|
-
function clearAITracingRegistry() {
|
|
1985
|
-
aiTracingRegistry.clear();
|
|
1986
|
-
}
|
|
1987
|
-
function getAllAITracing() {
|
|
1988
|
-
return aiTracingRegistry.getAll();
|
|
1989
|
-
}
|
|
1990
|
-
function hasAITracing(name) {
|
|
1991
|
-
const tracing = getAITracing(name);
|
|
1992
|
-
if (!tracing) return false;
|
|
1993
|
-
const config = tracing.getConfig();
|
|
1994
|
-
const sampling = config.sampling;
|
|
1995
|
-
return sampling.type !== observability.SamplingStrategyType.NEVER;
|
|
1996
|
-
}
|
|
1997
|
-
function isAITracingInstance(obj) {
|
|
1998
|
-
return obj instanceof BaseAITracing;
|
|
1999
|
-
}
|
|
2000
|
-
function setupAITracingRegistry(config) {
|
|
2001
|
-
if (!config) {
|
|
2002
|
-
return;
|
|
2073
|
+
hasInstance(name) {
|
|
2074
|
+
return !!this.#registry.get(name);
|
|
2003
2075
|
}
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
"Cannot use 'default' as a custom config name when default tracing is enabled. Please rename your custom config to avoid conflicts."
|
|
2007
|
-
);
|
|
2076
|
+
setConfigSelector(selector) {
|
|
2077
|
+
this.#registry.setSelector(selector);
|
|
2008
2078
|
}
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
serviceName: "mastra",
|
|
2012
|
-
name: "default",
|
|
2013
|
-
sampling: { type: observability.SamplingStrategyType.ALWAYS },
|
|
2014
|
-
exporters: [new DefaultExporter(), new CloudExporter()],
|
|
2015
|
-
processors: [new SensitiveDataFilter()]
|
|
2016
|
-
});
|
|
2017
|
-
registerAITracing("default", defaultInstance, true);
|
|
2018
|
-
}
|
|
2019
|
-
if (config.configs) {
|
|
2020
|
-
const instances = Object.entries(config.configs);
|
|
2021
|
-
instances.forEach(([name, tracingDef], index) => {
|
|
2022
|
-
const instance = isAITracingInstance(tracingDef) ? tracingDef : new DefaultAITracing({ ...tracingDef, name });
|
|
2023
|
-
const isDefault = !config.default?.enabled && index === 0;
|
|
2024
|
-
registerAITracing(name, instance, isDefault);
|
|
2025
|
-
});
|
|
2079
|
+
clear() {
|
|
2080
|
+
this.#registry.clear();
|
|
2026
2081
|
}
|
|
2027
|
-
|
|
2028
|
-
|
|
2082
|
+
async shutdown() {
|
|
2083
|
+
await this.#registry.shutdown();
|
|
2029
2084
|
}
|
|
2030
|
-
}
|
|
2085
|
+
};
|
|
2031
2086
|
|
|
2032
|
-
exports.BaseAISpan = BaseAISpan;
|
|
2033
|
-
exports.BaseAITracing = BaseAITracing;
|
|
2034
2087
|
exports.BaseExporter = BaseExporter;
|
|
2088
|
+
exports.BaseObservabilityInstance = BaseObservabilityInstance;
|
|
2089
|
+
exports.BaseSpan = BaseSpan;
|
|
2035
2090
|
exports.CloudExporter = CloudExporter;
|
|
2036
2091
|
exports.ConsoleExporter = ConsoleExporter;
|
|
2037
|
-
exports.DefaultAISpan = DefaultAISpan;
|
|
2038
|
-
exports.DefaultAITracing = DefaultAITracing;
|
|
2039
2092
|
exports.DefaultExporter = DefaultExporter;
|
|
2093
|
+
exports.DefaultObservabilityInstance = DefaultObservabilityInstance;
|
|
2094
|
+
exports.DefaultSpan = DefaultSpan;
|
|
2040
2095
|
exports.ModelSpanTracker = ModelSpanTracker;
|
|
2041
|
-
exports.
|
|
2096
|
+
exports.NoOpSpan = NoOpSpan;
|
|
2097
|
+
exports.Observability = Observability;
|
|
2098
|
+
exports.SamplingStrategyType = SamplingStrategyType;
|
|
2042
2099
|
exports.SensitiveDataFilter = SensitiveDataFilter;
|
|
2043
|
-
exports.clearAITracingRegistry = clearAITracingRegistry;
|
|
2044
2100
|
exports.deepClean = deepClean;
|
|
2045
|
-
exports.getAITracing = getAITracing;
|
|
2046
|
-
exports.getAllAITracing = getAllAITracing;
|
|
2047
|
-
exports.getDefaultAITracing = getDefaultAITracing;
|
|
2048
|
-
exports.getSelectedAITracing = getSelectedAITracing;
|
|
2049
|
-
exports.hasAITracing = hasAITracing;
|
|
2050
|
-
exports.registerAITracing = registerAITracing;
|
|
2051
|
-
exports.setSelector = setSelector;
|
|
2052
|
-
exports.setupAITracingRegistry = setupAITracingRegistry;
|
|
2053
|
-
exports.shutdownAITracingRegistry = shutdownAITracingRegistry;
|
|
2054
|
-
exports.unregisterAITracing = unregisterAITracing;
|
|
2055
2101
|
//# sourceMappingURL=index.cjs.map
|
|
2056
2102
|
//# sourceMappingURL=index.cjs.map
|