"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HttpInstrumentation = void 0;
/*
 * Copyright The OpenTelemetry Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
const api_1 = require("@opentelemetry/api");
const core_1 = require("@opentelemetry/core");
const semver = require("semver");
const url = require("url");
const version_1 = require("./version");
const instrumentation_1 = require("@opentelemetry/instrumentation");
const core_2 = require("@opentelemetry/core");
const events_1 = require("events");
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
const utils_1 = require("./utils");
/**
 * `node:http` and `node:https` instrumentation for OpenTelemetry
 */
class HttpInstrumentation extends instrumentation_1.InstrumentationBase {
    constructor(config = {}) {
        super('@opentelemetry/instrumentation-http', version_1.VERSION, config);
        /** keep track on spans not ended */
        this._spanNotEnded = new WeakSet();
        this._semconvStability = 2 /* OLD */;
        this._headerCapture = this._createHeaderCapture();
        for (const entry of (0, core_2.getEnv)().OTEL_SEMCONV_STABILITY_OPT_IN) {
            if (entry.toLowerCase() === 'http/dup') {
                // http/dup takes highest precedence. If it is found, there is no need to read the rest of the list
                this._semconvStability = 3 /* DUPLICATE */;
                break;
            }
            else if (entry.toLowerCase() === 'http') {
                this._semconvStability = 1 /* STABLE */;
            }
        }
    }
    _updateMetricInstruments() {
        this._oldHttpServerDurationHistogram = this.meter.createHistogram('http.server.duration', {
            description: 'Measures the duration of inbound HTTP requests.',
            unit: 'ms',
            valueType: api_1.ValueType.DOUBLE,
        });
        this._oldHttpClientDurationHistogram = this.meter.createHistogram('http.client.duration', {
            description: 'Measures the duration of outbound HTTP requests.',
            unit: 'ms',
            valueType: api_1.ValueType.DOUBLE,
        });
        this._stableHttpServerDurationHistogram = this.meter.createHistogram(semantic_conventions_1.METRIC_HTTP_SERVER_REQUEST_DURATION, {
            description: 'Duration of HTTP server requests.',
            unit: 's',
            valueType: api_1.ValueType.DOUBLE,
            advice: {
                explicitBucketBoundaries: [
                    0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5,
                    7.5, 10,
                ],
            },
        });
        this._stableHttpClientDurationHistogram = this.meter.createHistogram(semantic_conventions_1.METRIC_HTTP_CLIENT_REQUEST_DURATION, {
            description: 'Duration of HTTP client requests.',
            unit: 's',
            valueType: api_1.ValueType.DOUBLE,
            advice: {
                explicitBucketBoundaries: [
                    0.005, 0.01, 0.025, 0.05, 0.075, 0.1, 0.25, 0.5, 0.75, 1, 2.5, 5,
                    7.5, 10,
                ],
            },
        });
    }
    _recordServerDuration(durationMs, oldAttributes, stableAttributes) {
        if ((this._semconvStability & 2 /* OLD */) ===
            2 /* OLD */) {
            // old histogram is counted in MS
            this._oldHttpServerDurationHistogram.record(durationMs, oldAttributes);
        }
        if ((this._semconvStability & 1 /* STABLE */) ===
            1 /* STABLE */) {
            // stable histogram is counted in S
            this._stableHttpServerDurationHistogram.record(durationMs / 1000, stableAttributes);
        }
    }
    _recordClientDuration(durationMs, oldAttributes, stableAttributes) {
        if ((this._semconvStability & 2 /* OLD */) ===
            2 /* OLD */) {
            // old histogram is counted in MS
            this._oldHttpClientDurationHistogram.record(durationMs, oldAttributes);
        }
        if ((this._semconvStability & 1 /* STABLE */) ===
            1 /* STABLE */) {
            // stable histogram is counted in S
            this._stableHttpClientDurationHistogram.record(durationMs / 1000, stableAttributes);
        }
    }
    setConfig(config = {}) {
        super.setConfig(config);
        this._headerCapture = this._createHeaderCapture();
    }
    init() {
        return [this._getHttpsInstrumentation(), this._getHttpInstrumentation()];
    }
    _getHttpInstrumentation() {
        return new instrumentation_1.InstrumentationNodeModuleDefinition('http', ['*'], (moduleExports) => {
            const isESM = moduleExports[Symbol.toStringTag] === 'Module';
            if (!this.getConfig().disableOutgoingRequestInstrumentation) {
                const patchedRequest = this._wrap(moduleExports, 'request', this._getPatchOutgoingRequestFunction('http'));
                const patchedGet = this._wrap(moduleExports, 'get', this._getPatchOutgoingGetFunction(patchedRequest));
                if (isESM) {
                    // To handle `import http from 'http'`, which returns the default
                    // export, we need to set `module.default.*`.
                    moduleExports.default.request = patchedRequest;
                    moduleExports.default.get = patchedGet;
                }
            }
            if (!this.getConfig().disableIncomingRequestInstrumentation) {
                this._wrap(moduleExports.Server.prototype, 'emit', this._getPatchIncomingRequestFunction('http'));
            }
            return moduleExports;
        }, (moduleExports) => {
            if (moduleExports === undefined)
                return;
            if (!this.getConfig().disableOutgoingRequestInstrumentation) {
                this._unwrap(moduleExports, 'request');
                this._unwrap(moduleExports, 'get');
            }
            if (!this.getConfig().disableIncomingRequestInstrumentation) {
                this._unwrap(moduleExports.Server.prototype, 'emit');
            }
        });
    }
    _getHttpsInstrumentation() {
        return new instrumentation_1.InstrumentationNodeModuleDefinition('https', ['*'], (moduleExports) => {
            const isESM = moduleExports[Symbol.toStringTag] === 'Module';
            if (!this.getConfig().disableOutgoingRequestInstrumentation) {
                const patchedRequest = this._wrap(moduleExports, 'request', this._getPatchHttpsOutgoingRequestFunction('https'));
                const patchedGet = this._wrap(moduleExports, 'get', this._getPatchHttpsOutgoingGetFunction(patchedRequest));
                if (isESM) {
                    // To handle `import https from 'https'`, which returns the default
                    // export, we need to set `module.default.*`.
                    moduleExports.default.request = patchedRequest;
                    moduleExports.default.get = patchedGet;
                }
            }
            if (!this.getConfig().disableIncomingRequestInstrumentation) {
                this._wrap(moduleExports.Server.prototype, 'emit', this._getPatchIncomingRequestFunction('https'));
            }
            return moduleExports;
        }, (moduleExports) => {
            if (moduleExports === undefined)
                return;
            if (!this.getConfig().disableOutgoingRequestInstrumentation) {
                this._unwrap(moduleExports, 'request');
                this._unwrap(moduleExports, 'get');
            }
            if (!this.getConfig().disableIncomingRequestInstrumentation) {
                this._unwrap(moduleExports.Server.prototype, 'emit');
            }
        });
    }
    /**
     * Creates spans for incoming requests, restoring spans' context if applied.
     */
    _getPatchIncomingRequestFunction(component) {
        return (original) => {
            return this._incomingRequestFunction(component, original);
        };
    }
    /**
     * Creates spans for outgoing requests, sending spans' context for distributed
     * tracing.
     */
    _getPatchOutgoingRequestFunction(component) {
        return (original) => {
            return this._outgoingRequestFunction(component, original);
        };
    }
    _getPatchOutgoingGetFunction(clientRequest) {
        return (_original) => {
            // Re-implement http.get. This needs to be done (instead of using
            // getPatchOutgoingRequestFunction to patch it) because we need to
            // set the trace context header before the returned http.ClientRequest is
            // ended. The Node.js docs state that the only differences between
            // request and get are that (1) get defaults to the HTTP GET method and
            // (2) the returned request object is ended immediately. The former is
            // already true (at least in supported Node versions up to v10), so we
            // simply follow the latter. Ref:
            // https://nodejs.org/dist/latest/docs/api/http.html#http_http_get_options_callback
            // https://github.com/googleapis/cloud-trace-nodejs/blob/master/src/instrumentations/instrumentation-http.ts#L198
            return function outgoingGetRequest(options, ...args) {
                const req = clientRequest(options, ...args);
                req.end();
                return req;
            };
        };
    }
    /** Patches HTTPS outgoing requests */
    _getPatchHttpsOutgoingRequestFunction(component) {
        return (original) => {
            const instrumentation = this;
            return function httpsOutgoingRequest(
            // eslint-disable-next-line node/no-unsupported-features/node-builtins
            options, ...args) {
                var _a;
                // Makes sure options will have default HTTPS parameters
                if (component === 'https' &&
                    typeof options === 'object' &&
                    ((_a = options === null || options === void 0 ? void 0 : options.constructor) === null || _a === void 0 ? void 0 : _a.name) !== 'URL') {
                    options = Object.assign({}, options);
                    instrumentation._setDefaultOptions(options);
                }
                return instrumentation._getPatchOutgoingRequestFunction(component)(original)(options, ...args);
            };
        };
    }
    _setDefaultOptions(options) {
        options.protocol = options.protocol || 'https:';
        options.port = options.port || 443;
    }
    /** Patches HTTPS outgoing get requests */
    _getPatchHttpsOutgoingGetFunction(clientRequest) {
        return (original) => {
            const instrumentation = this;
            return function httpsOutgoingRequest(
            // eslint-disable-next-line node/no-unsupported-features/node-builtins
            options, ...args) {
                return instrumentation._getPatchOutgoingGetFunction(clientRequest)(original)(options, ...args);
            };
        };
    }
    /**
     * Attach event listeners to a client request to end span and add span attributes.
     *
     * @param request The original request object.
     * @param span representing the current operation
     * @param startTime representing the start time of the request to calculate duration in Metric
     * @param oldMetricAttributes metric attributes for old semantic conventions
     * @param stableMetricAttributes metric attributes for new semantic conventions
     */
    _traceClientRequest(request, span, startTime, oldMetricAttributes, stableMetricAttributes) {
        if (this.getConfig().requestHook) {
            this._callRequestHook(span, request);
        }
        /**
         * Determines if the request has errored or the response has ended/errored.
         */
        let responseFinished = false;
        /*
         * User 'response' event listeners can be added before our listener,
         * force our listener to be the first, so response emitter is bound
         * before any user listeners are added to it.
         */
        request.prependListener('response', (response) => {
            this._diag.debug('outgoingRequest on response()');
            if (request.listenerCount('response') <= 1) {
                response.resume();
            }
            const responseAttributes = (0, utils_1.getOutgoingRequestAttributesOnResponse)(response, this._semconvStability);
            span.setAttributes(responseAttributes);
            oldMetricAttributes = Object.assign(oldMetricAttributes, (0, utils_1.getOutgoingRequestMetricAttributesOnResponse)(responseAttributes));
            if (this.getConfig().responseHook) {
                this._callResponseHook(span, response);
            }
            this._headerCapture.client.captureRequestHeaders(span, header => request.getHeader(header));
            this._headerCapture.client.captureResponseHeaders(span, header => response.headers[header]);
            api_1.context.bind(api_1.context.active(), response);
            const endHandler = () => {
                this._diag.debug('outgoingRequest on end()');
                if (responseFinished) {
                    return;
                }
                responseFinished = true;
                let status;
                if (response.aborted && !response.complete) {
                    status = { code: api_1.SpanStatusCode.ERROR };
                }
                else {
                    // behaves same for new and old semconv
                    status = {
                        code: (0, utils_1.parseResponseStatus)(api_1.SpanKind.CLIENT, response.statusCode),
                    };
                }
                span.setStatus(status);
                if (this.getConfig().applyCustomAttributesOnSpan) {
                    (0, instrumentation_1.safeExecuteInTheMiddle)(() => this.getConfig().applyCustomAttributesOnSpan(span, request, response), () => { }, true);
                }
                this._closeHttpSpan(span, api_1.SpanKind.CLIENT, startTime, oldMetricAttributes, stableMetricAttributes);
            };
            response.on('end', endHandler);
            // See https://github.com/open-telemetry/opentelemetry-js/pull/3625#issuecomment-1475673533
            if (semver.lt(process.version, '16.0.0')) {
                response.on('close', endHandler);
            }
            response.on(events_1.errorMonitor, (error) => {
                this._diag.debug('outgoingRequest on error()', error);
                if (responseFinished) {
                    return;
                }
                responseFinished = true;
                (0, utils_1.setSpanWithError)(span, error, this._semconvStability);
                span.setStatus({
                    code: api_1.SpanStatusCode.ERROR,
                    message: error.message,
                });
                this._closeHttpSpan(span, api_1.SpanKind.CLIENT, startTime, oldMetricAttributes, stableMetricAttributes);
            });
        });
        request.on('close', () => {
            this._diag.debug('outgoingRequest on request close()');
            if (request.aborted || responseFinished) {
                return;
            }
            responseFinished = true;
            this._closeHttpSpan(span, api_1.SpanKind.CLIENT, startTime, oldMetricAttributes, stableMetricAttributes);
        });
        request.on(events_1.errorMonitor, (error) => {
            this._diag.debug('outgoingRequest on request error()', error);
            if (responseFinished) {
                return;
            }
            responseFinished = true;
            (0, utils_1.setSpanWithError)(span, error, this._semconvStability);
            this._closeHttpSpan(span, api_1.SpanKind.CLIENT, startTime, oldMetricAttributes, stableMetricAttributes);
        });
        this._diag.debug('http.ClientRequest return request');
        return request;
    }
    _incomingRequestFunction(component, original) {
        const instrumentation = this;
        return function incomingRequest(event, ...args) {
            // Only traces request events
            if (event !== 'request') {
                return original.apply(this, [event, ...args]);
            }
            const request = args[0];
            const response = args[1];
            const method = request.method || 'GET';
            instrumentation._diag.debug(`${component} instrumentation incomingRequest`);
            if ((0, instrumentation_1.safeExecuteInTheMiddle)(() => { var _a, _b; return (_b = (_a = instrumentation.getConfig()).ignoreIncomingRequestHook) === null || _b === void 0 ? void 0 : _b.call(_a, request); }, (e) => {
                if (e != null) {
                    instrumentation._diag.error('caught ignoreIncomingRequestHook error: ', e);
                }
            }, true)) {
                return api_1.context.with((0, core_1.suppressTracing)(api_1.context.active()), () => {
                    api_1.context.bind(api_1.context.active(), request);
                    api_1.context.bind(api_1.context.active(), response);
                    return original.apply(this, [event, ...args]);
                });
            }
            const headers = request.headers;
            const spanAttributes = (0, utils_1.getIncomingRequestAttributes)(request, {
                component: component,
                serverName: instrumentation.getConfig().serverName,
                hookAttributes: instrumentation._callStartSpanHook(request, instrumentation.getConfig().startIncomingSpanHook),
                semconvStability: instrumentation._semconvStability,
            }, instrumentation._diag);
            const spanOptions = {
                kind: api_1.SpanKind.SERVER,
                attributes: spanAttributes,
            };
            const startTime = (0, core_1.hrTime)();
            const oldMetricAttributes = (0, utils_1.getIncomingRequestMetricAttributes)(spanAttributes);
            // request method and url.scheme are both required span attributes
            const stableMetricAttributes = {
                [semantic_conventions_1.ATTR_HTTP_REQUEST_METHOD]: spanAttributes[semantic_conventions_1.ATTR_HTTP_REQUEST_METHOD],
                [semantic_conventions_1.ATTR_URL_SCHEME]: spanAttributes[semantic_conventions_1.ATTR_URL_SCHEME],
            };
            // recommended if and only if one was sent, same as span recommendation
            if (spanAttributes[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION]) {
                stableMetricAttributes[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION] =
                    spanAttributes[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION];
            }
            const ctx = api_1.propagation.extract(api_1.ROOT_CONTEXT, headers);
            const span = instrumentation._startHttpSpan(method, spanOptions, ctx);
            const rpcMetadata = {
                type: core_2.RPCType.HTTP,
                span,
            };
            return api_1.context.with((0, core_2.setRPCMetadata)(api_1.trace.setSpan(ctx, span), rpcMetadata), () => {
                api_1.context.bind(api_1.context.active(), request);
                api_1.context.bind(api_1.context.active(), response);
                if (instrumentation.getConfig().requestHook) {
                    instrumentation._callRequestHook(span, request);
                }
                if (instrumentation.getConfig().responseHook) {
                    instrumentation._callResponseHook(span, response);
                }
                instrumentation._headerCapture.server.captureRequestHeaders(span, header => request.headers[header]);
                // After 'error', no further events other than 'close' should be emitted.
                let hasError = false;
                response.on('close', () => {
                    if (hasError) {
                        return;
                    }
                    instrumentation._onServerResponseFinish(request, response, span, oldMetricAttributes, stableMetricAttributes, startTime);
                });
                response.on(events_1.errorMonitor, (err) => {
                    hasError = true;
                    instrumentation._onServerResponseError(span, oldMetricAttributes, stableMetricAttributes, startTime, err);
                });
                return (0, instrumentation_1.safeExecuteInTheMiddle)(() => original.apply(this, [event, ...args]), error => {
                    if (error) {
                        (0, utils_1.setSpanWithError)(span, error, instrumentation._semconvStability);
                        instrumentation._closeHttpSpan(span, api_1.SpanKind.SERVER, startTime, oldMetricAttributes, stableMetricAttributes);
                        throw error;
                    }
                });
            });
        };
    }
    _outgoingRequestFunction(component, original) {
        const instrumentation = this;
        return function outgoingRequest(options, ...args) {
            if (!(0, utils_1.isValidOptionsType)(options)) {
                return original.apply(this, [options, ...args]);
            }
            const extraOptions = typeof args[0] === 'object' &&
                (typeof options === 'string' || options instanceof url.URL)
                ? args.shift()
                : undefined;
            const { method, invalidUrl, optionsParsed } = (0, utils_1.getRequestInfo)(instrumentation._diag, options, extraOptions);
            /**
             * Node 8's https module directly call the http one so to avoid creating
             * 2 span for the same request we need to check that the protocol is correct
             * See: https://github.com/nodejs/node/blob/v8.17.0/lib/https.js#L245
             */
            if (component === 'http' &&
                semver.lt(process.version, '9.0.0') &&
                optionsParsed.protocol === 'https:') {
                return original.apply(this, [optionsParsed, ...args]);
            }
            if ((0, instrumentation_1.safeExecuteInTheMiddle)(() => {
                var _a, _b;
                return (_b = (_a = instrumentation
                    .getConfig()).ignoreOutgoingRequestHook) === null || _b === void 0 ? void 0 : _b.call(_a, optionsParsed);
            }, (e) => {
                if (e != null) {
                    instrumentation._diag.error('caught ignoreOutgoingRequestHook error: ', e);
                }
            }, true)) {
                return original.apply(this, [optionsParsed, ...args]);
            }
            const { hostname, port } = (0, utils_1.extractHostnameAndPort)(optionsParsed);
            const attributes = (0, utils_1.getOutgoingRequestAttributes)(optionsParsed, {
                component,
                port,
                hostname,
                hookAttributes: instrumentation._callStartSpanHook(optionsParsed, instrumentation.getConfig().startOutgoingSpanHook),
            }, instrumentation._semconvStability);
            const startTime = (0, core_1.hrTime)();
            const oldMetricAttributes = (0, utils_1.getOutgoingRequestMetricAttributes)(attributes);
            // request method, server address, and server port are both required span attributes
            const stableMetricAttributes = {
                [semantic_conventions_1.ATTR_HTTP_REQUEST_METHOD]: attributes[semantic_conventions_1.ATTR_HTTP_REQUEST_METHOD],
                [semantic_conventions_1.ATTR_SERVER_ADDRESS]: attributes[semantic_conventions_1.ATTR_SERVER_ADDRESS],
                [semantic_conventions_1.ATTR_SERVER_PORT]: attributes[semantic_conventions_1.ATTR_SERVER_PORT],
            };
            // required if and only if one was sent, same as span requirement
            if (attributes[semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE]) {
                stableMetricAttributes[semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE] =
                    attributes[semantic_conventions_1.ATTR_HTTP_RESPONSE_STATUS_CODE];
            }
            // recommended if and only if one was sent, same as span recommendation
            if (attributes[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION]) {
                stableMetricAttributes[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION] =
                    attributes[semantic_conventions_1.ATTR_NETWORK_PROTOCOL_VERSION];
            }
            const spanOptions = {
                kind: api_1.SpanKind.CLIENT,
                attributes,
            };
            const span = instrumentation._startHttpSpan(method, spanOptions);
            const parentContext = api_1.context.active();
            const requestContext = api_1.trace.setSpan(parentContext, span);
            if (!optionsParsed.headers) {
                optionsParsed.headers = {};
            }
            else {
                // Make a copy of the headers object to avoid mutating an object the
                // caller might have a reference to.
                optionsParsed.headers = Object.assign({}, optionsParsed.headers);
            }
            api_1.propagation.inject(requestContext, optionsParsed.headers);
            return api_1.context.with(requestContext, () => {
                /*
                 * The response callback is registered before ClientRequest is bound,
                 * thus it is needed to bind it before the function call.
                 */
                const cb = args[args.length - 1];
                if (typeof cb === 'function') {
                    args[args.length - 1] = api_1.context.bind(parentContext, cb);
                }
                const request = (0, instrumentation_1.safeExecuteInTheMiddle)(() => {
                    if (invalidUrl) {
                        // we know that the url is invalid, there's no point in injecting context as it will fail validation.
                        // Passing in what the user provided will give the user an error that matches what they'd see without
                        // the instrumentation.
                        return original.apply(this, [options, ...args]);
                    }
                    else {
                        return original.apply(this, [optionsParsed, ...args]);
                    }
                }, error => {
                    if (error) {
                        (0, utils_1.setSpanWithError)(span, error, instrumentation._semconvStability);
                        instrumentation._closeHttpSpan(span, api_1.SpanKind.CLIENT, startTime, oldMetricAttributes, stableMetricAttributes);
                        throw error;
                    }
                });
                instrumentation._diag.debug(`${component} instrumentation outgoingRequest`);
                api_1.context.bind(parentContext, request);
                return instrumentation._traceClientRequest(request, span, startTime, oldMetricAttributes, stableMetricAttributes);
            });
        };
    }
    _onServerResponseFinish(request, response, span, oldMetricAttributes, stableMetricAttributes, startTime) {
        const attributes = (0, utils_1.getIncomingRequestAttributesOnResponse)(request, response, this._semconvStability);
        oldMetricAttributes = Object.assign(oldMetricAttributes, (0, utils_1.getIncomingRequestMetricAttributesOnResponse)(attributes));
        stableMetricAttributes = Object.assign(stableMetricAttributes, (0, utils_1.getIncomingStableRequestMetricAttributesOnResponse)(attributes));
        this._headerCapture.server.captureResponseHeaders(span, header => response.getHeader(header));
        span.setAttributes(attributes).setStatus({
            code: (0, utils_1.parseResponseStatus)(api_1.SpanKind.SERVER, response.statusCode),
        });
        const route = attributes[semantic_conventions_1.SEMATTRS_HTTP_ROUTE];
        if (route) {
            span.updateName(`${request.method || 'GET'} ${route}`);
        }
        if (this.getConfig().applyCustomAttributesOnSpan) {
            (0, instrumentation_1.safeExecuteInTheMiddle)(() => this.getConfig().applyCustomAttributesOnSpan(span, request, response), () => { }, true);
        }
        this._closeHttpSpan(span, api_1.SpanKind.SERVER, startTime, oldMetricAttributes, stableMetricAttributes);
    }
    _onServerResponseError(span, oldMetricAttributes, stableMetricAttributes, startTime, error) {
        (0, utils_1.setSpanWithError)(span, error, this._semconvStability);
        // TODO get error attributes for metrics
        this._closeHttpSpan(span, api_1.SpanKind.SERVER, startTime, oldMetricAttributes, stableMetricAttributes);
    }
    _startHttpSpan(name, options, ctx = api_1.context.active()) {
        /*
         * If a parent is required but not present, we use a `NoopSpan` to still
         * propagate context without recording it.
         */
        const requireParent = options.kind === api_1.SpanKind.CLIENT
            ? this.getConfig().requireParentforOutgoingSpans
            : this.getConfig().requireParentforIncomingSpans;
        let span;
        const currentSpan = api_1.trace.getSpan(ctx);
        if (requireParent === true && currentSpan === undefined) {
            span = api_1.trace.wrapSpanContext(api_1.INVALID_SPAN_CONTEXT);
        }
        else if (requireParent === true && (currentSpan === null || currentSpan === void 0 ? void 0 : currentSpan.spanContext().isRemote)) {
            span = currentSpan;
        }
        else {
            span = this.tracer.startSpan(name, options, ctx);
        }
        this._spanNotEnded.add(span);
        return span;
    }
    _closeHttpSpan(span, spanKind, startTime, oldMetricAttributes, stableMetricAttributes) {
        if (!this._spanNotEnded.has(span)) {
            return;
        }
        span.end();
        this._spanNotEnded.delete(span);
        // Record metrics
        const duration = (0, core_1.hrTimeToMilliseconds)((0, core_1.hrTimeDuration)(startTime, (0, core_1.hrTime)()));
        if (spanKind === api_1.SpanKind.SERVER) {
            this._recordServerDuration(duration, oldMetricAttributes, stableMetricAttributes);
        }
        else if (spanKind === api_1.SpanKind.CLIENT) {
            this._recordClientDuration(duration, oldMetricAttributes, stableMetricAttributes);
        }
    }
    _callResponseHook(span, response) {
        (0, instrumentation_1.safeExecuteInTheMiddle)(() => this.getConfig().responseHook(span, response), () => { }, true);
    }
    _callRequestHook(span, request) {
        (0, instrumentation_1.safeExecuteInTheMiddle)(() => this.getConfig().requestHook(span, request), () => { }, true);
    }
    _callStartSpanHook(request, hookFunc) {
        if (typeof hookFunc === 'function') {
            return (0, instrumentation_1.safeExecuteInTheMiddle)(() => hookFunc(request), () => { }, true);
        }
    }
    _createHeaderCapture() {
        var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
        const config = this.getConfig();
        return {
            client: {
                captureRequestHeaders: (0, utils_1.headerCapture)('request', (_c = (_b = (_a = config.headersToSpanAttributes) === null || _a === void 0 ? void 0 : _a.client) === null || _b === void 0 ? void 0 : _b.requestHeaders) !== null && _c !== void 0 ? _c : []),
                captureResponseHeaders: (0, utils_1.headerCapture)('response', (_f = (_e = (_d = config.headersToSpanAttributes) === null || _d === void 0 ? void 0 : _d.client) === null || _e === void 0 ? void 0 : _e.responseHeaders) !== null && _f !== void 0 ? _f : []),
            },
            server: {
                captureRequestHeaders: (0, utils_1.headerCapture)('request', (_j = (_h = (_g = config.headersToSpanAttributes) === null || _g === void 0 ? void 0 : _g.server) === null || _h === void 0 ? void 0 : _h.requestHeaders) !== null && _j !== void 0 ? _j : []),
                captureResponseHeaders: (0, utils_1.headerCapture)('response', (_m = (_l = (_k = config.headersToSpanAttributes) === null || _k === void 0 ? void 0 : _k.server) === null || _l === void 0 ? void 0 : _l.responseHeaders) !== null && _m !== void 0 ? _m : []),
            },
        };
    }
}
exports.HttpInstrumentation = HttpInstrumentation;
//# sourceMappingURL=http.js.map