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