{"id":19146850,"url":"https://github.com/conormcd/clj-honeycomb","last_synced_at":"2025-05-07T02:04:51.948Z","repository":{"id":62435351,"uuid":"163897587","full_name":"conormcd/clj-honeycomb","owner":"conormcd","description":"A Clojure interface to Honeycomb.io, built on libhoney-java.","archived":false,"fork":false,"pushed_at":"2023-12-15T14:53:26.000Z","size":178,"stargazers_count":25,"open_issues_count":2,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-07T02:04:46.971Z","etag":null,"topics":["clojure","honeycomb","honeycombio","monitoring","observability"],"latest_commit_sha":null,"homepage":null,"language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/conormcd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","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":"2019-01-02T22:50:04.000Z","updated_at":"2024-04-20T18:57:11.000Z","dependencies_parsed_at":"2024-11-09T07:48:19.119Z","dependency_job_id":"e59b36c6-d25a-4871-8799-68af6ee2e9a3","html_url":"https://github.com/conormcd/clj-honeycomb","commit_stats":{"total_commits":54,"total_committers":1,"mean_commits":54.0,"dds":0.0,"last_synced_commit":"f309bf3b85af2239f4172189fcd934076306240b"},"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/conormcd%2Fclj-honeycomb","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/conormcd%2Fclj-honeycomb/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/conormcd%2Fclj-honeycomb/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/conormcd%2Fclj-honeycomb/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/conormcd","download_url":"https://codeload.github.com/conormcd/clj-honeycomb/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252798855,"owners_count":21805887,"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","honeycomb","honeycombio","monitoring","observability"],"created_at":"2024-11-09T07:48:10.454Z","updated_at":"2025-05-07T02:04:51.922Z","avatar_url":"https://github.com/conormcd.png","language":"Clojure","funding_links":[],"categories":[],"sub_categories":[],"readme":"# clj-honeycomb\n\nA library for sending events to [Honeycomb.io](https://www.honeycomb.io/),\nwrapping [libhoney-java 1.0.6](https://github.com/honeycombio/libhoney-java).\n\n[![Clojars Project](https://img.shields.io/clojars/v/conormcd/clj-honeycomb.svg)](https://clojars.org/conormcd/clj-honeycomb)\n[![CircleCI](https://circleci.com/gh/conormcd/clj-honeycomb.svg?style=svg)](https://circleci.com/gh/conormcd/clj-honeycomb)\n[![codecov](https://codecov.io/gh/conormcd/clj-honeycomb/branch/master/graph/badge.svg)](https://codecov.io/gh/conormcd/clj-honeycomb)\n\n- [Usage](#usage)\n  - [Global and dynamic fields](#global-and-dynamic-fields)\n  - [Sampling](#sampling)\n  - [Tracing](#tracing)\n  - [Pre- and Post-processing events](#pre--and-post-processing-events)\n  - [Middleware](#middleware)\n  - [Monitoring](#monitoring)\n  - [Managing client state](#managing-client-state)\n  - [Testing](#testing)\n- [API Documentation](#api-documentation)\n- [License](#license)\n\n## Usage\n\nInclude the following in your `project.clj`:\n\n```clojure\n; This is libhoney-java 1.0.6 build number 1 on CircleCI\n; The most recent version can be found via the Clojars badge above\n[conormcd/clj-honeycomb \"1.0.6.1\"]\n```\n\nThen initialize the library somewhere:\n\n```clojure\n(require '[clj-honeycomb.core :as honeycomb])\n\n(honeycomb/init {:data-set \"The name of your Honeycomb data set\"\n                 :write-key \"Your Honeycomb team write key (API key)\"})\n```\n\nNow send an event:\n\n```clojure\n(honeycomb/send {:foo \"bar\"})\n```\n\nYou can also wrap a chunk of code and trigger an event when the code\ncompletes. It also times the code and instruments any exceptions thrown from\nthe body.\n\n```clojure\n(honeycomb/with-event {:foo \"foo\"} {}\n  (honeycomb/add-to-event {:bar \"bar\"})\n  ... code ...\n  (honeycomb/add-to-event :baz \"baz\")\n  ... code ...)\n\n; The above sends an event like this:\n; {\"foo\" \"foo\"\n;  \"bar\" \"bar\"\n;  \"baz\" \"baz\"\n;  \"durationMs\" 1234.567}\n```\n\n### Global and dynamic fields\n\nYou can add fields to every event by setting them on the client at\ninitialization time. You can delay computation of the value of those fields\nuntil the time the event is sent by using atoms, delays or promises.\n\n```clojure\n(def my-dynamic-field (atom 1))\n\n(honeycomb/init {:global-fields {:global-dynamic my-dynamic-field\n                                 :global-static \"static\"}})\n(honeycomb/send {:event-specific \"event\"})\n(swap! my-dynamic-field inc)\n(honeycomb/send {:event-specific \"event\"})\n\n; The above results in the following two events being sent:\n; {\"event-specific\" \"event\" \"global-dynamic\" 1 \"global-static\" \"static\"}\n; {\"event-specific\" \"event\" \"global-dynamic\" 2 \"global-static\" \"static\"}\n```\n\nYou can make more sophisticated dynamic fields by implementing a\n[ValueSupplier](https://honeycombio.github.io/libhoney-java/io/honeycomb/libhoney/ValueSupplier.html).\nThere is a convenience function `clj-honeycomb.fields/-\u003eValueSupplier` to\ntransform a Clojure function into one.\n\n```clojure\n(honeycomb/init {:global-fields {:vs (-\u003eValueSupplier rand-int 100)}})\n(honeycomb/send {:event \"event\"})\n\n; The above produces:\n; {\"event\" \"event\" \"vs\" 15}\n```\n\n### Sampling\n\nHoneycomb provides [useful\ninformation](https://docs.honeycomb.io/getting-data-in/sampling/) on why and\nwhen to sample. Sample rates can be set globally at initialization time with\nthe `:sample-rate` key or individually on each `send` by passing a\n`sample-rate` in the options map which is the third argument to `send`. If you\nimplement your own sampling, you must pass `{:sample-rate ... :pre-sampled\ntrue}` to each call to `send`.\n\n### Pre- and Post-processing events\n\nEvents can be manipulated before they're sampled (with a clj-honeycomb\npre-processor function) and after they're sampled (with a libhoney-java\n[EventPostProcessor](https://honeycombio.github.io/libhoney-java/io/honeycomb/libhoney/EventPostProcessor.html)).\n\nTo pre-process an event before it's handed off to libhoney-java, add a\nfunction like `(fn [event-data options] ...)` to the `:event-pre-processor`\noptional argument to `init` which returns a tuple `[event-data options]`. The\narguments and return value are identical to the arguments to\n`clj-honeycomb.core/send` and may be manipulated in any way so long as the\nreturned value is a valid set of arguments for `send`.\n\nExample:\n\n```clojure\n(defn- event-pre-processor\n  \"Add an extra field to the event data which is a count of the number of\n   fields being sent.\"\n  [event-data options]\n  [(merge event-data\n          {:num-items (inc (count event-data))})\n   options])\n\n(honeycomb/init {...\n                 :event-pre-processor event-pre-processor\n                 ...})\n```\n\nTo post-process an event, add an\n[EventPostProcessor](https://honeycombio.github.io/libhoney-java/io/honeycomb/libhoney/EventPostProcessor.html)\nto the `:event-post-processor` optional argument to `init`. The `process`\nmethod on that object will be called with a single, mutable\n[EventData](https://honeycombio.github.io/libhoney-java/io/honeycomb/libhoney/eventdata/EventData.html)\nobject. This is called after sampling has taken place, so it will only be run\non events which will be sent to Honeycomb.io. See the documentation for the\nEventPostProcessor class to understand the constraints on modifying the event.\n\n### Tracing\n\nTL;DR: Wrap something with `with-event` and make sure it includes `:traceId`\nin the event data in order to create tracing spans.\n\nHoneycomb provides powerful tracing capabilities and this library attempts to\ncreate tracing spans from every use of `with-event`. The following event data\nfields control the creation of spans:\n\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003cth\u003eField\u003c/th\u003e\n    \u003cth\u003eRequired?\u003c/th\u003e\n    \u003cth\u003eDefault\u003c/th\u003e\n    \u003cth\u003eDescription\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003e:traceId\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eYes\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003eThe ID of the trace this belongs to. This is the ID that will tie\n      multiple spans together.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003e:id\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eNo\u003c/td\u003e\n    \u003ctd\u003e\u003ccode\u003e(honeycomb/generate-span-id)\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eA unique ID for the span. If not provided, one will be randomly\n      generated for you and added to the event if (and only if) a\n      \u003ccode\u003etraceId\u003c/code\u003e has been added to the event before.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003e:durationMs\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eYes\u003c/td\u003e\n    \u003ctd\u003eAutomatically calculated by \u003ccode\u003ewith-event\u003c/code\u003e.\u003c/td\u003e\n    \u003ctd\u003eThe duration of the span, in milliseconds.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003e:name\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eNo\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003eThe name of the function/method/API handler generating the event. This\n      can be blank.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003e:parentId\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eNo\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003eThe ID of this span's parent span. This is set automatically for you\n      if you nest calls to \u003ccode\u003ewith-event\u003c/code\u003e. You can set this manually\n      if you need to \"waterfall\" spans which don't enclose each other.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003ccode\u003e:serviceName\u003c/code\u003e\u003c/td\u003e\n    \u003ctd\u003eNo\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003eThe name of the service generating this span. This can be blank.\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\nHere is the simplest example of creating a tracing span:\n\n```clojure\n(honeycomb/with-event {:traceId (honeycomb/generate-trace-id)} {}\n  ... Do stuff here ...)\n```\n\nYou can create multiple spans that are part of the same trace. This will\ncreate two spans within a trace, but won't establish any relationship between\nthem:\n\n```clojure\n(honeycomb/with-trace-id (honeycomb/generate-trace-id)\n  (honeycomb/with-event {:name \"X\"} {}\n    ... Do stuff ...)\n  ... Do more stuff ...\n  (honeycomb/with-event {:name \"Y\"} {}\n    ... Do even more stuff ...))\n```\n\nThis produces a trace that looks like this:\n\n```\n+-----------------+ +-------+\n|        X        | |   Y   |\n+-----------------+ +-------+\n```\n\nIt's usually useful to establish a relationship between spans in a trace.\nThere are two patterns:\n\n1. _While_ doing X we did Y.\n2. _Because_ we did X we _subsequently_ did Y.\n\nThe first of those is most easily expressed by nesting `with-event` calls:\n\n```clojure\n(honeycomb/with-event {:name \"X\" :traceId (honeycomb/generate-trace-id)} {}\n  ... Some X ...\n  (honeycomb/with-event {:name \"Y\"} {}\n    ...  Some Y ...)\n  ... Some more X ...)\n```\n\nThis produces a trace that looks like this:\n\n```\n+-----------------+\n|        X        |\n+-----------------+\n     +-------+\n     |   Y   |\n     +-------+\n```\n\nThe second case can be accomplished like so:\n\n```clojure\n(honeycomb/with-trace-id (honeycomb/generate-trace-id)\n  (let [x-id (honeycomb/generate-span-id)]\n    (honeycomb/with-event {:name \"X\" :id x-id} {}\n      ... Some X ...)\n    (honeycomb/with-event {:name \"Y\" :parentId x-id} {}\n      ... Some Y ....)))\n```\n\nThis produces a trace that looks like this:\n\n```\n+-----------------+\n|        X        |\n+-----------------+\n                  +-------+\n                  |   Y   |\n                  +-------+\n```\n\nThe above may not make a lot of sense in the context of a single chunk of\ncode, but sequential (as opposed to enclosing) spans are a natural way of\nexpressing chains of calls between services in a distributed system. There is\nsupport in the clj-honeycomb Ring middleware to make this feel natural in a\nClojure application.\n\n### Middleware\n\n#### Ring\n\nYou can turn every request served by a Ring-compatible HTTP server into a\nHoneycomb event with `clj-honeycomb.middleware.ring/with-honeycomb-event`. By\ndefault the event will contain a selection of items from the request map and\nfrom the response it will contain the status and a small subset of the\nheaders. You can customize the fields added to the event by passing an options\nmap to the middleware.\n\nThe following defines a custom honeycomb middleware that extracts only some\nof the request and response data but adds some static and dynamic fields.\n\n```clojure\n(def count-of-thingers\n  \"An atom keeping track of the count of something, to demonstrate dynamic\n   fields. This will be dereferenced whenever the event fires.\"\n  (atom 0))\n\n(def my-custom-honeycomb-middleware\n  (partial with-honeycomb-event\n           {:honeycomb-event-data {:static-field \"sent with every event\"\n                                   :num-thingers count-of-thingers}\n            :extract-request-fields (fn [req]\n                                      {:num-headers (count (:headers req))})\n            :extract-response-fields (fn [res]\n                                       {:status (:status res)})}))\n\n; This will produce events that look like this:\n; {\"durationMs\" 83.932\n;  \"num-headers\" 12\n;  \"num-thingers\" 3\n;  \"static-field\" \"sent with every event\"\n;  \"status\" 404}\n```\n\nTracing support is included in the Ring middleware. If the `X-Honeycomb-Trace`\nheader is present in a request then it will be decoded to propagate the trace\nID and to link the event to the parent span in the calling code. The format of\nthe header is the same as in other Honeycomb libraries and is as follows:\n\n```bnf\n\u003cheader\u003e  ::= \"X-Honeycomb-Trace: \" \u003cvalue\u003e\n\u003cvalue\u003e   ::= version \";\" data\n\u003cversion\u003e ::= \"1\"\n\u003cdata\u003e    ::= \u003cpair\u003e \",\" \u003cpair\u003e\n            | \u003cpair\u003e\n\u003cpair\u003e    ::= \"trace_id=\" [^,]+\n            | \"parent_id=\" [^,]+\n            | \"context=\" \u003cbase64-encoded-json\u003e\n```\n\nIf the version is wrong or if the `trace_id` is not present then the\nmiddleware will act as if the header was not present. The `parent_id` is\noptional but recommended and must refer to the ID of a span associated with\nthe same trace ID. The `context` is also optional and must be a Base64-encoded\nJSON string containing data that should be added to the event.\n\n### Monitoring\n\nThe libhoney-java library sends events to Honeycomb asynchronously on a\nbackground thread. It also batches events. To monitor the progress of the\nsending of events you can add a\n[ResponseObserver](https://honeycombio.github.io/libhoney-java/io/honeycomb/libhoney/ResponseObserver.html)\nto the client. The easiest way to do that is by adding one or more functions\nto a `:response-observer` map in the client options passed to either `init` or\n`client`.\n\n```clojure\n(honeycomb/init {:data-set \"data-set\"\n                 :write-key \"write-key\"\n                 :response-observer {:on-client-rejected\n                                     (fn [client-rejected]\n                                       ...)\n                                     :on-server-accepted\n                                     (fn [server-accepted]\n                                       ...)\n                                     :on-server-rejected\n                                     (fn [server-rejected]\n                                       ...)\n                                     :on-unknown\n                                     (fn [unknown]\n                                       ...)}})\n```\n\nYou may omit any of the functions in the `:response-observer` map with no ill\neffects. Each of the functions takes a single argument and the types of the\narguments are as follows:\n\n- :on-client-rejected - [ClientRejected](https://honeycombio.github.io/libhoney-java/io/honeycomb/libhoney/responses/ClientRejected.html)\n- :on-server-accepted - [ServerAccepted](https://honeycombio.github.io/libhoney-java/io/honeycomb/libhoney/responses/ServerAccepted.html)\n- :on-server-rejected - [ServerRejected](https://honeycombio.github.io/libhoney-java/io/honeycomb/libhoney/responses/ServerRejected.html)\n- :on-unkonwn - [Unknown](https://honeycombio.github.io/libhoney-java/io/honeycomb/libhoney/responses/Unknown.html)\n\n### Managing client state\n\nIf you would like to avoid using this library in a stateful manner you can\navoid calling `clj-honeycomb.core/init` and accomplish everything with\n`clj-honeycomb.core/client` and `clj-honeycomb.core/send`. The former is used\nto create a [HoneyClient](https://honeycombio.github.io/libhoney-java/io/honeycomb/libhoney/HoneyClient.html)\nwhich can then be passed as the first argument to `send` in order to send\nevents. The `with-event` macro will throw if you attempt to use it without\ncalling `init` first. You are responsible for calling `.close` on the client\nbefore disposing of it. It's recommended that you use `with-open` or some\nstate management system like component or mount.\n\n### Testing\n\nIf you're testing code that uses the implicit client created with `init` then\nyou can use `clj-honeycomb.testing-utils/validate-events` to make assertions\nabout the events sent by some code. This also prevents events from being sent\nto Honeycomb. The events passed to the second function is a vector of\n[ResolvedEvent](https://honeycombio.github.io/libhoney-java/io/honeycomb/libhoney/eventdata/ResolvedEvent.html).\n\n```clojure\n(require '[clj-honeycomb.testing-utils :refer (validate-events)])\n\n(validate-events\n (fn []\n   ... code that emits events ...\n   )\n (fn [events errors]\n   ... events contains all the events that would have been sent ...\n   ... errors contains any errors emitted by libhoney-java ...))\n```\n\nYou can also create a fake `HoneyClient` which will record all the events sent\nto it.\n\n```clojure\n(require '[clj-honeycomb.core :as honeycomb])\n(require '[clj-honeycomb.testing-utils :refer (recording-client)])\n\n(let [events (atom [])]\n  (with-open [client (recording-client events {})]\n    (honeycomb/send client {:foo \"bar\"}))\n  ... events now contains the ResolvedEvent ...)\n```\n\n## API Documentation\n\nAutomatically generated API documentation is uploaded to GitHub Pages on every\nrelease. It can be viewed here:\n\nhttps://conormcd.github.io/clj-honeycomb/\n\nSince this library wraps `libhoney-java` it may also be useful to refer to the\nAPI documentation for that from time to time:\n\nhttps://honeycombio.github.io/libhoney-java/index.html?overview-summary.html\n\n## License\n\nCopyright 2018-2019 Conor McDermottroe\n\nLicensed under the Apache License, Version 2.0 (the \"License\"); you may not\nuse this software except in compliance with the License. You may obtain a copy\nof the License at\n\nhttp://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS, WITHOUT\nWARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the\nLicense for the specific language governing permissions and limitations under\nthe License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconormcd%2Fclj-honeycomb","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fconormcd%2Fclj-honeycomb","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fconormcd%2Fclj-honeycomb/lists"}