GitPedia

Hs opentelemetry

OpenTelemetry support for the Haskell programming language

From iand675·Updated June 18, 2026·View on GitHub·

Traces, metrics, and logs for Haskell applications and libraries The project is written primarily in Haskell, distributed under the Other license, first published in 2021. Key topics include: haskell, honeycomb, logging, metrics, observability.

<p align="center"> <a href="https://opentelemetry.io"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/cncf/artwork/main/projects/opentelemetry/horizontal/white/opentelemetry-horizontal-white.svg"> <source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/cncf/artwork/main/projects/opentelemetry/horizontal/color/opentelemetry-horizontal-color.svg"> <img alt="OpenTelemetry" src="https://raw.githubusercontent.com/cncf/artwork/main/projects/opentelemetry/horizontal/color/opentelemetry-horizontal-color.svg" width="400"> </picture> </a> </p> <h2 align="center">OpenTelemetry for Haskell</h2> <p align="center"> <em>Traces, metrics, and logs for Haskell applications and libraries</em> </p> <p align="center"> <a href="https://hackage.haskell.org/package/hs-opentelemetry-api"><img alt="Hackage" src="https://img.shields.io/hackage/v/hs-opentelemetry-api?style=flat-square&logo=haskell&label=api"></a> <a href="https://hackage.haskell.org/package/hs-opentelemetry-sdk"><img alt="Hackage" src="https://img.shields.io/hackage/v/hs-opentelemetry-sdk?style=flat-square&logo=haskell&label=sdk"></a> <img alt="GHC" src="https://img.shields.io/badge/GHC-9.4_|_9.6_|_9.8_|_9.10_|_9.12-blue?style=flat-square&logo=haskell"> <a href="https://github.com/iand675/hs-opentelemetry/blob/main/LICENSE"><img alt="License" src="https://img.shields.io/badge/license-BSD--3--Clause-green?style=flat-square"></a> <a href="https://github.com/sponsors/iand675"><img alt="Sponsor" src="https://img.shields.io/github/sponsors/iand675?style=flat-square&label=sponsor&color=ea4aaa"></a> </p>

In Brief

hs-opentelemetry is a native Haskell implementation of
OpenTelemetry, the vendor-neutral observability
standard backed by the CNCF. It lets you instrument your Haskell code to emit

  • Traces - distributed request flows across services
  • Metrics - counters, histograms, and gauges
  • Logs - structured log records correlated with traces

and export them to any OpenTelemetry-compatible backend (Jaeger, Honeycomb,
Datadog, Grafana, etc.) without coupling your code to a specific vendor.

The project follows the upstream OpenTelemetry
specification
closely, with a clean
separation between the API (for library authors) and the SDK (for
application authors) - the same split used by the official Go, Python, and Java
implementations.

Why Instrument with OpenTelemetry?

If you've ever added putStrLn-based debugging to track down why a request was
slow, or scattered ad-hoc metrics across your codebase, you've felt the problem
OpenTelemetry solves.

Without OpenTelemetry, observability in Haskell tends to look like:

haskell
handleRequest req = do t0 <- getCurrentTime putStrLn $ "Processing " <> show (requestPath req) result <- processRequest req t1 <- getCurrentTime putStrLn $ "Done in " <> show (diffUTCTime t1 t0) pure result

This doesn't compose. It doesn't correlate across services. It doesn't let you
switch from stdout to Datadog to Honeycomb without rewriting your code. And it
pollutes your business logic with observability concerns.

With hs-opentelemetry, the same intent becomes:

haskell
handleRequest req = inSpan tracer "handleRequest" defaultSpanArguments $ do processRequest req

One line. The span carries timing, a unique trace ID that correlates across
service boundaries, and you can attach structured attributes to it. The SDK
decides where the data goes - stdout in development, OTLP to your collector in
production - and your application code doesn't change.

Getting Started

There are two packages to know about:

You are...Use
Instrumenting a library (e.g., a database driver, HTTP client wrapper)hs-opentelemetry-api
Building an application that configures and exports telemetryhs-opentelemetry-sdk

Library authors depend on the API so their users aren't forced into a particular
SDK configuration. Application authors pull in the SDK, which initializes
providers, installs exporters, and wires everything together.

Traces

Traces represent the path of a request through your system. Each unit of work
is a span; spans nest to form a tree.

haskell
import OpenTelemetry.Trace (withTracerProvider, getTracer, tracerOptions) import OpenTelemetry.Trace.Core (inSpan, defaultSpanArguments) main :: IO () main = withTracerProvider $ \tp -> do let tracer = getTracer tp "my-service" tracerOptions inSpan tracer "main" defaultSpanArguments $ do inSpan tracer "step-1" defaultSpanArguments $ putStrLn "doing work" inSpan tracer "step-2" defaultSpanArguments $ putStrLn "more work"

withTracerProvider reads standard OTEL_* environment variables (service
name, exporter endpoint, sampling rate, etc.), initializes the global provider,
and shuts it down cleanly on exit - including flushing any buffered spans.

Use inSpan' when you need access to the Span handle, for example to attach
attributes during execution:

haskell
inSpan' tracer "fetchUser" defaultSpanArguments $ \span -> do user <- lookupUser uid addAttribute span "user.id" (toAttribute uid) pure user

Metrics

Metrics capture measurements over time: request counts, latencies, queue depths.

haskell
import OpenTelemetry.Metric.Core main :: IO () main = do mp <- getGlobalMeterProvider meter <- getMeter mp "my-service" counter <- meterCreateCounterInt64 meter "http.requests" "" Nothing defaultAdvisoryParameters latency <- meterCreateHistogram meter "http.request.duration" "ms" Nothing defaultAdvisoryParameters -- In your request handler: counterAdd counter 1 [("method", toAttribute ("GET" :: Text))] histogramRecord latency 42.5 [("method", toAttribute ("GET" :: Text))]

The SDK supports synchronous instruments (counters, histograms, up-down
counters) and asynchronous/observable instruments for system-level metrics like
GHC runtime statistics:

haskell
import OpenTelemetry.Instrumentation.GHCMetrics (registerGHCMetrics) meter <- getMeter mp "ghc-metrics" registerGHCMetrics meter -- GC pause times, allocation rates, thread counts, etc. are now exported

Logs

The logging API is a bridge: it lets existing Haskell logging libraries
(katip, co-log, monad-logger) emit structured log records that are
automatically correlated with the active trace context.

haskell
import OpenTelemetry.Log (withLoggerProvider, makeLogger) import OpenTelemetry.Log.Core (emitLogRecord, LogRecordArguments(..), SeverityNumber(..)) main :: IO () main = withLoggerProvider $ \lp -> do let logger = makeLogger lp "my-app" emitLogRecord logger $ emptyLogRecordArguments { body = Just (toValue ("Application started" :: Text)) , severityNumber = Just SeverityNumberInfo }

Or use one of the bridge libraries to connect your existing logging framework:

LoggerBridge
katiphs-opentelemetry-instrumentation-katip
co-loghs-opentelemetry-instrumentation-co-log
monad-loggerhs-opentelemetry-instrumentation-monad-logger

WAI Middleware

For web applications, a single line of middleware instruments every incoming
HTTP request:

haskell
import Network.Wai.Handler.Warp (run) import OpenTelemetry.Instrumentation.Wai (newOpenTelemetryWaiMiddleware) import OpenTelemetry.Trace (withTracerProvider) main :: IO () main = withTracerProvider $ \_ -> do otelMiddleware <- newOpenTelemetryWaiMiddleware run 8080 $ otelMiddleware myApp

Each request gets a server span with method, route, status code, and timing.
Downstream calls (database queries, HTTP clients) automatically nest as child
spans when you use the corresponding instrumentation libraries.

Specification Conformance

Traces, metrics, and logs are fully implemented. See the detailed conformance
checklist
for per-feature coverage against the
OpenTelemetry specification.

SignalAPI ModuleSDK ModuleStatus
TracesOpenTelemetry.Trace.CoreOpenTelemetry.TraceStable
MetricsOpenTelemetry.Metric.CoreOpenTelemetry.MeterProviderStable
LogsOpenTelemetry.Log.CoreOpenTelemetry.LogStable

Performance

The library is designed for minimal overhead in instrumented applications. When
the SDK is not installed or has no processors configured, inSpan is a no-op
that costs 13.6 ns and allocates 15 bytes.

Benchmarks (GHC 9.10, aarch64-osx, -O1 -N1 -A32m):

OperationTimeAllocated
inSpan no-op (no SDK)13.6 ns15 B
inSpan active218–445 ns1.2–2.5 KB
bare span (create+end)209 ns1.2 KB
HTTP span (3 attrs)410 ns2.5 KB
DB span (5 attrs)520 ns3.3 KB
getContext2.9 ns15 B
lookupSpan0.6 ns0 B

For comparison, bare span create+end on the same workload (no attributes,
AlwaysSample) is ~279 ns in the Go
SDK
and ~349 ns
in the Rust
SDK
.
Cross-language numbers are from different machines, so ratios are approximate.

<details> <summary><strong>Key design choices for low overhead</strong></summary>
  • Unboxed Word64 trace/span IDs (no heap-allocated byte arrays)
  • Thread-local xoshiro256++ RNG in C (no contention, no syscalls after seed)
  • Direct clock_gettime FFI for timestamps (no alloca/errno overhead)
  • Dedicated context slots for span and baggage (O(1), no Vault lookup)
  • No-op fast path skips mask, context writes, and ID generation entirely
  • INLINE on hot-path functions with case-of-case optimization for samplers

Run make bench.save to establish a baseline on your machine, then
make bench.check after changes to catch regressions above 20%.

</details>

Package Ecosystem

Instrumentation Libraries

LibraryPackageSignals
waihs-opentelemetry-instrumentation-waiTraces, Metrics
yesod-corehs-opentelemetry-instrumentation-yesodTraces
persistent / esqueletohs-opentelemetry-instrumentation-persistentTraces
persistent-mysqlhs-opentelemetry-instrumentation-persistent-mysqlTraces
postgresql-simplehs-opentelemetry-instrumentation-postgresql-simpleTraces
http-client / http-conduiths-opentelemetry-instrumentation-http-clientTraces, Metrics
conduiths-opentelemetry-instrumentation-conduitTraces
hw-kafka-clienths-opentelemetry-instrumentation-hw-kafka-clientTraces
amazonkahs-opentelemetry-instrumentation-amazonkaTraces
gogolhs-opentelemetry-instrumentation-gogolTraces
GHC runtimehs-opentelemetry-instrumentation-ghc-metricsMetrics
hspechs-opentelemetry-instrumentation-hspecTraces
tastyhs-opentelemetry-instrumentation-tastyTraces
katiphs-opentelemetry-instrumentation-katipLogs
co-loghs-opentelemetry-instrumentation-co-logLogs
monad-loggerhs-opentelemetry-instrumentation-monad-loggerLogs
cloudflarehs-opentelemetry-instrumentation-cloudflareTraces

Exporters

FormatPackageSignals
OTLPhs-opentelemetry-exporter-otlpTraces, Metrics, Logs
Handle (stdout)hs-opentelemetry-exporter-handleTraces, Metrics, Logs
In-Memoryhs-opentelemetry-exporter-in-memoryTraces, Metrics, Logs
Prometheushs-opentelemetry-exporter-prometheusMetrics

Tip: For Honeycomb, Datadog, Grafana Cloud, and other OTLP-compatible backends,
use hs-opentelemetry-exporter-otlp with the appropriate endpoint.

Propagators

FormatPackageModule
W3C TraceContexths-opentelemetry-propagator-w3cOpenTelemetry.Propagator.W3CTraceContext
W3C Baggagehs-opentelemetry-propagator-w3cOpenTelemetry.Propagator.W3CBaggage
B3hs-opentelemetry-propagator-b3OpenTelemetry.Propagator.B3
Jaegerhs-opentelemetry-propagator-jaegerOpenTelemetry.Propagator.Jaeger
Datadoghs-opentelemetry-propagator-datadogOpenTelemetry.Propagator.Datadog
AWS X-Rayhs-opentelemetry-propagator-xrayOpenTelemetry.Propagator.XRay

GHC Compatibility

GHCStack resolverNotes
9.4lts-21.25No hw-kafka-client, no gogol
9.6lts-22.44No gogol
9.8lts-23.28No gogol
9.10lts-24.35Full support
9.12nightly-2026-04-04No persistent-mysql; proto-lens via allow-newer

Examples

Working application examples are in the examples/ directory:

Contributing

See CONTRIBUTING.md.

Maintainer: Ian Duncan

Contributors

Showing top 12 contributors by commit count.

View all contributors on GitHub →

This article is auto-generated from iand675/hs-opentelemetry via the GitHub API.Last fetched: 6/22/2026