import OpenTelemetryAPI from "@opentelemetry/api";
import { logs } from "@opentelemetry/api-logs";
import { OTLPLogExporter } from "@opentelemetry/exporter-logs-otlp-http";
import { OTLPMetricExporter } from "@opentelemetry/exporter-metrics-otlp-proto";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { AWSXRayIdGenerator } from "@opentelemetry/id-generator-aws-xray";
import { CompressionAlgorithm } from "@opentelemetry/otlp-exporter-base";
import { Resource } from "@opentelemetry/resources";
import { BatchLogRecordProcessor, LoggerProvider, } from "@opentelemetry/sdk-logs";
import { Aggregation, MeterProvider, PeriodicExportingMetricReader, } from "@opentelemetry/sdk-metrics";
import * as opentelemetry from "@opentelemetry/sdk-node";
import { BatchSpanProcessor, } from "@opentelemetry/sdk-trace-base";
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION, } from "@opentelemetry/semantic-conventions";
import URL from "../../Types/API/URL";
import { AppVersion, Env, DisableTelemetry } from "../EnvironmentConfig";
import logger from "./Logger";
import GracefulShutdown, { ShutdownPriority } from "./GracefulShutdown";
import ContextSpanProcessor from "./Telemetry/ContextSpanProcessor";
import RuntimeMetrics from "./Telemetry/RuntimeMetrics";
export var SpanStatusCode;
(function (SpanStatusCode) {
    SpanStatusCode[SpanStatusCode["UNSET"] = 0] = "UNSET";
    SpanStatusCode[SpanStatusCode["OK"] = 1] = "OK";
    SpanStatusCode[SpanStatusCode["ERROR"] = 2] = "ERROR";
})(SpanStatusCode || (SpanStatusCode = {}));
class Telemetry {
    static getHeaders() {
        if (!process.env["OPENTELEMETRY_EXPORTER_OTLP_HEADERS"]) {
            return {};
        }
        const headersStrings = process.env["OPENTELEMETRY_EXPORTER_OTLP_HEADERS"].split(";");
        const headers = {};
        for (const headerString of headersStrings) {
            const header = headerString.split("=");
            if (header.length === 2) {
                headers[header[0].toString()] = header[1].toString();
            }
        }
        return headers;
    }
    static getOtlpEndpoint() {
        if (!process.env["OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT"]) {
            return null;
        }
        return URL.fromString(process.env["OPENTELEMETRY_EXPORTER_OTLP_ENDPOINT"] || "");
    }
    static getOltpLogsEndpoint() {
        const oltpEndpoint = this.getOtlpEndpoint();
        if (!oltpEndpoint) {
            return null;
        }
        return URL.fromString(oltpEndpoint.toString() + "/v1/logs");
    }
    static getOltpMetricsEndpoint() {
        const oltpEndpoint = this.getOtlpEndpoint();
        if (!oltpEndpoint) {
            return null;
        }
        return URL.fromString(oltpEndpoint.toString() + "/v1/metrics");
    }
    static getOltpTracesEndpoint() {
        const oltpEndpoint = this.getOtlpEndpoint();
        if (!oltpEndpoint) {
            return null;
        }
        return URL.fromString(oltpEndpoint.toString() + "/v1/traces");
    }
    static getResource(data) {
        return new Resource({
            [ATTR_SERVICE_NAME]: data.serviceName,
            [ATTR_SERVICE_VERSION]: AppVersion,
            ["deployment.environment"]: Env,
        });
    }
    static init(data) {
        this.serviceName = data.serviceName;
        if (DisableTelemetry) {
            return null;
        }
        if (!this.sdk) {
            const headers = this.getHeaders();
            const hasHeaders = Object.keys(headers).length > 0;
            let traceExporter = undefined;
            if (this.getOltpTracesEndpoint() && hasHeaders) {
                traceExporter = new OTLPTraceExporter({
                    url: this.getOltpTracesEndpoint().toString(),
                    headers: headers,
                    compression: CompressionAlgorithm.GZIP,
                });
            }
            if (this.getOltpMetricsEndpoint() && hasHeaders) {
                const metricExporter = new OTLPMetricExporter({
                    url: this.getOltpMetricsEndpoint().toString(),
                    headers: headers,
                    compression: CompressionAlgorithm.GZIP,
                });
                // Force an SDK-side aggregation selector that matches the modern metrics API.
                if (typeof metricExporter
                    .selectAggregation === "function") {
                    metricExporter.selectAggregation = () => {
                        return Aggregation.Default();
                    };
                }
                this.metricReader = new PeriodicExportingMetricReader({
                    exporter: metricExporter,
                });
            }
            const resource = this.getResource({
                serviceName: data.serviceName,
            });
            const logRecordProcessors = [];
            const loggerProviderResource = resource;
            if (typeof loggerProviderResource.getRawAttributes !== "function") {
                loggerProviderResource.getRawAttributes = () => {
                    return Object.entries(resource.attributes);
                };
            }
            if (this.getOltpLogsEndpoint() && hasHeaders) {
                const logExporter = new OTLPLogExporter({
                    url: this.getOltpLogsEndpoint().toString(),
                    headers: headers,
                    compression: CompressionAlgorithm.GZIP,
                });
                logRecordProcessors.push(new BatchLogRecordProcessor(logExporter));
            }
            const loggerProviderConfig = {
                resource: loggerProviderResource,
            };
            if (logRecordProcessors.length > 0) {
                loggerProviderConfig.processors = logRecordProcessors;
            }
            this.loggerProvider = new LoggerProvider(loggerProviderConfig);
            logs.setGlobalLoggerProvider(this.loggerProvider);
            const nodeSdkConfiguration = {
                idGenerator: new AWSXRayIdGenerator(),
                instrumentations: [],
                resource: loggerProviderResource,
                autoDetectResources: true,
            };
            /*
             * Always run the ContextSpanProcessor so the ambient TelemetryContext
             * attributes (projectId, userId, monitorId, incidentId, requestId, ...)
             * are stamped onto every span at creation. The BatchSpanProcessor that
             * actually exports spans is added after it, and only when an exporter is
             * configured. (traceExporter is deprecated in favour of spanProcessors.)
             */
            const spanProcessors = [new ContextSpanProcessor()];
            if (traceExporter) {
                spanProcessors.push(new BatchSpanProcessor(traceExporter));
            }
            nodeSdkConfiguration.spanProcessors = spanProcessors;
            /*
             * We will skip this becasue we're attachng this metric reader to the meter provider later.
             * if (this.metricReader) {
             *   nodeSdkConfiguration.metricReader = this.metricReader;
             * }
             */
            if (logRecordProcessors.length > 0) {
                nodeSdkConfiguration.logRecordProcessors = logRecordProcessors;
            }
            const sdk = new opentelemetry.NodeSDK(nodeSdkConfiguration);
            this.getMeterProvider();
            this.getMeter();
            /*
             * Flush traces / metrics / logs last (Telemetry tier) so spans and logs
             * emitted by the rest of the shutdown still get exported. GracefulShutdown
             * owns process.exit now — this handler must NOT call it itself, or it
             * would race the other tiers and abandon the datastore pools (the exact
             * bug this replaced).
             */
            GracefulShutdown.registerHandler("Telemetry", ShutdownPriority.Telemetry, () => {
                return sdk.shutdown();
            });
            sdk.start();
            this.sdk = sdk;
            try {
                RuntimeMetrics.init();
            }
            catch (err) {
                logger.error("Failed to initialize runtime metrics");
                logger.error(err);
            }
        }
        return this.sdk;
    }
    static getLogger() {
        if (!this.loggerProvider) {
            return null;
        }
        return this.loggerProvider.getLogger("default");
    }
    static getMeterProvider() {
        if (!this.meterProvider) {
            this.meterProvider = new MeterProvider({
                resource: this.getResource({
                    serviceName: this.serviceName || "default",
                }),
                readers: this.metricReader ? [this.metricReader] : [],
            });
            OpenTelemetryAPI.metrics.setGlobalMeterProvider(this.meterProvider);
        }
        return this.meterProvider;
    }
    static getMeter() {
        if (!this.meter) {
            this.meter = OpenTelemetryAPI.metrics.getMeter("default");
        }
        return this.meter;
    }
    static getCounter(data) {
        const { name, description } = data;
        const metricOptions = {
            description: description,
        };
        if (data.unit) {
            metricOptions.unit = data.unit;
        }
        const counter = this.getMeter().createCounter(name, metricOptions);
        return counter;
    }
    // guage
    static getGauge(data) {
        const { name, description } = data;
        const metricOptions = {
            description: description,
        };
        if (data.unit) {
            metricOptions.unit = data.unit;
        }
        const guage = this.getMeter().createUpDownCounter(name, metricOptions);
        return guage;
    }
    // histogram
    static getHistogram(data) {
        const { name, description } = data;
        const metricOptions = {
            description: description,
        };
        if (data.unit) {
            metricOptions.unit = data.unit;
        }
        const histogram = this.getMeter().createHistogram(name, metricOptions);
        return histogram;
    }
    static getObservableGauge(data) {
        const metricOptions = {
            description: data.description,
        };
        if (data.unit) {
            metricOptions.unit = data.unit;
        }
        const gauge = this.getMeter().createObservableGauge(data.name, metricOptions);
        gauge.addCallback(data.callback);
        return gauge;
    }
    static getObservableCounter(data) {
        const metricOptions = {
            description: data.description,
        };
        if (data.unit) {
            metricOptions.unit = data.unit;
        }
        const counter = this.getMeter().createObservableCounter(data.name, metricOptions);
        counter.addCallback(data.callback);
        return counter;
    }
    static getObservableUpDownCounter(data) {
        const metricOptions = {
            description: data.description,
        };
        if (data.unit) {
            metricOptions.unit = data.unit;
        }
        const counter = this.getMeter().createObservableUpDownCounter(data.name, metricOptions);
        counter.addCallback(data.callback);
        return counter;
    }
    static isMetricsEnabled() {
        if (DisableTelemetry) {
            return false;
        }
        return Boolean(this.metricReader);
    }
    static getTracer() {
        const tracer = OpenTelemetryAPI.trace.getTracer("default");
        return tracer;
    }
    static startActiveSpan(data) {
        const { name } = data;
        return this.getTracer().startActiveSpan(name, data.options || {}, data.fn);
    }
    static recordExceptionMarkSpanAsErrorAndEndSpan(data) {
        const { span, exception } = data;
        // log the exception as well
        logger.error(exception);
        span.recordException(exception);
        span.setStatus({
            code: SpanStatusCode.ERROR,
        });
        this.endSpan(span);
    }
    static endSpan(span) {
        span.end();
    }
}
Telemetry.sdk = null;
Telemetry.logger = null;
Telemetry.meter = null;
Telemetry.meterProvider = null;
Telemetry.loggerProvider = null;
Telemetry.serviceName = null;
export default Telemetry;
//# sourceMappingURL=Telemetry.js.map