{"id":20936106,"url":"https://github.com/flow-storm/hansel","last_synced_at":"2025-05-13T21:30:55.775Z","repository":{"id":64501983,"uuid":"559179740","full_name":"flow-storm/hansel","owner":"flow-storm","description":"Instrument Clojure[Script] forms to trace it","archived":false,"fork":false,"pushed_at":"2025-02-05T17:24:47.000Z","size":597,"stargazers_count":30,"open_issues_count":3,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-02T08:08:56.795Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"unlicense","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/flow-storm.png","metadata":{"files":{"readme":"Readme.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-10-29T10:01:43.000Z","updated_at":"2025-02-07T10:21:31.000Z","dependencies_parsed_at":"2025-02-05T18:32:31.996Z","dependency_job_id":"3b44f8e0-4286-4113-bd4c-afecdf4e136a","html_url":"https://github.com/flow-storm/hansel","commit_stats":null,"previous_names":["flow-storm/hansel"],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flow-storm%2Fhansel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flow-storm%2Fhansel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flow-storm%2Fhansel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/flow-storm%2Fhansel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/flow-storm","download_url":"https://codeload.github.com/flow-storm/hansel/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254030869,"owners_count":22002665,"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":[],"created_at":"2024-11-18T22:17:54.552Z","updated_at":"2025-05-13T21:30:54.938Z","avatar_url":"https://github.com/flow-storm.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"\n![hansel](./docs/hansel.png)\n\nHansel leaving a trail o pebbles.\n\nHansel allows you to instrument Clojure[Script] forms and entire namespaces, so they leave a trail when they run.\n\nIt is ment as a platform to build tooling that depends on code instrumentation, like debuggers.\n\n[![Clojars Project](https://img.shields.io/clojars/v/com.github.flow-storm/hansel.svg)](https://clojars.org/com.github.flow-storm/hansel)\n\n## Clojure QuickStart\n\n### Basic instrumentation\n\n```clojure\n(require '[hansel.api :as hansel]) ;; first require hansel api\n\n;; Then define your \"trace handlers\"\n\n(defn print-form-init [data]\n  (println \"[form-init] data:\" data))\n\n(defn print-fn-call [data]\n  (println \"[fn-call] data:\" data))\n\n(defn print-fn-return [{:keys [return] :as data}]\n  (println \"[fn-return] data:\" data)\n  return) ;; must return return!\n\n(defn print-fn-unwind [data]\n  (println \"[fn-unwind] data:\" data))\n  \n(defn print-expr-exec [{:keys [result] :as data}]\n  (println \"[expr-exec] data:\" data)\n  result) ;; must return result!\n\n(defn print-bind [data]\n  (println \"[bind] data: \" data))\n\n;; If you have any form as data you can instrument it with \n;; hansel.api/instrument-form, providing the tracing handlers you\n;; are interested in. It will return the instrumented form\n\n(hansel/instrument-form '{:trace-fn-call dev/print-fn-call\n                          :trace-fn-return dev/print-fn-return}\n                        '(defn foo [a b] (+ a b)))\n\n;; If you want to evaluate the instrumented fn also you\n;; have a convenience macro hansel.api/instrument also providing\n;; your tracing handlers\n\n(hansel/instrument {:trace-form-init dev/print-form-init\n                    :trace-fn-call dev/print-fn-call\n                    :trace-fn-return dev/print-fn-return\n                    :trace-fn-unwind dev/print-fn-unwind\n                    :trace-expr-exec dev/print-expr-exec\n                    :trace-bind dev/print-bind}\n                   (defn foo [a b] (+ a b)))\n\n;; then you can call the function\n\n(foo 2 3)\n\n;; and the repl should print :\n\n;; [form-init] data: {:form-id -1653360108, :form (defn foo [a b] (+ a b)), :ns \"dev\", :def-kind :defn}\n;; [fn-call] data: {:ns \"dev\", :fn-name \"foo\", :fn-args [2 3], :form-id -1653360108}\n;; [bind] data: {:val 2, :coor nil, :symb a, :form-id -1653360108}\n;; [bind] data: {:val 3, :coor nil, :symb b, :form-id -1653360108}\n;; [expr-exec] data: {:coor [3 1], :result 2, :form-id -1653360108}\n;; [expr-exec] data: {:coor [3 2], :result 3, :form-id -1653360108}\n;; [expr-exec] data: {:coor [3], :result 5, :form-id -1653360108}\n;; [fn-return] data: {:return 5, :form-id -1653360108}\n```\n\n### Namespaces instrumentation\n\n```clojure\n\n(hansel/instrument-namespaces-clj #{\"clojure.set\"}\n                                    '{:trace-form-init dev/print-form-init\n                                      :trace-fn-call dev/print-fn-call\n                                      :trace-fn-return dev/print-fn-return\n                                      :trace-fn-unwind dev/print-fn-unwind\n                                      :trace-expr-exec dev/print-expr-exec\n                                      :trace-bind dev/print-bind\n\t\t\t\t\t\t\t\t\t  :prefixes? false})\n\t\t\t\t\t\t\t\t\t  \n(clojure.set/difference #{1 2 3} #{2})\n```\n\n### Vars and deep instrumentation\n\nYou can instrument any var by using `hansel/instrument-var-clj` like this :\n```clojure\n(hansel/instrument-var-clj\n   'clojure.set/join\n   '{:trace-form-init dev/print-form-init\n     :trace-fn-call dev/print-fn-call\n     :trace-fn-return dev/print-fn-return\n     :trace-fn-unwind dev/print-fn-unwind\n     :trace-expr-exec dev/print-expr-exec\n     :trace-bind dev/print-bind\n     :deep? true}) ;; deep? is nil by default\n\n;; it will return all the instrumented vars\n;; =\u003e #{clojure.set/bubble-max-key\n        clojure.set/index\n        clojure.set/intersection\n        clojure.set/join\n        clojure.set/map-invert\n        clojure.set/rename-keys}\n```\n\n## ClojureScript\n\n### ClojureScript namespaces instrumentation (shadow-cljs only)\n\nFirst start your shadow app :\n\n```\nrlwrap npx shadow-cljs browser-repl\n```\n\nOn the ClojureScript side you need to define your handlers, so lets assume you have defined all your print-fns as before\ninside cljs.user namespace.\n\nOn your shadow clj repl (you can connect to it via nRepl or by `npx shadow-cljs clj :browser-repl`) :\n```\n\nshadow.user\u003e (require '[hansel.api :as hansel])\n\n;; instrument your namespaces providing the handlers you defined in the ClojureScript runtime side\n\nshadow.user\u003e (hansel/instrument-namespaces-shadow-cljs\n                #{\"clojure.set\"}\n                '{:trace-form-init cljs.user/print-form-init\n                  :trace-fn-call cljs.user/print-fn-call\n                  :trace-fn-return cljs.user/print-fn-return\n                  :trace-fn-unwind cljs.user/print-fn-unwind\n                  :trace-expr-exec cljs.user/print-expr-exec\n                  :trace-bind cljs.user/print-bind\n                  :build-id :browser-repl}) ;; \u003c- need to provide your shadow app build-id\n```\n\nNow go back to your ClojureScript repl and run :\n\n```\ncljs.user\u003e (clojure.set/difference #{1 2 3} #{2})\n```\n\nThere is also `hansel.api/instrument-var-shadow-cljs` which works exactly like the Clojure version but \nthe config also needs a `:build-id`.\n\n## The coordinate system\n\nAll expression and bind traces data will contain a `:coor` field, which specifies the coordinate inside the form with `:form-id` this value refers to.\n\nSo the coordinate `[3 2]` in the form :\n\n```clojure\n(defn foo [a b] (+ a b))\n```\n\nrefers to the `b` symbol in `(+ a b)` which is under coordinate `[3]`.\n\nThe coords are a vector of positional indexes from the root but for maps instead of an index it will be for :\n\n    map key : a string K followed by the hash of the key form\n    map value: a string V followed by the hash of the key form for the val\n\nFor sets it will also be a string K followed by the hash of the set\nelement form.\n\nAs an example :\n\n(defn foo [a b]\n  (assoc {1 10\n          (+ 42 43) 100}\n         :x #{(+ 1 2) (+ 3 4) (+ 4 5) (+ 1 1 (* 2 2))}))\n\nsome examples coordinates :\n\n    [3 1 \"K-240379483\"]   =\u003e (+ 42 43)\n    [3 2 \"K1305480196\" 3] =\u003e (* 2 2)\n\n## How do I relate fn-return to its fn-call?\n\nHansel isn't tracking what is the current fn call per thread, so it can't tell you what was the fn-call for your current\nfn-return, but you can do it pretty easily but keeping track of it yourself:\n\n```clojure\n(def threads-stacks (atom {}))\n\n(defn push-thread-frame [stacks thread-id data]\n  (swap! stacks update thread-id conj data))\n\n(defn pop-thread-frame [stacks thread-id]\n  (swap! stacks update thread-id pop))\n\n(defn peek-thread-frame [stacks thread-id]\n  (peek (get @stacks thread-id)))\n\n(defn trace-fn-call [fn-call-data]\n  (let [curr-thread-id (.getId (Thread/currentThread))]\n    (push-thread-frame threads-stacks curr-thread-id fn-call-data)))\n\n(defn trace-fn-return [{:keys [return]}]\n  (let [curr-thread-id (.getId (Thread/currentThread))\n        frame-data (peek-thread-frame threads-stacks curr-thread-id)]\n\n    (println \"RETURNING \" return \"for fn-call\" frame-data)\n\n    (pop-thread-frame threads-stacks curr-thread-id))\n  return)\n```\n## Projects currently using Hansel\n\n- [FlowStorm debugger](https://github.com/jpmonettas/flow-storm-debugger)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflow-storm%2Fhansel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflow-storm%2Fhansel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflow-storm%2Fhansel/lists"}