enhanced-adot-node-autoinstrumentation
Version:
This package provides Amazon Web Services distribution of the OpenTelemetry Node Instrumentation, which allows for auto-instrumentation of NodeJS applications.
109 lines • 5.68 kB
JavaScript
"use strict";
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
Object.defineProperty(exports, "__esModule", { value: true });
exports.AwsSpanMetricsProcessor = void 0;
const api_1 = require("@opentelemetry/api");
const semantic_conventions_1 = require("@opentelemetry/semantic-conventions");
const aws_attribute_keys_1 = require("./aws-attribute-keys");
/**
* This processor will generate metrics based on span data. It depends on a
* {@link MetricAttributeGenerator} being provided on instantiation, which will provide a means to
* determine attributes which should be used to create metrics. A {@link Resource} must also be
* provided, which is used to generate metrics. Finally, three {@link Histogram}'s must be provided,
* which will be used to actually create desired metrics (see below)
*
* <p>AwsSpanMetricsProcessor produces metrics for errors (e.g. HTTP 4XX status codes), faults (e.g.
* HTTP 5XX status codes), and latency (in Milliseconds). Errors and faults are counted, while
* latency is measured with a histogram. Metrics are emitted with attributes derived from span
* attributes.
*
* <p>For highest fidelity metrics, this processor should be coupled with the {@link AlwaysRecordSampler},
* which will result in 100% of spans being sent to the processor.
*/
class AwsSpanMetricsProcessor {
constructor(errorHistogram, faultHistogram, latencyHistogram, generator, resource, forceFlushFunction) {
this.NANOS_TO_MILLIS_DIVIDER = 1000000;
this.SECONDS_TO_MILLIS_MULTIPLIER = 1000;
// Constants for deriving error and fault metrics
this.ERROR_CODE_LOWER_BOUND = 400;
this.ERROR_CODE_UPPER_BOUND = 499;
this.FAULT_CODE_LOWER_BOUND = 500;
this.FAULT_CODE_UPPER_BOUND = 599;
// EC2 Metadata API IP Address
// https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html#instancedata-inside-access
this.EC2_METADATA_API_IP = '169.254.169.254';
this.errorHistogram = errorHistogram;
this.faultHistogram = faultHistogram;
this.latencyHistogram = latencyHistogram;
this.generator = generator;
this.resource = resource;
this.forceFlushFunction = forceFlushFunction;
}
/** Use {@link AwsSpanMetricsProcessorBuilder} to construct this processor. */
static create(errorHistogram, faultHistogram, latencyHistogram, generator, resource, forceFlushFunction) {
return new AwsSpanMetricsProcessor(errorHistogram, faultHistogram, latencyHistogram, generator, resource, forceFlushFunction);
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
onStart(span, parentContext) { }
onEnd(span) {
const attributeMap = this.generator.generateMetricAttributeMapFromSpan(span, this.resource);
for (const attribute in attributeMap) {
this.recordMetrics(span, attributeMap[attribute]);
}
}
// The logic to record error and fault should be kept in sync with the aws-xray exporter whenever
// possible except for the throttle
// https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/exporter/awsxrayexporter/internal/translator/cause.go#L121-L160
recordErrorOrFault(spanData, attributes) {
let httpStatusCode = spanData.attributes[semantic_conventions_1.SEMATTRS_HTTP_STATUS_CODE];
const statusCode = spanData.status.code;
if (typeof httpStatusCode !== 'number') {
httpStatusCode = attributes[semantic_conventions_1.SEMATTRS_HTTP_STATUS_CODE];
}
if (typeof httpStatusCode !== 'number' ||
httpStatusCode < this.ERROR_CODE_LOWER_BOUND ||
httpStatusCode > this.FAULT_CODE_UPPER_BOUND) {
if (api_1.SpanStatusCode.ERROR === statusCode) {
this.errorHistogram.record(0, attributes);
this.faultHistogram.record(1, attributes);
}
else {
this.errorHistogram.record(0, attributes);
this.faultHistogram.record(0, attributes);
}
}
else if (httpStatusCode >= this.ERROR_CODE_LOWER_BOUND && httpStatusCode <= this.ERROR_CODE_UPPER_BOUND) {
this.errorHistogram.record(1, attributes);
this.faultHistogram.record(0, attributes);
}
else if (httpStatusCode >= this.FAULT_CODE_LOWER_BOUND && httpStatusCode <= this.FAULT_CODE_UPPER_BOUND) {
this.errorHistogram.record(0, attributes);
this.faultHistogram.record(1, attributes);
}
}
recordLatency(span, attributes) {
const millisFromSeconds = span.duration[0] * this.SECONDS_TO_MILLIS_MULTIPLIER;
const millisFromNanos = span.duration[1] / this.NANOS_TO_MILLIS_DIVIDER;
const millis = millisFromSeconds + millisFromNanos;
this.latencyHistogram.record(millis, attributes);
}
recordMetrics(span, attributes) {
// Only record metrics if non-empty attributes are returned.
if (Object.keys(attributes).length > 0 && !this.isEc2MetadataApiSpan(attributes)) {
this.recordErrorOrFault(span, attributes);
this.recordLatency(span, attributes);
}
}
shutdown() {
return this.forceFlush();
}
forceFlush() {
return this.forceFlushFunction();
}
isEc2MetadataApiSpan(attributes) {
return attributes[aws_attribute_keys_1.AWS_ATTRIBUTE_KEYS.AWS_REMOTE_SERVICE] === this.EC2_METADATA_API_IP;
}
}
exports.AwsSpanMetricsProcessor = AwsSpanMetricsProcessor;
//# sourceMappingURL=aws-span-metrics-processor.js.map