{"version":3,"sources":["../../src/tracing/exporter.ts"],"sourcesContent":["/**\n * Copyright 2024 Google LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { HrTime, SpanKind } from '@opentelemetry/api';\nimport {\n  ExportResult,\n  ExportResultCode,\n  hrTimeToMilliseconds,\n} from '@opentelemetry/core';\nimport { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base';\nimport { logger } from '../logging.js';\nimport { deleteUndefinedProps } from '../utils.js';\nimport { SpanData, TraceData } from './types.js';\n\nexport let telemetryServerUrl: string | undefined;\n\n/**\n * @hidden\n */\nexport function setTelemetryServerUrl(url: string) {\n  telemetryServerUrl = url;\n}\n\n/**\n * Exports collected OpenTelemetetry spans to the telemetry server.\n */\nexport class TraceServerExporter implements SpanExporter {\n  /**\n   * Export spans.\n   * @param spans\n   * @param resultCallback\n   */\n  export(\n    spans: ReadableSpan[],\n    resultCallback: (result: ExportResult) => void\n  ): void {\n    this._sendSpans(spans, resultCallback);\n  }\n\n  /**\n   * Shutdown the exporter.\n   */\n  shutdown(): Promise<void> {\n    this._sendSpans([]);\n    return this.forceFlush();\n  }\n\n  /**\n   * Converts span info into trace store format.\n   * @param span\n   */\n  private _exportInfo(span: ReadableSpan): SpanData {\n    const spanData: Partial<SpanData> = {\n      spanId: span.spanContext().spanId,\n      traceId: span.spanContext().traceId,\n      startTime: transformTime(span.startTime),\n      endTime: transformTime(span.endTime),\n      attributes: { ...span.attributes },\n      displayName: span.name,\n      links: span.links,\n      spanKind: SpanKind[span.kind],\n      parentSpanId: span.parentSpanId,\n      sameProcessAsParentSpan: { value: !span.spanContext().isRemote },\n      status: span.status,\n      timeEvents: {\n        timeEvent: span.events.map((e) => ({\n          time: transformTime(e.time),\n          annotation: {\n            attributes: e.attributes ?? {},\n            description: e.name,\n          },\n        })),\n      },\n    };\n    if (span.instrumentationLibrary !== undefined) {\n      spanData.instrumentationLibrary = {\n        name: span.instrumentationLibrary.name,\n      };\n      if (span.instrumentationLibrary.schemaUrl !== undefined) {\n        spanData.instrumentationLibrary.schemaUrl =\n          span.instrumentationLibrary.schemaUrl;\n      }\n      if (span.instrumentationLibrary.version !== undefined) {\n        spanData.instrumentationLibrary.version =\n          span.instrumentationLibrary.version;\n      }\n    }\n    deleteUndefinedProps(spanData);\n    return spanData as SpanData;\n  }\n\n  /**\n   * Exports any pending spans in exporter\n   */\n  forceFlush(): Promise<void> {\n    return Promise.resolve();\n  }\n\n  private async _sendSpans(\n    spans: ReadableSpan[],\n    done?: (result: ExportResult) => void\n  ): Promise<void> {\n    const traces = {} as Record<string, ReadableSpan[]>;\n    for (const span of spans) {\n      if (!traces[span.spanContext().traceId]) {\n        traces[span.spanContext().traceId] = [];\n      }\n      traces[span.spanContext().traceId].push(span);\n    }\n    let error = false;\n    for (const traceId of Object.keys(traces)) {\n      try {\n        await this.save(traceId, traces[traceId]);\n      } catch (e) {\n        error = true;\n        logger.error(`Failed to save trace ${traceId}`, e);\n      }\n      if (done) {\n        return done({\n          code: error ? ExportResultCode.FAILED : ExportResultCode.SUCCESS,\n        });\n      }\n    }\n  }\n\n  private async save(traceId, spans: ReadableSpan[]): Promise<void> {\n    if (!telemetryServerUrl) {\n      logger.debug(\n        `Telemetry server is not configured, trace ${traceId} not saved!`\n      );\n      return;\n    }\n    // TODO: add interface for Firestore doc\n    const data = {\n      traceId,\n      spans: {},\n    } as TraceData;\n    for (const span of spans) {\n      const convertedSpan = this._exportInfo(span);\n      data.spans[convertedSpan.spanId] = convertedSpan;\n      if (!convertedSpan.parentSpanId) {\n        data.displayName = convertedSpan.displayName;\n        data.startTime = convertedSpan.startTime;\n        data.endTime = convertedSpan.endTime;\n      }\n    }\n    await fetch(`${telemetryServerUrl}/api/traces`, {\n      method: 'POST',\n      headers: {\n        Accept: 'application/json',\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify(data),\n    });\n  }\n}\n\n// Converts an HrTime to milliseconds.\nfunction transformTime(time: HrTime) {\n  return hrTimeToMilliseconds(time);\n}\n"],"mappings":"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","names":[]}