Clojure Integration
Instrument Clojure applications with one Clojars dependency. Ring middleware tracing, JVM runtime metrics, core.async channel monitoring, and JDBC query spans — zero configuration.
How It Works
Add the Clojars Artifact
Add [io.tigerops/tigerops-clojure "0.1.0"] to your project.clj dependencies or deps.edn :deps map. The artifact bundles OpenTelemetry Clojure wrappers and a TigerOps OTLP exporter compatible with Leiningen and deps.edn projects.
Wrap Your Ring Handler
Require [tigerops.ring :refer [wrap-tigerops]] and wrap your root Ring handler with (wrap-tigerops handler). For Compojure, wrap the routes map. For Pedestal, add the tigerops interceptor to your service map interceptors vector.
Configure via Environment or EDN
Set TIGEROPS_API_KEY, TIGEROPS_SERVICE_NAME, and TIGEROPS_ENVIRONMENT as environment variables, or provide a tigerops.edn config file at the classpath root. Integrates with Component, Integrant, and Mount lifecycle systems.
Traces, JVM Metrics & Async Channels
Within seconds TigerOps receives Ring request spans, JVM GC and heap metrics, core.async channel put/take latency, and JDBC query spans from your Clojure application running on any JVM server.
What You Get Out of the Box
Ring Middleware Tracing
The wrap-tigerops middleware creates root spans for every Ring request with route pattern, HTTP method, status code, and response time. Supports ring-defaults, ring-json, and custom middleware chains with correct parent-child span linking.
JVM Runtime Metrics
Heap usage, GC pause duration, GC collection counts, thread states, class loading counts, and CPU usage are collected via JMX and exported as OTLP metrics every 30 seconds. Alerts fire on heap pressure or GC overhead ratio.
core.async Channel Monitoring
Instrument go blocks and thread calls to measure channel put and take latency, buffer utilization, and dropped values. Slow consumers and backpressure events are flagged with the channel var name and buffer size.
JDBC & next.jdbc Query Spans
Every SQL query issued through clojure.java.jdbc or next.jdbc becomes a child span with normalized SQL, database URL, execution time, and row count. Supports HikariCP and c3p0 connection pool metrics.
Compojure & Reitit Route Attribution
Route patterns from Compojure defroutes and Reitit router maps are extracted and attached to spans as http.route. Nested routes are resolved to their full path so traces group correctly by endpoint rather than by raw URI.
Manual Span API
Use (tigerops/with-span ["operation.name" {:attr "value"}] ...) to wrap any Clojure form in a custom span. Spans are automatically parented to the active trace context, and exceptions thrown inside the form are recorded and re-thrown.
Install & Initialize
One Clojars artifact. One middleware wrapper. Full Clojure observability.
;; project.clj — add to :dependencies
(defproject my-app "0.1.0"
:dependencies [[org.clojure/clojure "1.11.1"]
[io.tigerops/tigerops-clojure "0.1.0"]
[ring/ring-core "1.11.0"]
[compojure "1.7.0"]])
;; Set environment variables
;; export TIGEROPS_API_KEY="your-api-key"
;; export TIGEROPS_SERVICE_NAME="my-clojure-app"
;; export TIGEROPS_ENVIRONMENT="production"
;; Or use tigerops.edn at classpath root
;; {:api-key #env TIGEROPS_API_KEY
;; :service "my-clojure-app"
;; :env "production"}
;; core.clj — wrap your Ring handler
(ns my-app.core
(:require [compojure.core :refer [defroutes GET POST]]
[ring.adapter.jetty :refer [run-jetty]]
[tigerops.ring :refer [wrap-tigerops]]
[tigerops.core :as t]))
(defroutes app-routes
(GET "/health" [] {:status 200 :body "ok"})
(POST "/orders" req (handle-order req)))
(def app
(-> app-routes
wrap-tigerops)) ;; <-- single wrap call
;; Manual span in a handler
(defn handle-order [req]
(t/with-span ["order.process" {:order/source "api"}]
(let [order (create-order! (:body req))]
(t/set-attribute! "order.id" (str (:id order)))
{:status 201 :body order})))
;; Start the server
(defn -main []
(run-jetty app {:port 3000 :join? false}))Common Questions
Which Clojure and JVM versions are supported?
Clojure 1.10, 1.11, and 1.12 are fully supported on JVM 11, 17, and 21. GraalVM native-image is supported in JVM mode. Native image compilation requires the --initialize-at-run-time flag for the tigerops.exporter namespace.
How does TigerOps integrate with Component or Integrant lifecycle systems?
TigerOps provides a tigerops.component/map->TigerOps record for Component and a :tigerops/tracer key for Integrant. The tracer is started on system start and shut down gracefully on system stop, flushing any buffered spans.
Can I trace individual Clojure functions without modifying their signatures?
Yes. Use the (tigerops/deftraced) macro as a drop-in replacement for defn. It wraps the function body in a span named after the fully qualified var name and records the first argument as a span attribute when it is a plain map.
Does the Ring middleware capture request body or query params?
No. By default only URL pattern, HTTP method, response status, and timing are captured. You can opt in to recording specific request parameters by calling (tigerops/set-attribute! "param.name" value) inside your handler.
How do I instrument Kafka consumers built with the jackdaw library?
Add the tigerops-jackdaw artifact and wrap your consumer topology with (tigerops.jackdaw/instrument-topology topology). Produce and consume events create linked spans using W3C Trace Context headers stored in Kafka record headers.
Full Clojure Observability in One Clojars Artifact
Ring traces, JVM metrics, core.async monitoring, and JDBC query spans — no code changes required.