@mastra/observability 1.17.1 → 1.17.2-alpha.1

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 CHANGED
@@ -1,5 +1,27 @@
1
1
  # @mastra/observability
2
2
 
3
+ ## 1.17.2-alpha.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Fix PostgreSQL observability writes failing on NUL characters and unpaired Unicode surrogates ([#21728](https://github.com/mastra-ai/mastra/pull/21728))
8
+
9
+ Span serialization truncated strings by UTF-16 code unit, so a cut inside an emoji left a lone surrogate that PostgreSQL rejected on the jsonb cast (`22P02`). NUL characters were rejected as well (`22P05`). Because observability events are inserted as a single multi-row statement, one malformed field discarded the entire batch.
10
+
11
+ Truncation now preserves complete surrogate pairs, and the v-next PostgreSQL observability encoder sanitizes NUL and unpaired surrogates before the jsonb cast, using the same sanitizer that workflow snapshots already rely on. Valid Unicode, including complete emoji, is preserved.
12
+
13
+ - Updated dependencies [[`2c85f42`](https://github.com/mastra-ai/mastra/commit/2c85f428e04ccd63ea31a7ec80b5b327afdad555), [`11bbeb9`](https://github.com/mastra-ai/mastra/commit/11bbeb9b108ef2264e05acefc6dafb9cbb342921), [`1a485f3`](https://github.com/mastra-ai/mastra/commit/1a485f3538f5ec64d58bd8b5e1e99de0c695c87b), [`0d37487`](https://github.com/mastra-ai/mastra/commit/0d37487d9f349388a3f1cef6a536cf9dcc4b6273), [`8661d7d`](https://github.com/mastra-ai/mastra/commit/8661d7d7179f0a024456aabdd8679bcecd09ac28), [`575e343`](https://github.com/mastra-ai/mastra/commit/575e343900451021d96110916497d334af7bc252), [`cacb839`](https://github.com/mastra-ai/mastra/commit/cacb8392d9e74189b56d857290b0615f98a2683d), [`b47b26e`](https://github.com/mastra-ai/mastra/commit/b47b26e6fe95cb8a3482be2c5e52de157fe59d0b), [`0d37487`](https://github.com/mastra-ai/mastra/commit/0d37487d9f349388a3f1cef6a536cf9dcc4b6273), [`c46eb09`](https://github.com/mastra-ai/mastra/commit/c46eb09ce4987509af57a0ac582c61241a6dd2f1), [`30ed33e`](https://github.com/mastra-ai/mastra/commit/30ed33ee14084a26019aba15fceadda6d6ddefaf), [`91ad69d`](https://github.com/mastra-ai/mastra/commit/91ad69d64994c89199b0c55399e64ed91c61df2f), [`8dc408d`](https://github.com/mastra-ai/mastra/commit/8dc408d34438f9e13297f792c11a5cfd6cf952e1), [`c92def1`](https://github.com/mastra-ai/mastra/commit/c92def10a13c822972c96f0a4ca6ffc1f4258aed), [`c5eaec5`](https://github.com/mastra-ai/mastra/commit/c5eaec5a860d80d0e3805e67db0414b87ac8cbed), [`e66b2ba`](https://github.com/mastra-ai/mastra/commit/e66b2ba100db63eaeab6e21e1ea34b113f2ec781)]:
14
+ - @mastra/core@1.62.0-alpha.3
15
+
16
+ ## 1.17.2-alpha.0
17
+
18
+ ### Patch Changes
19
+
20
+ - Fixed `MastraStorageExporter` staying disabled when a configured observability store is temporarily unavailable during startup. The exporter now recovers on a later event, so traces resume without restarting the process. (#21943) ([#21957](https://github.com/mastra-ai/mastra/pull/21957))
21
+
22
+ - Updated dependencies [[`f95f468`](https://github.com/mastra-ai/mastra/commit/f95f468cf1e7c2b924a13826494f98b8f2ccd581)]:
23
+ - @mastra/core@1.61.1-alpha.1
24
+
3
25
  ## 1.17.1
4
26
 
5
27
  ### Patch Changes
@@ -23,6 +23,9 @@ export declare class MastraStorageExporter extends BaseExporter {
23
23
  * Initialize the exporter (called after all dependencies are ready)
24
24
  */
25
25
  init(options: InitExporterOptions): Promise<void>;
26
+ private warnUnavailable;
27
+ private initializeStorage;
28
+ private ensureInitialized;
26
29
  /**
27
30
  * Checks if buffer should be flushed based on size or time triggers
28
31
  */
@@ -63,12 +66,6 @@ export declare class MastraStorageExporter extends BaseExporter {
63
66
  */
64
67
  private flushBuffer;
65
68
  _exportTracingEvent(event: TracingEvent): Promise<void>;
66
- /**
67
- * Resolves when an ongoing init call is finished
68
- * Doesn't wait for the caller to call init
69
- * @returns
70
- */
71
- private waitForInit;
72
69
  /**
73
70
  * Handle metric events — buffer for batch flush.
74
71
  */
@@ -1 +1 @@
1
- {"version":3,"file":"mastra-storage.d.ts","sourceRoot":"","sources":["../../src/exporters/mastra-storage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACnB,WAAW,EACX,QAAQ,EACR,UAAU,EACV,aAAa,EAId,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAwB,sBAAsB,EAAwB,MAAM,sBAAsB,CAAC;AAS/G,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAItC,4FAA4F;AAC5F,MAAM,WAAW,2BAA4B,SAAQ,kBAAkB;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,QAAQ,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAAC;CAC5C;AAoCD;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,YAAY;;IACrD,IAAI,SAA6B;gBAgBrB,MAAM,GAAE,2BAAgC;IAiBpD;;OAEG;IACG,IAAI,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqDvD;;OAEG;IACH,OAAO,CAAC,WAAW;IAyBnB;;OAEG;IACH,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,iBAAiB;IAiBzB;;;;OAIG;YACW,kBAAkB;IAQhC,OAAO,CAAC,iBAAiB;IAgBzB,OAAO,CAAC,QAAQ;IAsBhB;;;;OAIG;YACW,YAAY;IAkC1B;;;OAGG;YACW,gBAAgB;IA+D9B;;;;;;;OAOG;YACW,WAAW;IA+HnB,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAW7D;;;;OAIG;YACW,WAAW;IAOzB;;OAEG;IACG,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAQtD;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAQhD;;OAEG;IACG,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAQpD;;OAEG;IACG,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ1D;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAStB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAiBhC"}
1
+ {"version":3,"file":"mastra-storage.d.ts","sourceRoot":"","sources":["../../src/exporters/mastra-storage.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,YAAY,EACZ,mBAAmB,EACnB,WAAW,EACX,QAAQ,EACR,UAAU,EACV,aAAa,EAId,MAAM,4BAA4B,CAAC;AAEpC,OAAO,KAAK,EAAwB,sBAAsB,EAAwB,MAAM,sBAAsB,CAAC;AAS/G,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,QAAQ,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAItC,4FAA4F;AAC5F,MAAM,WAAW,2BAA4B,SAAQ,kBAAkB;IACrE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IAGtB,QAAQ,CAAC,EAAE,sBAAsB,GAAG,MAAM,CAAC;CAC5C;AAkCD;;;GAGG;AACH,qBAAa,qBAAsB,SAAQ,YAAY;;IACrD,IAAI,SAA6B;gBAgBrB,MAAM,GAAE,2BAAgC;IAiBpD;;OAEG;IACG,IAAI,CAAC,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAUvD,OAAO,CAAC,eAAe;YAUT,iBAAiB;YA6CjB,iBAAiB;IAS/B;;OAEG;IACH,OAAO,CAAC,WAAW;IAyBnB;;OAEG;IACH,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,iBAAiB;IAiBzB;;;;OAIG;YACW,kBAAkB;IAQhC,OAAO,CAAC,iBAAiB;IAgBzB,OAAO,CAAC,QAAQ;IAsBhB;;;;OAIG;YACW,YAAY;IAkC1B;;;OAGG;YACW,gBAAgB;IA+D9B;;;;;;;OAOG;YACW,WAAW;IA+HnB,mBAAmB,CAAC,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAW7D;;OAEG;IACG,aAAa,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAQtD;;OAEG;IACG,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC;IAQhD;;OAEG;IACG,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAQpD;;OAEG;IACG,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ1D;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAStB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;CAiBhC"}
package/dist/index.cjs CHANGED
@@ -246,10 +246,18 @@ function mergeSerializationOptions(userOptions) {
246
246
  }
247
247
  /**
248
248
  * Hard-cap any string to prevent unbounded growth.
249
+ *
250
+ * The cut never splits a UTF-16 surrogate pair: if it would land immediately
251
+ * after a lone high surrogate (U+D800..U+DBFF), it backs off one code unit so
252
+ * the pair is dropped as a whole. `JSON.stringify` emits a lone `\ud83d` for a
253
+ * split pair, which PostgreSQL rejects on a jsonb cast with 22P02
254
+ * ("Unicode low surrogate must follow a high surrogate").
249
255
  */
250
256
  function truncateString(s, maxChars) {
251
257
  if (s.length <= maxChars) return s;
252
- return s.slice(0, maxChars) + "…[truncated]";
258
+ const code = s.charCodeAt(maxChars - 1);
259
+ const safeEnd = code >= 55296 && code <= 56319 ? maxChars - 1 : maxChars;
260
+ return s.slice(0, safeEnd) + "…[truncated]";
253
261
  }
254
262
  function formatSerializationError(error) {
255
263
  return `[${error instanceof Error ? truncateString(error.message, 256) : "unknown error"}]`;
@@ -7123,8 +7131,8 @@ const FLUSH_SUCCEEDED = { failed: false };
7123
7131
  var MastraStorageExporter = class extends BaseExporter {
7124
7132
  name = "mastra-storage-exporter";
7125
7133
  #config;
7126
- #isInitializing = false;
7127
- #initPromises = /* @__PURE__ */ new Set();
7134
+ #initializationPromise;
7135
+ #wasUnavailable = false;
7128
7136
  #eventBuffer;
7129
7137
  #storage;
7130
7138
  #observabilityStorage;
@@ -7149,41 +7157,56 @@ var MastraStorageExporter = class extends BaseExporter {
7149
7157
  * Initialize the exporter (called after all dependencies are ready)
7150
7158
  */
7151
7159
  async init(options) {
7160
+ this.#emitDropEvent = options.emitDropEvent;
7161
+ this.#storage = options.mastra?.getStorage();
7162
+ if (!this.#storage) {
7163
+ this.logger.warn("MastraStorageExporter disabled: Storage not available. Traces will not be persisted.");
7164
+ return;
7165
+ }
7166
+ await this.ensureInitialized();
7167
+ }
7168
+ warnUnavailable(message, error) {
7169
+ if (this.#wasUnavailable) return;
7170
+ this.#wasUnavailable = true;
7171
+ if (error === void 0) {
7172
+ this.logger.warn(message);
7173
+ return;
7174
+ }
7175
+ this.logger.warn(message, { error: error instanceof Error ? error.message : String(error) });
7176
+ }
7177
+ async initializeStorage() {
7178
+ const storage = this.#storage;
7179
+ if (!storage) return;
7152
7180
  try {
7153
- this.#isInitializing = true;
7154
- this.#emitDropEvent = options.emitDropEvent;
7155
- this.#storage = options.mastra?.getStorage();
7156
- if (!this.#storage) {
7157
- this.logger.warn("MastraStorageExporter disabled: Storage not available. Traces will not be persisted.");
7158
- return;
7159
- }
7160
- this.#observabilityStorage = await this.#storage.getStore("observability");
7161
- if (!this.#observabilityStorage) {
7162
- this.logger.warn("MastraStorageExporter disabled: Observability storage not available. Traces will not be persisted.");
7163
- return;
7164
- }
7165
- if (!this.#resolvedStrategy) {
7166
- this.#resolvedStrategy = resolveTracingStorageStrategy(this.#config, this.#observabilityStorage, this.#storage.constructor.name, this.logger);
7167
- this.logger.debug("tracing storage exporter initialized", {
7168
- strategy: this.#resolvedStrategy,
7169
- source: this.#config.strategy !== "auto" ? "user" : "auto",
7170
- storageAdapter: this.#storage.constructor.name,
7171
- maxBatchSize: this.#config.maxBatchSize,
7172
- maxBatchWaitMs: this.#config.maxBatchWaitMs
7173
- });
7174
- }
7175
- if (this.#resolvedStrategy) this.#eventBuffer.init({ strategy: this.#resolvedStrategy });
7176
- } finally {
7177
- this.#isInitializing = false;
7178
- /**
7179
- * Assumes caller waits until export of a parent span is completed before calling
7180
- * export for child spans , order is not relevant for resolve
7181
- */
7182
- this.#initPromises.forEach((resolve) => {
7183
- resolve();
7184
- });
7185
- this.#initPromises.clear();
7181
+ this.#observabilityStorage = await storage.getStore("observability");
7182
+ } catch (error) {
7183
+ this.warnUnavailable("MastraStorageExporter unavailable: Failed to initialize observability storage. Traces will not be persisted until storage becomes available.", error);
7184
+ return;
7185
+ }
7186
+ if (!this.#observabilityStorage) {
7187
+ this.warnUnavailable("MastraStorageExporter unavailable: Observability storage not available. Traces will not be persisted until storage becomes available.");
7188
+ return;
7186
7189
  }
7190
+ this.#resolvedStrategy = resolveTracingStorageStrategy(this.#config, this.#observabilityStorage, storage.constructor.name, this.logger);
7191
+ this.#eventBuffer.init({ strategy: this.#resolvedStrategy });
7192
+ this.logger.debug("tracing storage exporter initialized", {
7193
+ strategy: this.#resolvedStrategy,
7194
+ source: this.#config.strategy !== "auto" ? "user" : "auto",
7195
+ storageAdapter: storage.constructor.name,
7196
+ maxBatchSize: this.#config.maxBatchSize,
7197
+ maxBatchWaitMs: this.#config.maxBatchWaitMs
7198
+ });
7199
+ if (this.#wasUnavailable) {
7200
+ this.#wasUnavailable = false;
7201
+ this.logger.info("MastraStorageExporter recovered: Observability storage is available. Traces will be persisted.");
7202
+ }
7203
+ }
7204
+ async ensureInitialized() {
7205
+ if (this.#observabilityStorage || !this.#storage) return;
7206
+ this.#initializationPromise ??= this.initializeStorage().finally(() => {
7207
+ this.#initializationPromise = void 0;
7208
+ });
7209
+ await this.#initializationPromise;
7187
7210
  }
7188
7211
  /**
7189
7212
  * Checks if buffer should be flushed based on size or time triggers
@@ -7435,7 +7458,7 @@ var MastraStorageExporter = class extends BaseExporter {
7435
7458
  }
7436
7459
  }
7437
7460
  async _exportTracingEvent(event) {
7438
- await this.waitForInit();
7461
+ await this.ensureInitialized();
7439
7462
  if (!this.#observabilityStorage) {
7440
7463
  this.logger.debug("Cannot store traces. Observability storage is not initialized");
7441
7464
  return;
@@ -7444,21 +7467,10 @@ var MastraStorageExporter = class extends BaseExporter {
7444
7467
  await this.handleBatchedFlush();
7445
7468
  }
7446
7469
  /**
7447
- * Resolves when an ongoing init call is finished
7448
- * Doesn't wait for the caller to call init
7449
- * @returns
7450
- */
7451
- async waitForInit() {
7452
- if (!this.#isInitializing) return;
7453
- return new Promise((resolve) => {
7454
- this.#initPromises.add(resolve);
7455
- });
7456
- }
7457
- /**
7458
7470
  * Handle metric events — buffer for batch flush.
7459
7471
  */
7460
7472
  async onMetricEvent(event) {
7461
- await this.waitForInit();
7473
+ await this.ensureInitialized();
7462
7474
  if (!this.#observabilityStorage) return;
7463
7475
  this.#eventBuffer.addEvent(event);
7464
7476
  await this.handleBatchedFlush();
@@ -7467,7 +7479,7 @@ var MastraStorageExporter = class extends BaseExporter {
7467
7479
  * Handle log events — buffer for batch flush.
7468
7480
  */
7469
7481
  async onLogEvent(event) {
7470
- await this.waitForInit();
7482
+ await this.ensureInitialized();
7471
7483
  if (!this.#observabilityStorage) return;
7472
7484
  this.#eventBuffer.addEvent(event);
7473
7485
  await this.handleBatchedFlush();
@@ -7476,7 +7488,7 @@ var MastraStorageExporter = class extends BaseExporter {
7476
7488
  * Handle score events — buffer for batch flush.
7477
7489
  */
7478
7490
  async onScoreEvent(event) {
7479
- await this.waitForInit();
7491
+ await this.ensureInitialized();
7480
7492
  if (!this.#observabilityStorage) return;
7481
7493
  this.#eventBuffer.addEvent(event);
7482
7494
  await this.handleBatchedFlush();
@@ -7485,7 +7497,7 @@ var MastraStorageExporter = class extends BaseExporter {
7485
7497
  * Handle feedback events — buffer for batch flush.
7486
7498
  */
7487
7499
  async onFeedbackEvent(event) {
7488
- await this.waitForInit();
7500
+ await this.ensureInitialized();
7489
7501
  if (!this.#observabilityStorage) return;
7490
7502
  this.#eventBuffer.addEvent(event);
7491
7503
  await this.handleBatchedFlush();