From 36bc05cc9e8cc88043776c5f940e704a55f243d9 Mon Sep 17 00:00:00 2001 From: krynju Date: Mon, 22 Jun 2026 14:50:33 +0200 Subject: [PATCH 1/3] Support HTTP.jl 2.0 in OTLP proto/http exporter HTTP.jl 2.0 reworked client timeout keywords: `readtimeout` is now a deprecated alias that maps to the inactivity-based `read_idle_timeout`, and the overall request deadline uses the new `request_timeout` keyword. Detect the HTTP major version once at load time and pass the matching timeout keyword: `request_timeout` on 2.x, `readtimeout` on 1.x. The `retry` / `retry_non_idempotent` keywords are unchanged across versions. Widen the HTTP compat bound to "1, 2" and bump the package to 0.1.7. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/exporter/otlp/proto/http/Project.toml | 4 ++-- .../src/OpenTelemetryExporterOtlpProtoHttp.jl | 24 ++++++++++++++----- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/exporter/otlp/proto/http/Project.toml b/src/exporter/otlp/proto/http/Project.toml index 9816b34..477587d 100644 --- a/src/exporter/otlp/proto/http/Project.toml +++ b/src/exporter/otlp/proto/http/Project.toml @@ -1,7 +1,7 @@ name = "OpenTelemetryExporterOtlpProtoHttp" uuid = "f175482f-0544-4037-b362-361efbf21c04" authors = ["Jun Tian "] -version = "0.1.6" +version = "0.1.7" [deps] HTTP = "cd3eb016-35fb-5094-929b-558a96fad6f3" @@ -11,7 +11,7 @@ OpenTelemetrySDK = "f5929ecf-be04-446c-b2f2-494e1fe70ee4" ProtoBuf = "3349acd9-ac6a-5e09-bcdb-63829b23a429" [compat] -HTTP = "1" +HTTP = "1, 2" OpenTelemetryAPI = "0.4,0.5" OpenTelemetryProto = "0.19,0.20" OpenTelemetrySDK = "0.3,0.4" diff --git a/src/exporter/otlp/proto/http/src/OpenTelemetryExporterOtlpProtoHttp.jl b/src/exporter/otlp/proto/http/src/OpenTelemetryExporterOtlpProtoHttp.jl index bacfaac..15019bf 100644 --- a/src/exporter/otlp/proto/http/src/OpenTelemetryExporterOtlpProtoHttp.jl +++ b/src/exporter/otlp/proto/http/src/OpenTelemetryExporterOtlpProtoHttp.jl @@ -20,6 +20,14 @@ import OpenTelemetryProto.opentelemetry.proto.metrics.v1 as METRICS import OpenTelemetryProto.opentelemetry.proto.common.v1 as COMMON import OpenTelemetryProto.opentelemetry.proto.resource.v1 as RESOURCE +# HTTP.jl 2.0 renamed the request-wide deadline keyword: `readtimeout` (HTTP 1.x) +# became a deprecated alias mapping to the inactivity-based `read_idle_timeout`, +# while overall request deadlines now use `request_timeout`. Detect the major +# version once at load time and pick the right keyword. `pkgversion` exists since +# Julia 1.9; HTTP 2 itself requires Julia >= 1.10, so on older Julia (no +# `pkgversion`) only HTTP 1 can be installed and `false` is the correct default. +const _HTTP_V2 = isdefined(Base, :pkgversion) ? Base.pkgversion(HTTP) >= v"2" : false + struct OtlpHttpExporter{Req,Resp} <: SDK.AbstractExporter url::String headers::Vector{Pair{String,String}} @@ -76,14 +84,18 @@ function SDK.export!(x::OtlpHttpExporter{Req,Resp}, batch::Union{AbstractVector, encode(e, convert(Req, batch)) seekstart(io) + # `retry` / `retry_non_idempotent` remain valid keywords in both HTTP 1.x + # and 2.x; only the timeout keyword changed (see `_HTTP_V2` above). + timeout_kw = _HTTP_V2 ? (; request_timeout = x.timeout) : (; readtimeout = x.timeout) + res = API.with_context(; API.SUPPRESS_INSTRUMENTATION_KEY => true) do HTTP.post( - x.url, - x.headers; - body = io, - readtimeout=x.timeout, - retry=true, - retry_non_idempotent=true + x.url, + x.headers; + body = io, + retry = true, + retry_non_idempotent = true, + timeout_kw..., ) end From c60078139ba1d79a56230e8a222fbf241d87a317 Mon Sep 17 00:00:00 2001 From: krynju Date: Tue, 23 Jun 2026 11:16:04 +0200 Subject: [PATCH 2/3] Support HTTP.jl 2.0 in Prometheus exporter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HTTP.jl 2.0 reworked the server API: the `stream = true` mode of `serve!` and the `setstatus` / `setheader` / `startwrite` streaming helpers were removed in favor of returning an `HTTP.Response` from the handler, and the running-server handle type changed from `HTTP.Servers.Server` to `HTTP.Server`. Rewrite the `/metrics` handler to buffer the body and return a `Response` — a single code path that works on both HTTP 1.x and 2.x via the still-supported `Router` / `register!` / `serve!` request-handler API. Drop the now version-specific field type annotation on the stored server handle. The pushgateway path (`HTTP.request`, `HTTP.Headers`) is unchanged and already cross-version. Widen the HTTP compat bound to "1, 2" and bump the package to 0.3.2. Verified: the package's test suite (33 tests, including the end-to-end `/metrics` HTTP round-trip) passes under both HTTP 1.x and HTTP 2.4.0. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/exporter/prometheus/Project.toml | 4 ++-- .../src/OpenTelemetryExporterPrometheus.jl | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/exporter/prometheus/Project.toml b/src/exporter/prometheus/Project.toml index 257d7e9..c9498f8 100644 --- a/src/exporter/prometheus/Project.toml +++ b/src/exporter/prometheus/Project.toml @@ -1,7 +1,7 @@ name = "OpenTelemetryExporterPrometheus" uuid = "34e26579-e93c-4b5e-ba07-7afb633408c2" authors = ["Jun Tian "] -version = "0.3.1" +version = "0.3.2" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" @@ -10,7 +10,7 @@ OpenTelemetrySDK = "f5929ecf-be04-446c-b2f2-494e1fe70ee4" URIs = "5c2747f8-b7ea-4ff2-ba2e-563bfd36b1d4" [compat] -HTTP = "1" +HTTP = "1, 2" OpenTelemetrySDK = "0.4" URIs = "1" julia = "1" diff --git a/src/exporter/prometheus/src/OpenTelemetryExporterPrometheus.jl b/src/exporter/prometheus/src/OpenTelemetryExporterPrometheus.jl index dae2f09..17eae63 100644 --- a/src/exporter/prometheus/src/OpenTelemetryExporterPrometheus.jl +++ b/src/exporter/prometheus/src/OpenTelemetryExporterPrometheus.jl @@ -5,8 +5,11 @@ export PrometheusExporter using OpenTelemetrySDK using HTTP +# A request handler returning an `HTTP.Response`. This works identically across +# HTTP.jl 1.x and 2.x. (HTTP 2.0 dropped the `stream = true` server mode and the +# `setstatus`/`setheader`/`startwrite` streaming helpers in favor of returning a +# `Response`; buffering the body here keeps a single code path for both.) function handler( - io, provider::Ref{MeterProvider}, resource_to_telemetry_conversion, with_timestamp, @@ -14,11 +17,9 @@ function handler( for ins in provider[].async_instruments ins() end - HTTP.setstatus(io, 200) - HTTP.setheader(io, "Content-Type" => "text/plain") - HTTP.startwrite(io) + io = IOBuffer() text_based_format(io, provider[], resource_to_telemetry_conversion; with_timestamp) - nothing + HTTP.Response(200, ["Content-Type" => "text/plain"]; body = take!(io)) end """ @@ -40,7 +41,9 @@ r = MetricReader(PrometheusExporter()) Note that `PrometheusExporter` is a pull based exporter. There's no need to execute `r()` to update the metrics. """ mutable struct PrometheusExporter <: OpenTelemetrySDK.AbstractExporter - server::HTTP.Servers.Server + # Untyped: the running-server handle type differs between HTTP.jl versions + # (`HTTP.Servers.Server` on 1.x, `HTTP.Server` on 2.x). + server::Any provider::Ref{MeterProvider} function PrometheusExporter(; host = OTEL_EXPORTER_PROMETHEUS_HOST(), @@ -57,9 +60,9 @@ mutable struct PrometheusExporter <: OpenTelemetrySDK.AbstractExporter router, "GET", path, - io -> handler(io, provider, resource_to_telemetry_conversion, with_timestamp), + _ -> handler(provider, resource_to_telemetry_conversion, with_timestamp), ) - server = HTTP.serve!(router, host, port; stream = true, kw...) + server = HTTP.serve!(router, host, port; kw...) new(server, provider) end From 89d46067204724f5f7e98adad08003f6a67846f1 Mon Sep 17 00:00:00 2001 From: krynju Date: Wed, 24 Jun 2026 12:34:54 +0200 Subject: [PATCH 3/3] Harden HTTP.jl 2.0 version detection in OTLP exporter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `Base.pkgversion(m)` can return `nothing` (its documented behavior when a module wasn't loaded from a versioned package); `nothing >= v"2"` would then throw at module load. Guard it and fall back to API feature detection — `HTTP.Servers` exists throughout HTTP 1.x and was removed in 2.0 — which also covers Julia < 1.9 where `pkgversion` is unavailable. Verified the primary and fallback branches agree on both HTTP 1.11 and 2.4, and that a full export round-trip (encode → POST → decode response body) returns EXPORT_SUCCESS against a local collector on both versions. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../src/OpenTelemetryExporterOtlpProtoHttp.jl | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/exporter/otlp/proto/http/src/OpenTelemetryExporterOtlpProtoHttp.jl b/src/exporter/otlp/proto/http/src/OpenTelemetryExporterOtlpProtoHttp.jl index 15019bf..5308dbd 100644 --- a/src/exporter/otlp/proto/http/src/OpenTelemetryExporterOtlpProtoHttp.jl +++ b/src/exporter/otlp/proto/http/src/OpenTelemetryExporterOtlpProtoHttp.jl @@ -23,10 +23,17 @@ import OpenTelemetryProto.opentelemetry.proto.resource.v1 as RESOURCE # HTTP.jl 2.0 renamed the request-wide deadline keyword: `readtimeout` (HTTP 1.x) # became a deprecated alias mapping to the inactivity-based `read_idle_timeout`, # while overall request deadlines now use `request_timeout`. Detect the major -# version once at load time and pick the right keyword. `pkgversion` exists since -# Julia 1.9; HTTP 2 itself requires Julia >= 1.10, so on older Julia (no -# `pkgversion`) only HTTP 1 can be installed and `false` is the correct default. -const _HTTP_V2 = isdefined(Base, :pkgversion) ? Base.pkgversion(HTTP) >= v"2" : false +# version once at load time and pick the right keyword. +# +# Primary signal is the package version. `pkgversion(::Module)` exists since +# Julia 1.9 but may return `nothing` (e.g. when a module wasn't loaded from a +# versioned package), so we fall back to API feature detection: the `HTTP.Servers` +# submodule exists throughout HTTP 1.x and was removed in 2.0, making it a +# reliable discriminator that also covers Julia < 1.9 (where only HTTP 1 can +# resolve, since HTTP 2 requires Julia >= 1.10). +const _HTTP_V2 = let v = isdefined(Base, :pkgversion) ? Base.pkgversion(HTTP) : nothing + v === nothing ? !isdefined(HTTP, :Servers) : v >= v"2" +end struct OtlpHttpExporter{Req,Resp} <: SDK.AbstractExporter url::String