@mastra/datadog 1.4.16-alpha.0 → 1.4.16

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/dist/index.js CHANGED
@@ -327,8 +327,14 @@ var DatadogBridge = class extends BaseExporter {
327
327
  startLinkKeys = /* @__PURE__ */ new WeakMap();
328
328
  traceContext = /* @__PURE__ */ new Map();
329
329
  openSpanCounts = /* @__PURE__ */ new Map();
330
+ flushGraceMs;
331
+ /** True when spans or evaluations were handed to dd-trace since the last flush() */
332
+ pendingSend = false;
333
+ /** Grace wait in progress, shared by flush() calls that overlap it */
334
+ graceWait = null;
330
335
  constructor(config = {}) {
331
336
  super(config);
337
+ this.flushGraceMs = config.flushGraceMs ?? 2e3;
332
338
  const mlApp = config.mlApp ?? process.env.DD_LLMOBS_ML_APP;
333
339
  const apiKey = config.apiKey ?? process.env.DD_API_KEY;
334
340
  const site = config.site ?? process.env.DD_SITE ?? "datadoghq.com";
@@ -435,6 +441,7 @@ var DatadogBridge = class extends BaseExporter {
435
441
  return;
436
442
  }
437
443
  try {
444
+ this.pendingSend = true;
438
445
  tracer.llmobs.submitEvaluation({
439
446
  traceId: score.traceId,
440
447
  spanId: toDatadogSpanId(score.spanId)
@@ -567,7 +574,10 @@ var DatadogBridge = class extends BaseExporter {
567
574
  });
568
575
  }
569
576
  try {
570
- if (typeof ddSpan.finish === "function") ddSpan.finish(endTime.getTime());
577
+ if (typeof ddSpan.finish === "function") {
578
+ this.pendingSend = true;
579
+ ddSpan.finish(endTime.getTime());
580
+ }
571
581
  } catch (error) {
572
582
  this.logger.error("[DatadogBridge] Failed to finish dd span", {
573
583
  error,
@@ -672,6 +682,7 @@ var DatadogBridge = class extends BaseExporter {
672
682
  } catch (e) {
673
683
  this.logger.error("Error flushing llmobs", { error: e });
674
684
  }
685
+ await this.waitForBackgroundSend();
675
686
  try {
676
687
  await flushApmExporter();
677
688
  this.logger.debug("Datadog APM exporter flushed");
@@ -679,11 +690,32 @@ var DatadogBridge = class extends BaseExporter {
679
690
  this.logger.error("Error flushing Datadog APM exporter", { error: e });
680
691
  }
681
692
  }
693
+ /**
694
+ * `llmobs.flush()` returns before the background writer's HTTP request
695
+ * completes and dd-trace gives nothing to await, so wait a bounded time
696
+ * instead. Starts one wait per batch of new data, so the repeated flushes
697
+ * during shutdown don't stack. A flush that overlaps a running wait joins
698
+ * it, so no caller resolves before the data it produced had its wait.
699
+ */
700
+ async waitForBackgroundSend() {
701
+ if (!(this.flushGraceMs > 0) || !Number.isFinite(this.flushGraceMs)) return;
702
+ if (this.pendingSend) {
703
+ this.pendingSend = false;
704
+ const wait = new Promise((resolve) => setTimeout(resolve, this.flushGraceMs)).finally(() => {
705
+ if (this.graceWait === wait) this.graceWait = null;
706
+ });
707
+ this.graceWait = wait;
708
+ }
709
+ await this.graceWait;
710
+ }
682
711
  async shutdown() {
683
712
  for (const [spanId, ddSpan] of this.ddSpanMap) {
684
713
  this.logger.warn(`[DatadogBridge] Force-finishing APM span that was not properly closed [id=${spanId}]`);
685
714
  try {
686
- if (typeof ddSpan.finish === "function") ddSpan.finish();
715
+ if (typeof ddSpan.finish === "function") {
716
+ this.pendingSend = true;
717
+ ddSpan.finish();
718
+ }
687
719
  } catch {}
688
720
  }
689
721
  this.ddSpanMap.clear();
@@ -748,8 +780,14 @@ var DatadogExporter = class extends BaseExporter {
748
780
  config;
749
781
  traceContext = /* @__PURE__ */ new Map();
750
782
  traceState = /* @__PURE__ */ new Map();
783
+ flushGraceMs;
784
+ /** True when spans or evaluations were handed to dd-trace since the last flush() */
785
+ pendingSend = false;
786
+ /** Grace wait in progress, shared by flush() calls that overlap it */
787
+ graceWait = null;
751
788
  constructor(config = {}) {
752
789
  super(config);
790
+ this.flushGraceMs = config.flushGraceMs ?? 2e3;
753
791
  const mlApp = config.mlApp ?? process.env.DD_LLMOBS_ML_APP;
754
792
  const apiKey = config.apiKey ?? process.env.DD_API_KEY;
755
793
  const site = config.site ?? process.env.DD_SITE ?? "datadoghq.com";
@@ -927,6 +965,7 @@ var DatadogExporter = class extends BaseExporter {
927
965
  return;
928
966
  }
929
967
  try {
968
+ this.pendingSend = true;
930
969
  tracer.llmobs.submitEvaluation({
931
970
  traceId: exported.traceId,
932
971
  spanId: exported.spanId
@@ -961,6 +1000,25 @@ var DatadogExporter = class extends BaseExporter {
961
1000
  } catch (e) {
962
1001
  this.logger.error("Error flushing llmobs", { error: e });
963
1002
  }
1003
+ await this.waitForBackgroundSend();
1004
+ }
1005
+ /**
1006
+ * `llmobs.flush()` returns before the background writer's HTTP request
1007
+ * completes and dd-trace gives nothing to await, so wait a bounded time
1008
+ * instead. Starts one wait per batch of new data, so the repeated flushes
1009
+ * during shutdown don't stack. A flush that overlaps a running wait joins
1010
+ * it, so no caller resolves before the data it produced had its wait.
1011
+ */
1012
+ async waitForBackgroundSend() {
1013
+ if (!(this.flushGraceMs > 0) || !Number.isFinite(this.flushGraceMs)) return;
1014
+ if (this.pendingSend) {
1015
+ this.pendingSend = false;
1016
+ const wait = new Promise((resolve) => setTimeout(resolve, this.flushGraceMs)).finally(() => {
1017
+ if (this.graceWait === wait) this.graceWait = null;
1018
+ });
1019
+ this.graceWait = wait;
1020
+ }
1021
+ await this.graceWait;
964
1022
  }
965
1023
  /**
966
1024
  * Gracefully shuts down the exporter.
@@ -1165,6 +1223,7 @@ var DatadogExporter = class extends BaseExporter {
1165
1223
  model: span.attributes?.model,
1166
1224
  provider: span.attributes?.provider
1167
1225
  } : inheritedModelAttrs;
1226
+ this.pendingSend = true;
1168
1227
  tracer.llmobs.trace(traceOptions, (ddSpan) => {
1169
1228
  const annotations = this.buildAnnotations(span);
1170
1229
  if (Object.keys(annotations).length > 0) tracer.llmobs.annotate(ddSpan, annotations);