{"id":19917226,"url":"https://github.com/athos/postmortem","last_synced_at":"2025-04-06T02:11:41.353Z","repository":{"id":37483975,"uuid":"156850949","full_name":"athos/Postmortem","owner":"athos","description":"A simple debug library for Clojure(Script) that features data-oriented logging and tracing","archived":false,"fork":false,"pushed_at":"2022-11-06T12:39:41.000Z","size":209,"stargazers_count":155,"open_issues_count":0,"forks_count":2,"subscribers_count":7,"default_branch":"master","last_synced_at":"2024-05-01T20:06:45.520Z","etag":null,"topics":["clojure","clojurescript","data-oriented","debugging","logging","tracing","transducer"],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-1.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/athos.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-11-09T11:07:33.000Z","updated_at":"2024-03-27T09:29:29.000Z","dependencies_parsed_at":"2023-01-23T13:01:30.210Z","dependency_job_id":null,"html_url":"https://github.com/athos/Postmortem","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/athos%2FPostmortem","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/athos%2FPostmortem/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/athos%2FPostmortem/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/athos%2FPostmortem/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/athos","download_url":"https://codeload.github.com/athos/Postmortem/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247423515,"owners_count":20936626,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["clojure","clojurescript","data-oriented","debugging","logging","tracing","transducer"],"created_at":"2024-11-12T21:49:11.127Z","updated_at":"2025-04-06T02:11:41.329Z","avatar_url":"https://github.com/athos.png","language":"Clojure","readme":"# Postmortem\n[![Clojars Project](https://img.shields.io/clojars/v/postmortem.svg)](https://clojars.org/postmortem)\n![build](https://github.com/athos/Postmortem/workflows/build/badge.svg)\n[![codecov](https://codecov.io/gh/athos/postmortem/branch/master/graph/badge.svg)](https://codecov.io/gh/athos/postmortem)\n[![bb compatible](https://raw.githubusercontent.com/babashka/babashka/master/logo/badge.svg)](https://babashka.org)\n\nA simple debug library for Clojure(Script) that features data-oriented logging and tracing\n\n## Features\n\n- Postmortem strongly encourages the data-oriented debugging approach\n  - Logs are just Clojure data, so you can use DataScript, REBL or whatever tools for more sophisticated log analysis\n- [Integration with transducers](#integration-with-transducers) enables various flexible logging strategies\n- [Instrumentation](#instrumentation) on vars makes it easier to debug functions without touching their code\n- Supports most of Clojure platforms (namely, Clojure, ClojureScript, self-hosted ClojureScript and Babashka)\n- Possible to use for debugging multi-threaded programs\n\n## Synopsis\n\n```clojure\n(require '[postmortem.core :as pm]\n         '[postmortem.xforms :as xf])\n\n(defn sum [n]\n  (loop [i n sum 0]\n    (pm/dump :sum (xf/take-last 5))\n    (if (= i 0)\n      sum\n      (recur (dec i) (+ i sum)))))\n\n(sum 100) ;=\u003e 5050\n\n(pm/log-for :sum)\n;=\u003e [{:n 100, :i 4, :sum 5040}\n;    {:n 100, :i 3, :sum 5044}\n;    {:n 100, :i 2, :sum 5047}\n;    {:n 100, :i 1, :sum 5049}\n;    {:n 100, :i 0, :sum 5050}]\n\n\n(require '[postmortem.instrument :as pi])\n\n(defn broken-factorial [n]\n  (cond (= n 0) 1\n        (= n 7) (/ (broken-factorial (dec n)) 0) ;; \u003c- BUG HERE!!\n        :else (* n (broken-factorial (dec n)))))\n\n(pi/instrument `broken-factorial\n               {:xform (comp (xf/take-until :err) (xf/take-last 5))})\n\n(broken-factorial 10)\n;; Execution error (ArithmeticException) at user/broken-factorial.\n;; Divide by zero\n\n(pm/log-for `broken-factorial)\n;=\u003e [{:args (3), :ret 6}\n;    {:args (4), :ret 24}\n;    {:args (5), :ret 120}\n;    {:args (6), :ret 720}\n;    {:args (7), :err #error {:cause \"Divide by zero\" ...}}]\n```\n\n## Table of Contents\n\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Usage](#usage)\n  - [Basic usage](#basic-usage)\n    - [`spy\u003e\u003e` / `log-for`](#spy--log-for)\n    - [`reset-key!` / `completed?` / `keys`](#reset-key--completed--keys)\n    - [`logs` / `stats` / `reset!`](#logs--stats--reset)\n    - [`spy\u003e`](#spy)\n    - [`dump`](#dump)\n  - [Integration with transducers](#integration-with-transducers)\n  - [Sessions](#sessions)\n    - [Handling sessions](#handling-sessions)\n    - [Attaching a transducer](#attaching-a-transducer)\n    - [`void-session`](#void-session)\n    - [Indexed sessions](#indexed-sessions)\n    - [`make-unsafe-session`](#make-unsafe-session)\n  - [Simple logger](#simple-logger)\n  - [Instrumentation](#instrumentation)\n- [Related works](#related-works)\n\n## Requirements\n\n- Clojure 1.8+, or\n- ClojureScript 1.10.238+, or\n- Babashka v0.10.163+, or\n- Planck 2.24.0+, or\n- Lumo 1.10.1+\n\nWe have only tested on the above environments, but you could possibly use\nthe library on some older versions of the ClojureScript runtimes as well.\n\n## Installation\n\nAdd the following to your project dev dependencies or the `:user` profile in `~/.lein/profiles.clj`:\n\n[![Clojars Project](https://clojars.org/postmortem/latest-version.svg)](https://clojars.org/postmortem)\n\n## Usage\n\n### Basic usage\n\n#### `spy\u003e\u003e` / `log-for`\n\nIn Postmortem, `spy\u003e\u003e` and `log-for` are two of the most fundamental functions.\n`spy\u003e\u003e` is for logging data, and `log-for` is for retrieving logged data.\n\n`spy\u003e\u003e` is used as follows:\n\n```clojure\n(require '[postmortem.core :as pm])\n\n(defn sum [n]\n  (loop [i 0 sum 0]\n    (if (\u003e i n)\n      sum\n      (recur (inc i)\n             (pm/spy\u003e\u003e :sum (+ i sum))))))\n```\n\n`(spy\u003e\u003e \u003ckey\u003e \u003cexpr\u003e)` stores the value of the `\u003cexpr\u003e` to a log entry for the key `\u003ckey\u003e`\neach time the `spy\u003e\u003e` form is evaluated. In the above example, `(pm/spy\u003e\u003e :sum (+ i sum))`\nwill store intermediate results of summation for each iteration to the log entry for\nthe key `:sum`.\n\n`(log-for \u003ckey\u003e)`, on the other hand, retrieves all the logged data stored\nin the log entry for the key `\u003ckey\u003e`. In the following example, `(log-for :sum)` results in\n`[0 1 3 6 10 15]`, which corresponds to the intermediate summations from 0 to 5:\n\n```clojure\n(sum 5)\n;=\u003e 15\n\n(pm/log-for :sum)\n;=\u003e [0 1 3 6 10 15]\n```\n\nAny Clojure data can be used as a log entry key, such as keywords (as in the above examples),\nsymbols, integers, strings or whatever.\nIn fact, you can even use a runtime value as a key, as well as literal values, and\nthus entry keys can also be used as a handy way to collect and group log data:\n\n```clojure\n(defn f [n]\n  (pm/spy\u003e\u003e [:f (even? n)] (inc n)))\n\n(mapv f [1 2 3 4 5])\n;=\u003e [2 3 4 5 6]\n(pm/log-for [:f true])\n;=\u003e [3 5]\n(pm/log-for [:f false])\n;=\u003e [2 4 6]\n```\n\n#### `reset-key!` / `completed?` / `keys`\n\nTo clear the logged data at the log entry `\u003ckey\u003e`, call `(reset-key! \u003ckey\u003e)`:\n\n```clojure\n(pm/log-for :sum)\n;=\u003e [0 1 3 6 10 15]\n\n(pm/reset-key! :sum)\n\n(pm/log-for :sum)\n;=\u003e nil\n```\n\nNote that once you call `log-for` for a key `k`, the log entry for `k` will be *completed*.\nA completed log entry will not be changed anymore until you call `reset-key!` for the log entry `k`:\n\n```clojure\n(pm/spy\u003e\u003e :foobar 1)\n(pm/spy\u003e\u003e :foobar 2)\n(pm/log-for :foobar)\n;=\u003e [1 2]\n\n(pm/spy\u003e\u003e :foobar 3)\n(pm/spy\u003e\u003e :foobar 4)\n(pm/log-for :foobar)\n;=\u003e [1 2]\n\n(pm/reset-key! :foobar)\n\n(pm/spy\u003e\u003e :foobar 3)\n(pm/spy\u003e\u003e :foobar 4)\n(pm/log-for :foobar)\n;=\u003e [3 4]\n```\n\nYou can check if a log entry has been completed using `(completed? \u003ckey\u003e)`:\n\n```clojure\n(pm/spy\u003e\u003e :barbaz 10)\n(pm/spy\u003e\u003e :barbaz 20)\n(pm/completed? :barbaz)\n;=\u003e false\n\n(pm/log-for :barbaz)\n;=\u003e [10 20]\n(pm/completed? :barbaz)\n;=\u003e true\n\n(pm/reset-key! :barbaz)\n(pm/completed? :barbaz)\n;=\u003e false\n```\n\nIf you want to know what log entry keys have been logged so far without completing\nany log entry, `keys` suits your desire:\n\n```clojure\n(pm/spy\u003e\u003e :bazqux 10)\n(pm/spy\u003e\u003e :quxquux 20)\n\n(pm/keys)\n;=\u003e #{:bazqux :quxquux}\n(pm/completed? :bazqux)\n;=\u003e false\n(pm/completed? :quxquux)\n;=\u003e false\n```\n\n#### `logs` / `stats` / `reset!`\n\nYou can also logs some data to more than one log entries at once.\nIn such a case, `logs` is more useful to look into the whole log data\nthan just calling `log-for` for each log entry:\n\n```clojure\n(defn sum [n]\n  (loop [i 0 sum 0]\n    (pm/spy\u003e\u003e :i i)\n    (if (\u003e i n)\n      sum\n      (recur (inc i)\n             (pm/spy\u003e\u003e :sum (+ i sum))))))\n\n(sum 5)\n;=\u003e 15\n\n(pm/logs)\n;=\u003e {:i [0 1 2 3 4 5 6],\n;    :sum [0 1 3 6 10 15]}\n```\n\nAlternatively, `stats` helps you grasp how many log items have been\nstored so far for each log entry key, without seeing the actual log data:\n\n```clojure\n(defn sum [n]\n  (loop [i 0 sum 0]\n    (pm/spy\u003e\u003e :i i)\n    (if (\u003e i n)\n      sum\n      (recur (inc i)\n             (pm/spy\u003e\u003e :sum (+ i sum))))))\n\n(sum 5) ;=\u003e 15\n\n(pm/stats)\n;=\u003e {:i 7 :sum 6}\n\n;; As compared to:\n;; (pm/logs)\n;; =\u003e {:i [0 1 2 3 4 5 6],\n;      :sum [0 1 3 6 10 15]}\n```\n\nNote that once you call `stats`, all the log entries will be\n*completed*, as with the `logs` fn.\n\nFor those who are using older versions (\u003c= 0.4.0), `pm/stats` is the new name\nfor `pm/frequencies` added in 0.4.1. They can be used totally interchangeably.\nNow `pm/stats` is recommended for primary use.\n\nAnalogous to `logs`, `reset!` is useful to clear the whole log data at a time,\nrather than clearing each individual log entry one by one calling `reset-key!`:\n\n```clojure\n(pm/logs)\n;=\u003e {:i [0 1 2 3 4 5 6],\n;    :sum [0 1 3 6 10 15]}\n\n(pm/reset!)\n\n(pm/logs)\n;=\u003e {}\n```\n\n#### `spy\u003e`\n\n`spy\u003e\u003e` has a look-alike cousin named `spy\u003e`. They have no semantic difference,\nexcept that `spy\u003e` is primarily intended to be used in *thread-first* contexts\nand therefore takes the log data as its *first* argument while `spy\u003e\u003e` is mainly\nintended to be used in *thread-last* contexts and therefore takes the log data\nas its *last* argument.\n\nFor example, the following two expressions behave in exactly the same way:\n\n```clojure\n;; thread-last version\n(-\u003e\u003e (+ 1 2)\n     (pm/spy\u003e\u003e :sum)\n     (* 10)\n     (pm/spy\u003e\u003e :prod))\n\n;; thread-first version\n(-\u003e (+ 1 2)\n    (pm/spy\u003e :sum)\n    (* 10)\n    (pm/spy\u003e :prod))\n```\n\n#### `dump`\n\n`dump` is another convenient tool to take snapshots of the values of local bindings.\n\n`(dump \u003ckey\u003e)` stores a local environment map to the log entry `\u003ckey\u003e`.\nA *local environment map* is a map of keywords representing local names in the scope\nat the callsite, to the values that the corresponding local names are bound to.\n\nThe example code below shows how `dump` logs the values of the local bindings\nat the callsite (namely, `n`, `i` and `sum`) for each iteration:\n\n```clojure\n(defn sum [n]\n  (loop [i 0 sum 0]\n    (pm/dump :sum)\n    (if (\u003e i n)\n      sum\n      (recur (inc i) (+ i sum)))))\n\n(sum 5)\n;=\u003e 15\n\n(pm/log-for :sum)\n;=\u003e [{:n 5, :i 0, :sum 0}\n;    {:n 5, :i 1, :sum 0}\n;    {:n 5, :i 2, :sum 1}\n;    {:n 5, :i 3, :sum 3}\n;    {:n 5, :i 4, :sum 6}\n;    {:n 5, :i 5, :sum 10}\n;    {:n 5, :i 6, :sum 15}]\n```\n\n### Integration with transducers\n\nAfter reading this document so far, you may wonder what if the loop would be repeated millions of times?\nWhat if you only need the last few log items out of them?\n\nThat's where Postmortem really shines. It achieves extremely flexible customizability of logging strategies\nby integration with transducers (If you are not familiar with transducers, we recommend that you take\na look at the [official reference](https://clojure.org/reference/transducers) first).\n\nPostmortem's logging operators (`spy\u003e\u003e`, `spy\u003e` and `dump`) are optionally takes a transducer\nafter the log entry key. When you call `(spy\u003e\u003e \u003ckey\u003e \u003cxform\u003e \u003cexpr\u003e)`, the transducer `\u003cxform\u003e`\ncontrols whether or not the given data will be logged and/or what data will actually be stored.\n\nFor example:\n\n```clojure\n(defn sum1 [n]\n  (loop [i 0 sum 0]\n    (if (\u003e i n)\n      sum\n      (recur (inc i)\n             (pm/spy\u003e\u003e :sum1 (+ i sum))))))\n\n(defn sum2 [n]\n  (loop [i 0 sum 0]\n    (if (\u003e i n)\n      sum\n      (recur (inc i)\n             (pm/spy\u003e\u003e :sum2 (filter odd?) (+ i sum))))))\n\n(sum1 5) ;=\u003e 15\n(sum2 5) ;=\u003e 15\n\n(pm/log-for :sum1)\n;=\u003e [0 1 3 6 10 15]\n\n(pm/log-for :sum2)\n;=\u003e [1 3 15]\n```\n\nYou see two invocations to `spy\u003e\u003e` in the example code. The first one is an ordinary\ninvocation without a transducer. The second one is called with a transducer `(filter odd?)`.\nWith that transducer, the log entry for the key `:sum2` only stores odd numbers\nwhile the entry for `:sum1` holds every intermediate sum result.\n\nNot only `filter`, you can use any transducer to customize how a log item will be stored.\nThe following code uses a transducer `(map (fn [m] (select-keys m [:sum])))`, which\nonly stores the value of the local binding `sum` for each iteration:\n\n```clojure\n(defn sum [n]\n  (loop [i 0 sum 0]\n    (pm/dump :sum (map (fn [m] (select-keys m [:sum]))))\n    (if (\u003e i n)\n      sum\n      (recur (inc i) (+ i sum)))))\n\n(sum 5) ;=\u003e 15\n\n(pm/log-for :sum)\n;=\u003e [{:sum 0}\n;    {:sum 0}\n;    {:sum 1}\n;    {:sum 3}\n;    {:sum 6}\n;    {:sum 10}\n;    {:sum 15}]\n```\n\nAlso, transducers can be used to restrict the maximum log size.\nFor example, `(take \u003cn\u003e)` only allows the first up to `\u003cn\u003e` items to be logged:\n\n```clojure\n(defn sum [n]\n  (loop [i 0 sum 0]\n    (pm/dump :sum (take 3))\n    (if (\u003e i n)\n      sum\n      (recur (inc i) (+ i sum)))))\n\n(sum 5) ;=\u003e 15\n\n(pm/log-for :sum)\n;=\u003e [{:n 5, :i 0, :sum 0} {:n 5, :i 1, :sum 0} {:n 5, :i 2, :sum 1}]\n```\n\n`(drop-while \u003cpred\u003e)` would drop log data until `\u003cpred\u003e` returns `false`:\n\n```clojure\n(defn sum [n]\n  (loop [i 0 sum 0]\n    (pm/dump :sum (drop-while (fn [{:keys [sum]}] (\u003c sum 5))))\n    (if (\u003e i n)\n      sum\n      (recur (inc i) (+ i sum)))))\n\n(sum 5) ;=\u003e 15\n\n(pm/log-for :Sum)\n;=\u003e [{:n 5, :i 4, :sum 6} {:n 5, :i 5, :sum 10} {:n 5, :i 6, :sum 15}]\n```\n\nYou can even pick up logs by random sampling using `(random-sample \u003cprob\u003e)`:\n\n```clojure\n(defn sum [n]\n  (loop [i 0 sum 0]\n    (pm/dump :sum (random-sample 0.3))\n    (if (\u003e i n)\n      sum\n      (recur (inc i) (+ i sum)))))\n\n(sum 5) ;=\u003e 15\n\n(pm/log-for :sum)\n;=\u003e [{:n 5, :i 0, :sum 0} {:n 5, :i 3, :sum 3}]\n\n(pm/reset!)\n(sum 5) ;=\u003e 15\n\n(pm/log-for :sum)\n;=\u003e [{:n 5, :i 2, :sum 1} {:n 5, :i 4, :sum 6} {:n 5, :i 5, :sum 10}]\n```\n\nPostmortem also has its own set of utility transducers. The namespace\n`postmortem.xforms` provides several useful transducers to implement\nspecific logging strategies.\n\n`take-last` is one of the most useful transducer of those.\n`(take-last \u003cn\u003e)` just logs the last `\u003cn\u003e` items, which only\nrequires a fixed-size buffer (rather than an indefinite-size one)\nto store the desired range of logs even when the logging operator is\nrepeatedly invoked millions of times:\n\n```clojure\n(require '[postmortem.xforms :as xf])\n\n(defn sum [n]\n  (loop [i 0 sum 0]\n    (pm/dump :sum (xf/take-last 5))\n    (if (\u003e i n)\n      sum\n      (recur (inc i) (+ i sum)))))\n\n(sum 1000000) ;=\u003e 500000500000\n\n(pm/log-for :sum)\n;=\u003e [{:n 1000000, :i 999997, :sum 499996500006}\n;    {:n 1000000, :i 999998, :sum 499997500003}\n;    {:n 1000000, :i 999999, :sum 499998500001}\n;    {:n 1000000, :i 1000000, :sum 499999500000}\n;    {:n 1000000, :i 1000001, :sum 500000500000}]\n```\n\n### Sessions\n\nA session is an abstraction responsible for what actually happens when\nstoring and retrieving logs and where the actual log data will be stored.\nIt can be used to completely isolate some logs from the other, or to\nenable/disable the entire logging mechanism, etc.\n\n#### Handling sessions\n\nPostmortem's logging operators takes another optional argument for session.\nFor example, `(spy\u003e\u003e \u003csession\u003e \u003ckey\u003e \u003cexpr\u003e \u003cxforms\u003e)` stores logs\ninto the `\u003csession\u003e`.\n\nTo make a new session, use `make-session`:\n\n```clojure\n(def sess (pm/make-session))\n\n(pm/spy\u003e\u003e sess :foo 1 identity)\n(pm/spy\u003e\u003e sess :bar 2 identity)\n(pm/spy\u003e\u003e sess :foo 3 identity)\n```\n\nNote that to specify your session explicitly, you'll need to specify a transducer\neven if you don't really want to change a logging strategy using it.\nHere, the `identity` transducer is specified as a placeholder.\n\nTo retrieve log data from the session `sess`, call `log-for` / `logs` with it:\n\n```clojure\n(pm/log-for sess :foo)\n;=\u003e [1 3]\n(pm/logs sess)\n;=\u003e {:foo [1 3) :bar [2]]}\n```\n\nWhen you omit a session, things behave as if the current session were specified.\nThe *current session* is the default session that can be accessed globally.\n\nTo get the current session, use `current-session`:\n\n```clojure\n(pm/current-session)\n;; Returns the current session object\n```\n\nAnd the following pairs of expressions are semantically identical, respectively:\n\n```clojure\n(pm/spy\u003e\u003e :foo identity {:foo 42})\n(pm/spy\u003e\u003e (pm/current-session) :foo identity {:foo 42})\n\n(pm/dump :foo identity)\n(pm/dump (pm/current-session) :foo identity)\n\n(pm/log-for :foo)\n(pm/log-for (pm/current-session) :foo)\n\n(pm/logs)\n(pm/logs (pm/current-session))\n```\n\nTo set the current session to your own session, you can use `set-current-session!`:\n\n```clojure\n(def sess (pm/make-session))\n(pm/set-current-session! sess)\n\n(pm/spy\u003e\u003e :foo 1)\n(pm/spy\u003e\u003e :foo 2)\n(pm/spy\u003e\u003e :foo 3)\n\n(pm/log-for :foo)\n;=\u003e [1 2 3]\n(pm/log-for sess :foo)\n;=\u003e [1 2 3]\n```\n\nOr you can temporarily change the current session with `with-session`:\n\n```clojure\n(def sess (pm/make-session))\n\n(pm/spy\u003e\u003e :foo 1)\n(pm/with-session sess\n  (pm/spy\u003e\u003e :foo 2)\n  (pm/spy\u003e\u003e :foo 3))\n(pm/spy\u003e\u003e :foo 4)\n\n(pm/log-for :foo)\n;=\u003e [1 4]\n(pm/log-for sess :foo)\n;=\u003e [2 3]\n```\n\n#### Attaching a transducer\n\nTransducers can be attached to sessions as well. Those transducers attached to a session\nare called a *base transducer* of the session. To make a session with a base transducer\nattached, call `(make-session \u003cxform\u003e)`.\nIf a session has a base transducer, a logging operator operating on the session will behave\nas if it were called (1) with that base transducer, or (2) with a transducer produced\nby prepending (a la `comp`) the base transducer to the transducer that is originally \npassed to the logging operator, if any.\n\nFor example for case 1, this code\n\n```clojure\n(pm/set-current-session! (pm/make-session (take 5)))\n\n(pm/dump :key)\n```\n\nis equivalent to the following:\n\n```clojure\n(pm/set-current-session! (pm/make-session))\n\n(pm/dump :key (take 5))\n```\n\nAnd for case 2, this\n\n```clojure\n(pm/set-current-session! (pm/make-session (drop 5)))\n\n(pm/dump :key (take 5))\n```\n\nis equivalent to:\n\n```clojure\n(pm/set-current-session! (pm/make-session))\n\n(pm/dump :key (comp (drop 5) (take 5)))\n```\n\nThis feature is useful to apply a common transducer to all the logging operators\noperating on the same session.\n\n#### `void-session`\n\nA void session is another implementation of Postmortem session which does nothing\nat all. It's useful to disable the logging operators operating on the current session.\n\nTo get the void session, call `void-session`:\n\n```clojure\n(pm/set-current-session! (pm/void-session))\n\n(pm/spy\u003e\u003e :foo 1)\n(pm/spy\u003e\u003e :foo 2)\n(pm/spy\u003e\u003e :foo 3)\n\n(pm/log-for :foo)\n;=\u003e []\n```\n\nUsing it together with `with-session` disables logging temporarily:\n\n```clojure\n(pm/set-current-session! (pm/make-session))\n\n(pm/spy\u003e\u003e :foo 1)\n(pm/with-session (pm/void-session)\n  (pm/spy\u003e\u003e :foo 2)\n  (pm/spy\u003e\u003e :foo 3))\n(pm/spy\u003e\u003e :foo 4)\n\n(pm/log-for :foo)\n;=\u003e [1 4]\n```\n\n#### Indexed sessions\n\nWhen dealing with multiple log entries, it is sometimes useful to have a sequential\nnumber (or index) for each log item throughout all the entries.\n\nAn *indexed session* automatically adds an auto-increment index to each log item.\nTo create a new indexed session, use `make-indexed-session`:\n\n```clojure\n(pm/set-current-session! (pm/make-indexed-session))\n\n(pm/spy\u003e\u003e :foo 100)\n(pm/spy\u003e\u003e :bar 101)\n(pm/spy\u003e\u003e :foo 102)\n\n(pm/logs)\n;=\u003e {:foo [{:id 0 :val 100}\n;          {:id 2 :val 102}]\n;    :bar [{:id 1 :val 101}]}\n```\n\nCalling `reset!` on the indexed session resets the index:\n\n```clojure\n(pm/spy\u003e\u003e :foo 100)\n(pm/spy\u003e\u003e :foo 101)\n(pm/log-for :foo)\n;=\u003e [{:id 0 :val 100} {:id 1 :val 101}]\n\n(pm/reset!)\n\n(pm/spy\u003e\u003e :foo 102)\n(pm/spy\u003e\u003e :foo 103)\n(pm/log-for :foo)\n;=\u003e [{:id 0 :val 102} {:id 1 :val 103}]\n```\n\n`make-indexed-session` takes an optional function to specify how the indexed\nsession will attach the index to each log item.\n\nThe function must take two arguments, the index and the log item, and return\na new log item. The default function is `(fn [id item] {:id id :val item})`.\n\nThe example below shows how it takes effect:\n\n```clojure\n(pm/set-current-session!\n  (pm/make-indexed-session (fn [id item] [id item])))\n\n(pm/spy\u003e\u003e :foo :a)\n(pm/spy\u003e\u003e :foo :b)\n(pm/spy\u003e\u003e :foo :c)\n(pm/log-for :foo)\n;=\u003e [[0 :a] [1 :b] [2 :c]]\n\n(pm/set-current-session!\n  (pm/make-indexed-session #(assoc %2 :i %1)))\n\n(pm/spy\u003e\u003e :point {:x 100 :y 100})\n(pm/spy\u003e\u003e :point {:x 200 :y 200})\n(pm/spy\u003e\u003e :point {:x 300 :y 300})\n(pm/log-for :point)\n;=\u003e [{:i 0 :x 100 :y 100}\n;    {:i 1 :x 200 :y 200}\n;    {:i 2 :x 300 :y 300}]\n```\n\nThe `indexed` function is another way to create an indexed session.\n`(indexed \u003csession\u003e)` creates a new indexed session based on another session.\nIn fact, `(make-indexed-session)` is equivalent to `(indexed (make-session))`.\n\nIt's especially useful to make an session with base transducer into an indexed session:\n\n```clojure\n(pm/set-current-session!\n  (pm/indexed (pm/make-session (take-while #(\u003c (:id %) 3)))))\n\n(doseq [v [:a :b :c :d :e]]\n  (pm/spy\u003e\u003e :foo v))\n(pm/log-for :foo)\n;=\u003e [{:id 0 :val :a}\n;    {:id 1 :val :b}\n;    {:id 2 :val :c}]\n```\n\n#### `make-unsafe-session`\n\nIn Clojure, an ordinary session (created by `make-session`) is inherently\nthread safe, so you can safely share and update it across more than\none threads:\n\n```clojure\n(def sess (pm/make-session))\n(pm/set-current-session! sess)\n\n(defn f [n]\n  (pm/dump :f))\n\n(run! deref [(future (dotimes [i 10000] (f i)))\n             (future (dotimes [i 10000] (f i)))])\n\n(count (pm/log-for sess :f)) ;=\u003e 20000\n```\n\nThis thread safety is achieved by means of pessimistic locking during\nthe session update. If it is guaranteed that no more than one updates\nnever happen simultaneously on a single session, you can use\n`make-unsafe-session` instead to avoid the overhead of mutual exclusion.\n`make-unsafe-session` behaves almost same as `make-session` except for\nthread safety and performance.\n\n```clojure\n;; you can use `make-unsafe-session` in the same way as `make-session`\n(def sess (pm/make-unsafe-session))\n(pm/set-current-session! sess)\n\n(pm/spy\u003e\u003e :foo 1)\n(pm/spy\u003e\u003e :foo 2)\n(pm/spy\u003e\u003e :foo 3)\n\n(pm/log-for sess :foo) ;=\u003e [1 2 3]\n\n;; but `make-unsafe-session` is not thread safe\n(defn f [n]\n  (pm/dump :f))\n\n(run! deref [(future (dotimes [i 10000] (f i)))\n             (future (dotimes [i 10000] (f i)))])\n\n(count (pm/log-for sess :f)) ;=\u003e 11055\n```\n\nIn ClojureScript, `make-session` is completely identical to `make-unsafe-session`.\n\n### Simple logger\n\nPostmortem 0.5.0+ provides a new feature, *simple loggers*. A simple logger\nessentially works like `spy\u003e\u003e` and `log-for`, but offers a more simplified API\nfor common use cases.\n\nA simple logger is implemented as a function with two arities that closes over\nan implicit session:\n\n```clojure\n(def f (pm/make-logger))\n\n(f 1)\n(f 2)\n(f 3)\n(f 4)\n(f) ;=\u003e [1 2 3 4]\n```\n\nAs you may see, if a simple logger is called with one argument, it acts like\n`(spy\u003e\u003e :key \u003cargument\u003e)` on the implicit session whereas if called with no\nargument, it acts like `(log-for :key)`.\n\nIf you create a simple logger by passing an transducer as the optional argument,\nit will behave as if you attached that transducer to the implicit session:\n\n```clojure\n(def f (pm/make-logger (map #(* % %))))\n\n(f 1)\n(f 2)\n(f 3)\n(f 4)\n(f) ;=\u003e [1 4 9 16]\n```\n\nA *multi logger* is a variant of the simple logger that has three arities, i.e.\n2-arg for `(spy\u003e\u003e \u003carg1\u003e \u003carg2\u003e)`, 1-arg for `(log-for \u003carg\u003e)` and 0-arg for `(logs)`.\n\n```clojure\n(def f (pm/make-multi-logger))\n\n(loop [n 5, sum 0]\n  (if (= n 0)\n    sum\n    (recur (f :n (dec n)) (f :sum (+ sum n)))))\n;=\u003e 15\n\n(f) ;=\u003e {:n [4 3 2 1 0], :sum [5 9 12 14 15]}\n(f :n) ;=\u003e [4 3 2 1 0]\n(f :sum) ;=\u003e [5 9 12 14 15]\n```\n\n### Instrumentation\n\nPostmortem has one more powerful feature: instrumentation. It looks like clojure.spec's\nfeature with the same name. Once you instrument a function, you can collect execution log\nfor it without touching its code.\n\nTo use the instrumentation feature, you'll need to require the `postmortem.instrument`\nnamespace. The namespace provides two macros, `instrument` and `unstrument`, which\nenables/disables logging for a specified var, respectively.\n\n```clojure\n(require '[postmortem.core :as pm]\n         '[postmortem.instrument :as pi])\n\n(defn f [x] (inc x))\n\n;; Instruments `f` to enable logging\n(pi/instrument `f)\n;=\u003e [user/f]\n\n(dotimes [i 5] (prn (f i)))\n\n(pm/log-for `f)\n;=\u003e [{:args (0)}\n;    {:args (0), :ret 1}\n;    {:args (1)}\n;    {:args (1), :ret 2}\n;    {:args (2)}\n;    {:args (2), :ret 3}\n;    {:args (3)}\n;    {:args (3), :ret 4}\n;    {:args (4)}\n;    {:args (4), :ret 5}]\n\n;; Unstruments `f` to disable logging\n(pi/unstrument `f)\n;=\u003e [user/f]\n\n(pm/reset!)\n\n(dotimes [i 5] (prn (f i)))\n(pm/log-for `f) ;=\u003e nil\n```\n\nAs you can see, the execution log for an instrumented function consists of two types\nof log items, ones for *entry* and ones for *exit*. An entry log item is logged\nimmediately after the function gets called whereas an exit log item is logged\nimmediately after the function either returns or throws.\n\nBoth types of the log items contain the `:args` key that represents the arguments\npassed to the function when it's called. An exit log item contains an extra key:\nIf the function exits normally, the corresponding exit log item will have the `:ret` key\nthat holds the return value, and otherwise (i.e. if the function fails) the exit log\nitem will have the `:err` key that holds the error object thrown in the function.\n\nVirtually, instrumenting a function `f` replaces `f` with something like this:\n\n```clojure\n(fn [\u0026 args]\n  (pm/spy\u003e\u003e `f {:args args})\n  (try\n    (let [ret (apply f args)]\n      (pm/spy\u003e\u003e `f {:args args :ret ret}))\n    (catch Throwable t\n      (pm/spy\u003e\u003e `f {:args args :err t}))))\n```\n\nThis error handling behavior may be useful to identify which function call\nactually caused an error especially when you are debugging a recursive function:\n\n```clojure\n(defn broken-factorial [n]\n  (cond (= n 0) 1\n        (= n 5) (/ (broken-factorial (dec n)) 0) ;; \u003c- BUG HERE!!\n        :else (* n (broken-factorial (dec n)))))\n\n;; So far so good\n(map broken-factorial (range 5))\n;=\u003e (1 1 2 6 24)\n\n;; Boom!!\n(broken-factorial 7)\n;; Execution error (ArithmeticException) at user/broken-factorial.\n;; Divide by zero\n\n;; Now let's look into it further!\n(pi/instrument `broken-factorial)\n;=\u003e [user/broken-factorial]\n\n(broken-factorial 7) ;; throws error\n(pm/log-for `broken-factorial)\n;=\u003e [{:args (7)}\n;    {:args (6)}\n;    {:args (5)}\n;    {:args (4)}\n;    {:args (3)}\n;    {:args (2)}\n;    {:args (1)}\n;    {:args (0)}\n;    {:args (0), :ret 1}\n;    {:args (1), :ret 1}\n;    {:args (2), :ret 2}\n;    {:args (3), :ret 6}\n;    {:args (4), :ret 24}\n;    {:args (5), :err #error {:cause \"Divide by zero\" ...}}\n;    {:args (6), :err #error {:cause \"Divide by zero\" ...}}\n;    {:args (7), :err #error {:cause \"Divide by zero\" ...}}]\"\n```\n\nThere are a couple of options to specify for the `instrument` macro:\n\n- `:with-depth \u003cbool\u003e`: Attaches `:depth` to each execution log\n- `:xform \u003cxform\u003e`: Enables transducer integration\n- `:session \u003csession\u003e`: Specifies the session to use\n\nIf you call `instrument` with the option `{:with-depth true}`,\nPostmortem automatically counts the current nesting level (depth) of\nfunction calls and attach it to each execution log:\n\n```clojure\n(defn fact [n]\n  (if (= n 0)\n    1\n    (* n (fact (dec n)))))\n\n(pi/instrument `fact {:with-depth true})\n\n(fact 5) ;=\u003e 120\n(pm/log-for `fact)\n;=\u003e [{:depth 1, :args (5)}\n;    {:depth 2, :args (4)}\n;    {:depth 3, :args (3)}\n;    {:depth 4, :args (2)}\n;    {:depth 5, :args (1)}\n;    {:depth 6, :args (0)}\n;    {:depth 6, :args (0), :ret 1}\n;    {:depth 5, :args (1), :ret 1}\n;    {:depth 4, :args (2), :ret 2}\n;    {:depth 3, :args (3), :ret 6}\n;    {:depth 2, :args (4), :ret 24}\n;    {:depth 1, :args (5), :ret 120}]\n```\n\nCalling `instrument` with the option `{:xform \u003cxform\u003e}` enables integration\nof transducers with the instrumentation facility, passing the transducer\n`\u003cxform\u003e` to the underlying logging operators behind instrumentation.\nThe example below shows how you can utilize a transducer to narrow down\nthe execution log to the last few items until immediately before an error occurs:\n\n```clojure\n(require '[postmortem.core :as pm]\n         '[postmortem.instrument :as pi]\n         '[postmortem.xforms :as xf])\n\n(pm/reset!)\n\n(pi/instrument `broken-factorial\n               {:xform (comp (xf/take-until :err) (xf/take-last 5))})\n;=\u003e [user/broken-factorial]\n\n(broken-factorial 7) ;; throws error\n(pm/log-for `broken-factorial)\n;=\u003e [{:args (1), :ret 1}\n;    {:args (2), :ret 2}\n;    {:args (3), :ret 6}\n;    {:args (4), :ret 24}\n;    {:args (5), :err #error {:cause \"Divide by zero\" ...}}]\n```\n\nAlso, you can even isolate the session for a function instrumentation\nby specifying the `{:session \u003csession\u003e}` option:\n\n```clojure\n(def sess (pm/make-session))\n\n(defn f [x] (inc x))\n\n(pi/instrument `f {:session sess})\n\n(dotimes [i 5] (prn (f i)))\n\n(pm/log-for `f) ;=\u003e nil\n(pm/log-for sess `f)\n;=\u003e [{:args (0)}\n;    {:args (0), :ret 1}\n;    {:args (1)}\n;    {:args (1), :ret 2}\n;    {:args (2)}\n;    {:args (2), :ret 3}\n;    {:args (3)}\n;    {:args (3), :ret 4}\n;    {:args (4)}\n;    {:args (4), :ret 5}]\n```\n\n## Related works\n\n- [scope-capture](https://github.com/vvvvalvalval/scope-capture): Focuses on recreating a local environment at some point and seems rather intended to be a simple alternative for rich debuggers while Postmortem aims to compensate for some weakness in existing debuggers\n- [miracle.save](https://github.com/Saikyun/miracle.save): Closely resembles Postmortem's basics, but Postmortem has more fancy features such as transducer integration and instrumentation\n\n## License\n\nCopyright © 2018 Shogo Ohta\n\nDistributed under the Eclipse Public License either version 1.0 or (at\nyour option) any later version.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fathos%2Fpostmortem","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fathos%2Fpostmortem","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fathos%2Fpostmortem/lists"}