# Dynamic log capture

**URL:** <https://forum.crystal-lang.org/t/dynamic-log-capture/7908>\
**Category:** Help & Support\
**Created:** [March 28, 2025, 11:41pm UTC](https://forum.crystal-lang.org/t/dynamic-log-capture/7908 "2025-03-28T23:41:47Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![Xen](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/xen/32/2288_2.png) [@Xen](https://forum.crystal-lang.org/u/Xen)\
**Post date:** [March 28, 2025, 11:41pm UTC](https://forum.crystal-lang.org/t/dynamic-log-capture/7908/1 "2025-03-28T23:41:48Z")

</div>

If you don’t mind rubberducking this with me:

I got this Kemal app where I have a `User` class with an associated `User::Log`. I then have a log backend that routes the log entries to appropriate files by inspecting the log context. Works marvelously.

Now, I have some Kemal endpoints where I would like to capture whatever goes into the log while processing that request.

First thought was making a memory based log sink backend one could request the log entries from, but then we have to garbage collect log entries all the time, so that doesn’t seem so elegant.

Then I thought of a backend that’s an observable. When it doesn’t have any observers, it just discards everything, else it passes log entries on to it’s observers. I could then wrap the observer setup in a nice method and end up with something like:

```auto
log = capture_log do
  # stuff that logs
end

```

Then I got nervous when I remembered that requests are served by their own fiber, and what happens when the backend instantiated in the main fiber gets observed by an object that’s scoped in a fiber (I guess it’s all right, as long as `capture_log` ensures it’s un-observed before it returns?)? I’ll admit that I’m still squinting very hard when thinking about how fibers work.

But maybe there’s an elegant trick I haven’t discovered?

---

<div class="post-metadata">

**Author:** ![Blacksmoke16](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/blacksmoke16/32/1241_2.png) [@Blacksmoke16](https://forum.crystal-lang.org/u/Blacksmoke16)\
**Post date:** [March 29, 2025, 12:33am UTC](https://forum.crystal-lang.org/t/dynamic-log-capture/7908/2 "2025-03-29T00:33:15Z")

</div>

What you’re basically describing is [`Log.capture`](https://crystal-lang.org/api/1.15.0/Log.html#capture%28source%3AString%3D%22%2A%22%2Clevel%3ASeverity%3DLog%3A%3ASeverity%3A%3ATrace%2C%2A%2Cbuilder%3DLog.builder%2C%26%29-class-method). However the catch is it’s primarily intended as a spec helper. So would either have to require `spec` code into your production build, or duplicate its source to your own method.

Tho I guess I gotta ask, what’s the use case for this? Normally logs aren’t something your application really needs to be aware of?

---

<div class="post-metadata">

**Author:** ![Xen](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/xen/32/2288_2.png) [@Xen](https://forum.crystal-lang.org/u/Xen)\
**Post date:** [March 29, 2025, 8:11pm UTC](https://forum.crystal-lang.org/t/dynamic-log-capture/7908/3 "2025-03-29T20:11:37Z")

</div>

> [@Blacksmoke16](#):
>
> Tho I guess I gotta ask, what’s the use case for this? Normally logs aren’t something your application really needs to be aware of?

Well, forgot to explicitly state that the capturing backend would be in addition to the normal backend.

For the nitty gritty: I have a Kemal app that runs things in the background for the user, which logs thing, obviously. But the user can also explicitly trigger a run, and in that case I’m giving them the log of what happened (more user-friendly than “Did stuff, check the logs for status”).

Right now I’m doing it by having the worker both log _and_ return the same strings logged as a result, but this doesn’t play well with exception handling, and just feels a bit dumb.

> [@Blacksmoke16](#):
>
> What you’re basically describing is [`Log.capture`](https://crystal-lang.org/api/1.15.0/Log.html#capture%28source%3AString%3D%22%2A%22%2Clevel%3ASeverity%3DLog%3A%3ASeverity%3A%3ATrace%2C%2A%2Cbuilder%3DLog.builder%2C%26%29-class-method). However the catch is it’s primarily intended as a spec helper. So would either have to require `spec` code into your production build, or duplicate its source to your own method.

Yeah, I skipped over that, but on closer inspection it doesn’t allow one to just get the log entries. It does serve as a nice example, but I’m a bit iffy about using an undocumented method (`Builder#unbind`)

---

<div class="post-metadata">

**Author:** ![Blacksmoke16](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/blacksmoke16/32/1241_2.png) [@Blacksmoke16](https://forum.crystal-lang.org/u/Blacksmoke16)\
**Post date:** [March 29, 2025, 10:41pm UTC](https://forum.crystal-lang.org/t/dynamic-log-capture/7908/4 "2025-03-29T22:41:15Z")

</div>

> [@Xen](#):
>
> But the user can also explicitly trigger a run, and in that case I’m giving them the log of what happened (more user-friendly than “Did stuff, check the logs for status”).

Assuming the logs are sent to the client after it’s fully done processing (not in real time), one option could be to [303 See Other - HTTP | MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/303) to the log endpoint for that job? This way user would just be taken to the logs after their job finishes and you wont have to have special logic just for this one context?

---

<div class="post-metadata">

**Author:** ![Xen](https://yyz2.discourse-cdn.com/flex036/user_avatar/forum.crystal-lang.org/xen/32/2288_2.png) [@Xen](https://forum.crystal-lang.org/u/Xen)\
**Post date:** [March 30, 2025, 9:38pm UTC](https://forum.crystal-lang.org/t/dynamic-log-capture/7908/5 "2025-03-30T21:38:30Z")

</div>

Yeah, could be done like that.

I’m using HTMX to provide a spinner and then load the result into the existing page, so an endpoint that just provide the new log entries is nicer UX.

An extra log backend turned out to quite simple to implement, so I went that route.
