{"version":3,"file":"spanBuffer.js","sources":["../../../../src/tracing/spans/spanBuffer.ts"],"sourcesContent":["import type { Client } from '../../client';\nimport { DEBUG_BUILD } from '../../debug-build';\nimport type { SerializedStreamedSpan } from '../../types-hoist/span';\nimport { debug } from '../../utils/debug-logger';\nimport { safeUnref } from '../../utils/timer';\nimport { getDynamicSamplingContextFromSpan } from '../dynamicSamplingContext';\nimport type { SerializedStreamedSpanWithSegmentSpan } from './captureSpan';\nimport { createStreamedSpanEnvelope } from './envelope';\nimport { estimateSerializedSpanSizeInBytes } from './estimateSize';\n\n/**\n * We must not send more than 1000 spans in one envelope.\n * Otherwise the envelope is dropped by Relay.\n */\nconst MAX_SPANS_PER_ENVELOPE = 1000;\n\nconst MAX_TRACE_WEIGHT_IN_BYTES = 5_000_000;\n\ninterface TraceBucket {\n  spans: Set<SerializedStreamedSpanWithSegmentSpan>;\n  size: number;\n  timeout: ReturnType<typeof setTimeout>;\n}\n\nexport interface SpanBufferOptions {\n  /**\n   * Max spans per trace before auto-flush\n   * Must not exceed 1000.\n   *\n   * @default 1_000\n   */\n  maxSpanLimit?: number;\n\n  /**\n   * Per-trace flush timeout in ms. A timeout is started when a trace bucket is first created\n   * and fires flush() for that specific trace when it expires.\n   * Must be greater than 0.\n   *\n   * @default 5_000\n   */\n  flushInterval?: number;\n\n  /**\n   * Max accumulated byte weight of spans per trace before auto-flush.\n   * Size is estimated, not exact. Uses 2 bytes per character for strings (UTF-16).\n   *\n   * @default 5_000_000 (5 MB)\n   */\n  maxTraceWeightInBytes?: number;\n}\n\n/**\n * A buffer for serialized streamed span JSON objects that flushes them to Sentry in Span v2 envelopes.\n * Handles per-trace timeout-based flushing, size thresholds, and graceful shutdown.\n * Also handles computation of the Dynamic Sampling Context (DSC) for the trace, if it wasn't yet\n * frozen onto the segment span.\n *\n * For this, we need the reference to the segment span instance, from\n * which we compute the DSC. Doing this in the buffer ensures that we compute the DSC as late as possible,\n * allowing span name and data updates up to this point. Worth noting here that the segment span is likely\n * still active and modifyable when child spans are added to the buffer.\n */\nexport class SpanBuffer {\n  /* Bucket spans by their trace id, along with accumulated size and a per-trace flush timeout */\n  private _traceBuckets: Map<string, TraceBucket>;\n\n  private _client: Client;\n  private _maxSpanLimit: number;\n  private _flushInterval: number;\n  private _maxTraceWeight: number;\n\n  public constructor(client: Client, options?: SpanBufferOptions) {\n    this._traceBuckets = new Map();\n    this._client = client;\n\n    const { maxSpanLimit, flushInterval, maxTraceWeightInBytes } = options ?? {};\n\n    this._maxSpanLimit =\n      maxSpanLimit && maxSpanLimit > 0 && maxSpanLimit <= MAX_SPANS_PER_ENVELOPE\n        ? maxSpanLimit\n        : MAX_SPANS_PER_ENVELOPE;\n    this._flushInterval = flushInterval && flushInterval > 0 ? flushInterval : 5_000;\n    this._maxTraceWeight =\n      maxTraceWeightInBytes && maxTraceWeightInBytes > 0 ? maxTraceWeightInBytes : MAX_TRACE_WEIGHT_IN_BYTES;\n\n    this._client.on('flush', () => {\n      this.drain();\n    });\n\n    this._client.on('close', () => {\n      // No need to drain the buffer here as `Client.close()` internally already calls `Client.flush()`\n      // which already invokes the `flush` hook and thus drains the buffer.\n      this._traceBuckets.forEach(bucket => {\n        clearTimeout(bucket.timeout);\n      });\n      this._traceBuckets.clear();\n    });\n  }\n\n  /**\n   * Add a span to the buffer.\n   */\n  public add(spanJSON: SerializedStreamedSpanWithSegmentSpan): void {\n    const traceId = spanJSON.trace_id;\n    let bucket = this._traceBuckets.get(traceId);\n\n    if (!bucket) {\n      bucket = {\n        spans: new Set(),\n        size: 0,\n        timeout: safeUnref(\n          setTimeout(() => {\n            this.flush(traceId);\n          }, this._flushInterval),\n        ),\n      };\n      this._traceBuckets.set(traceId, bucket);\n    }\n\n    bucket.spans.add(spanJSON);\n    bucket.size += estimateSerializedSpanSizeInBytes(spanJSON);\n\n    if (bucket.spans.size >= this._maxSpanLimit || bucket.size >= this._maxTraceWeight) {\n      this.flush(traceId);\n    }\n  }\n\n  /**\n   * Drain and flush all buffered traces.\n   */\n  public drain(): void {\n    if (!this._traceBuckets.size) {\n      return;\n    }\n\n    DEBUG_BUILD && debug.log(`Flushing span tree map with ${this._traceBuckets.size} traces`);\n\n    this._traceBuckets.forEach((_, traceId) => {\n      this.flush(traceId);\n    });\n  }\n\n  /**\n   * Flush spans of a specific trace.\n   * In contrast to {@link SpanBuffer.drain}, this method does not flush all traces, but only the one with the given traceId.\n   */\n  public flush(traceId: string): void {\n    const bucket = this._traceBuckets.get(traceId);\n    if (!bucket) {\n      return;\n    }\n\n    if (!bucket.spans.size) {\n      // we should never get here, given we always add a span when we create a new bucket\n      // and delete the bucket once we flush out the trace\n      this._removeTrace(traceId);\n      return;\n    }\n\n    const spans = Array.from(bucket.spans);\n\n    const segmentSpan = spans[0]?._segmentSpan;\n    if (!segmentSpan) {\n      DEBUG_BUILD && debug.warn('No segment span reference found on span JSON, cannot compute DSC');\n      this._removeTrace(traceId);\n      return;\n    }\n\n    const dsc = getDynamicSamplingContextFromSpan(segmentSpan);\n\n    const cleanedSpans: SerializedStreamedSpan[] = spans.map(spanJSON => {\n      // eslint-disable-next-line @typescript-eslint/no-unused-vars\n      const { _segmentSpan, ...cleanSpanJSON } = spanJSON;\n      return cleanSpanJSON;\n    });\n\n    const envelope = createStreamedSpanEnvelope(cleanedSpans, dsc, this._client);\n\n    DEBUG_BUILD && debug.log(`Sending span envelope for trace ${traceId} with ${cleanedSpans.length} spans`);\n\n    this._client.sendEnvelope(envelope).then(null, reason => {\n      DEBUG_BUILD && debug.error('Error while sending streamed span envelope:', reason);\n    });\n\n    this._removeTrace(traceId);\n  }\n\n  private _removeTrace(traceId: string): void {\n    const bucket = this._traceBuckets.get(traceId);\n    if (bucket) {\n      clearTimeout(bucket.timeout);\n    }\n    this._traceBuckets.delete(traceId);\n  }\n}\n"],"names":["safeUnref","estimateSerializedSpanSizeInBytes","DEBUG_BUILD","debug","getDynamicSamplingContextFromSpan","envelope","createStreamedSpanEnvelope"],"mappings":"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"}