@azure/monitor-opentelemetry-exporter
Version:
Application Insights exporter for the OpenTelemetry JavaScript (Node.js) SDK
235 lines • 10.8 kB
JavaScript
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
import { diag } from "@opentelemetry/api";
import { FileSystemPersist } from "./persist/index.js";
import { ExportResultCode } from "@opentelemetry/core";
import { NetworkStatsbeatMetrics } from "../../export/statsbeat/networkStatsbeatMetrics.js";
import { getInstance } from "../../export/statsbeat/longIntervalStatsbeatMetrics.js";
import { MAX_STATSBEAT_FAILURES, isStatsbeatShutdownStatus } from "../../export/statsbeat/types.js";
import { isRetriable } from "../../utils/breezeUtils.js";
import { RetriableRestErrorTypes } from "../../Declarations/Constants.js";
const DEFAULT_BATCH_SEND_RETRY_INTERVAL_MS = 60000;
/**
* Base sender class
* @internal
*/
export class BaseSender {
constructor(options) {
this.statsbeatFailureCount = 0;
this.batchSendRetryIntervalMs = DEFAULT_BATCH_SEND_RETRY_INTERVAL_MS;
this.numConsecutiveRedirects = 0;
this.disableOfflineStorage = options.exporterOptions.disableOfflineStorage || false;
this.persister = new FileSystemPersist(options.instrumentationKey, options.exporterOptions);
if (options.trackStatsbeat) {
// Initialize statsbeatMetrics
this.networkStatsbeatMetrics = new NetworkStatsbeatMetrics({
instrumentationKey: options.instrumentationKey,
endpointUrl: options.endpointUrl,
disableOfflineStorage: this.disableOfflineStorage,
});
this.longIntervalStatsbeatMetrics = getInstance({
instrumentationKey: options.instrumentationKey,
endpointUrl: options.endpointUrl,
disableOfflineStorage: this.disableOfflineStorage,
});
}
this.retryTimer = null;
this.isStatsbeatSender = options.isStatsbeatSender || false;
}
/**
* Export envelopes
*/
async exportEnvelopes(envelopes) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
diag.info(`Exporting ${envelopes.length} envelope(s)`);
if (envelopes.length < 1) {
return { code: ExportResultCode.SUCCESS };
}
try {
const startTime = new Date().getTime();
const { result, statusCode } = await this.send(envelopes);
const endTime = new Date().getTime();
const duration = endTime - startTime;
this.numConsecutiveRedirects = 0;
if (statusCode === 200) {
// Success -- @todo: start retry timer
if (!this.retryTimer) {
this.retryTimer = setTimeout(() => {
this.retryTimer = null;
this.sendFirstPersistedFile();
}, this.batchSendRetryIntervalMs);
this.retryTimer.unref();
}
// If we are not exportings statsbeat and statsbeat is not disabled -- count success
(_a = this.networkStatsbeatMetrics) === null || _a === void 0 ? void 0 : _a.countSuccess(duration);
return { code: ExportResultCode.SUCCESS };
}
else if (statusCode && isRetriable(statusCode)) {
// Failed -- persist failed data
if (statusCode === 429 || statusCode === 439) {
(_b = this.networkStatsbeatMetrics) === null || _b === void 0 ? void 0 : _b.countThrottle(statusCode);
}
if (result) {
diag.info(result);
const breezeResponse = JSON.parse(result);
const filteredEnvelopes = [];
if (breezeResponse.errors) {
breezeResponse.errors.forEach((error) => {
if (error.statusCode && isRetriable(error.statusCode)) {
filteredEnvelopes.push(envelopes[error.index]);
}
});
}
if (filteredEnvelopes.length > 0) {
(_c = this.networkStatsbeatMetrics) === null || _c === void 0 ? void 0 : _c.countRetry(statusCode);
// calls resultCallback(ExportResult) based on result of persister.push
return await this.persist(filteredEnvelopes);
}
// Failed -- not retriable
(_d = this.networkStatsbeatMetrics) === null || _d === void 0 ? void 0 : _d.countFailure(duration, statusCode);
return {
code: ExportResultCode.FAILED,
};
}
else {
// calls resultCallback(ExportResult) based on result of persister.push
(_e = this.networkStatsbeatMetrics) === null || _e === void 0 ? void 0 : _e.countRetry(statusCode);
return await this.persist(envelopes);
}
}
else {
// Failed -- not retriable
if (this.networkStatsbeatMetrics) {
if (statusCode) {
this.networkStatsbeatMetrics.countFailure(duration, statusCode);
}
}
else {
// Handles all other status codes or client exceptions for Statsbeat
this.incrementStatsbeatFailure();
}
return {
code: ExportResultCode.FAILED,
};
}
}
catch (error) {
const restError = error;
if (restError.statusCode &&
(restError.statusCode === 307 || // Temporary redirect
restError.statusCode === 308)) {
// Permanent redirect
this.numConsecutiveRedirects++;
// To prevent circular redirects
if (this.numConsecutiveRedirects < 10) {
if (restError.response && restError.response.headers) {
const location = restError.response.headers.get("location");
if (location) {
// Update sender URL
this.handlePermanentRedirect(location);
// Send to redirect endpoint as HTTPs library doesn't handle redirect automatically
return this.exportEnvelopes(envelopes);
}
}
}
else {
const redirectError = new Error("Circular redirect");
(_f = this.networkStatsbeatMetrics) === null || _f === void 0 ? void 0 : _f.countException(redirectError);
return { code: ExportResultCode.FAILED, error: redirectError };
}
}
else if (restError.statusCode &&
isRetriable(restError.statusCode) &&
!this.isStatsbeatSender) {
(_g = this.networkStatsbeatMetrics) === null || _g === void 0 ? void 0 : _g.countRetry(restError.statusCode);
return this.persist(envelopes);
}
else if (restError.statusCode === 400 &&
restError.message.includes("Invalid instrumentation key")) {
// Invalid instrumentation key, shutdown statsbeat, fail silently
this.shutdownStatsbeat();
return { code: ExportResultCode.SUCCESS };
}
else if (restError.statusCode &&
this.isStatsbeatSender &&
isStatsbeatShutdownStatus(restError.statusCode)) {
// If the status code is a shutdown status code for statsbeat, shutdown statsbeat and fail silently
this.incrementStatsbeatFailure();
return { code: ExportResultCode.SUCCESS };
}
if (this.isRetriableRestError(restError)) {
if (restError.statusCode) {
(_h = this.networkStatsbeatMetrics) === null || _h === void 0 ? void 0 : _h.countRetry(restError.statusCode);
}
if (!this.isStatsbeatSender) {
diag.error("Retrying due to transient client side error. Error message:", restError.message);
}
return this.persist(envelopes);
}
(_j = this.networkStatsbeatMetrics) === null || _j === void 0 ? void 0 : _j.countException(restError);
if (!this.isStatsbeatSender) {
diag.error("Envelopes could not be exported and are not retriable. Error message:", restError.message);
}
return { code: ExportResultCode.FAILED, error: restError };
}
}
/**
* Persist envelopes to disk
*/
async persist(envelopes) {
var _a;
try {
const success = await this.persister.push(envelopes);
return success
? { code: ExportResultCode.SUCCESS }
: {
code: ExportResultCode.FAILED,
error: new Error("Failed to persist envelope in disk."),
};
}
catch (ex) {
(_a = this.networkStatsbeatMetrics) === null || _a === void 0 ? void 0 : _a.countWriteFailure();
return { code: ExportResultCode.FAILED, error: ex };
}
}
/**
* Disable collection of statsbeat metrics after max failures
*/
incrementStatsbeatFailure() {
this.statsbeatFailureCount++;
if (this.statsbeatFailureCount > MAX_STATSBEAT_FAILURES) {
this.shutdownStatsbeat();
}
}
/**
* Shutdown statsbeat metrics
*/
shutdownStatsbeat() {
var _a, _b;
(_a = this.networkStatsbeatMetrics) === null || _a === void 0 ? void 0 : _a.shutdown();
(_b = this.longIntervalStatsbeatMetrics) === null || _b === void 0 ? void 0 : _b.shutdown();
this.networkStatsbeatMetrics = undefined;
this.statsbeatFailureCount = 0;
}
async sendFirstPersistedFile() {
var _a;
try {
const envelopes = (await this.persister.shift());
if (envelopes) {
await this.send(envelopes);
}
}
catch (err) {
(_a = this.networkStatsbeatMetrics) === null || _a === void 0 ? void 0 : _a.countReadFailure();
diag.warn(`Failed to fetch persisted file`, err);
}
}
isRetriableRestError(error) {
const restErrorTypes = Object.values(RetriableRestErrorTypes);
if (error && error.code && restErrorTypes.includes(error.code)) {
return true;
}
return false;
}
}
//# sourceMappingURL=baseSender.js.map