UNPKG

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.

780 lines 33.2 kB
"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.AWSCloudWatchEMFExporter = void 0; /** * OpenTelemetry EMF (Embedded Metric Format) Exporter for CloudWatch. * This exporter converts OTel metrics into CloudWatch EMF format. */ const api_1 = require("@opentelemetry/api"); const client_cloudwatch_logs_1 = require("@aws-sdk/client-cloudwatch-logs"); const sdk_metrics_1 = require("@opentelemetry/sdk-metrics"); const core_1 = require("@opentelemetry/core"); const Crypto = require("crypto"); // Constants for CloudWatch Logs limits const CW_MAX_EVENT_PAYLOAD_BYTES = 256 * 1024; // 256KB const CW_MAX_REQUEST_EVENT_COUNT = 10000; const CW_PER_EVENT_HEADER_BYTES = 26; const BATCH_FLUSH_INTERVAL = 60 * 1000; const CW_MAX_REQUEST_PAYLOAD_BYTES = 1 * 1024 * 1024; // 1MB const CW_TRUNCATED_SUFFIX = '[Truncated...]'; const CW_EVENT_TIMESTAMP_LIMIT_PAST = 14 * 24 * 60 * 60 * 1000; // 14 days in milliseconds const CW_EVENT_TIMESTAMP_LIMIT_FUTURE = 2 * 60 * 60 * 1000; // 2 hours in milliseconds /** * OpenTelemetry metrics exporter for CloudWatch EMF format. * * This exporter converts OTel metrics into CloudWatch EMF logs which are then * sent to CloudWatch Logs. CloudWatch Logs automatically extracts the metrics * from the EMF logs. */ class AWSCloudWatchEMFExporter { /** * Initialize the CloudWatch EMF exporter. * * @param namespace CloudWatch namespace for metrics * @param logGroupName CloudWatch log group name * @param logStreamName Optional CloudWatch log stream name (auto-generated if not provided) * @param AggregationTemporality Optional AggregationTemporality to indicate the way additive quantities are expressed * @param cloudwatchLogsConfig Optional CloudWatch Logs Client Configuration. Configure region here if needed explicitly. */ constructor(namespace = 'default', logGroupName, logStreamName, aggregationTemporality = sdk_metrics_1.AggregationTemporality.DELTA, cloudwatchLogsConfig = {}) { this.EMF_SUPPORTED_UNITS = new Set([ 'Seconds', 'Microseconds', 'Milliseconds', 'Bytes', 'Kilobytes', 'Megabytes', 'Gigabytes', 'Terabytes', 'Bits', 'Kilobits', 'Megabits', 'Gigabits', 'Terabits', 'Percent', 'Count', 'Bytes/Second', 'Kilobytes/Second', 'Megabytes/Second', 'Gigabytes/Second', 'Terabytes/Second', 'Bits/Second', 'Kilobits/Second', 'Megabits/Second', 'Gigabits/Second', 'Terabits/Second', 'Count/Second', 'None', ]); // OTel to CloudWatch unit mapping this.UNIT_MAPPING = new Map(Object.entries({ '1': '', ns: '', ms: 'Milliseconds', s: 'Seconds', us: 'Microseconds', By: 'Bytes', bit: 'Bits', })); this.namespace = namespace; this.logGroupName = logGroupName; this.logStreamName = logStreamName || this.generateLogStreamName(); this.aggregationTemporality = aggregationTemporality; this.logsClient = new client_cloudwatch_logs_1.CloudWatchLogs(cloudwatchLogsConfig); // Determine that Log group/stream exists asynchronously. The Constructor cannot wait on async // operations, so whether or not the group/stream actually exists will be determined later. this.logStreamExists = false; this.logStreamExistsPromise = this.ensureLogGroupExists().then(async () => { await this.ensureLogStreamExists(); }); } /** * Generate a unique log stream name. * * @returns {string} */ generateLogStreamName() { const uniqueId = Crypto.randomUUID().substring(0, 8); return `otel-js-${uniqueId}`; } /** * Ensure the log group exists, create if it doesn't. */ async ensureLogGroupExists() { try { await this.logsClient.createLogGroup({ logGroupName: this.logGroupName, }); api_1.diag.info(`Created log group: ${this.logGroupName}`); } catch (e) { if (e instanceof Error && e.name === 'ResourceAlreadyExistsException') { api_1.diag.info(`Log group ${this.logStreamName} already exists.`); } else { api_1.diag.error(`Error occurred when creating log group ${this.logGroupName}: ${e}`); throw e; } } } /** * Ensure the log stream exists, create if it doesn't. */ async ensureLogStreamExists() { try { await this.logsClient.createLogStream({ logGroupName: this.logGroupName, logStreamName: this.logStreamName, }); api_1.diag.info(`Created log stream: ${this.logStreamName}`); this.logStreamExists = true; } catch (e) { if (e instanceof Error && e.name === 'ResourceAlreadyExistsException') { api_1.diag.info(`Log stream ${this.logStreamName} already exists.`); } else { api_1.diag.error(`Error occurred when creating log stream "${this.logStreamName}": ${e}`); throw e; } } } /** * Get CloudWatch unit from unit in MetricRecord * * @param record Metric Record * @returns {string | undefined} */ getUnit(record) { const unit = record.unit; if (this.EMF_SUPPORTED_UNITS.has(unit)) { return unit; } return this.UNIT_MAPPING.get(unit); } /** * Extract dimension names from attributes. * For now, use all attributes as dimensions for the dimension selection logic. * * @param attributes OpenTelemetry Attributes to extract Dimension Names from * @returns {string[]} */ getDimensionNames(attributes) { return Object.keys(attributes); } /** * Create a hashable key from attributes for grouping metrics. * * @param attributes OpenTelemetry Attributes used to create an attributes key * @returns {string} */ getAttributesKey(attributes) { // Sort the attributes to ensure consistent keys const sortedAttrs = Object.entries(attributes).sort(); // Create a string representation of the attributes return sortedAttrs.toString(); } /** * Normalize an OpenTelemetry timestamp to milliseconds for CloudWatch. * * @param hrTime Datapoint timestamp * @returns {number} Timestamp in milliseconds */ normalizeTimestamp(hrTime) { // Convert from second and nanoseconds to milliseconds const secondsToMillis = hrTime[0] * 1000; const nanosToMillis = Math.floor(hrTime[1] / 1000000); return secondsToMillis + nanosToMillis; } /** * Create a base metric record with instrument information. * * @param metricName Name of the metric * @param metricUnit Unit of the metric * @param metricDescription Description of the metric * @param timestamp Normalized end epoch timestamp when metric data was collected * @param attributes Attributes of the metric data * @returns {MetricRecord} */ createMetricRecord(metricName, metricUnit, metricDescription, timestamp, attributes) { const record = { name: metricName, unit: metricUnit, description: metricDescription, timestamp, attributes, }; return record; } /** * Convert a Gauge metric datapoint to a metric record. * * @param metric Gauge Metric Data * @param dataPoint The datapoint to convert * @returns {MetricRecord} */ convertGauge(metric, dataPoint) { const timestampMs = this.normalizeTimestamp(dataPoint.endTime); // Create base record const metricRecord = this.createMetricRecord(metric.descriptor.name, metric.descriptor.unit, metric.descriptor.description, timestampMs, dataPoint.attributes); metricRecord.value = dataPoint.value; // For Gauge, set the value directly return metricRecord; } /** * Convert a Sum metric datapoint to a metric record. * * @param metric The metric object * @param dataPoint The datapoint to convert * @returns {MetricRecord} */ convertSum(metric, dataPoint) { const timestampMs = this.normalizeTimestamp(dataPoint.endTime); // Create base record const record = this.createMetricRecord(metric.descriptor.name, metric.descriptor.unit, metric.descriptor.description, timestampMs, dataPoint.attributes); record.sumData = dataPoint.value; return record; } /** * Convert a Histogram metric datapoint to a metric record. * * @param metric The metric object * @param dataPoint The datapoint to convert * @returns {MetricRecord} */ convertHistogram(metric, dataPoint) { var _a, _b, _c; const timestampMs = this.normalizeTimestamp(dataPoint.endTime); // Create base record const record = this.createMetricRecord(metric.descriptor.name, metric.descriptor.unit, metric.descriptor.description, timestampMs, dataPoint.attributes); record.histogramData = { // For Histogram, set the histogram_data Count: dataPoint.value.count, Sum: (_a = dataPoint.value.sum) !== null && _a !== void 0 ? _a : 0, Min: (_b = dataPoint.value.min) !== null && _b !== void 0 ? _b : 0, Max: (_c = dataPoint.value.max) !== null && _c !== void 0 ? _c : 0, }; return record; } /** * Convert an ExponentialHistogram metric datapoint to a metric record. * This function follows the logic of CalculateDeltaDatapoints in the Go implementation, * converting exponential buckets to their midpoint values. * * @param metric The metric object * @param dataPoint The datapoint to convert * @returns {MetricRecord} */ convertExpHistogram(metric, dataPoint) { var _a, _b, _c, _d, _e; // Set timestamp const timestampMs = this.normalizeTimestamp(dataPoint.endTime); // Initialize arrays for values and counts const arrayValues = []; const arrayCounts = []; // Get scale const scale = dataPoint.value.scale; // Calculate base using the formula: 2^(2^(-scale)) const base = Math.pow(2, Math.pow(2, -scale)); // Process positive buckets if ((_b = (_a = dataPoint.value) === null || _a === void 0 ? void 0 : _a.positive) === null || _b === void 0 ? void 0 : _b.bucketCounts) { const positiveOffset = dataPoint.value.positive.offset; const positiveBucketCounts = dataPoint.value.positive.bucketCounts; let bucketBegin = 0; let bucketEnd = 0; for (const [i, count] of positiveBucketCounts.entries()) { const index = i + positiveOffset; if (bucketBegin === 0) { bucketBegin = Math.pow(base, index); } else { bucketBegin = bucketEnd; } bucketEnd = Math.pow(base, index + 1); // Calculate midpoint value of the bucket const metricVal = (bucketBegin + bucketEnd) / 2; // Only include buckets with positive counts if (count > 0) { arrayValues.push(metricVal); arrayCounts.push(count); } } } // Process zero bucket const zeroCount = dataPoint.value.zeroCount; if (zeroCount > 0) { arrayValues.push(0); arrayCounts.push(zeroCount); } // Process negative buckets if (dataPoint.value.negative.bucketCounts) { const negativeOffset = dataPoint.value.negative.offset; const negativeBucketCounts = dataPoint.value.negative.bucketCounts; let bucketBegin = 0; let bucketEnd = 0; for (const [i, count] of Object.entries(negativeBucketCounts)) { const index = parseInt(i) + negativeOffset; if (bucketEnd === 0) { bucketEnd = -Math.pow(base, index); } else { bucketEnd = bucketBegin; } bucketBegin = -Math.pow(base, index + 1); // Calculate midpoint value of the bucket const metricVal = (bucketBegin + bucketEnd) / 2; // Only include buckets with positive counts if (count > 0) { arrayValues.push(metricVal); arrayCounts.push(count); } } } // Create base record const metricRecord = this.createMetricRecord(metric.descriptor.name, metric.descriptor.unit, metric.descriptor.description, timestampMs, dataPoint.attributes); metricRecord.expHistogramData = { // Set the histogram data in the format expected by CloudWatch EMF Values: arrayValues, Counts: arrayCounts, Count: dataPoint.value.count, Sum: (_c = dataPoint.value.sum) !== null && _c !== void 0 ? _c : 0, Max: (_d = dataPoint.value.max) !== null && _d !== void 0 ? _d : 0, Min: (_e = dataPoint.value.min) !== null && _e !== void 0 ? _e : 0, }; return metricRecord; } /** * Group metric record by attributes and timestamp. * * @param record The metric record * @param timestampMs The timestamp in milliseconds * @returns {[string, number]} Values for the key to group metrics */ groupByAttributesAndTimestamp(record) { // Create a key for grouping based on attributes const attrsKey = this.getAttributesKey(record.attributes); return [attrsKey, record.timestamp]; } /** * Create EMF log from metric records. * metricRecords is already grouped by attributes, so this * function creates a single EMF Log for these records. * * @param metricRecords List of MetricRecords * @param resource * @param timestamp * @returns {EMFLog} */ createEmfLog(metricRecords, resource, timestamp = undefined) { var _a, _b; // Start with base structure const emfLog = { _aws: { Timestamp: timestamp || Date.now(), CloudWatchMetrics: [], }, Version: '1', }; // Add resource attributes to EMF log but not as dimensions if (resource && resource.attributes) { for (const [key, value] of Object.entries(resource.attributes)) { emfLog[`otel.resource.${key}`] = (_a = value === null || value === void 0 ? void 0 : value.toString()) !== null && _a !== void 0 ? _a : 'undefined'; } } // Initialize collections for dimensions and metrics // Attributes of each record in the list should be the same const allAttributes = metricRecords.length > 0 ? metricRecords[0].attributes : {}; const metricDefinitions = []; // Process each metric record for (const record of metricRecords) { const metricName = record.name; // Skip processing if metric name is falsy if (!metricName) { continue; } // Process different types of aggregations if (record.expHistogramData) { // Base2 Exponential Histogram - Store value directly in emfLog emfLog[metricName] = record.expHistogramData; } else if (record.histogramData) { // Regular Histogram metrics - Store value directly in emfLog emfLog[metricName] = record.histogramData; } else if (record.sumData) { // Counter/UpDownCounter - Store value directly in emfLog emfLog[metricName] = record.sumData; } else { // Other aggregations (e.g., LastValue) if (record.value) { // Store value directly in emfLog emfLog[metricName] = record.value; } else { api_1.diag.debug(`Skipping metric ${metricName} as it does not have valid metric value`); continue; } } // Create metric data const metricData = { Name: metricName, }; const unit = this.getUnit(record); if (unit) { metricData.Unit = unit; } // Add to metric definitions list metricDefinitions.push(metricData); } // Get dimension names from collected attributes const dimensionNames = this.getDimensionNames(allAttributes); // Add attribute values to the root of the EMF log for (const [name, value] of Object.entries(allAttributes)) { emfLog[name] = (_b = value === null || value === void 0 ? void 0 : value.toString()) !== null && _b !== void 0 ? _b : 'undefined'; } // Add the single dimension set to CloudWatch Metrics if we have dimensions and metrics if (dimensionNames && metricDefinitions) { emfLog._aws.CloudWatchMetrics.push({ Namespace: this.namespace, Dimensions: [dimensionNames], Metrics: metricDefinitions, }); } return emfLog; } /** * Method to handle safely pushing a MetricRecord into a Map of a Map of a list of MetricRecords * * @param groupedMetrics * @param groupAttribute * @param groupTimestamp * @param record */ pushMetricRecordIntoGroupedMetrics(groupedMetrics, groupAttribute, groupTimestamp, record) { let metricsGroupedByAttribute = groupedMetrics.get(groupAttribute); if (!metricsGroupedByAttribute) { metricsGroupedByAttribute = new Map(); groupedMetrics.set(groupAttribute, metricsGroupedByAttribute); } let metricsGroupedByAttributeAndTimestamp = metricsGroupedByAttribute.get(groupTimestamp); if (!metricsGroupedByAttributeAndTimestamp) { metricsGroupedByAttributeAndTimestamp = []; metricsGroupedByAttribute.set(groupTimestamp, metricsGroupedByAttributeAndTimestamp); } metricsGroupedByAttributeAndTimestamp.push(record); } /** * Export metrics as EMF logs to CloudWatch. * Groups metrics by attributes and timestamp before creating EMF logs. * * @param resourceMetrics Resource Metrics data containing scope metrics * @param resultCallback callback for when the export has completed * @returns {Promise<void>} */ async export(resourceMetrics, resultCallback) { try { if (!resourceMetrics) { resultCallback({ code: core_1.ExportResultCode.SUCCESS }); return; } // Process all metrics from resource metrics their scope metrics // The resource is now part of each resource_metrics object const resource = resourceMetrics.resource; for (const scopeMetrics of resourceMetrics.scopeMetrics /*resource_metrics.scope_metrics*/) { // Map of maps to group metrics by attributes and timestamp // Keys: (attributes_key, timestamp_ms) // Value: list of metric records const groupedMetrics = new Map(); // Process all metrics in this scope for (const metric of scopeMetrics.metrics) { // Convert metrics to a format compatible with create_emf_log // Process metric.dataPoints for different metric types if (metric.dataPointType === sdk_metrics_1.DataPointType.GAUGE) { for (const dataPoint of metric.dataPoints) { const record = this.convertGauge(metric, dataPoint); const [groupAttribute, groupTimestamp] = this.groupByAttributesAndTimestamp(record); this.pushMetricRecordIntoGroupedMetrics(groupedMetrics, groupAttribute, groupTimestamp, record); } } else if (metric.dataPointType === sdk_metrics_1.DataPointType.SUM) { for (const dataPoint of metric.dataPoints) { const record = this.convertSum(metric, dataPoint); const [groupAttribute, groupTimestamp] = this.groupByAttributesAndTimestamp(record); this.pushMetricRecordIntoGroupedMetrics(groupedMetrics, groupAttribute, groupTimestamp, record); } } else if (metric.dataPointType === sdk_metrics_1.DataPointType.HISTOGRAM) { for (const dataPoint of metric.dataPoints) { const record = this.convertHistogram(metric, dataPoint); const [groupAttribute, groupTimestamp] = this.groupByAttributesAndTimestamp(record); this.pushMetricRecordIntoGroupedMetrics(groupedMetrics, groupAttribute, groupTimestamp, record); } } else if (metric.dataPointType === sdk_metrics_1.DataPointType.EXPONENTIAL_HISTOGRAM) { for (const dataPoint of metric.dataPoints) { const record = this.convertExpHistogram(metric, dataPoint); const [groupAttribute, groupTimestamp] = this.groupByAttributesAndTimestamp(record); this.pushMetricRecordIntoGroupedMetrics(groupedMetrics, groupAttribute, groupTimestamp, record); } } else { // This else block should never run, all metric types are accounted for above api_1.diag.debug(`Unsupported Metric Type in metric: ${metric}`); } } const sendLogEventPromises = []; // Now process each group separately to create one EMF log per group groupedMetrics.forEach((metricsRecordsGroupedByAttribute, attrsKey) => { // metricRecords is grouped by attribute and timestamp metricsRecordsGroupedByAttribute.forEach((metricRecords, timestampMs) => { if (metricRecords) { api_1.diag.debug(`Creating EMF log for group with ${metricRecords.length} metrics. Timestamp: ${timestampMs}, Attributes: ${attrsKey.substring(0, 100)}...`); // Create EMF log for this batch of metrics with the group's timestamp const emfLog = this.createEmfLog(metricRecords, resource, Number(timestampMs)); // Convert to JSON const logEvent = { message: JSON.stringify(emfLog), timestamp: timestampMs, }; // Send to CloudWatch Logs sendLogEventPromises.push(this.sendLogEvent(logEvent)); } }); }); await Promise.all(sendLogEventPromises); } resultCallback({ code: core_1.ExportResultCode.SUCCESS }); } catch (e) { api_1.diag.error(`Failed to export metrics: ${e}`); const exportResult = { code: core_1.ExportResultCode.FAILED }; if (e instanceof Error) { exportResult.error = e; } resultCallback(exportResult); } } /** * Validate the log event according to CloudWatch Logs constraints. * Implements the same validation logic as the Go version. * * @param logEvent The log event to validate * @returns {boolean} */ validateLogEvent(logEvent) { // Check message size const messageSize = logEvent.message.length + CW_PER_EVENT_HEADER_BYTES; if (messageSize > CW_MAX_EVENT_PAYLOAD_BYTES) { api_1.diag.warn(`Log event size ${messageSize} exceeds maximum allowed size {CW_MAX_EVENT_PAYLOAD_BYTES}. Truncating.`); const maxMessageSize = CW_MAX_EVENT_PAYLOAD_BYTES - CW_PER_EVENT_HEADER_BYTES - CW_TRUNCATED_SUFFIX.length; logEvent.message = logEvent.message.substring(0, maxMessageSize) + CW_TRUNCATED_SUFFIX; } // Check empty message if (logEvent.message === '') { api_1.diag.error('Empty log event message'); return false; } // Check timestamp constraints const currentTime = Date.now(); // Current time in milliseconds const eventTime = logEvent.timestamp; // Calculate the time difference const timeDiff = currentTime - eventTime; // Check if too old or too far in the future if (timeDiff > CW_EVENT_TIMESTAMP_LIMIT_PAST || timeDiff < -CW_EVENT_TIMESTAMP_LIMIT_FUTURE) { api_1.diag.error(`Log event timestamp ${eventTime} is either older than 14 days or more than 2 hours in the future. Current time: ${currentTime}`); return false; } return true; } /** * Create a new log event batch * * @returns {EventBatch} */ createEventBatch() { return { logEvents: [], byteTotal: 0, minTimestampMs: 0, maxTimestampMs: 0, createdTimestampMs: Date.now(), }; } /** * Check if adding the next event would exceed CloudWatch Logs limits. * * @param batch The current batch * @param nextEventSize Size of the next event in bytes CW_MAX_REQUEST_EVENT_COUNT * @returns {boolean} true if adding the next event would exceed limits */ eventBatchExceedsLimit(batch, nextEventSize) { return (batch.logEvents.length >= CW_MAX_REQUEST_EVENT_COUNT || batch.byteTotal + nextEventSize > CW_MAX_REQUEST_PAYLOAD_BYTES); } /** * Check if the event batch spans more than 24 hours. * * @param batch The event batch * @param targetTimestampMs The timestamp of the event to add * @returns {boolean} true if the batch is active and can accept the event */ isBatchActive(batch, targetTimestampMs) { // New log event batch if (batch.minTimestampMs === 0 || batch.maxTimestampMs === 0) { return true; } // Check if adding the event would make the batch span more than 24 hours if (targetTimestampMs - batch.minTimestampMs > 24 * 3600 * 1000) { return false; } if (batch.maxTimestampMs - targetTimestampMs > 24 * 3600 * 1000) { return false; } // flush the event batch when reached 60s interval const currentTime = Date.now(); if (currentTime - batch.createdTimestampMs >= BATCH_FLUSH_INTERVAL) { return false; } return true; } /** * Append a log event to the batch. * * @param batch The event batch * @param logEvent The log event to append * @param eventSize Size of the event in bytes */ appendToBatch(batch, logEvent, eventSize) { batch.logEvents.push(logEvent); batch.byteTotal += eventSize; const timestamp = logEvent.timestamp; if (batch.minTimestampMs === 0 || batch.minTimestampMs > timestamp) { batch.minTimestampMs = timestamp; } if (batch.maxTimestampMs === 0 || batch.maxTimestampMs < timestamp) { batch.maxTimestampMs = timestamp; } } /** * Sort log events in the batch by timestamp. * * @param batch The event batch */ sortLogEvents(batch) { batch.logEvents = batch.logEvents.sort((a, b) => a.timestamp - b.timestamp); } /** * Send a batch of log events to CloudWatch Logs. * * @param batch The event batch * @returns {Promise<void>} */ async sendLogBatch(batch) { if (!batch.logEvents || batch.logEvents.length === 0) { return; } // Sort log events by timestamp this.sortLogEvents(batch); // Prepare the PutLogEvents request const putLogEventsInput = { logStreamName: this.logStreamName, logEvents: batch.logEvents, logGroupName: this.logGroupName, }; const startTime = Date.now(); try { if (!this.logStreamExists) { // Must perform logGroupExists check here because promises cannot be "awaited" in constructor await this.logStreamExistsPromise; } // Make the PutLogEvents call await this.logsClient.putLogEvents(putLogEventsInput); const elapsedMs = Date.now() - startTime; api_1.diag.debug(`Successfully sent ${batch.logEvents.length} log events (${(batch.byteTotal / 1024).toFixed(2)} KB) in ${elapsedMs} ms`); } catch (e) { api_1.diag.error(`Failed to send log events: ${e}`); throw e; } } /** * Send a log event to CloudWatch Logs. * * This function implements the same logic as the Go version in the OTel Collector. * It batches log events according to CloudWatch Logs constraints and sends them * when the batch is full or spans more than 24 hours. * * @param logEvent The log event to send * @returns {Promise<void>} */ async sendLogEvent(logEvent) { try { // Validate the log event if (!this.validateLogEvent(logEvent)) { return; } // Calculate event size const eventSize = logEvent.message.length + CW_PER_EVENT_HEADER_BYTES; // Initialize event batch if needed if (this.eventBatch === undefined) { this.eventBatch = this.createEventBatch(); } // Check if we need to send the current batch and create a new one let currentBatch = this.eventBatch; if (this.eventBatchExceedsLimit(currentBatch, eventSize) || !this.isBatchActive(currentBatch, logEvent.timestamp)) { // Create a new batch this.eventBatch = this.createEventBatch(); // Send the current batch await this.sendLogBatch(currentBatch); currentBatch = this.eventBatch; } // Add the log event to the batch this.appendToBatch(currentBatch, logEvent, eventSize); } catch (e) { api_1.diag.error(`Failed to process log event: ${e}`); throw e; } } /** * Force flush any pending metrics. * * @param timeoutMillis Timeout in milliseconds */ async forceFlush(timeoutMillis = 10000) { var _a; if (this.eventBatch !== undefined && ((_a = this.eventBatch.logEvents) === null || _a === void 0 ? void 0 : _a.length) > 0) { const currentBatch = this.eventBatch; this.eventBatch = this.createEventBatch(); await this.sendLogBatch(currentBatch); } api_1.diag.debug('AWSCloudWatchEMFExporter force flushes the bufferred metrics'); } /** * Shutdown the exporter after force flush. * * @returns {Promise<void>} */ async shutdown() { await this.forceFlush(); api_1.diag.debug('AWSCloudWatchEMFExporter shutdown called'); return Promise.resolve(); } selectAggregationTemporality(instrumentType) { return this.aggregationTemporality; } selectAggregation(instrumentType) { switch (instrumentType) { case sdk_metrics_1.InstrumentType.HISTOGRAM: { return sdk_metrics_1.Aggregation.ExponentialHistogram(); } } return sdk_metrics_1.Aggregation.Default(); } } exports.AWSCloudWatchEMFExporter = AWSCloudWatchEMFExporter; //# sourceMappingURL=otlp-aws-emf-exporter.js.map