mirror of
https://github.com/denoland/deno.git
synced 2025-09-27 04:39:10 +00:00
Merge 256a94d64f
into cd0a592b2d
This commit is contained in:
commit
176b18a4b0
6 changed files with 456 additions and 124 deletions
|
@ -289,7 +289,7 @@ zip = { version = "2.4.1", default-features = false, features = ["flate2"] }
|
||||||
|
|
||||||
opentelemetry = "0.27.0"
|
opentelemetry = "0.27.0"
|
||||||
opentelemetry-http = "0.27.0"
|
opentelemetry-http = "0.27.0"
|
||||||
opentelemetry-otlp = { version = "0.27.0", features = ["logs", "http-proto", "http-json", "populate-logs-event-name"] }
|
opentelemetry-otlp = { version = "0.27.0", features = ["logs", "http-proto", "http-json", "populate-logs-event-name", "grpc-tonic"] }
|
||||||
opentelemetry-semantic-conventions = { version = "0.27.0", features = ["semconv_experimental"] }
|
opentelemetry-semantic-conventions = { version = "0.27.0", features = ["semconv_experimental"] }
|
||||||
opentelemetry_sdk = { version = "0.27.0", features = ["rt-tokio", "trace"] }
|
opentelemetry_sdk = { version = "0.27.0", features = ["rt-tokio", "trace"] }
|
||||||
|
|
||||||
|
|
|
@ -870,10 +870,10 @@ pub fn init(
|
||||||
|
|
||||||
// Parse the `OTEL_EXPORTER_OTLP_PROTOCOL` variable. The opentelemetry_*
|
// Parse the `OTEL_EXPORTER_OTLP_PROTOCOL` variable. The opentelemetry_*
|
||||||
// crates don't do this automatically.
|
// crates don't do this automatically.
|
||||||
// TODO(piscisaureus): enable GRPC support.
|
|
||||||
let protocol = match env::var("OTEL_EXPORTER_OTLP_PROTOCOL").as_deref() {
|
let protocol = match env::var("OTEL_EXPORTER_OTLP_PROTOCOL").as_deref() {
|
||||||
Ok("http/protobuf") => Protocol::HttpBinary,
|
Ok("http/protobuf") => Protocol::HttpBinary,
|
||||||
Ok("http/json") => Protocol::HttpJson,
|
Ok("http/json") => Protocol::HttpJson,
|
||||||
|
Ok("grpc") => Protocol::Grpc,
|
||||||
Ok("") | Err(env::VarError::NotPresent) => Protocol::HttpBinary,
|
Ok("") | Err(env::VarError::NotPresent) => Protocol::HttpBinary,
|
||||||
Ok(protocol) => {
|
Ok(protocol) => {
|
||||||
return Err(deno_core::anyhow::anyhow!(
|
return Err(deno_core::anyhow::anyhow!(
|
||||||
|
@ -930,45 +930,78 @@ pub fn init(
|
||||||
// `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable. Additional headers can
|
// `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable. Additional headers can
|
||||||
// be specified using `OTEL_EXPORTER_OTLP_HEADERS`.
|
// be specified using `OTEL_EXPORTER_OTLP_HEADERS`.
|
||||||
|
|
||||||
let client = hyper_client::HyperClient::new()?;
|
let (span_exporter, metric_exporter, log_exporter) = match protocol {
|
||||||
|
Protocol::Grpc => {
|
||||||
|
let span_exporter = opentelemetry_otlp::SpanExporter::builder()
|
||||||
|
.with_tonic()
|
||||||
|
.build()?;
|
||||||
|
let temporality_preference =
|
||||||
|
env::var("OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE")
|
||||||
|
.ok()
|
||||||
|
.map(|s| s.to_lowercase());
|
||||||
|
let temporality = match temporality_preference.as_deref() {
|
||||||
|
None | Some("cumulative") => Temporality::Cumulative,
|
||||||
|
Some("delta") => Temporality::Delta,
|
||||||
|
Some("lowmemory") => Temporality::LowMemory,
|
||||||
|
Some(other) => {
|
||||||
|
return Err(deno_core::anyhow::anyhow!(
|
||||||
|
"Invalid value for OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: {}",
|
||||||
|
other
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let metric_exporter = opentelemetry_otlp::MetricExporter::builder()
|
||||||
|
.with_tonic()
|
||||||
|
.with_temporality(temporality)
|
||||||
|
.build()?;
|
||||||
|
let log_exporter = opentelemetry_otlp::LogExporter::builder()
|
||||||
|
.with_tonic()
|
||||||
|
.build()?;
|
||||||
|
(span_exporter, metric_exporter, log_exporter)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let client = hyper_client::HyperClient::new()?;
|
||||||
|
let span_exporter = HttpExporterBuilder::default()
|
||||||
|
.with_http_client(client.clone())
|
||||||
|
.with_protocol(protocol)
|
||||||
|
.build_span_exporter()?;
|
||||||
|
let temporality_preference =
|
||||||
|
env::var("OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE")
|
||||||
|
.ok()
|
||||||
|
.map(|s| s.to_lowercase());
|
||||||
|
let temporality = match temporality_preference.as_deref() {
|
||||||
|
None | Some("cumulative") => Temporality::Cumulative,
|
||||||
|
Some("delta") => Temporality::Delta,
|
||||||
|
Some("lowmemory") => Temporality::LowMemory,
|
||||||
|
Some(other) => {
|
||||||
|
return Err(deno_core::anyhow::anyhow!(
|
||||||
|
"Invalid value for OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: {}",
|
||||||
|
other
|
||||||
|
));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let metric_exporter = HttpExporterBuilder::default()
|
||||||
|
.with_http_client(client.clone())
|
||||||
|
.with_protocol(protocol)
|
||||||
|
.build_metrics_exporter(temporality)?;
|
||||||
|
let log_exporter = HttpExporterBuilder::default()
|
||||||
|
.with_http_client(client)
|
||||||
|
.with_protocol(protocol)
|
||||||
|
.build_log_exporter()?;
|
||||||
|
(span_exporter, metric_exporter, log_exporter)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let span_exporter = HttpExporterBuilder::default()
|
|
||||||
.with_http_client(client.clone())
|
|
||||||
.with_protocol(protocol)
|
|
||||||
.build_span_exporter()?;
|
|
||||||
let mut span_processor =
|
let mut span_processor =
|
||||||
BatchSpanProcessor::builder(span_exporter, OtelSharedRuntime).build();
|
BatchSpanProcessor::builder(span_exporter, OtelSharedRuntime).build();
|
||||||
span_processor.set_resource(&resource);
|
span_processor.set_resource(&resource);
|
||||||
|
|
||||||
let temporality_preference =
|
|
||||||
env::var("OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE")
|
|
||||||
.ok()
|
|
||||||
.map(|s| s.to_lowercase());
|
|
||||||
let temporality = match temporality_preference.as_deref() {
|
|
||||||
None | Some("cumulative") => Temporality::Cumulative,
|
|
||||||
Some("delta") => Temporality::Delta,
|
|
||||||
Some("lowmemory") => Temporality::LowMemory,
|
|
||||||
Some(other) => {
|
|
||||||
return Err(deno_core::anyhow::anyhow!(
|
|
||||||
"Invalid value for OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE: {}",
|
|
||||||
other
|
|
||||||
));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let metric_exporter = HttpExporterBuilder::default()
|
|
||||||
.with_http_client(client.clone())
|
|
||||||
.with_protocol(protocol)
|
|
||||||
.build_metrics_exporter(temporality)?;
|
|
||||||
let metric_reader = DenoPeriodicReader::new(metric_exporter);
|
let metric_reader = DenoPeriodicReader::new(metric_exporter);
|
||||||
let meter_provider = SdkMeterProvider::builder()
|
let meter_provider = SdkMeterProvider::builder()
|
||||||
.with_reader(metric_reader)
|
.with_reader(metric_reader)
|
||||||
.with_resource(resource.clone())
|
.with_resource(resource.clone())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
let log_exporter = HttpExporterBuilder::default()
|
|
||||||
.with_http_client(client)
|
|
||||||
.with_protocol(protocol)
|
|
||||||
.build_log_exporter()?;
|
|
||||||
let log_processor =
|
let log_processor =
|
||||||
BatchLogProcessor::builder(log_exporter, OtelSharedRuntime).build();
|
BatchLogProcessor::builder(log_exporter, OtelSharedRuntime).build();
|
||||||
log_processor.set_resource(&resource);
|
log_processor.set_resource(&resource);
|
||||||
|
|
1
tests/specs/cli/otel_basic/.gitignore
vendored
Normal file
1
tests/specs/cli/otel_basic/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
opentelemetry-proto/
|
|
@ -2,6 +2,17 @@
|
||||||
"tests": {
|
"tests": {
|
||||||
"basic": {
|
"basic": {
|
||||||
"args": "run -A main.ts basic.ts",
|
"args": "run -A main.ts basic.ts",
|
||||||
|
"envs": {
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
|
||||||
|
"OTEL_EXPORTER_OTLP_CERTIFICATE": "../../../testdata/tls/RootCA.crt"
|
||||||
|
},
|
||||||
|
"output": "basic.out"
|
||||||
|
},
|
||||||
|
"basic_grpc": {
|
||||||
|
"args": "run -A main.ts basic.ts",
|
||||||
|
"envs": {
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc"
|
||||||
|
},
|
||||||
"output": "basic.out"
|
"output": "basic.out"
|
||||||
},
|
},
|
||||||
"basic_vsock": {
|
"basic_vsock": {
|
||||||
|
@ -26,6 +37,16 @@
|
||||||
},
|
},
|
||||||
"metric": {
|
"metric": {
|
||||||
"envs": {
|
"envs": {
|
||||||
|
"OTEL_METRIC_EXPORT_INTERVAL": "1000",
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
|
||||||
|
"OTEL_EXPORTER_OTLP_CERTIFICATE": "../../../testdata/tls/RootCA.crt"
|
||||||
|
},
|
||||||
|
"args": "run -A main.ts metric.ts",
|
||||||
|
"output": "metric.out"
|
||||||
|
},
|
||||||
|
"metric_grpc": {
|
||||||
|
"envs": {
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
|
||||||
"OTEL_METRIC_EXPORT_INTERVAL": "1000"
|
"OTEL_METRIC_EXPORT_INTERVAL": "1000"
|
||||||
},
|
},
|
||||||
"args": "run -A main.ts metric.ts",
|
"args": "run -A main.ts metric.ts",
|
||||||
|
@ -33,44 +54,104 @@
|
||||||
},
|
},
|
||||||
"fetch": {
|
"fetch": {
|
||||||
"args": "run -A main.ts fetch.ts",
|
"args": "run -A main.ts fetch.ts",
|
||||||
|
"envs": {
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
|
||||||
|
"OTEL_EXPORTER_OTLP_CERTIFICATE": "../../../testdata/tls/RootCA.crt"
|
||||||
|
},
|
||||||
"output": "fetch.out"
|
"output": "fetch.out"
|
||||||
},
|
},
|
||||||
"http_metric": {
|
"http_metric": {
|
||||||
"envs": {
|
"envs": {
|
||||||
"OTEL_METRIC_EXPORT_INTERVAL": "1000"
|
"OTEL_METRIC_EXPORT_INTERVAL": "1000",
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
|
||||||
|
"OTEL_EXPORTER_OTLP_CERTIFICATE": "../../../testdata/tls/RootCA.crt"
|
||||||
},
|
},
|
||||||
"args": "run -A main.ts http_metric.ts",
|
"args": "run -A main.ts http_metric.ts",
|
||||||
"output": "http_metric.out"
|
"output": "http_metric.out"
|
||||||
},
|
},
|
||||||
"http_propagators": {
|
"http_propagators": {
|
||||||
"args": "run -A main.ts http_propagators.ts",
|
"args": "run -A main.ts http_propagators.ts",
|
||||||
|
"envs": {
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
|
||||||
|
"OTEL_EXPORTER_OTLP_CERTIFICATE": "../../../testdata/tls/RootCA.crt"
|
||||||
|
},
|
||||||
"output": "http_propagators.out"
|
"output": "http_propagators.out"
|
||||||
},
|
},
|
||||||
"node_http_metric": {
|
"node_http_metric": {
|
||||||
"envs": {
|
"envs": {
|
||||||
"OTEL_METRIC_EXPORT_INTERVAL": "1000"
|
"OTEL_METRIC_EXPORT_INTERVAL": "1000",
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
|
||||||
|
"OTEL_EXPORTER_OTLP_CERTIFICATE": "../../../testdata/tls/RootCA.crt"
|
||||||
},
|
},
|
||||||
"args": "run -A main.ts node_http_metric.ts",
|
"args": "run -A main.ts node_http_metric.ts",
|
||||||
"output": "node_http_metric.out"
|
"output": "node_http_metric.out"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"args": "run -A main.ts links.ts",
|
"args": "run -A main.ts links.ts",
|
||||||
|
"envs": {
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
|
||||||
|
"OTEL_EXPORTER_OTLP_CERTIFICATE": "../../../testdata/tls/RootCA.crt"
|
||||||
|
},
|
||||||
|
"output": "links.out"
|
||||||
|
},
|
||||||
|
"links_grpc": {
|
||||||
|
"args": "run -A main.ts links.ts",
|
||||||
|
"envs": {
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc"
|
||||||
|
},
|
||||||
"output": "links.out"
|
"output": "links.out"
|
||||||
},
|
},
|
||||||
"start_active_span": {
|
"start_active_span": {
|
||||||
"args": "run -A main.ts start_active_span.ts",
|
"args": "run -A main.ts start_active_span.ts",
|
||||||
|
"envs": {
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
|
||||||
|
"OTEL_EXPORTER_OTLP_CERTIFICATE": "../../../testdata/tls/RootCA.crt"
|
||||||
|
},
|
||||||
|
"output": "start_active_span.out"
|
||||||
|
},
|
||||||
|
"start_active_span_grpc": {
|
||||||
|
"args": "run -A main.ts start_active_span.ts",
|
||||||
|
"envs": {
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc"
|
||||||
|
},
|
||||||
"output": "start_active_span.out"
|
"output": "start_active_span.out"
|
||||||
},
|
},
|
||||||
"node_http_request": {
|
"node_http_request": {
|
||||||
"args": "run -A main.ts node_http_request.ts",
|
"args": "run -A main.ts node_http_request.ts",
|
||||||
|
"envs": {
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
|
||||||
|
"OTEL_EXPORTER_OTLP_CERTIFICATE": "../../../testdata/tls/RootCA.crt"
|
||||||
|
},
|
||||||
"output": "node_http_request.out"
|
"output": "node_http_request.out"
|
||||||
},
|
},
|
||||||
"events": {
|
"events": {
|
||||||
"args": "run -A main.ts events.ts",
|
"args": "run -A main.ts events.ts",
|
||||||
|
"envs": {
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
|
||||||
|
"OTEL_EXPORTER_OTLP_CERTIFICATE": "../../../testdata/tls/RootCA.crt"
|
||||||
|
},
|
||||||
|
"output": "events.out"
|
||||||
|
},
|
||||||
|
"events_grpc": {
|
||||||
|
"args": "run -A main.ts events.ts",
|
||||||
|
"envs": {
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc"
|
||||||
|
},
|
||||||
"output": "events.out"
|
"output": "events.out"
|
||||||
},
|
},
|
||||||
"metric_temporality_delta": {
|
"metric_temporality_delta": {
|
||||||
"envs": {
|
"envs": {
|
||||||
|
"OTEL_METRIC_EXPORT_INTERVAL": "1000",
|
||||||
|
"OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": "delta",
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
|
||||||
|
"OTEL_EXPORTER_OTLP_CERTIFICATE": "../../../testdata/tls/RootCA.crt"
|
||||||
|
},
|
||||||
|
"args": "run -A main.ts metric_temporality.ts",
|
||||||
|
"output": "metric_temporality_delta.out"
|
||||||
|
},
|
||||||
|
"metric_temporality_delta_grpc": {
|
||||||
|
"envs": {
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
|
||||||
"OTEL_METRIC_EXPORT_INTERVAL": "1000",
|
"OTEL_METRIC_EXPORT_INTERVAL": "1000",
|
||||||
"OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": "delta"
|
"OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": "delta"
|
||||||
},
|
},
|
||||||
|
@ -79,6 +160,17 @@
|
||||||
},
|
},
|
||||||
"metric_temporality_cumulative": {
|
"metric_temporality_cumulative": {
|
||||||
"envs": {
|
"envs": {
|
||||||
|
"OTEL_METRIC_EXPORT_INTERVAL": "1000",
|
||||||
|
"OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": "cumulative",
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "http/json",
|
||||||
|
"OTEL_EXPORTER_OTLP_CERTIFICATE": "../../../testdata/tls/RootCA.crt"
|
||||||
|
},
|
||||||
|
"args": "run -A main.ts metric_temporality.ts",
|
||||||
|
"output": "metric_temporality_cumulative.out"
|
||||||
|
},
|
||||||
|
"metric_temporality_cumulative_grpc": {
|
||||||
|
"envs": {
|
||||||
|
"OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
|
||||||
"OTEL_METRIC_EXPORT_INTERVAL": "1000",
|
"OTEL_METRIC_EXPORT_INTERVAL": "1000",
|
||||||
"OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": "cumulative"
|
"OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": "cumulative"
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,4 +1,2 @@
|
||||||
OTEL_DENO=true
|
OTEL_DENO=true
|
||||||
DENO_UNSTABLE_OTEL_DETERMINISTIC=0
|
DENO_UNSTABLE_OTEL_DETERMINISTIC=0
|
||||||
OTEL_EXPORTER_OTLP_PROTOCOL=http/json
|
|
||||||
OTEL_EXPORTER_OTLP_CERTIFICATE=../../../testdata/tls/RootCA.crt
|
|
||||||
|
|
|
@ -1,11 +1,130 @@
|
||||||
// Copyright 2018-2025 the Deno authors. MIT license.
|
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||||
|
|
||||||
|
import grpc from "npm:@grpc/grpc-js";
|
||||||
|
import protoLoader from "npm:@grpc/proto-loader";
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
spans: [],
|
spans: [],
|
||||||
logs: [],
|
logs: [],
|
||||||
metrics: [],
|
metrics: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Download and load OTLP proto definitions (traces, metrics, logs) from GitHub if not present
|
||||||
|
// Try to match with what's cloned into opentelemetry-proto crate
|
||||||
|
const opentelemetryProtoTag = "1.3.2";
|
||||||
|
async function ensureProtoFiles() {
|
||||||
|
if (await fileExists("./opentelemetry-proto")) return;
|
||||||
|
console.log("Downloading OpenTelemetry proto repo...");
|
||||||
|
const repo = "open-telemetry/opentelemetry-proto";
|
||||||
|
const url =
|
||||||
|
`https://github.com/${repo}/archive/refs/tags/v${opentelemetryProtoTag}.zip`;
|
||||||
|
const zipPath = "opentelemetry-proto.zip";
|
||||||
|
const unzipDir = "opentelemetry-proto";
|
||||||
|
// Download zip
|
||||||
|
const resp = await fetch(url);
|
||||||
|
if (!resp.ok) throw new Error(`Failed to download proto zip: ${resp.status}`);
|
||||||
|
const zipData = new Uint8Array(await resp.arrayBuffer());
|
||||||
|
await Deno.writeFile(zipPath, zipData);
|
||||||
|
// Unzip
|
||||||
|
const p = Deno.run({ cmd: ["unzip", "-q", zipPath, "-d", unzipDir] });
|
||||||
|
const status = await p.status();
|
||||||
|
if (!status.success) throw new Error("Failed to unzip proto files");
|
||||||
|
// Clean up
|
||||||
|
await Deno.remove(zipPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fileExists(path) {
|
||||||
|
try {
|
||||||
|
await Deno.stat(path);
|
||||||
|
return true;
|
||||||
|
} catch (_) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleOtlpRequest(call, callback, type) {
|
||||||
|
// call.request is the OTLP protobuf message
|
||||||
|
// For test purposes, just push to data and return success
|
||||||
|
console.log(
|
||||||
|
`[grpc] Received ${type} export request`,
|
||||||
|
JSON.stringify(call.request),
|
||||||
|
);
|
||||||
|
if (type === "traces") {
|
||||||
|
call.request.resourceSpans?.forEach((rSpans) => {
|
||||||
|
rSpans.scopeSpans.forEach((sSpans) => {
|
||||||
|
data.spans.push(...sSpans.spans);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (type === "metrics") {
|
||||||
|
call.request.resourceMetrics?.forEach((rMetrics) => {
|
||||||
|
rMetrics.scopeMetrics.forEach((sMetrics) => {
|
||||||
|
data.metrics.push(...sMetrics.metrics);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (type === "logs") {
|
||||||
|
call.request.resourceLogs?.forEach((rLogs) => {
|
||||||
|
rLogs.scopeLogs.forEach((sLogs) => {
|
||||||
|
data.logs.push(...sLogs.logRecords);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
callback(null, { partialSuccess: {} });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startGrpcServer(port, onReady) {
|
||||||
|
// Ensure proto files are present before loading
|
||||||
|
let otlp = await ensureProtoFiles().then(() => {
|
||||||
|
const packageDefinition = protoLoader.loadSync([
|
||||||
|
`opentelemetry/proto/collector/trace/v1/trace_service.proto`,
|
||||||
|
`opentelemetry/proto/collector/metrics/v1/metrics_service.proto`,
|
||||||
|
`opentelemetry/proto/collector/logs/v1/logs_service.proto`,
|
||||||
|
], {
|
||||||
|
includeDirs: [
|
||||||
|
`./opentelemetry-proto/opentelemetry-proto-${opentelemetryProtoTag}`,
|
||||||
|
],
|
||||||
|
keepCase: true,
|
||||||
|
longs: String,
|
||||||
|
enums: String,
|
||||||
|
defaults: true,
|
||||||
|
oneofs: true,
|
||||||
|
});
|
||||||
|
return grpc.loadPackageDefinition(packageDefinition).opentelemetry.proto;
|
||||||
|
});
|
||||||
|
const server = new grpc.Server();
|
||||||
|
// Register minimal OTLP services
|
||||||
|
server.addService(otlp.collector.trace.v1.TraceService.service, {
|
||||||
|
Export: (call, callback) => handleOtlpRequest(call, callback, "traces"),
|
||||||
|
});
|
||||||
|
server.addService(otlp.collector.metrics.v1.MetricsService.service, {
|
||||||
|
Export: (call, callback) => handleOtlpRequest(call, callback, "metrics"),
|
||||||
|
});
|
||||||
|
server.addService(otlp.collector.logs.v1.LogsService.service, {
|
||||||
|
Export: (call, callback) => handleOtlpRequest(call, callback, "logs"),
|
||||||
|
});
|
||||||
|
/// Read TLS key and cert files (same as HTTP server)
|
||||||
|
// const key = Deno.readTextFileSync("../../../testdata/tls/localhost.key");
|
||||||
|
// const cert = Deno.readTextFileSync("../../../testdata/tls/localhost.crt");
|
||||||
|
// const keyBuf = Buffer.from(key);
|
||||||
|
// const certBuf = Buffer.from(cert);
|
||||||
|
// const creds = grpc.ServerCredentials.createSsl(
|
||||||
|
// null,
|
||||||
|
// [{ private_key: keyBuf, cert_chain: certBuf }],
|
||||||
|
// true,
|
||||||
|
// );
|
||||||
|
/// Error: Not implemented: http2.createSecureServer
|
||||||
|
/// Use insecure credentials for gRPC in Deno (TLS not supported)
|
||||||
|
const creds = grpc.ServerCredentials.createInsecure();
|
||||||
|
server.bindAsync(
|
||||||
|
`0.0.0.0:${port}`,
|
||||||
|
creds,
|
||||||
|
(err, actualPort) => {
|
||||||
|
if (err) throw err;
|
||||||
|
console.log(`[grpc] Server listening on port ${actualPort}`);
|
||||||
|
onReady(actualPort, server);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
async function handler(req) {
|
async function handler(req) {
|
||||||
const body = await req.json();
|
const body = await req.json();
|
||||||
body.resourceLogs?.forEach((rLogs) => {
|
body.resourceLogs?.forEach((rLogs) => {
|
||||||
|
@ -26,100 +145,189 @@ async function handler(req) {
|
||||||
return Response.json({ partialSuccess: {} }, { status: 200 });
|
return Response.json({ partialSuccess: {} }, { status: 200 });
|
||||||
}
|
}
|
||||||
|
|
||||||
let server;
|
const protocol = Deno.env.get("OTEL_EXPORTER_OTLP_PROTOCOL")?.toLowerCase();
|
||||||
|
|
||||||
function onListen({ port }) {
|
// Only run the necessary collector server
|
||||||
const command = new Deno.Command(Deno.execPath(), {
|
switch (protocol) {
|
||||||
args: [
|
case "grpc": {
|
||||||
"run",
|
startGrpcServer(0, async (port, server) => {
|
||||||
"--env-file=env_file",
|
const command = new Deno.Command(Deno.execPath(), {
|
||||||
"-A",
|
args: [
|
||||||
"-q",
|
"run",
|
||||||
Deno.args[0],
|
"--env-file=env_file",
|
||||||
],
|
"-A",
|
||||||
env: {
|
"-q",
|
||||||
// rest of env is in env_file
|
Deno.args[0],
|
||||||
OTEL_EXPORTER_OTLP_ENDPOINT: `https://localhost:${port}`,
|
],
|
||||||
},
|
env: {
|
||||||
stdout: "null",
|
// rest of env is in env_file
|
||||||
});
|
OTEL_EXPORTER_OTLP_ENDPOINT: `http://localhost:${port}`,
|
||||||
const child = command.spawn();
|
},
|
||||||
child.status
|
stdout: "null",
|
||||||
.then((status) => {
|
});
|
||||||
if (status.signal) {
|
const child = command.spawn();
|
||||||
throw new Error("child process failed: " + JSON.stringify(status));
|
child.status
|
||||||
}
|
.then((status) => {
|
||||||
return server.shutdown();
|
if (status.signal) {
|
||||||
})
|
throw new Error("child process failed: " + JSON.stringify(status));
|
||||||
.then(() => {
|
|
||||||
data.logs.sort((a, b) =>
|
|
||||||
Number(
|
|
||||||
BigInt(a.observedTimeUnixNano) - BigInt(b.observedTimeUnixNano),
|
|
||||||
)
|
|
||||||
);
|
|
||||||
data.spans.sort((a, b) =>
|
|
||||||
Number(BigInt(`0x${a.spanId}`) - BigInt(`0x${b.spanId}`))
|
|
||||||
);
|
|
||||||
// v8js metrics are non-deterministic
|
|
||||||
data.metrics = data.metrics.filter((m) => !m.name.startsWith("v8js"));
|
|
||||||
data.metrics.sort((a, b) => a.name.localeCompare(b.name));
|
|
||||||
for (const metric of data.metrics) {
|
|
||||||
if ("histogram" in metric) {
|
|
||||||
metric.histogram.dataPoints.sort((a, b) => {
|
|
||||||
const aKey = a.attributes
|
|
||||||
.sort((x, y) => x.key.localeCompare(y.key))
|
|
||||||
.map(({ key, value }) => `${key}:${JSON.stringify(value)}`)
|
|
||||||
.join("|");
|
|
||||||
const bKey = b.attributes
|
|
||||||
.sort((x, y) => x.key.localeCompare(y.key))
|
|
||||||
.map(({ key, value }) => `${key}:${JSON.stringify(value)}`)
|
|
||||||
.join("|");
|
|
||||||
return aKey.localeCompare(bKey);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const dataPoint of metric.histogram.dataPoints) {
|
|
||||||
dataPoint.attributes.sort((a, b) => {
|
|
||||||
return a.key.localeCompare(b.key);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
server.tryShutdown(() => {});
|
||||||
if ("sum" in metric) {
|
})
|
||||||
metric.sum.dataPoints.sort((a, b) => {
|
.then(() => {
|
||||||
const aKey = a.attributes
|
data.logs.sort((a, b) =>
|
||||||
.sort((x, y) => x.key.localeCompare(y.key))
|
Number(
|
||||||
.map(({ key, value }) => `${key}:${JSON.stringify(value)}`)
|
BigInt(a.observedTimeUnixNano) - BigInt(b.observedTimeUnixNano),
|
||||||
.join("|");
|
)
|
||||||
const bKey = b.attributes
|
);
|
||||||
.sort((x, y) => x.key.localeCompare(y.key))
|
data.spans.sort((a, b) =>
|
||||||
.map(({ key, value }) => `${key}:${JSON.stringify(value)}`)
|
Number(BigInt(`0x${a.spanId}`) - BigInt(`0x${b.spanId}`))
|
||||||
.join("|");
|
);
|
||||||
return aKey.localeCompare(bKey);
|
// v8js metrics are non-deterministic
|
||||||
});
|
data.metrics = data.metrics.filter((m) => !m.name.startsWith("v8js"));
|
||||||
|
data.metrics.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
for (const metric of data.metrics) {
|
||||||
|
if ("histogram" in metric) {
|
||||||
|
metric.histogram.dataPoints.sort((a, b) => {
|
||||||
|
const aKey = a.attributes
|
||||||
|
.sort((x, y) => x.key.localeCompare(y.key))
|
||||||
|
.map(({ key, value }) => `${key}:${JSON.stringify(value)}`)
|
||||||
|
.join("|");
|
||||||
|
const bKey = b.attributes
|
||||||
|
.sort((x, y) => x.key.localeCompare(y.key))
|
||||||
|
.map(({ key, value }) => `${key}:${JSON.stringify(value)}`)
|
||||||
|
.join("|");
|
||||||
|
return aKey.localeCompare(bKey);
|
||||||
|
});
|
||||||
|
|
||||||
for (const dataPoint of metric.sum.dataPoints) {
|
for (const dataPoint of metric.histogram.dataPoints) {
|
||||||
dataPoint.attributes.sort((a, b) => {
|
dataPoint.attributes.sort((a, b) => {
|
||||||
return a.key.localeCompare(b.key);
|
return a.key.localeCompare(b.key);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ("sum" in metric) {
|
||||||
|
metric.sum.dataPoints.sort((a, b) => {
|
||||||
|
const aKey = a.attributes
|
||||||
|
.sort((x, y) => x.key.localeCompare(y.key))
|
||||||
|
.map(({ key, value }) => `${key}:${JSON.stringify(value)}`)
|
||||||
|
.join("|");
|
||||||
|
const bKey = b.attributes
|
||||||
|
.sort((x, y) => x.key.localeCompare(y.key))
|
||||||
|
.map(({ key, value }) => `${key}:${JSON.stringify(value)}`)
|
||||||
|
.join("|");
|
||||||
|
return aKey.localeCompare(bKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const dataPoint of metric.sum.dataPoints) {
|
||||||
|
dataPoint.attributes.sort((a, b) => {
|
||||||
|
return a.key.localeCompare(b.key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
console.log(JSON.stringify(data, null, 2));
|
||||||
}
|
});
|
||||||
console.log(JSON.stringify(data, null, 2));
|
|
||||||
});
|
});
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
|
case "http/protobuf":
|
||||||
|
case "http/json": {
|
||||||
|
let server;
|
||||||
|
function onListen({ port }) {
|
||||||
|
const command = new Deno.Command(Deno.execPath(), {
|
||||||
|
args: [
|
||||||
|
"run",
|
||||||
|
"--env-file=env_file",
|
||||||
|
"-A",
|
||||||
|
"-q",
|
||||||
|
Deno.args[0],
|
||||||
|
],
|
||||||
|
env: {
|
||||||
|
OTEL_EXPORTER_OTLP_ENDPOINT: `https://localhost:${port}`,
|
||||||
|
},
|
||||||
|
stdout: "null",
|
||||||
|
});
|
||||||
|
const child = command.spawn();
|
||||||
|
child.status
|
||||||
|
.then((status) => {
|
||||||
|
if (status.signal) {
|
||||||
|
throw new Error("child process failed: " + JSON.stringify(status));
|
||||||
|
}
|
||||||
|
server.shutdown();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
data.logs.sort((a, b) =>
|
||||||
|
Number(
|
||||||
|
BigInt(a.observedTimeUnixNano) - BigInt(b.observedTimeUnixNano),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
data.spans.sort((a, b) =>
|
||||||
|
Number(BigInt(`0x${a.spanId}`) - BigInt(`0x${b.spanId}`))
|
||||||
|
);
|
||||||
|
// v8js metrics are non-deterministic
|
||||||
|
data.metrics = data.metrics.filter((m) => !m.name.startsWith("v8js"));
|
||||||
|
data.metrics.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
|
for (const metric of data.metrics) {
|
||||||
|
if ("histogram" in metric) {
|
||||||
|
metric.histogram.dataPoints.sort((a, b) => {
|
||||||
|
const aKey = a.attributes
|
||||||
|
.sort((x, y) => x.key.localeCompare(y.key))
|
||||||
|
.map(({ key, value }) => `${key}:${JSON.stringify(value)}`)
|
||||||
|
.join("|");
|
||||||
|
const bKey = b.attributes
|
||||||
|
.sort((x, y) => x.key.localeCompare(y.key))
|
||||||
|
.map(({ key, value }) => `${key}:${JSON.stringify(value)}`)
|
||||||
|
.join("|");
|
||||||
|
return aKey.localeCompare(bKey);
|
||||||
|
});
|
||||||
|
|
||||||
if (Deno.env.get("OTEL_DENO_VSOCK")) {
|
for (const dataPoint of metric.histogram.dataPoints) {
|
||||||
server = Deno.serve({
|
dataPoint.attributes.sort((a, b) => {
|
||||||
cid: -1,
|
return a.key.localeCompare(b.key);
|
||||||
port: 4317,
|
});
|
||||||
onListen,
|
}
|
||||||
handler,
|
}
|
||||||
});
|
if ("sum" in metric) {
|
||||||
} else {
|
metric.sum.dataPoints.sort((a, b) => {
|
||||||
server = Deno.serve({
|
const aKey = a.attributes
|
||||||
key: Deno.readTextFileSync("../../../testdata/tls/localhost.key"),
|
.sort((x, y) => x.key.localeCompare(y.key))
|
||||||
cert: Deno.readTextFileSync("../../../testdata/tls/localhost.crt"),
|
.map(({ key, value }) => `${key}:${JSON.stringify(value)}`)
|
||||||
port: 0,
|
.join("|");
|
||||||
onListen,
|
const bKey = b.attributes
|
||||||
handler,
|
.sort((x, y) => x.key.localeCompare(y.key))
|
||||||
});
|
.map(({ key, value }) => `${key}:${JSON.stringify(value)}`)
|
||||||
|
.join("|");
|
||||||
|
return aKey.localeCompare(bKey);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const dataPoint of metric.sum.dataPoints) {
|
||||||
|
dataPoint.attributes.sort((a, b) => {
|
||||||
|
return a.key.localeCompare(b.key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(JSON.stringify(data, null, 2));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (Deno.env.get("OTEL_DENO_VSOCK")) {
|
||||||
|
server = Deno.serve({
|
||||||
|
cid: -1,
|
||||||
|
port: 4317,
|
||||||
|
onListen,
|
||||||
|
handler,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
server = Deno.serve({
|
||||||
|
key: Deno.readTextFileSync("../../../testdata/tls/localhost.key"),
|
||||||
|
cert: Deno.readTextFileSync("../../../testdata/tls/localhost.crt"),
|
||||||
|
port: 0,
|
||||||
|
onListen,
|
||||||
|
handler,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`Unknown OTEL_EXPORTER_OTLP_PROTOCOL: ${protocol}`);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue