tokio-rs / tracing

Application level tracing for Rust.
https://tracing.rs
MIT License
5.4k stars 709 forks source link

Sifting layer, decide what writer to use on the fly #2877

Closed mateiandrei94 closed 1 month ago

mateiandrei94 commented 7 months ago

Feature Request

Crates

tracing_subscriber

Motivation

I come from the Java world. In Java I used a logging library called Logback. Logback has 2 core notions:

Logback has a SiftingAppender Log4j2 (also a popular java logging library) has a similar RoutingAppender. It's akin to a tracing subscriber layer that creates and caches layers on the fly based on information about the event and it's associated spans and routes the events to the correct cached layer (or if not present yet, creates one on they fly, caches it, and routes the event to the newly created layer).

Considerations

I have read issue #971 Yes if you know in advance what are the possible values you can do this with existing layers and per layer filtering, an example would be a bool key and depending on the value you have 3 different layers (none, true, false). The point of sifting is that you do not know the possible values at compile time, so you need to dynamically create layers at runtime.

Proposal

Implement a "SiftingLayer" which is a tracing subscriber layer. Perhaps it could be placed in the layer module besides the Identity layer and named SiftingLayer or RoutingLayer, the name doesn't really matter.

Use case

Imagine you're not a big corporation, you don't use the ELK Stack, you can't afford a database for logs, what you can, is write a log file. Your application has thousands of users and requests, all going to the same log file. Wouldn't it be nice to have a SiftingLayer that writes to a different file based on information about (for example) the requester ? In this example the application would create a span that would last for the entire "request", this span would trace a filed, for example requester_id. The SiftingLayer would sift based on requester_id, creating a new fmt layer with a file writer which filename could look like format!("{}/{}.log", root_folder, requester_id);. The result would be a folder inside of which you have n log files for each requester_id.

How should the new feature be implemented, and why?

The SiftingLayer should be implemented as a tracing subscriber layer which would take in as parameters:

Lastly the create_layer function should be called by the SiftingLayer only once per tuple of selected keys. What do I mean by that ? imagine i chose a SiftSelector that selects level and a custom field named customer_id. if ever there is a debug event with customer_id=1 the sifting selector would lookup in it's in memory storage of dyn Layer whether there is a Layer for (debug, customer_id=1) if there is one, use it, otherwise if there isn't one call create_layer and pass it the tuple (debug, customer_id=1) then cache the result, further events (or I should say layer "method" calls) with (debug, customer_id=1) should be routed to that existing layer.

Why a Layer ?

Because Layers are composable, the SiftingLayer's job is to sift events based on some data about the span or event and route them to dynamically created layers.

Alternatives

mateiandrei94 commented 1 month ago

I just published my first crate, tracing-config it contains a sifting layer.