{"version":3,"sources":["../../src/common/spanCollector.ts"],"sourcesContent":["import {\n  context,\n  SpanKind,\n  SpanStatusCode,\n  trace,\n  type Tracer,\n} from \"@opentelemetry/api\";\nimport {\n  type ReadableSpan,\n  type Span,\n  SpanProcessor,\n} from \"@opentelemetry/sdk-trace-base\";\n\nimport { ApitallyClient } from \"./client.js\";\n\nexport type SpanData = {\n  spanId: string;\n  parentSpanId: string | null;\n  name: string;\n  kind: string;\n  startTime: string; // bigint as string\n  endTime: string; // bigint as string\n  status?: string;\n  attributes?: Record<string, unknown>;\n};\n\nexport type SpanHandle = {\n  traceId?: string;\n  setName: (name: string) => void;\n  runInContext: <T>(fn: () => T) => T;\n  enterContext: () => void;\n  end: () => SpanData[] | undefined;\n};\n\nconst TRACE_MAX_AGE = 5 * 60 * 1000; // 5 minutes\n\nexport default class SpanCollector implements SpanProcessor {\n  public enabled: boolean;\n  private includedSpanIds: Map<string, Set<string>> = new Map();\n  private collectedSpans: Map<string, SpanData[]> = new Map();\n  private traceStartTimes: Map<string, number> = new Map();\n  private maintainIntervalId?: NodeJS.Timeout;\n  private tracer?: Tracer;\n\n  constructor(enabled: boolean) {\n    this.enabled = enabled;\n\n    if (enabled) {\n      this.tracer = trace.getTracer(\"apitally\");\n      this.maintainIntervalId = setInterval(() => {\n        this.maintain();\n      }, 60_000);\n    }\n  }\n\n  startSpan(): SpanHandle {\n    if (!this.enabled || !this.tracer) {\n      return {\n        setName: () => void 0,\n        runInContext: <T>(fn: () => T): T => {\n          return fn();\n        },\n        enterContext: () => void 0,\n        end: () => undefined,\n      };\n    }\n\n    const span = this.tracer.startSpan(\"root\");\n    const spanCtx = span.spanContext();\n    const traceId = spanCtx.traceId;\n    const ctx = trace.setSpan(context.active(), span);\n    let ended = false;\n\n    this.includedSpanIds.set(traceId, new Set([spanCtx.spanId]));\n    this.collectedSpans.set(traceId, []);\n    this.traceStartTimes.set(traceId, Date.now());\n\n    return {\n      traceId,\n      setName: (name: string) => {\n        span.updateName(name);\n      },\n      runInContext: <T>(fn: () => T): T => {\n        return context.with(ctx, fn);\n      },\n      enterContext: () => {\n        try {\n          // Access the global context manager's internal AsyncLocalStorage\n          const contextManager = (context as any)._getContextManager?.();\n          contextManager?._asyncLocalStorage?.enterWith(ctx);\n        } catch {\n          // Ignore errors accessing internals\n        }\n      },\n      end: () => {\n        if (ended) return;\n        span.end();\n        ended = true;\n        return this.getAndClearSpans(traceId);\n      },\n    };\n  }\n\n  private getAndClearSpans(traceId: string): SpanData[] {\n    const spans = this.collectedSpans.get(traceId) ?? [];\n    this.collectedSpans.delete(traceId);\n    this.includedSpanIds.delete(traceId);\n    this.traceStartTimes.delete(traceId);\n    return spans;\n  }\n\n  onStart(span: Span): void {\n    if (!this.enabled) return;\n\n    const ctx = span.spanContext();\n    const traceId = ctx.traceId;\n    const spanId = ctx.spanId;\n\n    const includedSpans = this.includedSpanIds.get(traceId);\n    if (!includedSpans) return;\n\n    const parentSpanId = span.parentSpanContext?.spanId;\n    if (parentSpanId && includedSpans.has(parentSpanId)) {\n      includedSpans.add(spanId);\n    }\n  }\n\n  onEnd(span: ReadableSpan): void {\n    if (!this.enabled) return;\n\n    const ctx = span.spanContext();\n    const traceId = ctx.traceId;\n    const spanId = ctx.spanId;\n\n    const includedSpans = this.includedSpanIds.get(traceId);\n    if (!includedSpans || !includedSpans.has(spanId)) return;\n\n    const spans = this.collectedSpans.get(traceId);\n    if (spans) {\n      spans.push(this.serializeSpan(span));\n    }\n  }\n\n  private serializeSpan(span: ReadableSpan): SpanData {\n    const ctx = span.spanContext();\n\n    const data: SpanData = {\n      spanId: ctx.spanId,\n      parentSpanId: span.parentSpanContext?.spanId || null,\n      name: span.name,\n      kind: SpanKind[span.kind] ?? \"INTERNAL\",\n      // HrTime is [seconds, nanoseconds], convert to nanoseconds as string to avoid precision loss\n      startTime: (\n        BigInt(span.startTime[0]) * 1_000_000_000n +\n        BigInt(span.startTime[1])\n      ).toString(),\n      endTime: (\n        BigInt(span.endTime[0]) * 1_000_000_000n +\n        BigInt(span.endTime[1])\n      ).toString(),\n    };\n\n    if (span.status.code !== SpanStatusCode.UNSET) {\n      data.status = SpanStatusCode[span.status.code];\n    }\n\n    if (span.attributes && Object.keys(span.attributes).length > 0) {\n      data.attributes = { ...span.attributes };\n    }\n\n    return data;\n  }\n\n  private maintain() {\n    const now = Date.now();\n    for (const [traceId, startTime] of this.traceStartTimes) {\n      if (now - startTime > TRACE_MAX_AGE) {\n        this.collectedSpans.delete(traceId);\n        this.includedSpanIds.delete(traceId);\n        this.traceStartTimes.delete(traceId);\n      }\n    }\n  }\n\n  async shutdown(): Promise<void> {\n    this.enabled = false;\n    this.includedSpanIds.clear();\n    this.collectedSpans.clear();\n    this.traceStartTimes.clear();\n    if (this.maintainIntervalId) {\n      clearInterval(this.maintainIntervalId);\n    }\n  }\n\n  async forceFlush(): Promise<void> {\n    // Nothing to flush since we collect spans synchronously\n  }\n}\n\nexport class ApitallySpanProcessor implements SpanProcessor {\n  private getCollector(): SpanCollector | undefined {\n    try {\n      return ApitallyClient.getInstance().spanCollector;\n    } catch {\n      return undefined;\n    }\n  }\n\n  onStart(span: Span) {\n    this.getCollector()?.onStart(span);\n  }\n\n  onEnd(span: ReadableSpan) {\n    this.getCollector()?.onEnd(span);\n  }\n\n  async shutdown() {\n    this.getCollector()?.shutdown();\n  }\n\n  async forceFlush() {\n    this.getCollector()?.forceFlush();\n  }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;;;;;;AAAA,iBAMO;AAOP,oBAA+B;AAqB/B,MAAMA,gBAAgB,IAAI,KAAK;AAE/B,MAAqBC,iBAArB,MAAqBA,eAAAA;EACZC;EACCC,kBAA4C,oBAAIC,IAAAA;EAChDC,iBAA0C,oBAAID,IAAAA;EAC9CE,kBAAuC,oBAAIF,IAAAA;EAC3CG;EACAC;EAER,YAAYN,SAAkB;AAC5B,SAAKA,UAAUA;AAEf,QAAIA,SAAS;AACX,WAAKM,SAASC,iBAAMC,UAAU,UAAA;AAC9B,WAAKH,qBAAqBI,YAAY,MAAA;AACpC,aAAKC,SAAQ;MACf,GAAG,GAAA;IACL;EACF;EAEAC,YAAwB;AACtB,QAAI,CAAC,KAAKX,WAAW,CAAC,KAAKM,QAAQ;AACjC,aAAO;QACLM,SAAS,6BAAM,QAAN;QACTC,cAAc,wBAAIC,OAAAA;AAChB,iBAAOA,GAAAA;QACT,GAFc;QAGdC,cAAc,6BAAM,QAAN;QACdC,KAAK,6BAAMC,QAAN;MACP;IACF;AAEA,UAAMC,OAAO,KAAKZ,OAAOK,UAAU,MAAA;AACnC,UAAMQ,UAAUD,KAAKE,YAAW;AAChC,UAAMC,UAAUF,QAAQE;AACxB,UAAMC,MAAMf,iBAAMgB,QAAQC,mBAAQC,OAAM,GAAIP,IAAAA;AAC5C,QAAIQ,QAAQ;AAEZ,SAAKzB,gBAAgB0B,IAAIN,SAAS,oBAAIO,IAAI;MAACT,QAAQU;KAAO,CAAA;AAC1D,SAAK1B,eAAewB,IAAIN,SAAS,CAAA,CAAE;AACnC,SAAKjB,gBAAgBuB,IAAIN,SAASS,KAAKC,IAAG,CAAA;AAE1C,WAAO;MACLV;MACAT,SAAS,wBAACoB,SAAAA;AACRd,aAAKe,WAAWD,IAAAA;MAClB,GAFS;MAGTnB,cAAc,wBAAIC,OAAAA;AAChB,eAAOU,mBAAQU,KAAKZ,KAAKR,EAAAA;MAC3B,GAFc;MAGdC,cAAc,6BAAA;AArFpB;AAsFQ,YAAI;AAEF,gBAAMoB,kBAAkBX,+BAAgBY,uBAAhBZ;AACxBW,iEAAgBE,uBAAhBF,mBAAoCG,UAAUhB;QAChD,QAAQ;QAER;MACF,GARc;MASdN,KAAK,6BAAA;AACH,YAAIU,MAAO;AACXR,aAAKF,IAAG;AACRU,gBAAQ;AACR,eAAO,KAAKa,iBAAiBlB,OAAAA;MAC/B,GALK;IAMP;EACF;EAEQkB,iBAAiBlB,SAA6B;AACpD,UAAMmB,QAAQ,KAAKrC,eAAesC,IAAIpB,OAAAA,KAAY,CAAA;AAClD,SAAKlB,eAAeuC,OAAOrB,OAAAA;AAC3B,SAAKpB,gBAAgByC,OAAOrB,OAAAA;AAC5B,SAAKjB,gBAAgBsC,OAAOrB,OAAAA;AAC5B,WAAOmB;EACT;EAEAG,QAAQzB,MAAkB;AA/G5B;AAgHI,QAAI,CAAC,KAAKlB,QAAS;AAEnB,UAAMsB,MAAMJ,KAAKE,YAAW;AAC5B,UAAMC,UAAUC,IAAID;AACpB,UAAMQ,SAASP,IAAIO;AAEnB,UAAMe,gBAAgB,KAAK3C,gBAAgBwC,IAAIpB,OAAAA;AAC/C,QAAI,CAACuB,cAAe;AAEpB,UAAMC,gBAAe3B,UAAK4B,sBAAL5B,mBAAwBW;AAC7C,QAAIgB,gBAAgBD,cAAcG,IAAIF,YAAAA,GAAe;AACnDD,oBAAcI,IAAInB,MAAAA;IACpB;EACF;EAEAoB,MAAM/B,MAA0B;AAC9B,QAAI,CAAC,KAAKlB,QAAS;AAEnB,UAAMsB,MAAMJ,KAAKE,YAAW;AAC5B,UAAMC,UAAUC,IAAID;AACpB,UAAMQ,SAASP,IAAIO;AAEnB,UAAMe,gBAAgB,KAAK3C,gBAAgBwC,IAAIpB,OAAAA;AAC/C,QAAI,CAACuB,iBAAiB,CAACA,cAAcG,IAAIlB,MAAAA,EAAS;AAElD,UAAMW,QAAQ,KAAKrC,eAAesC,IAAIpB,OAAAA;AACtC,QAAImB,OAAO;AACTA,YAAMU,KAAK,KAAKC,cAAcjC,IAAAA,CAAAA;IAChC;EACF;EAEQiC,cAAcjC,MAA8B;AA/ItD;AAgJI,UAAMI,MAAMJ,KAAKE,YAAW;AAE5B,UAAMgC,OAAiB;MACrBvB,QAAQP,IAAIO;MACZgB,gBAAc3B,UAAK4B,sBAAL5B,mBAAwBW,WAAU;MAChDG,MAAMd,KAAKc;MACXqB,MAAMC,oBAASpC,KAAKmC,IAAI,KAAK;;MAE7BE,YACEC,OAAOtC,KAAKqC,UAAU,CAAA,CAAE,IAAI,cAC5BC,OAAOtC,KAAKqC,UAAU,CAAA,CAAE,GACxBE,SAAQ;MACVC,UACEF,OAAOtC,KAAKwC,QAAQ,CAAA,CAAE,IAAI,cAC1BF,OAAOtC,KAAKwC,QAAQ,CAAA,CAAE,GACtBD,SAAQ;IACZ;AAEA,QAAIvC,KAAKyC,OAAOC,SAASC,0BAAeC,OAAO;AAC7CV,WAAKO,SAASE,0BAAe3C,KAAKyC,OAAOC,IAAI;IAC/C;AAEA,QAAI1C,KAAK6C,cAAcC,OAAOC,KAAK/C,KAAK6C,UAAU,EAAEG,SAAS,GAAG;AAC9Dd,WAAKW,aAAa;QAAE,GAAG7C,KAAK6C;MAAW;IACzC;AAEA,WAAOX;EACT;EAEQ1C,WAAW;AACjB,UAAMqB,MAAMD,KAAKC,IAAG;AACpB,eAAW,CAACV,SAASkC,SAAAA,KAAc,KAAKnD,iBAAiB;AACvD,UAAI2B,MAAMwB,YAAYzD,eAAe;AACnC,aAAKK,eAAeuC,OAAOrB,OAAAA;AAC3B,aAAKpB,gBAAgByC,OAAOrB,OAAAA;AAC5B,aAAKjB,gBAAgBsC,OAAOrB,OAAAA;MAC9B;IACF;EACF;EAEA,MAAM8C,WAA0B;AAC9B,SAAKnE,UAAU;AACf,SAAKC,gBAAgBmE,MAAK;AAC1B,SAAKjE,eAAeiE,MAAK;AACzB,SAAKhE,gBAAgBgE,MAAK;AAC1B,QAAI,KAAK/D,oBAAoB;AAC3BgE,oBAAc,KAAKhE,kBAAkB;IACvC;EACF;EAEA,MAAMiE,aAA4B;EAElC;AACF;AAjKqBvE;AAArB,IAAqBA,gBAArB;AAmKO,MAAMwE,yBAAN,MAAMA,uBAAAA;EACHC,eAA0C;AAChD,QAAI;AACF,aAAOC,6BAAeC,YAAW,EAAGC;IACtC,QAAQ;AACN,aAAO1D;IACT;EACF;EAEA0B,QAAQzB,MAAY;AAhNtB;AAiNI,eAAKsD,aAAY,MAAjB,mBAAqB7B,QAAQzB;EAC/B;EAEA+B,MAAM/B,MAAoB;AApN5B;AAqNI,eAAKsD,aAAY,MAAjB,mBAAqBvB,MAAM/B;EAC7B;EAEA,MAAMiD,WAAW;AAxNnB;AAyNI,eAAKK,aAAY,MAAjB,mBAAqBL;EACvB;EAEA,MAAMG,aAAa;AA5NrB;AA6NI,eAAKE,aAAY,MAAjB,mBAAqBF;EACvB;AACF;AAxBaC;AAAN,IAAMA,wBAAN;","names":["TRACE_MAX_AGE","SpanCollector","enabled","includedSpanIds","Map","collectedSpans","traceStartTimes","maintainIntervalId","tracer","trace","getTracer","setInterval","maintain","startSpan","setName","runInContext","fn","enterContext","end","undefined","span","spanCtx","spanContext","traceId","ctx","setSpan","context","active","ended","set","Set","spanId","Date","now","name","updateName","with","contextManager","_getContextManager","_asyncLocalStorage","enterWith","getAndClearSpans","spans","get","delete","onStart","includedSpans","parentSpanId","parentSpanContext","has","add","onEnd","push","serializeSpan","data","kind","SpanKind","startTime","BigInt","toString","endTime","status","code","SpanStatusCode","UNSET","attributes","Object","keys","length","shutdown","clear","clearInterval","forceFlush","ApitallySpanProcessor","getCollector","ApitallyClient","getInstance","spanCollector"]}