Haskell Integration
Instrument Haskell applications with one Cabal dependency. GHC runtime metrics, WAI middleware tracing, STM transaction monitoring, and persistent query spans — zero configuration.
How It Works
Add the Cabal Package
Add tigerops-haskell to your package.cabal build-depends or stack.yaml extra-deps. The package bundles hs-opentelemetry-sdk bindings with a TigerOps OTLP exporter and a GHC eventlog reader for runtime metric collection.
Add WAI Middleware
Import Network.Wai.Middleware.TigerOps and add tigerOpsMiddleware to your Wai Application. For Servant or Yesod, use the provided mkTigerOpsMiddleware function with your service configuration record.
Configure via Environment or Config Record
Set TIGEROPS_API_KEY, TIGEROPS_SERVICE_NAME, and TIGEROPS_ENVIRONMENT. Alternatively, construct a TigerOpsConfig record and pass it to initTigerOps in your main function before the Wai server starts.
Traces, GHC Metrics & STM Events
Within seconds TigerOps receives Wai request spans, GHC heap and GC stats, STM transaction retry rates, and persistent SQL query spans from your Haskell application running on GHC or GHCi.
What You Get Out of the Box
WAI Middleware Tracing
tigerOpsMiddleware creates root spans for every Wai request with path, HTTP method, status code, and response time. Servant route types are reflected at compile time to populate http.route with the exact endpoint pattern.
GHC Runtime Metrics
Heap allocation rate, live bytes, GC pause duration, GC generation counts, thread count, and sparks created are read from the GHC eventlog and emitted as OTLP metrics. Enable with +RTS -l-agu -RTS at process startup.
STM Transaction Monitoring
Software Transactional Memory retry counts, transaction duration, and conflict rates are tracked per call site. High retry rates are surfaced as anomalies in TigerOps with the STM action source location when compiled with profiling.
Persistent & Esqueleto Query Spans
Every SQL query issued through persistent or esqueleto becomes a child span with normalized SQL, entity name, execution time, and row count. Supports PostgreSQL via postgresql-simple and MySQL via mysql-simple backends.
Servant & Yesod Route Attribution
Route patterns from Servant type-level APIs and Yesod route files are extracted and attached to spans. Type-safe route parameters are recorded as span attributes without capturing their raw values to protect sensitive data.
Manual Span API
Use withSpan "operation.name" defaultSpanArguments $ \span -> ... to wrap any IO action in a custom span. The span is automatically parented to the active TraceContext in the ReaderT environment and exceptions are recorded.
Install & Initialize
One Cabal dependency. One middleware application. Full Haskell observability.
-- package.cabal — add to build-depends
executable my-app
build-depends:
base >= 4.16
, wai >= 3.2
, warp >= 3.3
, tigerops-haskell >= 0.1
-- Set environment variables
-- export TIGEROPS_API_KEY="your-api-key"
-- export TIGEROPS_SERVICE_NAME="my-haskell-app"
-- export TIGEROPS_ENVIRONMENT="production"
-- Main.hs
module Main where
import Network.Wai (Application)
import Network.Wai.Handler.Warp (run)
import Network.Wai.Middleware.TigerOps
import TigerOps (initTigerOps, TigerOpsConfig(..))
import TigerOps.Span (withSpan, setAttribute)
app :: Application
app req respond = do
withSpan "request.handle" [] $ sp -> do
setAttribute sp "request.path" (show (pathInfo req))
respond $ responseLBS status200 [] "Hello, World!"
main :: IO ()
main = do
initTigerOps TigerOpsConfig
{ apiKey = "your-api-key"
, serviceName = "my-haskell-app"
, environment = "production"
}
run 3000 (tigerOpsMiddleware app)
-- Enable GHC eventlog:
-- Run with: +RTS -l-agu -RTSCommon Questions
Which GHC versions and build tools are supported?
GHC 9.2, 9.4, 9.6, and 9.8 are fully supported. Both Cabal (3.8+) and Stack (2.13+) build tools work. The package is available on Hackage and Stackage nightly. LTS Stackage snapshots are added within two weeks of release.
Does TigerOps work with the async and concurrency libraries?
Yes. TigerOps propagates trace context through async actions, forkIO threads, and the unliftio library. The active span is stored in a thread-local IORef and automatically copied to forked threads using the TigerOps withSpan combinator.
How do I instrument pure functions that perform no IO?
Pure functions cannot be directly traced since spans require IO. Wrap the call site in an IO context using withSpan and evaluate the pure function inside. For expensive pure computations, use evaluate or force to ensure lazy evaluation happens within the span.
Can TigerOps instrument GraphQL APIs built with morpheus-graphql?
Yes. Add the tigerops-morpheus package and wrap your GQL root resolvers with tigerOpsResolver. Each resolver becomes a child span named after the field, and query depth and complexity are recorded as span attributes.
What is the overhead of GHC eventlog collection in production?
Eventlog collection adds less than 1% CPU overhead in benchmarks with GHC 9.4+. The -l-agu flags collect only allocation and GC events. Thread events are disabled by default and can be enabled with TIGEROPS_GHC_THREAD_EVENTS=true for debugging.
Full Haskell Observability in One Cabal Package
WAI traces, GHC metrics, STM monitoring, and persistent query spans — no code changes required.