@mastra/observability 1.17.2-alpha.0 → 1.17.2-alpha.2

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,34 @@
1
1
  # @mastra/observability
2
2
 
3
+ ## 1.17.2-alpha.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Added `span.endTree()` for closing a span together with every descendant span that is still open, so an operation that is abandoned rather than completed can still emit a full trace. The options you pass are applied to every span it closes, so a force-closed child is distinguishable from one that finished on its own. ([#22278](https://github.com/mastra-ai/mastra/pull/22278))
8
+
9
+ ```ts
10
+ // Ends the span and any child spans still open beneath it, marking each canceled
11
+ workflowSpan.endTree({ attributes: { status: 'canceled' } });
12
+ ```
13
+
14
+ Repeat calls to `span.end()` are now ignored. A span that was force-closed this way keeps the state it was closed with and reports its end exactly once, even if the work it covered finishes later and ends the span again.
15
+
16
+ - Updated dependencies [[`b05f486`](https://github.com/mastra-ai/mastra/commit/b05f48612984d5fe2447ea2d6cdd5c604d285b97), [`7960688`](https://github.com/mastra-ai/mastra/commit/7960688828e04eaf3106e34f7758fa580257eef6)]:
17
+ - @mastra/core@1.62.0-alpha.10
18
+
19
+ ## 1.17.2-alpha.1
20
+
21
+ ### Patch Changes
22
+
23
+ - Fix PostgreSQL observability writes failing on NUL characters and unpaired Unicode surrogates ([#21728](https://github.com/mastra-ai/mastra/pull/21728))
24
+
25
+ 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.
26
+
27
+ 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.
28
+
29
+ - 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)]:
30
+ - @mastra/core@1.62.0-alpha.3
31
+
3
32
  ## 1.17.2-alpha.0
4
33
 
5
34
  ### Patch Changes
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"}]`;
@@ -2710,7 +2718,7 @@ function getExternalParentId(options) {
2710
2718
  if (parent.isInternal || parent.isExcluded) return parent.getParentSpanId(false);
2711
2719
  return parent.id;
2712
2720
  }
2713
- var BaseSpan = class {
2721
+ var BaseSpan = class BaseSpan {
2714
2722
  name;
2715
2723
  type;
2716
2724
  attributes;
@@ -2760,6 +2768,8 @@ var BaseSpan = class {
2760
2768
  isExcluded;
2761
2769
  /** Cached canonical correlation context for this live span */
2762
2770
  correlationContext;
2771
+ /** Child spans that have started but not yet ended */
2772
+ #openChildren;
2763
2773
  /**
2764
2774
  * Subclasses can override to unconditionally mark the span as excluded.
2765
2775
  * NoOpSpan uses this because it is never exported regardless of config.
@@ -2781,6 +2791,8 @@ var BaseSpan = class {
2781
2791
  this.isEvent = options.isEvent ?? false;
2782
2792
  this.tracingPolicy = options.tracingPolicy;
2783
2793
  this.traceState = options.traceState;
2794
+ const parent = options.parent;
2795
+ if (!this.isEvent && parent instanceof BaseSpan && parent.isValid) (parent.#openChildren ??= /* @__PURE__ */ new Set()).add(this);
2784
2796
  this.tags = !options.parent && options.tags?.length ? options.tags : void 0;
2785
2797
  const entityParent = this.getParentSpan(false);
2786
2798
  this.entityType = options.entityType ?? entityParent?.entityType;
@@ -2818,6 +2830,16 @@ var BaseSpan = class {
2818
2830
  return value;
2819
2831
  }
2820
2832
  }
2833
+ /** End the span and any descendant spans that are still open, applying `options` to each */
2834
+ endTree(options) {
2835
+ if (this.#openChildren) for (const child of [...this.#openChildren]) child.endTree(options);
2836
+ this.end(options);
2837
+ }
2838
+ /** Release this span from its parent's open-child set once it has ended */
2839
+ detachFromParent() {
2840
+ const parent = this.parent;
2841
+ if (parent instanceof BaseSpan) parent.#openChildren?.delete(this);
2842
+ }
2821
2843
  createChildSpan(options) {
2822
2844
  return this.observabilityInstance.startSpan({
2823
2845
  ...options,
@@ -3021,7 +3043,9 @@ var DefaultSpan = class extends BaseSpan {
3021
3043
  }
3022
3044
  end(options) {
3023
3045
  if (this.isEvent) return;
3046
+ if (this.endTime) return;
3024
3047
  this.endTime = /* @__PURE__ */ new Date();
3048
+ this.detachFromParent();
3025
3049
  if (options?.metadata) this.metadata = {
3026
3050
  ...this.metadata,
3027
3051
  ...deepClean(this.prepareSpanMetadata(options.metadata), this.deepCleanOptions)
@@ -3477,6 +3501,7 @@ var BaseObservabilityInstance = class extends _mastra_core_base.MastraBase {
3477
3501
  this.logger.warn(`End event is not available on event spans`);
3478
3502
  return;
3479
3503
  }
3504
+ if (span.endTime) return;
3480
3505
  const rollupTarget = this.captureModelUsageRollup(span, options);
3481
3506
  const excludedModelUsage = this.captureExcludedModelUsage(span, options);
3482
3507
  originalEnd(options);