Skip to content

Instantly share code, notes, and snippets.

@xlfe
Last active February 8, 2025 03:33
Show Gist options
  • Save xlfe/e9e2cf23bd1dddcbb2fbd77ce31dcc8b to your computer and use it in GitHub Desktop.
Save xlfe/e9e2cf23bd1dddcbb2fbd77ce31dcc8b to your computer and use it in GitHub Desktop.
How to get Structured Logging on GCP with Telemere
(ns psithur.data-studio.cloud-run.start
(:require
[clojure.stacktrace :as st]
[clojure.string]
[jsonista.core :as jsonista]
[taoensso.telemere :as tele]
[taoensso.telemere.utils :as utils]))
; Make sure interop is working
(assert
(= (get (tele/check-interop) :slf4j)
{:present? true
:telemere-provider-present? true
:sending->telemere? true
:telemere-receiving? true}))
(def signal->clean (tele/clean-signal-fn {:incl-kvs? true}))
; Output a JSON log, and if it's an error, include the stack-trace so that it gets
; picked up by Google Cloud Platform ErrorReporting
(defn gcp-json-handler
([])
([{:keys [error]
:as signal}]
(let [cleaned (signal->clean signal)]
(println
(jsonista.core/write-value-as-string
(if (utils/error-signal? signal)
(assoc cleaned :stack_trace (with-out-str (st/print-stack-trace error)))
cleaned))))))
(def encore-levels->gcp-severity
{:trace :DEFAULT ; (0) The log entry has no assigned severity level.
:debug :DEBUG ; (100) Debug or trace information.
:info :INFO ; (200) Routine information, such as ongoing status or performance.
:warn :WARNING ; (400) Warning events might cause problems.
:error :ERROR ; (500) Error events are likely to cause problems.
:fatal :CRITICAL ; (600) Critical events cause more severe problems or outages.
:report :ALERT}) ; (700) A person must take an action immediately.
(defn setup-logging
[profile]
(when (= :production profile)
(tele/streams->telemere!)
(tele/add-handler!
:default/console
gcp-json-handler
{:ns-filter {:disallow "org.eclipse.jetty.server.*"}
:middleware (fn [{:as signal
:keys [level ctx]}]
(let [{:keys [::trace-id ::parent-id ::project]} ctx]
(cond-> (assoc signal :severity (get encore-levels->gcp-severity level))
; Put the trace ID in the right key for GCP Logging to group all signals for this request together
(some? trace-id)
(assoc "logging.googleapis.com/trace" (str "projects/" project "/traces/" trace-id))
; Include ParentID in the GCP Logging key
(some? parent-id) (assoc "logging.googleapis.com/spanId" parent-id)
true (dissoc :ctx))))})))
(defn get-context
[project req]
(when-let [tp (get-in req [:headers "traceparent"])]
(let [[version trace-id parent-id trace-flags] (clojure.string/split tp #"\-")]
{::version version
::project project
::trace-id trace-id
::parent-id parent-id
::trace-flags trace-flags})))
; Using Reitit-middleware to capture the incoming Trace ID that is saved to the telemere context to be later
; appended to the logged signals
(def ctx-middleware
{:name ::request-ctx
:compile (fn [data _]
(let [project (get-in data [:defaults :project])]
(fn [handler]
(fn [request]
(let [context (get-context project request)]
(tele/with-ctx+ context (handler request)))))))})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment