Initial commit
This commit is contained in:
686
skills/timbre/SKILL.md
Normal file
686
skills/timbre/SKILL.md
Normal file
@@ -0,0 +1,686 @@
|
||||
---
|
||||
name: timbre
|
||||
description: Pure Clojure/Script logging library with flexible configuration and powerful features
|
||||
---
|
||||
|
||||
# Timbre
|
||||
|
||||
Pure Clojure/Script logging library with zero dependencies, simple configuration, and powerful features like async logging, rate limiting, and flexible appenders.
|
||||
|
||||
## Overview
|
||||
|
||||
Timbre is a logging library designed for Clojure and ClojureScript applications. Unlike Java logging frameworks requiring XML or properties files, Timbre uses pure Clojure data structures for all configuration.
|
||||
|
||||
**Key characteristics:**
|
||||
- Full Clojure and ClojureScript support
|
||||
- Single config map - no XML/properties files
|
||||
- Zero overhead compile-time level/namespace elision
|
||||
- Built-in async logging and rate limiting
|
||||
- Function-based appenders and middleware
|
||||
- Optional tools.logging and SLF4J interop
|
||||
|
||||
**Library:** `com.taoensso/timbre`
|
||||
**Latest Version:** 6.8.0
|
||||
**License:** EPL-1.0
|
||||
|
||||
**Note:** For new projects, consider [Telemere](https://github.com/taoensso/telemere) - a modern rewrite of Timbre. Existing Timbre users have no pressure to migrate.
|
||||
|
||||
## Installation
|
||||
|
||||
```clojure
|
||||
;; deps.edn
|
||||
{:deps {com.taoensso/timbre {:mvn/version "6.8.0"}}}
|
||||
|
||||
;; Leiningen
|
||||
[com.taoensso/timbre "6.8.0"]
|
||||
```
|
||||
|
||||
## Core Concepts
|
||||
|
||||
### Log Levels
|
||||
|
||||
Seven standard levels in ascending severity:
|
||||
|
||||
- `:trace` - Detailed diagnostic information
|
||||
- `:debug` - Debugging information
|
||||
- `:info` - Informational messages
|
||||
- `:warn` - Warning messages
|
||||
- `:error` - Error messages
|
||||
- `:fatal` - Critical failures
|
||||
- `:report` - Special reporting level
|
||||
|
||||
### Appenders
|
||||
|
||||
Functions that handle log output: `(fn [data]) -> ?effects`
|
||||
|
||||
Each appender receives a data map containing:
|
||||
- `:level` - Log level keyword
|
||||
- `:?msg` - Log message
|
||||
- `:timestamp` - When log occurred
|
||||
- `:hostname` - System hostname
|
||||
- `:?ns-str` - Namespace string
|
||||
- `:?file`, `:?line` - Source location
|
||||
- `:?err` - Exception (if present)
|
||||
- Additional context data
|
||||
|
||||
### Middleware
|
||||
|
||||
Functions that transform log data: `(fn [data]) -> ?data`
|
||||
|
||||
Applied before appenders receive data, enabling:
|
||||
- Data enrichment
|
||||
- Filtering
|
||||
- Transformation
|
||||
- Context injection
|
||||
|
||||
### Configuration
|
||||
|
||||
Timbre uses a single atom `*config*` containing:
|
||||
- `:min-level` - Minimum log level
|
||||
- `:ns-filter` - Namespace filtering
|
||||
- `:middleware` - Middleware functions
|
||||
- `:timestamp-opts` - Timestamp formatting
|
||||
- `:output-fn` - Output formatter
|
||||
- `:appenders` - Appender map
|
||||
|
||||
## Basic Usage
|
||||
|
||||
### Simple Logging
|
||||
|
||||
```clojure
|
||||
(require '[taoensso.timbre :as timbre])
|
||||
|
||||
;; Basic logging at different levels
|
||||
(timbre/trace "Entering function")
|
||||
(timbre/debug "Variable value:" x)
|
||||
(timbre/info "Server started on port" port)
|
||||
(timbre/warn "Deprecated function used")
|
||||
(timbre/error "Failed to connect to database")
|
||||
(timbre/fatal "Critical system failure")
|
||||
(timbre/report "Daily metrics" {:users 1000 :requests 5000})
|
||||
```
|
||||
|
||||
### Formatted Logging
|
||||
|
||||
```clojure
|
||||
;; Printf-style formatting
|
||||
(timbre/infof "User %s logged in from %s" username ip)
|
||||
(timbre/warnf "Cache miss rate: %.2f%%" miss-rate)
|
||||
(timbre/errorf "Request failed: %d %s" status-code message)
|
||||
```
|
||||
|
||||
### Logging with Exceptions
|
||||
|
||||
```clojure
|
||||
(try
|
||||
(risky-operation)
|
||||
(catch Exception e
|
||||
(timbre/error e "Operation failed")))
|
||||
|
||||
;; Multiple values
|
||||
(timbre/error e "Failed processing" {:user-id 123 :item-id 456})
|
||||
```
|
||||
|
||||
### Spy - Log and Return
|
||||
|
||||
```clojure
|
||||
;; Log value and return it
|
||||
(let [result (timbre/spy :info (expensive-calculation x y))]
|
||||
(process result))
|
||||
|
||||
;; With custom message
|
||||
(timbre/spy :debug
|
||||
{:msg "Calculation result"}
|
||||
(expensive-calculation x y))
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Setting Minimum Level
|
||||
|
||||
```clojure
|
||||
;; Global minimum level
|
||||
(timbre/set-min-level! :info)
|
||||
|
||||
;; Per-namespace level
|
||||
(timbre/set-ns-min-level! {:deny #{"noisy.namespace.*"}
|
||||
:allow #{"important.namespace.*"}})
|
||||
```
|
||||
|
||||
### Modifying Config
|
||||
|
||||
```clojure
|
||||
;; Replace entire config
|
||||
(timbre/set-config! new-config)
|
||||
|
||||
;; Merge into existing config
|
||||
(timbre/merge-config! {:min-level :debug
|
||||
:appenders {:println (println-appender)}})
|
||||
|
||||
;; Swap with function
|
||||
(timbre/swap-config! update :min-level (constantly :warn))
|
||||
```
|
||||
|
||||
### Scoped Configuration
|
||||
|
||||
```clojure
|
||||
;; Temporarily change config
|
||||
(timbre/with-config custom-config
|
||||
(timbre/info "Logged with custom config"))
|
||||
|
||||
;; Merge temporary changes
|
||||
(timbre/with-merged-config {:min-level :trace}
|
||||
(timbre/trace "Temporarily enabled trace logging"))
|
||||
|
||||
;; Temporary level change
|
||||
(timbre/with-min-level :debug
|
||||
(timbre/debug "Debug enabled for this scope"))
|
||||
```
|
||||
|
||||
## Appenders
|
||||
|
||||
### Built-in Appenders
|
||||
|
||||
#### Console Output
|
||||
|
||||
```clojure
|
||||
(require '[taoensso.timbre.appenders.core :as appenders])
|
||||
|
||||
;; println appender (default)
|
||||
{:appenders {:println (appenders/println-appender)}}
|
||||
```
|
||||
|
||||
#### File Output
|
||||
|
||||
```clojure
|
||||
;; Simple file appender
|
||||
{:appenders {:spit (appenders/spit-appender
|
||||
{:fname "/var/log/app.log"})}}
|
||||
```
|
||||
|
||||
### Custom Appender
|
||||
|
||||
```clojure
|
||||
(defn my-appender
|
||||
"Appender that writes to a custom destination"
|
||||
[opts]
|
||||
{:enabled? true
|
||||
:async? false
|
||||
:min-level nil ; Inherit from config
|
||||
:rate-limit nil
|
||||
:output-fn :inherit
|
||||
:fn (fn [data]
|
||||
(let [{:keys [output-fn]} data
|
||||
formatted (output-fn data)]
|
||||
;; Custom logic here
|
||||
(send-to-custom-system formatted)))})
|
||||
|
||||
;; Use custom appender
|
||||
(timbre/merge-config!
|
||||
{:appenders {:custom (my-appender {})}})
|
||||
```
|
||||
|
||||
### Appender Configuration Options
|
||||
|
||||
```clojure
|
||||
{:enabled? true ; Enable/disable
|
||||
:async? false ; Async logging?
|
||||
:min-level :info ; Appender-specific min level
|
||||
:rate-limit [[5 1000]] ; Max 5 logs per 1000ms
|
||||
:output-fn :inherit ; Use config's output-fn
|
||||
:fn (fn [data] ...)} ; Handler function
|
||||
```
|
||||
|
||||
## Advanced Features
|
||||
|
||||
### Context (MDC)
|
||||
|
||||
```clojure
|
||||
;; Set context for current thread
|
||||
(timbre/with-context {:user-id 123 :request-id "abc"}
|
||||
(timbre/info "Processing request")
|
||||
(do-work))
|
||||
|
||||
;; Add to existing context
|
||||
(timbre/with-context+ {:session-id "xyz"}
|
||||
(timbre/info "Additional context"))
|
||||
```
|
||||
|
||||
### Middleware
|
||||
|
||||
```clojure
|
||||
;; Add hostname to all logs
|
||||
(defn add-hostname-middleware
|
||||
[data]
|
||||
(assoc data :hostname (get-hostname)))
|
||||
|
||||
;; Add timestamp middleware
|
||||
(defn add-custom-timestamp
|
||||
[data]
|
||||
(assoc data :custom-ts (System/currentTimeMillis)))
|
||||
|
||||
;; Apply middleware
|
||||
(timbre/merge-config!
|
||||
{:middleware [add-hostname-middleware
|
||||
add-custom-timestamp]})
|
||||
```
|
||||
|
||||
### Rate Limiting
|
||||
|
||||
```clojure
|
||||
;; Per-appender rate limit
|
||||
{:appenders
|
||||
{:println
|
||||
{:enabled? true
|
||||
:rate-limit [[10 1000] ; Max 10 per second
|
||||
[100 60000]] ; Max 100 per minute
|
||||
:fn (fn [data] (println (:output-fn data)))}}}
|
||||
|
||||
;; At log call site
|
||||
(timbre/log {:rate-limit [[1 5000]]} ; Max once per 5 seconds
|
||||
:info "Rate limited message")
|
||||
```
|
||||
|
||||
### Async Logging
|
||||
|
||||
```clojure
|
||||
;; Enable async for appender
|
||||
{:appenders
|
||||
{:async-file
|
||||
{:enabled? true
|
||||
:async? true ; Process logs asynchronously
|
||||
:fn (fn [data]
|
||||
(spit "/var/log/app.log"
|
||||
(str (:output-fn data) "\n")
|
||||
:append true))}}}
|
||||
```
|
||||
|
||||
### Conditional Logging
|
||||
|
||||
```clojure
|
||||
;; Log only sometimes (probabilistic)
|
||||
(timbre/sometimes 0.1 ; 10% probability
|
||||
(timbre/info "Sampled log message"))
|
||||
|
||||
;; Conditional error logging
|
||||
(timbre/log-errors
|
||||
(risky-operation)) ; Logs if exception thrown
|
||||
|
||||
;; Log and rethrow
|
||||
(timbre/log-and-rethrow-errors
|
||||
(risky-operation)) ; Logs then rethrows exception
|
||||
```
|
||||
|
||||
### Exception Handling
|
||||
|
||||
```clojure
|
||||
;; Capture uncaught JVM exceptions (Clojure only)
|
||||
(timbre/handle-uncaught-jvm-exceptions!)
|
||||
|
||||
;; Log errors in futures
|
||||
(timbre/logged-future
|
||||
(risky-async-operation))
|
||||
```
|
||||
|
||||
## Output Formatting
|
||||
|
||||
### Custom Output Function
|
||||
|
||||
```clojure
|
||||
(defn my-output-fn
|
||||
[{:keys [level ?ns-str ?msg-fmt vargs timestamp_ ?err]}]
|
||||
(str
|
||||
(force timestamp_) " "
|
||||
(str/upper-case (name level)) " "
|
||||
"[" ?ns-str "] - "
|
||||
(apply format ?msg-fmt vargs)
|
||||
(when ?err (str "\n" (timbre/stacktrace ?err)))))
|
||||
|
||||
;; Use custom output
|
||||
(timbre/merge-config! {:output-fn my-output-fn})
|
||||
```
|
||||
|
||||
### Timestamp Configuration
|
||||
|
||||
```clojure
|
||||
{:timestamp-opts
|
||||
{:pattern "yyyy-MM-dd HH:mm:ss.SSS"
|
||||
:locale (java.util.Locale/getDefault)
|
||||
:timezone (java.util.TimeZone/getDefault)}}
|
||||
```
|
||||
|
||||
### Colors
|
||||
|
||||
```clojure
|
||||
(require '[taoensso.timbre :refer [color-str]])
|
||||
|
||||
;; ANSI color output
|
||||
(defn colored-output-fn
|
||||
[{:keys [level] :as data}]
|
||||
(let [base-output (timbre/default-output-fn data)]
|
||||
(case level
|
||||
:error (color-str :red base-output)
|
||||
:warn (color-str :yellow base-output)
|
||||
:info (color-str :green base-output)
|
||||
base-output)))
|
||||
```
|
||||
|
||||
## Namespace Filtering
|
||||
|
||||
```clojure
|
||||
;; Whitelist/blacklist namespaces
|
||||
(timbre/merge-config!
|
||||
{:ns-filter
|
||||
{:deny #{"noisy.lib.*" "chatty.namespace"}
|
||||
:allow #{"important.module.*"}}})
|
||||
|
||||
;; Per-namespace min levels
|
||||
(timbre/set-ns-min-level!
|
||||
'{my.app.core :trace
|
||||
my.app.utils :info
|
||||
["com.external.*"] :warn})
|
||||
```
|
||||
|
||||
## Interoperability
|
||||
|
||||
### tools.logging Integration
|
||||
|
||||
```clojure
|
||||
;; Route tools.logging to Timbre
|
||||
(require '[taoensso.timbre.tools.logging :as tools-logging])
|
||||
|
||||
(tools-logging/use-timbre)
|
||||
|
||||
;; Now tools.logging calls use Timbre
|
||||
(require '[clojure.tools.logging :as log])
|
||||
(log/info "Routed through Timbre")
|
||||
```
|
||||
|
||||
### SLF4J Integration
|
||||
|
||||
Timbre can act as an SLF4J backend for Java logging. Requires additional setup with timbre-slf4j-appender or similar.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Application Setup
|
||||
|
||||
```clojure
|
||||
(ns myapp.logging
|
||||
(:require [taoensso.timbre :as timbre]
|
||||
[taoensso.timbre.appenders.core :as appenders]))
|
||||
|
||||
(defn init-logging!
|
||||
[]
|
||||
(timbre/merge-config!
|
||||
{:min-level :info
|
||||
:ns-filter {:deny #{"verbose.library.*"}}
|
||||
:middleware []
|
||||
:timestamp-opts {:pattern "yyyy-MM-dd HH:mm:ss"}
|
||||
:appenders
|
||||
{:println (appenders/println-appender)
|
||||
:spit (appenders/spit-appender
|
||||
{:fname "/var/log/myapp.log"})}}))
|
||||
|
||||
(init-logging!)
|
||||
```
|
||||
|
||||
### Structured Logging
|
||||
|
||||
```clojure
|
||||
;; Log with structured data
|
||||
(timbre/info "User action"
|
||||
{:action :login
|
||||
:user-id 123
|
||||
:ip "192.168.1.1"
|
||||
:timestamp (System/currentTimeMillis)})
|
||||
|
||||
;; Custom output for structured logs
|
||||
(defn json-output-fn
|
||||
[{:keys [level msg_ ?ns-str timestamp_ vargs]}]
|
||||
(json/write-str
|
||||
{:timestamp (force timestamp_)
|
||||
:level level
|
||||
:namespace ?ns-str
|
||||
:message (force msg_)
|
||||
:data (first vargs)}))
|
||||
```
|
||||
|
||||
### Environment-Specific Config
|
||||
|
||||
```clojure
|
||||
(defn config-for-env
|
||||
[env]
|
||||
(case env
|
||||
:dev {:min-level :trace
|
||||
:appenders {:println (appenders/println-appender)}}
|
||||
:staging {:min-level :debug
|
||||
:appenders {:spit (appenders/spit-appender
|
||||
{:fname "/var/log/staging.log"})}}
|
||||
:prod {:min-level :info
|
||||
:appenders {:spit (appenders/spit-appender
|
||||
{:fname "/var/log/production.log"
|
||||
:async? true})}}))
|
||||
|
||||
(timbre/set-config! (config-for-env :prod))
|
||||
```
|
||||
|
||||
### Request Logging
|
||||
|
||||
```clojure
|
||||
(defn wrap-logging
|
||||
[handler]
|
||||
(fn [request]
|
||||
(let [start (System/currentTimeMillis)
|
||||
request-id (str (random-uuid))]
|
||||
(timbre/with-context {:request-id request-id}
|
||||
(timbre/info "Request started" {:method (:request-method request)
|
||||
:uri (:uri request)})
|
||||
(let [response (handler request)
|
||||
duration (- (System/currentTimeMillis) start)]
|
||||
(timbre/info "Request completed"
|
||||
{:status (:status response)
|
||||
:duration-ms duration})
|
||||
response)))))
|
||||
```
|
||||
|
||||
### Database Query Logging
|
||||
|
||||
```clojure
|
||||
(defn log-query
|
||||
[query params]
|
||||
(timbre/spy :debug
|
||||
{:msg (str "Executing query: " query)}
|
||||
(db/execute! query params)))
|
||||
|
||||
;; Or with timing
|
||||
(defn log-slow-queries
|
||||
[query params]
|
||||
(let [start (System/currentTimeMillis)
|
||||
result (db/execute! query params)
|
||||
duration (- (System/currentTimeMillis) start)]
|
||||
(when (> duration 1000)
|
||||
(timbre/warn "Slow query detected"
|
||||
{:query query
|
||||
:duration-ms duration}))
|
||||
result))
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Exception Logging Patterns
|
||||
|
||||
```clojure
|
||||
;; Basic exception logging
|
||||
(try
|
||||
(risky-op)
|
||||
(catch Exception e
|
||||
(timbre/error e "Operation failed")))
|
||||
|
||||
;; With context
|
||||
(try
|
||||
(process-item item)
|
||||
(catch Exception e
|
||||
(timbre/error e "Failed to process item"
|
||||
{:item-id (:id item)
|
||||
:item-type (:type item)})))
|
||||
|
||||
;; Log and continue
|
||||
(defn safe-process
|
||||
[items]
|
||||
(doseq [item items]
|
||||
(timbre/log-errors
|
||||
(process-item item))))
|
||||
|
||||
;; Log and rethrow
|
||||
(defn critical-operation
|
||||
[]
|
||||
(timbre/log-and-rethrow-errors
|
||||
(perform-critical-task)))
|
||||
```
|
||||
|
||||
## Performance Considerations
|
||||
|
||||
### Compile-Time Elision
|
||||
|
||||
```clojure
|
||||
;; Set via JVM property or env var
|
||||
;; Only :info and above will be compiled
|
||||
;; :trace and :debug calls removed at compile time
|
||||
;; -Dtimbre.min-level=:info
|
||||
|
||||
;; Verify elision
|
||||
(timbre/debug "This won't be in bytecode if min-level >= :info")
|
||||
```
|
||||
|
||||
### Async Appenders
|
||||
|
||||
```clojure
|
||||
;; Offload I/O to background thread
|
||||
{:appenders
|
||||
{:file
|
||||
{:async? true
|
||||
:fn (fn [data]
|
||||
;; Expensive I/O operation
|
||||
(write-to-file data))}}}
|
||||
```
|
||||
|
||||
### Conditional Evaluation
|
||||
|
||||
```clojure
|
||||
;; Arguments evaluated only if level enabled
|
||||
(timbre/debug (expensive-debug-string)) ; Not called if debug disabled
|
||||
|
||||
;; For very expensive operations, use explicit check
|
||||
(when (timbre/log? :debug)
|
||||
(timbre/debug (very-expensive-operation)))
|
||||
```
|
||||
|
||||
## ClojureScript Usage
|
||||
|
||||
```clojure
|
||||
(ns myapp.core
|
||||
(:require [taoensso.timbre :as timbre]))
|
||||
|
||||
;; Same API as Clojure
|
||||
(timbre/info "Running in browser")
|
||||
|
||||
;; Configure for browser
|
||||
(timbre/set-config!
|
||||
{:level :debug
|
||||
:appenders
|
||||
{:console
|
||||
{:enabled? true
|
||||
:fn (fn [data]
|
||||
(let [{:keys [output-fn]} data]
|
||||
(.log js/console (output-fn data))))}}})
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Test Configuration
|
||||
|
||||
```clojure
|
||||
(ns myapp.test
|
||||
(:require [clojure.test :refer :all]
|
||||
[taoensso.timbre :as timbre]))
|
||||
|
||||
;; Disable logging in tests
|
||||
(use-fixtures :once
|
||||
(fn [f]
|
||||
(timbre/with-merged-config {:min-level :fatal}
|
||||
(f))))
|
||||
|
||||
;; Or capture logs for assertions
|
||||
(defn with-log-capture
|
||||
[f]
|
||||
(let [logs (atom [])]
|
||||
(timbre/with-merged-config
|
||||
{:appenders
|
||||
{:test {:enabled? true
|
||||
:fn (fn [data]
|
||||
(swap! logs conj data))}}}
|
||||
(f logs))))
|
||||
```
|
||||
|
||||
## Migration from tools.logging
|
||||
|
||||
```clojure
|
||||
;; Before (tools.logging)
|
||||
(require '[clojure.tools.logging :as log])
|
||||
(log/info "Message")
|
||||
(log/error e "Error occurred")
|
||||
|
||||
;; After (Timbre)
|
||||
(require '[taoensso.timbre :as timbre])
|
||||
(timbre/info "Message")
|
||||
(timbre/error e "Error occurred")
|
||||
|
||||
;; Or keep tools.logging imports and use bridge
|
||||
(require '[taoensso.timbre.tools.logging :as tools-logging])
|
||||
(tools-logging/use-timbre)
|
||||
;; Now existing tools.logging code routes to Timbre
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### No Output
|
||||
|
||||
Check configuration:
|
||||
```clojure
|
||||
;; Verify config
|
||||
@timbre/*config*
|
||||
|
||||
;; Check min level
|
||||
(:min-level @timbre/*config*)
|
||||
|
||||
;; Verify appenders enabled
|
||||
(->> @timbre/*config* :appenders vals (filter :enabled?))
|
||||
```
|
||||
|
||||
### Missing Logs
|
||||
|
||||
```clojure
|
||||
;; Check namespace filters
|
||||
(timbre/may-log? :info) ; In current namespace
|
||||
(timbre/may-log? :info "some.namespace") ; Specific namespace
|
||||
```
|
||||
|
||||
### Performance Issues
|
||||
|
||||
```clojure
|
||||
;; Enable async for expensive appenders
|
||||
{:appenders {:file {:async? true ...}}}
|
||||
|
||||
;; Add rate limiting
|
||||
{:appenders {:email {:rate-limit [[1 60000]] ...}}}
|
||||
|
||||
;; Use compile-time elision in production
|
||||
;; -Dtimbre.min-level=:info
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- [GitHub Repository](https://github.com/taoensso/timbre)
|
||||
- [API Documentation](https://taoensso.github.io/timbre/)
|
||||
- [Wiki](https://github.com/taoensso/timbre/wiki)
|
||||
- [Telemere (Modern Alternative)](https://github.com/taoensso/telemere)
|
||||
247
skills/timbre/examples.clj
Executable file
247
skills/timbre/examples.clj
Executable file
@@ -0,0 +1,247 @@
|
||||
#!/usr/bin/env bb
|
||||
|
||||
(require '[clojure.string :as str])
|
||||
|
||||
;; Add Timbre dependency for Babashka
|
||||
(require '[babashka.deps :as deps])
|
||||
(deps/add-deps '{:deps {com.taoensso/timbre {:mvn/version "6.8.0"}}})
|
||||
|
||||
(require '[taoensso.timbre :as timbre]
|
||||
'[taoensso.timbre.appenders.core :as appenders])
|
||||
|
||||
(println "\n=== Timbre Logging Examples ===\n")
|
||||
|
||||
;; Example 1: Basic Logging Levels
|
||||
(println "--- Example 1: Basic Logging Levels ---")
|
||||
(timbre/trace "Trace level - detailed diagnostics")
|
||||
(timbre/debug "Debug level - debugging information")
|
||||
(timbre/info "Info level - general information")
|
||||
(timbre/warn "Warn level - warning message")
|
||||
(timbre/error "Error level - error occurred")
|
||||
(timbre/fatal "Fatal level - critical failure")
|
||||
(timbre/report "Report level - special reporting")
|
||||
|
||||
;; Example 2: Formatted Logging
|
||||
(println "\n--- Example 2: Formatted Logging ---")
|
||||
(let [username "alice"
|
||||
port 8080
|
||||
percentage 95.5]
|
||||
(timbre/infof "User %s connected" username)
|
||||
(timbre/infof "Server started on port %d" port)
|
||||
(timbre/infof "Success rate: %.2f%%" percentage))
|
||||
|
||||
;; Example 3: Logging with Data
|
||||
(println "\n--- Example 3: Logging with Data ---")
|
||||
(timbre/info "User login event"
|
||||
{:user-id 123
|
||||
:username "bob"
|
||||
:ip "192.168.1.100"
|
||||
:timestamp (System/currentTimeMillis)})
|
||||
|
||||
;; Example 4: Exception Logging
|
||||
(println "\n--- Example 4: Exception Logging ---")
|
||||
(try
|
||||
(throw (ex-info "Simulated error" {:code 500}))
|
||||
(catch Exception e
|
||||
(timbre/error e "Operation failed with exception")))
|
||||
|
||||
;; Example 5: Spy - Log and Return Value
|
||||
(println "\n--- Example 5: Spy - Log and Return Value ---")
|
||||
(defn calculate [x y]
|
||||
(* x y))
|
||||
|
||||
(let [result (timbre/spy :info (calculate 6 7))]
|
||||
(println "Function returned:" result))
|
||||
|
||||
;; Example 6: Configuration - Setting Minimum Level
|
||||
(println "\n--- Example 6: Setting Minimum Level ---")
|
||||
(println "Current min level:" (:min-level @timbre/*config*))
|
||||
(timbre/set-min-level! :warn)
|
||||
(println "After setting to :warn:")
|
||||
(timbre/debug "This debug message won't appear")
|
||||
(timbre/warn "This warning will appear")
|
||||
(timbre/set-min-level! :debug) ; Reset
|
||||
|
||||
;; Example 7: Scoped Configuration
|
||||
(println "\n--- Example 7: Scoped Configuration ---")
|
||||
(timbre/info "Normal log level")
|
||||
(timbre/with-min-level :trace
|
||||
(timbre/trace "Trace enabled in this scope only"))
|
||||
(timbre/trace "Back to normal - trace won't show")
|
||||
|
||||
;; Example 8: Custom Appender
|
||||
(println "\n--- Example 8: Custom Appender ---")
|
||||
(def custom-logs (atom []))
|
||||
|
||||
(defn memory-appender
|
||||
"Appender that stores logs in memory"
|
||||
[]
|
||||
{:enabled? true
|
||||
:async? false
|
||||
:fn (fn [data]
|
||||
(swap! custom-logs conj
|
||||
{:level (:level data)
|
||||
:msg (force (:msg_ data))
|
||||
:timestamp (force (:timestamp_ data))}))})
|
||||
|
||||
(timbre/merge-config!
|
||||
{:appenders {:memory (memory-appender)}})
|
||||
|
||||
(timbre/info "Message 1 to memory")
|
||||
(timbre/warn "Message 2 to memory")
|
||||
|
||||
(println "Captured logs:" @custom-logs)
|
||||
|
||||
;; Restore default config
|
||||
(timbre/set-config! timbre/default-config)
|
||||
|
||||
;; Example 9: Context/MDC Support
|
||||
(println "\n--- Example 9: Context/MDC Support ---")
|
||||
(timbre/with-context {:request-id "req-12345" :user-id 456}
|
||||
(timbre/info "Processing request")
|
||||
(timbre/info "Request completed"))
|
||||
|
||||
;; Example 10: Conditional Logging with sometimes
|
||||
(println "\n--- Example 10: Probabilistic Logging ---")
|
||||
(println "Attempting 10 sometimes calls (10% probability):")
|
||||
(dotimes [_ 10]
|
||||
(timbre/sometimes 0.1
|
||||
(print ".")))
|
||||
(println " done")
|
||||
|
||||
;; Example 11: Rate Limiting
|
||||
(println "\n--- Example 11: Rate Limiting ---")
|
||||
(def rate-limited-logs (atom []))
|
||||
|
||||
(timbre/merge-config!
|
||||
{:appenders
|
||||
{:rate-limited
|
||||
{:enabled? true
|
||||
:rate-limit [[2 1000]] ; Max 2 logs per second
|
||||
:fn (fn [data]
|
||||
(swap! rate-limited-logs conj (force (:msg_ data))))}}})
|
||||
|
||||
(println "Sending 5 messages rapidly:")
|
||||
(dotimes [i 5]
|
||||
(timbre/info (str "Message " (inc i)))
|
||||
(Thread/sleep 100))
|
||||
|
||||
(println "Messages logged:" (count @rate-limited-logs))
|
||||
(println "Content:" @rate-limited-logs)
|
||||
|
||||
;; Restore default
|
||||
(timbre/set-config! timbre/default-config)
|
||||
|
||||
;; Example 12: Custom Output Function
|
||||
(println "\n--- Example 12: Custom Output Function ---")
|
||||
(defn simple-output-fn
|
||||
[{:keys [level ?ns-str msg_]}]
|
||||
(str (str/upper-case (name level))
|
||||
" [" ?ns-str "] "
|
||||
(force msg_)))
|
||||
|
||||
(timbre/with-merged-config {:output-fn simple-output-fn}
|
||||
(timbre/info "Custom formatted message")
|
||||
(timbre/error "Another custom format"))
|
||||
|
||||
;; Example 13: Namespace Filtering
|
||||
(println "\n--- Example 13: Namespace Filtering ---")
|
||||
(timbre/set-ns-min-level!
|
||||
{:deny #{"user"} ; Deny current namespace
|
||||
:allow #{}})
|
||||
|
||||
(timbre/info "This won't appear - namespace filtered")
|
||||
|
||||
;; Reset namespace filter
|
||||
(timbre/set-ns-min-level! {})
|
||||
(timbre/info "Namespace filter cleared - this appears")
|
||||
|
||||
;; Example 14: Log Errors Helper
|
||||
(println "\n--- Example 14: Log Errors Helper ---")
|
||||
(defn risky-operation []
|
||||
(throw (ex-info "Something went wrong" {:error-code 123})))
|
||||
|
||||
(println "Using log-errors (suppresses exception):")
|
||||
(timbre/log-errors
|
||||
(risky-operation))
|
||||
(println "Execution continued after exception")
|
||||
|
||||
;; Example 15: Multiple Appenders
|
||||
(println "\n--- Example 15: Multiple Appenders ---")
|
||||
(def console-logs (atom []))
|
||||
(def file-logs (atom []))
|
||||
|
||||
(timbre/set-config!
|
||||
{:min-level :debug
|
||||
:appenders
|
||||
{:console
|
||||
{:enabled? true
|
||||
:fn (fn [data]
|
||||
(swap! console-logs conj (force (:msg_ data))))}
|
||||
:file
|
||||
{:enabled? true
|
||||
:min-level :warn ; Only warnings and above
|
||||
:fn (fn [data]
|
||||
(swap! file-logs conj (force (:msg_ data))))}}})
|
||||
|
||||
(timbre/debug "Debug message")
|
||||
(timbre/info "Info message")
|
||||
(timbre/warn "Warning message")
|
||||
(timbre/error "Error message")
|
||||
|
||||
(println "Console appender received:" (count @console-logs) "messages")
|
||||
(println "File appender received:" (count @file-logs) "messages (warn+)")
|
||||
|
||||
;; Example 16: Structured Logging Pattern
|
||||
(println "\n--- Example 16: Structured Logging Pattern ---")
|
||||
(timbre/set-config! timbre/default-config)
|
||||
|
||||
(defn log-user-action
|
||||
[action user-id metadata]
|
||||
(timbre/info "User action"
|
||||
(merge {:action action
|
||||
:user-id user-id
|
||||
:timestamp (System/currentTimeMillis)}
|
||||
metadata)))
|
||||
|
||||
(log-user-action :login 789 {:ip "10.0.0.1" :device "mobile"})
|
||||
(log-user-action :purchase 789 {:amount 99.99 :currency "USD"})
|
||||
|
||||
;; Example 17: Environment-Specific Configuration
|
||||
(println "\n--- Example 17: Environment-Specific Configuration ---")
|
||||
(defn config-for-env
|
||||
[env]
|
||||
(case env
|
||||
:dev {:min-level :trace
|
||||
:appenders {:println (appenders/println-appender)}}
|
||||
:prod {:min-level :info
|
||||
:appenders {:println (appenders/println-appender)}}))
|
||||
|
||||
(println "Development config:")
|
||||
(timbre/with-config (config-for-env :dev)
|
||||
(timbre/trace "Trace visible in dev"))
|
||||
|
||||
(println "Production config:")
|
||||
(timbre/with-config (config-for-env :prod)
|
||||
(timbre/trace "Trace hidden in prod")
|
||||
(timbre/info "Info visible in prod"))
|
||||
|
||||
;; Example 18: Middleware Example
|
||||
(println "\n--- Example 18: Middleware Example ---")
|
||||
(defn add-app-version
|
||||
[data]
|
||||
(assoc data :app-version "1.0.0"))
|
||||
|
||||
(defn add-environment
|
||||
[data]
|
||||
(assoc data :environment "development"))
|
||||
|
||||
(timbre/merge-config!
|
||||
{:middleware [add-app-version add-environment]})
|
||||
|
||||
(timbre/info "Message with middleware-added data")
|
||||
|
||||
;; Reset to default
|
||||
(timbre/set-config! timbre/default-config)
|
||||
|
||||
(println "\n=== Examples Complete ===")
|
||||
82
skills/timbre/metadata.edn
Normal file
82
skills/timbre/metadata.edn
Normal file
@@ -0,0 +1,82 @@
|
||||
{:skill
|
||||
{:name "timbre"
|
||||
:version "1.0.0"
|
||||
:description "Pure Clojure/Script logging library with flexible configuration and powerful features"
|
||||
:tags [:logging :clojure :clojurescript :appenders :async :middleware]
|
||||
:use-cases
|
||||
["Application logging with structured data"
|
||||
"Error tracking and exception handling"
|
||||
"Performance monitoring and profiling"
|
||||
"Multi-destination log routing"
|
||||
"Development and production logging"
|
||||
"Request/response logging"
|
||||
"Database query logging"
|
||||
"Conditional and rate-limited logging"]
|
||||
:key-features
|
||||
["Zero-dependency pure Clojure/Script implementation"
|
||||
"Simple configuration with Clojure data structures"
|
||||
"Compile-time log level and namespace elision"
|
||||
"Async logging with configurable appenders"
|
||||
"Rate limiting per appender or log call"
|
||||
"Flexible middleware pipeline"
|
||||
"Context/MDC support for structured logging"
|
||||
"Built-in and extensible appenders"
|
||||
"tools.logging and SLF4J interoperability"
|
||||
"Printf-style and structured logging"]}
|
||||
|
||||
:library
|
||||
{:name "com.taoensso/timbre"
|
||||
:version "6.8.0"
|
||||
:url "https://github.com/taoensso/timbre"
|
||||
:documentation-url "https://taoensso.github.io/timbre/"
|
||||
:license "EPL-1.0"
|
||||
:platforms [:clojure :clojurescript]
|
||||
:notes "For new projects, consider Telemere - a modern rewrite of Timbre"}
|
||||
|
||||
:file-structure
|
||||
{:skill-md "Comprehensive API reference and usage patterns"
|
||||
:examples-clj "Runnable Babashka examples demonstrating core features"
|
||||
:metadata-edn "Skill metadata and library information"}
|
||||
|
||||
:learning-path
|
||||
{:beginner
|
||||
["Basic logging at different levels"
|
||||
"Simple configuration setup"
|
||||
"Using built-in appenders"
|
||||
"Exception logging"]
|
||||
:intermediate
|
||||
["Custom appenders"
|
||||
"Middleware functions"
|
||||
"Context/MDC usage"
|
||||
"Rate limiting and async logging"
|
||||
"Namespace filtering"]
|
||||
:advanced
|
||||
["Custom output formatting"
|
||||
"Complex appender configurations"
|
||||
"Performance optimization"
|
||||
"Multi-environment setup"
|
||||
"Integration with other logging systems"]}
|
||||
|
||||
:api-coverage
|
||||
{:logging-functions
|
||||
["trace" "debug" "info" "warn" "error" "fatal" "report"
|
||||
"tracef" "debugf" "infof" "warnf" "errorf" "fatalf" "reportf"
|
||||
"log" "logf" "spy" "sometimes"]
|
||||
:configuration
|
||||
["set-config!" "merge-config!" "swap-config!"
|
||||
"with-config" "with-merged-config"
|
||||
"set-min-level!" "set-ns-min-level!"
|
||||
"with-min-level"]
|
||||
:context
|
||||
["with-context" "with-context+"]
|
||||
:utilities
|
||||
["log-errors" "log-and-rethrow-errors"
|
||||
"logged-future" "handle-uncaught-jvm-exceptions!"
|
||||
"shutdown-appenders!"]
|
||||
:appenders
|
||||
["println-appender" "spit-appender"]
|
||||
:output
|
||||
["default-output-fn" "default-output-msg-fn"
|
||||
"color-str" "ansi-color"]
|
||||
:interop
|
||||
["use-timbre" "refer-timbre"]}}
|
||||
Reference in New Issue
Block a user