Logging

This guide explains how logging works in ShipClojure, how to configure log levels for different environments, and how to add logging to your own code.


Overview

ShipClojure uses Telemere as its logging library. Telemere is the next-generation replacement for Timbre, providing structured telemetry with a unified API.

Key Features:

  • Structured JSON logging for production

  • Contextual logging that accumulates request information

  • Unified backend for both Clojure logs and Java/SLF4J logs

  • Per-namespace log level configuration

  • Automatic sensitive data redaction


Telemere as the Unified Logging Backend

ShipClojure uses Telemere for all logging, including logs from Java libraries. This is achieved through telemere-slf4j, which routes all SLF4J logging calls through Telemere.

Dependencies

;; In deps.edn
com.taoensso/telemere {:mvn/version "1.2.0"}
com.taoensso/telemere-slf4j {:mvn/version "1.2.0"}
org.slf4j/slf4j-api {:mvn/version "2.0.17"}

This setup means:

  • Your Clojure code uses taoensso.telemere directly

  • Java libraries (HikariCP, HTTP-Kit, PostgreSQL driver, etc.) use SLF4J, which routes to Telemere

  • All logs go through the same handlers and filters

Verifying Interop

You can verify the SLF4J integration is working in your REPL:


Basic Logging

Creating Logs

Use t/log! for basic logging:

Log Levels

Telemere supports these levels (from most to least verbose):

  • :trace

  • :debug

  • :info

  • :warn

  • :error

  • :fatal


Contextual Logging

One of Telemere's most powerful features is contextual logging with t/with-ctx+. ShipClojure uses this to gradually add context as a request flows through the middleware chain.

How Context Accumulates

This means any log statement deep in your business logic automatically includes the request method, URI, request ID, user info, and more.

Implementation

The context is added through Ring middleware in saas.web.middleware.logger:

Later middleware adds more context:

Benefits

With this setup, every log entry contains the full context:

You can trace all logs for a single request by filtering on req-id.

Configuring Log Levels

Per-Environment Configuration

Log levels are configured in the environment-specific env.clj files:

  • env/dev/clj/saas/env.clj - Development settings

  • env/prod/clj/saas/env.clj - Production settings

  • env/test/clj/saas/env.clj - Test settings

Understanding Signal Kinds

Telemere uses "signal kinds" to distinguish log sources:

  • :log - Logs from Telemere's log! macro (your Clojure code)

  • :slf4j - Logs from Java libraries via SLF4J

When configuring levels, you need to specify the correct kind:

Silencing Noisy Dependencies

When you add a new Java dependency that's too chatty, silence it by adding to the appropriate configure-*-log-levels! function:

Development Configuration

Development is configured to be verbose for application code but quiet for third-party libraries:

Production Configuration

Production keeps application logs at INFO:

Test Configuration

Tests are configured to be as quiet as possible:


Sensitive Data Redaction

The logging middleware automatically redacts sensitive fields from request parameters:

Logged as:

Adding Custom Redaction

Edit src/clj/saas/logging.clj:


JSON File Logging

In production, logs are written to JSON files for easy querying.

Configuration

The JSON file handler is configured in env/prod/clj/saas/env.clj:

Log Format

Each line is a single JSON object:


Common jq Queries


Adding Logging to Your Code

In Handlers

Error Logging


Debugging Logging Issues

Check Active Handlers

Check Current Filters

Test Signal Creation

Temporarily Disable Filters


Further Reading

Last updated