UNPKG

@dynatrace/opentelemetry-core

Version:

Dynatrace OpenTelemetry core package

278 lines (275 loc) 12.3 kB
"use strict"; /* Copyright 2022 Dynatrace LLC 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 http://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. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.DtSpanProcessor = void 0; const events_1 = require("events"); const perf_hooks_1 = require("perf_hooks"); const core_1 = require("@opentelemetry/core"); const Types_1 = require("../Types"); const Logger_1 = require("../Logger"); const StartupConfig_1 = require("../configuration/StartupConfig"); const Timers_1 = require("../utils/Timers"); const Validators_1 = require("../utils/Validators"); const DtSpanExporter_1 = require("./DtSpanExporter"); const SpanEmbedder_1 = require("./SpanEmbedder"); const SpanMetaData_1 = require("./SpanMetaData"); const SpanUpdater_1 = require("./SpanUpdater"); const SemConvTrace = require("../../gen/dynatrace/odin/semconv/v1/SemConvTrace"); // ============================================================================ const cDefaultUpdateIntervalMs = 3000; const cDefaultKeepAliveTimeoutMs = 25000; const cDefaultExportSpanCntMax = 200; const cDefaultOpenSpanTimeoutMs = 115 * 60 * 1000; const cDefaultMaxSpans = 5000; const cDefaultForceFlushTimeoutMs = 5000; const cDefaultMinimumExportTimeMs = 500; const cDefaultAddStackOnStart = false; // ---------------------------------------------------------------------------- class DtSpanProcessor extends events_1.EventEmitter { constructor(exporter, config) { super(); this._export = (type = Types_1.ExportType.Normal, callback = () => { }) => { const debugEnabled = this._logger.debugEnabled; if (debugEnabled) { this._logger.debug(`_export ${this._stateToString()}`); } // avoid parallel exporting if (this._isExporting) { process.nextTick(callback); return; } if (this._exportIterator == null) { // previous export is finished if (this._spans.size === 0) { // nothing left to do process.nextTick(callback); return; } // start exporting by creating a new iterator (iterates in insertion order) this._exportIterator = this._spans.values(); } // take a bunch up to max size const now = perf_hooks_1.performance.now(); let rc = this._exportIterator.next(); while (!rc.done) { const span = rc.value; switch ((0, SpanEmbedder_1.getMetaData)(span).prepareSend(now, span.ended)) { case SpanMetaData_1.PrepareResult.Drop: this._spans.delete(span); break; case SpanMetaData_1.PrepareResult.Send: this._exportingSpans.push(span); break; case SpanMetaData_1.PrepareResult.Skip: break; } if (this._exportingSpans.length >= this._exportSpanCntMax) { break; } rc = this._exportIterator.next(); } if (rc.done) { this._exportIterator = undefined; } if (debugEnabled) { this._logger.debug(`export ${this._stateToString()}`); } this._exporter.export(this._exportingSpans, this._onExportDone(type, callback), type); this._isExporting = true; }; this._abort = () => { if (this._logger.debugEnabled) { this._logger.debug(`abort ${this._stateToString()}`); } this._exporter.abort(); this._exportIterator = undefined; }; this._isShutdown = false; this._spans = new Set(); this._exportingSpans = []; this._isExporting = false; this._maxSpansLogged = false; this._logger = new Logger_1.ComponentLogger("DtSpanProcessor"); if (!(exporter instanceof DtSpanExporter_1.DtSpanExporter)) { this._logger.error("exporter must be an instance of DtSpanExporter"); throw new TypeError("DtSpanProcessor: exporter must be an instance of DtSpanExporter"); } config = Object.assign(Object.assign({}, (0, StartupConfig_1.getConfiguration)()), config); if (config == null || typeof (config) !== "object") { throw new TypeError("DtSpanProcessor: config invalid"); } if (typeof (config.clusterId) !== "number") { throw new TypeError("DtSpanProcessor: clusterId must be a number"); } if (typeof (config.tenantId) !== "number") { throw new TypeError("DtSpanProcessor: tenantId must be a number"); } this._updateIntervalMs = (0, Validators_1.checkNumericOption)("DtSpanProcessor", config, "updateIntervalMs", cDefaultUpdateIntervalMs); this._keepAliveIntervalMs = (0, Validators_1.checkNumericOption)("DtSpanProcessor", config, "keepAliveIntervalMs", cDefaultKeepAliveTimeoutMs); this._exportSpanCntMax = (0, Validators_1.checkNumericOption)("DtSpanProcessor", config, "exportSpanCntMax", cDefaultExportSpanCntMax); this._openSpanTimeoutMs = (0, Validators_1.checkNumericOption)("DtSpanProcessor", config, "openSpanTimeoutMs", cDefaultOpenSpanTimeoutMs); this._forceFlushTimeoutMs = (0, Validators_1.checkNumericOption)("DtSpanProcessor", config, "forceFlushTimeoutMs", cDefaultForceFlushTimeoutMs); this._maxSpans = (0, Validators_1.checkNumericOption)("DtSpanProcessor", config, "maxSpans", cDefaultMaxSpans); this._minimumExportTimeMs = (0, Validators_1.checkNumericOption)("DtSpanProcessor", config, "minimumExportTimeMs", cDefaultMinimumExportTimeMs); this._addStackOnStart = (0, Validators_1.checkBooleanOption)("DtSpanProcessor", config, "debugAddStackOnStart", cDefaultAddStackOnStart); this._exporter = exporter; this._transmitOptions = { keepAliveIntervalMs: this._keepAliveIntervalMs, openSpanTimeoutMs: this._openSpanTimeoutMs, updateIntervalMs: this._updateIntervalMs }; this._ids = { clusterId: config.clusterId, tenantId: config.tenantId }; } onStart(span, parentContext) { if (this._isShutdown) { return; } if (this._addStackOnStart) { (0, SpanUpdater_1.addStacktrace)(SemConvTrace.DT_STACKTRACE_ONSTART, span, this._logger); } (0, SpanUpdater_1.prepareSpanForExport)(span, parentContext, this._ids, this._transmitOptions, this._logger); this._addSpan(span); } onEnd(span) { if (this._isShutdown) { return; } if (!this._spans.has(span)) { if (this._logger.debugEnabled) { this._logger.debug(`onEnd add span ${span.spanContext().spanId}`); } this._addSpan(span); } } forceFlush() { if (this._logger.debugEnabled) { this._logger.debug(`forceFlush ${this._stateToString()}`); } if (this._isShutdown) { return Promise.resolve(); } if (this._forceFlushPromise != null) { return this._forceFlushPromise; } this._forceFlushPromise = new Promise((resolve) => { const done = () => { this._forceFlushPromise = undefined; resolve(); }; if (this._isExporting) { this._exportAfterWait(done); } else { this._export(Types_1.ExportType.ForceFlush, done); } }); return this._forceFlushPromise; } shutdown() { this._logger.info(`shutdown ${this._isShutdown}`); if (this._isShutdown) { return Promise.resolve(); } const p = this.forceFlush(); this._isShutdown = true; return p; } _exportAfterWait(done) { const start = perf_hooks_1.performance.now(); const exportAfterWaiting = () => { const timeRemaining = this._forceFlushTimeoutMs - (perf_hooks_1.performance.now() - start); if (this._logger.debugEnabled) { this._logger.debug(`exportAfterWaiting ${this._stateToString()}, timeRemaining:${timeRemaining}`); } if (this._forceFlushTimer) { clearTimeout(this._forceFlushTimer); this._forceFlushTimer = undefined; } this.removeListener("export:done", exportAfterWaiting); // if flush cannot be completed in the time remaining after waiting, abort if (timeRemaining <= this._minimumExportTimeMs) { done(); } else { process.nextTick(this._export, Types_1.ExportType.ForceFlush, done); const flushTimeout = (0, Timers_1.setTimeoutUnref)(this._abort, timeRemaining); this.once("export:done", () => clearTimeout(flushTimeout)); } }; this.once("export:done", exportAfterWaiting); this._forceFlushTimer = (0, Timers_1.setTimeoutUnref)(() => { // timeout has passed and export is not finished. // abort currently running export. this._abort(); this.removeListener("export:done", exportAfterWaiting); this._forceFlushTimer = undefined; done(); }, this._forceFlushTimeoutMs); } _addSpan(span) { if (this._spans.size >= this._maxSpans) { if (!this._maxSpansLogged) { this._logger.warn("skip span as maxSpans limit is reached"); } return; } this._maxSpansLogged = false; this._spans.add(span); if (!this._isExporting && (this._spans.size === 1)) { this._startUnrefedTimer(); } } _startUnrefedTimer() { if (this._logger.debugEnabled) { this._logger.debug(`startTimer ${this._stateToString()}`); } (0, Timers_1.setTimeoutUnref)(this._export, this._updateIntervalMs); } _onExportDone(type, callback) { return (result) => { if (this._logger.debugEnabled) { this._logger.debug(`exportDone type: ${Types_1.ExportType[type]}, result: ${core_1.ExportResultCode[result.code]}, ${this._stateToString()}`); } this._isExporting = false; // remove finished spans even on failed exports as retry handling is in exporter for (const span of this._exportingSpans) { const metaData = (0, SpanEmbedder_1.getMetaData)(span); if (metaData.sendState === SpanMetaData_1.SendState.Finished) { this._spans.delete(span); } } this._exportingSpans.length = 0; // export next chunk or start timer if (this._exportIterator != null) { process.nextTick(this._export, type, callback); } else { process.nextTick(callback); this.emit("export:done"); if (this._spans.size > 0) { this._startUnrefedTimer(); } } }; } _stateToString() { return `spanCnt: ${this._spans.size}, exportingSpanCnt: ${this._exportingSpans.length}, exportIterator:${this._exportIterator != null}` + `, isExporting:${this._isExporting}, forceFlushTimer:${this._forceFlushTimer != null}`; } } exports.DtSpanProcessor = DtSpanProcessor; //# sourceMappingURL=DtSpanProcessor.js.map