Next.js website for Rocky Mountain Vending company featuring: - Product catalog with Stripe integration - Service areas and parts pages - Admin dashboard with Clerk authentication - SEO optimized pages with JSON-LD structured data Co-authored-by: Cursor <cursoragent@cursor.com>
614 lines
No EOL
32 KiB
Text
614 lines
No EOL
32 KiB
Text
"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 |