From a480f98cca6e29c5122a0490dae68b586c10783b Mon Sep 17 00:00:00 2001 From: Travis Shears Date: Tue, 12 Aug 2025 11:38:33 +0200 Subject: [PATCH] create pedestal telmere logger --- src/micro_blog/api.clj | 7 +- src/micro_blog/example_usage.clj | 154 ++++++++++++++++++++++++++++ src/micro_blog/logging.clj | 16 ++- src/micro_blog/main.clj | 5 +- src/micro_blog/pedestal_telmere.clj | 48 +++++++++ 5 files changed, 222 insertions(+), 8 deletions(-) create mode 100644 src/micro_blog/example_usage.clj create mode 100644 src/micro_blog/pedestal_telmere.clj diff --git a/src/micro_blog/api.clj b/src/micro_blog/api.clj index ae70d12..c918e0c 100644 --- a/src/micro_blog/api.clj +++ b/src/micro_blog/api.clj @@ -1,8 +1,10 @@ (ns micro-blog.api (:require [io.pedestal.connector :as conn] - [io.pedestal.http.http-kit :as hk])) + [io.pedestal.http.http-kit :as hk] + [io.pedestal.log :as log])) -(defn greet-handler [_request] +(defn greet-handler [request] + ;; (log/info :in 'greet-handler :method (:request-method request) :uri (:uri request)) {:status 200 :body "Hello, world!\n"}) @@ -16,4 +18,5 @@ (hk/create-connector nil))) (defn start [] + ;; (log/info :in 'start :msg "Starting Pedestal server with Telmere logging") (conn/start! (create-connector))) diff --git a/src/micro_blog/example_usage.clj b/src/micro_blog/example_usage.clj new file mode 100644 index 0000000..633095f --- /dev/null +++ b/src/micro_blog/example_usage.clj @@ -0,0 +1,154 @@ +(ns micro-blog.example-usage + (:require [io.pedestal.log :as log] + [io.pedestal.connector :as conn] + [io.pedestal.http.http-kit :as hk] + [taoensso.telemere :as tel] + [micro-blog.pedestal-telmere :as pt] + [micro-blog.logging :as logging])) + +;; Example handlers that demonstrate Pedestal logging with Telmere + +(defn health-check-handler [request] + (log/info :in 'health-check-handler + :request-id (get-in request [:headers "x-request-id"]) + :user-agent (get-in request [:headers "user-agent"])) + {:status 200 + :headers {"Content-Type" "application/json"} + :body "{\"status\": \"healthy\", \"timestamp\": \"2024-01-01T12:00:00Z\"}"}) + +(defn error-handler [request] + (try + (throw (ex-info "Simulated error for demonstration" + {:error-type :simulation + :request-path (:uri request)})) + (catch Exception e + (log/error :in 'error-handler + :exception e + :request-method (:request-method request) + :uri (:uri request)) + {:status 500 + :headers {"Content-Type" "application/json"} + :body "{\"error\": \"Internal server error\"}")))) + +(defn user-handler [request] + (let [user-id (get-in request [:path-params :user-id])] + (log/debug :in 'user-handler :user-id user-id :action "fetching user") + + (if (= user-id "123") + (do + (log/info :in 'user-handler :user-id user-id :result "found") + {:status 200 + :headers {"Content-Type" "application/json"} + :body (str "{\"user_id\": \"" user-id "\", \"name\": \"John Doe\"}")}) + (do + (log/warn :in 'user-handler :user-id user-id :result "not-found") + {:status 404 + :headers {"Content-Type" "application/json"} + :body "{\"error\": \"User not found\"}"}))))) + +;; Interceptor that adds request logging +(def request-logger-interceptor + {:name ::request-logger + :enter (fn [context] + (let [request (:request context) + start-time (System/currentTimeMillis)] + (log/debug :in 'request-logger-interceptor + :event :request-start + :method (:request-method request) + :uri (:uri request) + :start-time start-time) + (assoc context ::start-time start-time))) + :leave (fn [context] + (let [request (:request context) + response (:response context) + start-time (::start-time context) + duration (- (System/currentTimeMillis) start-time)] + (log/info :in 'request-logger-interceptor + :event :request-complete + :method (:request-method request) + :uri (:uri request) + :status (:status response) + :duration-ms duration) + context))}) + +;; Routes with logging examples +(def routes + #{["/health" :get health-check-handler :route-name :health-check] + ["/error" :get error-handler :route-name :error-demo] + ["/users/:user-id" :get user-handler :route-name :get-user]}) + +(defn create-example-connector [] + (-> (conn/default-connector-map 8891) + (conn/with-default-interceptors) + (conn/with-interceptors [request-logger-interceptor]) + (conn/with-routes routes) + (hk/create-connector nil))) + +(defn start-example-server [] + ;; Setup Telmere logging first + (logging/setup-logging) + + (log/info :in 'start-example-server + :msg "Starting example server with Pedestal + Telmere logging" + :port 8891) + + ;; Start the server + (conn/start! (create-example-connector))) + +(defn demo-logging [] + "Demonstrates various logging scenarios" + ;; Setup logging first + (logging/setup-logging) + + ;; Direct Telmere logging + (tel/log! {:level :info :data {:source "telmere-direct"}} + "This is direct Telmere logging") + + ;; Pedestal logging (which goes through Telmere) + (log/trace :source "pedestal" :level "trace" :msg "Trace level message") + (log/debug :source "pedestal" :level "debug" :msg "Debug level message") + (log/info :source "pedestal" :level "info" :msg "Info level message") + (log/warn :source "pedestal" :level "warn" :msg "Warning level message") + (log/error :source "pedestal" :level "error" :msg "Error level message") + + ;; Logging with structured data + (log/info :in 'demo-function + :operation "data-processing" + :records-processed 150 + :processing-time-ms 250 + :success true) + + ;; Logging with exception + (try + (/ 1 0) + (catch Exception e + (log/error :in 'demo-function + :operation "division" + :exception e + :msg "Division by zero error"))) + + ;; Using MDC context + (log/with-context {:user-id "user-123" :session-id "sess-456"} + (log/info :in 'demo-function :msg "Processing user request") + (log/debug :in 'demo-function :msg "Validating user permissions")) + + (println "Demo logging complete - check your console output!")) + +(comment + ;; To test the integration: + + ;; 1. Start the example server + (start-example-server) + + ;; 2. Make some requests: + ;; curl http://localhost:8891/health + ;; curl http://localhost:8891/users/123 + ;; curl http://localhost:8891/users/999 + ;; curl http://localhost:8891/error + + ;; 3. Run logging demo + (demo-logging) + + ;; 4. Test just the logging setup + (logging/test-logging) + ) diff --git a/src/micro_blog/logging.clj b/src/micro_blog/logging.clj index 462a260..4a17efd 100644 --- a/src/micro_blog/logging.clj +++ b/src/micro_blog/logging.clj @@ -3,7 +3,8 @@ (:require [taoensso.telemere :as tel] [cheshire.core :as json] - [cheshire.generate :as gen])) + [cheshire.generate :as gen] + [micro-blog.pedestal-telmere :as pt])) (def json-logging-handler (tel/handler:console @@ -24,8 +25,13 @@ (defn setup-logging [] (tel/remove-handler! :default/console) - (tel/add-handler! ::json-logger json-logging-handler)) + (tel/add-handler! ::json-logger json-logging-handler) + ;; Also setup Pedestal integration + (pt/setup-pedestal-telmere-logging!)) -(defn test-logging [] - (setup-logging) - (tel/log! {:level :info :data {:thing "example"}} "This is a test log message")) +;; (defn test-logging [] +;; (setup-logging) +;; (tel/log! {:level :info :data {:thing "example"}} "This is a test log message") +;; ;; Test Pedestal logging integration +;; (require '[io.pedestal.log :as log]) +;; ((resolve 'log/info) :test true :message "Pedestal logging via Telmere works!")) diff --git a/src/micro_blog/main.clj b/src/micro_blog/main.clj index dc79cec..f7b1ecc 100644 --- a/src/micro_blog/main.clj +++ b/src/micro_blog/main.clj @@ -3,7 +3,8 @@ (:import [java.time Instant Duration]) (:require [chime.core :as chime] [taoensso.telemere :as tel] - [micro-blog.logging] + micro-blog.logging + micro-blog.api [micro-blog.blue-sky :as blue-sky] [micro-blog.mastodon :as masto])) @@ -12,6 +13,8 @@ (defn -main [] (micro-blog.logging/setup-logging) + (tel/log! :info "Setting up API") + (micro-blog.api/start) (tel/log! :info "Setting up crons") (doseq [[i cron] (map-indexed vector crons)] (let [start (.plus (Instant/now) (Duration/ofMinutes (* i 5)))] diff --git a/src/micro_blog/pedestal_telmere.clj b/src/micro_blog/pedestal_telmere.clj new file mode 100644 index 0000000..7710bd8 --- /dev/null +++ b/src/micro_blog/pedestal_telmere.clj @@ -0,0 +1,48 @@ +(ns micro-blog.pedestal-telmere + (:require [io.pedestal.log :as pedestal-log] + [taoensso.telemere :as tel])) + +(defrecord TelmereLogger [logger-name] + pedestal-log/LoggerSource + (-trace [this body] + (tel/log! {:level :trace :id logger-name} body)) + (-trace [this body throwable] + (tel/log! {:level :trace :id logger-name :error throwable} body)) + + (-debug [this body] + (tel/log! {:level :debug :id logger-name} body)) + (-debug [this body throwable] + (tel/log! {:level :debug :id logger-name :error throwable} body)) + + (-info [this body] + (tel/log! {:level :info :id logger-name} body)) + (-info [this body throwable] + (tel/log! {:level :info :id logger-name :error throwable} body)) + + (-warn [this body] + (tel/log! {:level :warn :id logger-name} body)) + (-warn [this body throwable] + (tel/log! {:level :warn :id logger-name :error throwable} body)) + + (-error [this body] + (tel/log! {:level :error :id logger-name} body)) + (-error [this body throwable] + (tel/log! {:level :error :id logger-name :error throwable} body)) + + (-level-enabled? [this level-key] + ;; Telmere doesn't have a direct level check, so we'll assume all levels are enabled + ;; You can customize this based on your Telmere configuration + (contains? #{:trace :debug :info :warn :error} level-key))) + +(defn make-telmere-logger + "Creates a TelmereLogger instance for the given logger name" + [logger-name] + (->TelmereLogger logger-name)) + +(defn setup-pedestal-telmere-logging! + "Sets up Telmere as the logger source for Pedestal. + Call this before starting your Pedestal service." + [] + ;; Override the default logger creation function + (alter-var-root #'pedestal-log/make-logger + (constantly make-telmere-logger)))