This commit is contained in:
7Hazard 2025-09-19 08:31:07 +02:00 committed by GitHub
commit 176b18a4b0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 456 additions and 124 deletions

View file

@ -289,7 +289,7 @@ zip = { version = "2.4.1", default-features = false, features = ["flate2"] }
opentelemetry = "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_sdk = { version = "0.27.0", features = ["rt-tokio", "trace"] }

View file

@ -870,10 +870,10 @@ pub fn init(
// Parse the `OTEL_EXPORTER_OTLP_PROTOCOL` variable. The opentelemetry_*
// crates don't do this automatically.
// TODO(piscisaureus): enable GRPC support.
let protocol = match env::var("OTEL_EXPORTER_OTLP_PROTOCOL").as_deref() {
Ok("http/protobuf") => Protocol::HttpBinary,
Ok("http/json") => Protocol::HttpJson,
Ok("grpc") => Protocol::Grpc,
Ok("") | Err(env::VarError::NotPresent) => Protocol::HttpBinary,
Ok(protocol) => {
return Err(deno_core::anyhow::anyhow!(
@ -930,45 +930,78 @@ pub fn init(
// `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable. Additional headers can
// 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 =
BatchSpanProcessor::builder(span_exporter, OtelSharedRuntime).build();
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 meter_provider = SdkMeterProvider::builder()
.with_reader(metric_reader)
.with_resource(resource.clone())
.build();
let log_exporter = HttpExporterBuilder::default()
.with_http_client(client)
.with_protocol(protocol)
.build_log_exporter()?;
let log_processor =
BatchLogProcessor::builder(log_exporter, OtelSharedRuntime).build();
log_processor.set_resource(&resource);

1
tests/specs/cli/otel_basic/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
opentelemetry-proto/

View file

@ -2,6 +2,17 @@
"tests": {
"basic": {
"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"
},
"basic_vsock": {
@ -26,6 +37,16 @@
},
"metric": {
"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"
},
"args": "run -A main.ts metric.ts",
@ -33,44 +54,104 @@
},
"fetch": {
"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"
},
"http_metric": {
"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",
"output": "http_metric.out"
},
"http_propagators": {
"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"
},
"node_http_metric": {
"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",
"output": "node_http_metric.out"
},
"links": {
"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"
},
"start_active_span": {
"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"
},
"node_http_request": {
"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"
},
"events": {
"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"
},
"metric_temporality_delta": {
"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_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": "delta"
},
@ -79,6 +160,17 @@
},
"metric_temporality_cumulative": {
"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_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE": "cumulative"
},

View file

@ -1,4 +1,2 @@
OTEL_DENO=true
DENO_UNSTABLE_OTEL_DETERMINISTIC=0
OTEL_EXPORTER_OTLP_PROTOCOL=http/json
OTEL_EXPORTER_OTLP_CERTIFICATE=../../../testdata/tls/RootCA.crt

View file

@ -1,11 +1,130 @@
// Copyright 2018-2025 the Deno authors. MIT license.
import grpc from "npm:@grpc/grpc-js";
import protoLoader from "npm:@grpc/proto-loader";
const data = {
spans: [],
logs: [],
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) {
const body = await req.json();
body.resourceLogs?.forEach((rLogs) => {
@ -26,100 +145,189 @@ async function handler(req) {
return Response.json({ partialSuccess: {} }, { status: 200 });
}
let server;
const protocol = Deno.env.get("OTEL_EXPORTER_OTLP_PROTOCOL")?.toLowerCase();
function onListen({ port }) {
const command = new Deno.Command(Deno.execPath(), {
args: [
"run",
"--env-file=env_file",
"-A",
"-q",
Deno.args[0],
],
env: {
// rest of env is in env_file
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));
}
return 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);
});
for (const dataPoint of metric.histogram.dataPoints) {
dataPoint.attributes.sort((a, b) => {
return a.key.localeCompare(b.key);
});
// Only run the necessary collector server
switch (protocol) {
case "grpc": {
startGrpcServer(0, async (port, server) => {
const command = new Deno.Command(Deno.execPath(), {
args: [
"run",
"--env-file=env_file",
"-A",
"-q",
Deno.args[0],
],
env: {
// rest of env is in env_file
OTEL_EXPORTER_OTLP_ENDPOINT: `http://localhost:${port}`,
},
stdout: "null",
});
const child = command.spawn();
child.status
.then((status) => {
if (status.signal) {
throw new Error("child process failed: " + JSON.stringify(status));
}
}
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);
});
server.tryShutdown(() => {});
})
.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.sum.dataPoints) {
dataPoint.attributes.sort((a, b) => {
return a.key.localeCompare(b.key);
});
for (const dataPoint of metric.histogram.dataPoints) {
dataPoint.attributes.sort((a, b) => {
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")) {
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,
});
for (const dataPoint of metric.histogram.dataPoints) {
dataPoint.attributes.sort((a, b) => {
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));
});
}
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}`);
}