chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
import { type ExportResult, ExportResultCode } from "@opentelemetry/core";
|
||||
import type {
|
||||
PushMetricExporter,
|
||||
ResourceMetrics,
|
||||
MetricData,
|
||||
DataPoint,
|
||||
Histogram,
|
||||
ExponentialHistogram,
|
||||
} from "@opentelemetry/sdk-metrics";
|
||||
|
||||
/**
|
||||
* Compact metric exporter that logs metrics in a single-line format
|
||||
* Similar to Prometheus text format for better readability
|
||||
*/
|
||||
export class CompactMetricExporter implements PushMetricExporter {
|
||||
/**
|
||||
* Export metrics in a compact format
|
||||
*/
|
||||
export(metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void): void {
|
||||
try {
|
||||
this._exportMetrics(metrics);
|
||||
resultCallback({ code: ExportResultCode.SUCCESS });
|
||||
} catch (error) {
|
||||
resultCallback({
|
||||
code: ExportResultCode.FAILED,
|
||||
error: error instanceof Error ? error : new Error(String(error)),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the exporter
|
||||
*/
|
||||
async shutdown(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Force flush any buffered data
|
||||
*/
|
||||
async forceFlush(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process and log metrics in compact format
|
||||
*/
|
||||
private _exportMetrics(resourceMetrics: ResourceMetrics): void {
|
||||
for (const scopeMetric of resourceMetrics.scopeMetrics) {
|
||||
for (const metric of scopeMetric.metrics) {
|
||||
this._exportMetric(metric);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Export a single metric
|
||||
*/
|
||||
private _exportMetric(metric: MetricData): void {
|
||||
const metricName = metric.descriptor.name;
|
||||
|
||||
for (const dataPoint of metric.dataPoints) {
|
||||
const formattedLine = this._formatDataPoint(metricName, dataPoint);
|
||||
if (formattedLine) {
|
||||
console.log(formattedLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format a data point into a single line
|
||||
*/
|
||||
private _formatDataPoint(
|
||||
metricName: string,
|
||||
dataPoint: DataPoint<number> | DataPoint<Histogram> | DataPoint<ExponentialHistogram>
|
||||
): string | null {
|
||||
// Extract attributes/labels
|
||||
const labels = this._formatLabels(dataPoint.attributes);
|
||||
|
||||
// Extract value based on data point type
|
||||
const value = this._extractValue(dataPoint);
|
||||
|
||||
if (value === null || value === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Format as: metric_name{label1="value1",label2="value2"} = value
|
||||
if (labels) {
|
||||
return `${metricName}{${labels}} = ${value}`;
|
||||
}
|
||||
|
||||
return `${metricName} = ${value}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format attributes as Prometheus-style labels
|
||||
*/
|
||||
private _formatLabels(attributes: Record<string, unknown>): string {
|
||||
const entries = Object.entries(attributes);
|
||||
if (entries.length === 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return entries.map(([key, value]) => `${key}="${String(value)}"`).join(",");
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the numeric value from a data point
|
||||
*/
|
||||
private _extractValue(
|
||||
dataPoint: DataPoint<number> | DataPoint<Histogram> | DataPoint<ExponentialHistogram>
|
||||
): number | null {
|
||||
const value = dataPoint.value;
|
||||
|
||||
// Check if value is a simple number (Gauge, Sum, UpDownSum)
|
||||
if (typeof value === "number") {
|
||||
return value;
|
||||
}
|
||||
|
||||
// Check if value is a Histogram or ExponentialHistogram
|
||||
if (this._isHistogram(value) || this._isExponentialHistogram(value)) {
|
||||
return value.sum ?? null;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for Histogram
|
||||
*/
|
||||
private _isHistogram(value: unknown): value is Histogram {
|
||||
return (
|
||||
value !== null &&
|
||||
typeof value === "object" &&
|
||||
"sum" in value &&
|
||||
"count" in value &&
|
||||
"buckets" in value
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guard for ExponentialHistogram
|
||||
*/
|
||||
private _isExponentialHistogram(value: unknown): value is ExponentialHistogram {
|
||||
return (
|
||||
value !== null &&
|
||||
typeof value === "object" &&
|
||||
"sum" in value &&
|
||||
"count" in value &&
|
||||
"scale" in value
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { type ExportResult, ExportResultCode, hrTimeToMicroseconds } from "@opentelemetry/core";
|
||||
import type { ReadableSpan, SpanExporter } from "@opentelemetry/sdk-trace-base";
|
||||
|
||||
export class LoggerSpanExporter implements SpanExporter {
|
||||
/**
|
||||
* Export spans.
|
||||
* @param spans
|
||||
* @param resultCallback
|
||||
*/
|
||||
export(spans: ReadableSpan[], resultCallback: (result: ExportResult) => void): void {
|
||||
return this._sendSpans(spans, resultCallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shutdown the exporter.
|
||||
*/
|
||||
shutdown(): Promise<void> {
|
||||
this._sendSpans([]);
|
||||
return this.forceFlush();
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports any pending spans in exporter
|
||||
*/
|
||||
forceFlush(): Promise<void> {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
/**
|
||||
* converts span info into more readable format
|
||||
* @param span
|
||||
*/
|
||||
private _exportInfo(span: ReadableSpan) {
|
||||
const parentSpanId = span.parentSpanContext?.spanId;
|
||||
|
||||
return {
|
||||
traceId: span.spanContext().traceId,
|
||||
parentId:
|
||||
parentSpanId && parentSpanId !== "" && parentSpanId !== "0000000000000000"
|
||||
? parentSpanId
|
||||
: undefined,
|
||||
traceState: span.spanContext().traceState?.serialize(),
|
||||
message: span.name,
|
||||
spanId: span.spanContext().spanId,
|
||||
kind: span.kind,
|
||||
timestamp: hrTimeToMicroseconds(span.startTime),
|
||||
duration: hrTimeToMicroseconds(span.duration),
|
||||
attributes: span.attributes,
|
||||
status: span.status,
|
||||
events: span.events,
|
||||
links: span.links,
|
||||
level: "trace",
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Showing spans in console
|
||||
* @param spans
|
||||
* @param done
|
||||
*/
|
||||
private _sendSpans(spans: ReadableSpan[], done?: (result: ExportResult) => void): void {
|
||||
for (const span of spans) {
|
||||
console.log(JSON.stringify(this._exportInfo(span)));
|
||||
}
|
||||
if (done) {
|
||||
return done({ code: ExportResultCode.SUCCESS });
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user