Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Very WIP: Composable log filters #22

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft

Very WIP: Composable log filters #22

wants to merge 3 commits into from

Conversation

c42f
Copy link
Owner

@c42f c42f commented Oct 24, 2019

It strikes me that the current design of AbstractLogger is very sink-centric; every logger is a sink, and one cannot pass around log filter pipelines as standalone objects. There seems to be a very close analogy with iterators which are very source-centric (one can wrap an iterator-filter around an source iterator, but not naturally compose several iterator filters together).

For iterators, many problems of composition and efficiency are elegantly solved by moving to transducers. Can we benefit from many of the same ideas here?

@tkf @oxinabox — I thought I'd write a quick note here to let you know a slightly different direction I've considered for log routing. It's likely trying to solve the same problems as LoggingExtras, but take a bit of a different tack. The code here is just a quick WIP from a while back and I doubt it takes the ideas from transducers very seriously, so take it FWIW.

Largely, I'd just like to stimulate the discussion a bit :-)

@tkf
Copy link

tkf commented Oct 24, 2019

This is interesting. So, AbstractLogFilter is kind of a mapping logger -> logger′. We can make it literally so by defining (lxf::AbstractLogFilter)(logger::AbstractLogger) = FilteringLogger(logger, lxf). I'd actually generalize it and call it a "logger transform" (lxf) because there can be non-filtering logger transforms (e.g., LoggingExtras.TransformerLogger). Note that logger transform is not log event transform (which is what TransformerLogger does); it's a logger that is transformed.

Somewhat fun fact is, like transducers (which are "reducing function transforms"), would be "flipped"; i.e.,

lxf = LogLevelFilter(Error)  MaxLogFilter()

global_logger(lxf(global_logger()))

first filters based on the log level and then looks up/stores the id in message_limits. ...or more confusing (but equivalent) one-linear is:

global_logger() |> MaxLogFilter() |> LogLevelFilter(Error) |> global_logger
# log event goes right to left

I think one catch might be that DemuxLogger and alike cannot be implemented this way (or more like this formalism does not clarify the implementation/API) since it has to know multiple "loggers." Similar concepts in Transducers.jl are "Zip" (which is probably not the right name JuliaFolds/Transducers.jl#40) and GroupBy. They are unusual in the sense that they are transducers (or more precisely transducer factories) that take transducers and/or reducing functions. But I don't know if it applies to logger transforms, though. Maybe there is some interesting way to workaround this (which would be fun to find out).

@c42f
Copy link
Owner Author

c42f commented Oct 25, 2019

Very interesting, thanks so much for the thoughts. It's great fun to finally have a proper design discussion about this and related issues (here, in LoggingExtras and in Base).

there can be non-filtering logger transforms

True. I think I originally named them this way in an attempt to avoid being overly vague. (I wasn't sure what a filter-or-transformer should be called). But yes we could probably do better. I wonder what the filterlogs function should be called.

Somewhat fun fact is, like transducers (which are "reducing function transforms"), ∘ would be "flipped";

Ok this is interesting. It's a bit of a conundrum because I think the fact that this "seems surprising" and needs to be explained in transducer tutorials is a sign that users would also get confused by it here. In fact I think I originally implemented the opposite here by explicitly implementing and ComposedLogFilter and switching the order 😬. The "logger transform" idea cleans up the implementation a lot.

@tkf
Copy link

tkf commented Oct 26, 2019

It's a bit of a conundrum because I think the fact that this "seems surprising" and needs to be explained in transducer tutorials is a sign that users would also get confused by it here.

It was interesting to realize sink-oriented-ness of the loggers is very close the push-based approach of transducers. I guess reducing function is kind of a sink after all.

I agree that source-oriented API is more intuitive to use. My current thinking is that there is nothing wrong about source-oriented interface (e.g., iterator) as a high-level/surface API. You can always convert sink/source-oriented APIs back and forth as long as there is "adjoint" relationship (this realization led me to this PR JuliaLang/julia#33526 that suggests to convert iterator transforms to transducers just before foldl).

explicitly implementing and ComposedLogFilter and switching the order

Maybe flipping the order is OK, as long as you don't provide the call overload. I think this would mean to expose filters as "message source transforms" which are the adjoint of "logger (= message sink) transforms." Intuitively, it's just something like vector' * matrix' vs matrix * vector. But I guess whether using for non-callable types is recommended or not is not fully decided yet JuliaLang/julia#33573.

@c42f
Copy link
Owner Author

c42f commented Oct 28, 2019

You can always convert sink/source-oriented APIs back and forth as long as there is "adjoint" relationship (this realization led me to this PR JuliaLang/julia#33526 that suggests to convert iterator transforms to transducers just before foldl).

Oh, very interesting. Nice PR!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants