Alex MartsinovichSoftware Engineer |
||||
Home | Posts | Github |
PostHog Elixir SDK is good
The PostHog Elixir SDK 2.0 just dropped.
What is PostHog? Why should you care about its SDK? Why do I think it's good? Bear with me, I'm about to tell you how I spent my summer.
Dear Diary
In May 2025 I found myself with a lot of free time and an appetite for writing a logging library from scratch. How exactly I developed this appetite is a whole other story, but the bottom line is that I wasn't happy with how all existing logging libraries shared a lot of design decisions and workarounds, a lot of which were based on solvable problems or not relevant anymore.
And right when I was entertaining these thoughts, PostHog released its Error Tracking solution to open beta. This was a perfect opportunity to write a library.
So I bought a Claude Code subscription and one-shotted the whole thing in two
days. Actually, no. I locked in and put a ton of effort into it. I learned
everything I could about Elixir logging. Whenever I hit a roadblock, I would
take a step back and fix it at the origin, whether it was in
Plug,
LoggerJSON or Elixir
itself. I also documented
everything and released it as an educational Hex package
LoggerHandlerKit.
All of this resulted in LogHog – an okay library focused solely on PostHog Error Tracking.
It didn't take long for PostHog's very own Elixir champion Rafa Audibert to reach out. Naturally, error tracking would be a good addition to the official library. Things quickly got out of control though, and a couple of months later we have a new, completely overhauled version of the SDK on our hands. And, dear reader, I think it's good.
What to expect
So what is PostHog and why does it need an SDK? After all, the Elixir ecosystem is not big on SDKs. Just using Req is a great experience and sets a pretty high bar to clear for any bespoke library.
I'm glad you asked! Let me walk you through what PostHog brings to the table for an Elixir application.
Event Capture
I don't work for PostHog, so I'm going to tell you the truth. PostHog is a big ClickHouse cluster run by a team whose life goal is to see how many products they can build on top of it.

The most important, core PostHog feature is the ability to send events to the
server, where they will be accessible through an SQL-like query language. Each
event can have properties attached to it. You can shove anything in there. The
only required property is distinct_id
, which identifies users, persons, or
whatever you decide it does. Events happen to someone, after all.
Just emitting events is already great and is what we would call product
analytics. But PostHog has made events even more useful! Some events have special
meaning and experiences built for them. If you emit $exception_list
events,
they will be available in Error Tracking.
Capture $ai_generation
and you have LLM
Analytics.
Events are very versatile, and PostHog is building more and more products every day.
Context
Sending analytics events is not as simple as sprinkling HTTP requests over your codebase. You want to instrument your events with properties and they originate in very different places. In order to note a user's IP address, you need to tap into your Endpoint. For tenant ID, look inside the authentication stack.
If we had to pipe all relevant information through all the layers of our app, we would go insane. This is why there is an established pattern in Elixir to use the process dictionary for that kind of metadata.
The PostHog SDK comes with a context mechanism to address exactly this. You can set PostHog context and it will be attached to events captured in the same process.
PostHog.set_context(%{distinct_id: "distinct_id_of_the_user"})
PostHog.capture("page_opened")
Batching
Capturing events is important, but usually not important enough to block your
app code until the API request returns. That's why you want to offload sending
them to some background task. Sure, Task.start
is always there, but it would
also be nice to batch events together...
PostHog SDK manages a pool of sender processes for you that automatically
batch and send all the events you capture. So when you call
PostHog.capture
, it's merely a GenServer.cast
under the hood.
Integrations
Some things are solved problems. PostHog gives special meaning to properties
like $current_url
or $host
, and the best place to grab those is from the
Plug.Conn
struct. This knowledge is already baked into the SDK as
PostHog.Integrations.Plug
. All you need to do is add it to your pipeline.
For now this is the only integration, but it won't be long until there are more.
Product-specific API
Sometimes you just need a little helper. The feature flags product, for example, goes beyond simple event capture. The recommended flow to evaluate a feature flag is the following:
- Make a call to
/flags
endpoint - Capture
$feature_flag_called
event - Use
$feature/<flag_name>
property for all events captured for this user.
The SDK encapsulates all of this knowledge and exposes it as a single
PostHog.FeatureFlags.check
function.
Error Tracking
Finally, the part that was transferred from LogHog. PostHog comes with a logger handler that captures relevant log events and makes them accessible in PostHog Error Tracking.
I have to say, though, that unlike other PostHog products, Error Tracking requires language-specific backend support for the best experience, and Elixir support is not yet implemented. But it works, so give it a try!
Test Infrastructure
You want to test your analytic events, right? Right?
Well, you're in luck because the PostHog SDK does the right thing and uses NimbleOwnership to scope all captured events to their owner processes. Expect your async tests to remain async:
test "capture event" do
PostHog.capture("event", %{distinct_id: "distinct_id"})
assert [event] = PostHog.Test.all_captured()
assert %{
event: "event",
distinct_id: "distinct_id",
properties: %{},
timestamp: _
} = event
end
Conclusion
The new PostHog SDK is here and I think it's good. It was written by people who care about both Elixir and PostHog. It's fresh off the press and not battle-tested yet, so tread with caution, but if you're looking to capture events in your app, maybe give it a try.