// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
import { SpanStatusCode, createSpanFunction, getTraceParentHeader, isSpanContextValid, } from "@azure/core-tracing";
import { SpanKind } from "@azure/core-tracing";
import { getUserAgentValue } from "../util/userAgent";
import { logger } from "../log";
const createSpan = createSpanFunction({
    packagePrefix: "",
    namespace: "",
});
/**
 * The programmatic identifier of the tracingPolicy.
 */
export const tracingPolicyName = "tracingPolicy";
/**
 * A simple policy to create OpenTelemetry Spans for each request made by the pipeline
 * that has SpanOptions with a parent.
 * Requests made without a parent Span will not be recorded.
 * @param options - Options to configure the telemetry logged by the tracing policy.
 */
export function tracingPolicy(options = {}) {
    const userAgent = getUserAgentValue(options.userAgentPrefix);
    return {
        name: tracingPolicyName,
        async sendRequest(request, next) {
            var _a;
            if (!((_a = request.tracingOptions) === null || _a === void 0 ? void 0 : _a.tracingContext)) {
                return next(request);
            }
            const span = tryCreateSpan(request, userAgent);
            if (!span) {
                return next(request);
            }
            try {
                const response = await next(request);
                tryProcessResponse(span, response);
                return response;
            }
            catch (err) {
                tryProcessError(span, err);
                throw err;
            }
        },
    };
}
function tryCreateSpan(request, userAgent) {
    var _a, _b, _c;
    try {
        const createSpanOptions = Object.assign(Object.assign({}, (_a = request.tracingOptions) === null || _a === void 0 ? void 0 : _a.spanOptions), { kind: SpanKind.CLIENT });
        // Passing spanOptions as part of tracingOptions to maintain compatibility @azure/core-tracing@preview.13 and earlier.
        // We can pass this as a separate parameter once we upgrade to the latest core-tracing.
        // As per spec, we do not need to differentiate between HTTP and HTTPS in span name.
        const { span } = createSpan(`HTTP ${request.method}`, {
            tracingOptions: Object.assign(Object.assign({}, request.tracingOptions), { spanOptions: createSpanOptions }),
        });
        // If the span is not recording, don't do any more work.
        if (!span.isRecording()) {
            span.end();
            return undefined;
        }
        const namespaceFromContext = (_c = (_b = request.tracingOptions) === null || _b === void 0 ? void 0 : _b.tracingContext) === null || _c === void 0 ? void 0 : _c.getValue(Symbol.for("az.namespace"));
        if (typeof namespaceFromContext === "string") {
            span.setAttribute("az.namespace", namespaceFromContext);
        }
        span.setAttributes({
            "http.method": request.method,
            "http.url": request.url,
            requestId: request.requestId,
        });
        if (userAgent) {
            span.setAttribute("http.user_agent", userAgent);
        }
        // set headers
        const spanContext = span.spanContext();
        const traceParentHeader = getTraceParentHeader(spanContext);
        if (traceParentHeader && isSpanContextValid(spanContext)) {
            request.headers.set("traceparent", traceParentHeader);
            const traceState = spanContext.traceState && spanContext.traceState.serialize();
            // if tracestate is set, traceparent MUST be set, so only set tracestate after traceparent
            if (traceState) {
                request.headers.set("tracestate", traceState);
            }
        }
        return span;
    }
    catch (error) {
        logger.warning(`Skipping creating a tracing span due to an error: ${error.message}`);
        return undefined;
    }
}
function tryProcessError(span, err) {
    try {
        span.setStatus({
            code: SpanStatusCode.ERROR,
            message: err.message,
        });
        if (err.statusCode) {
            span.setAttribute("http.status_code", err.statusCode);
        }
        span.end();
    }
    catch (error) {
        logger.warning(`Skipping tracing span processing due to an error: ${error.message}`);
    }
}
function tryProcessResponse(span, response) {
    try {
        span.setAttribute("http.status_code", response.status);
        const serviceRequestId = response.headers.get("x-ms-request-id");
        if (serviceRequestId) {
            span.setAttribute("serviceRequestId", serviceRequestId);
        }
        span.setStatus({
            code: SpanStatusCode.OK,
        });
        span.end();
    }
    catch (error) {
        logger.warning(`Skipping tracing span processing due to an error: ${error.message}`);
    }
}
//# sourceMappingURL=tracingPolicy.js.map