-
-
Notifications
You must be signed in to change notification settings - Fork 2.4k
log/alt_experimental_level: concept demonstration #368
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
Conversation
Demonstrate an alternate approach to the "level" package offered via the "github.com/go-kit/kit/log/experimental_level" import path, allowing use of either a global log event level filtering threshold or local filtering via instances of the level.Leveler interface. Features not accommodated here for now: - Returning an error from log.Log when squelching an event - Returning an error from log.Log for events that lack a level - Squelching events that lack a level - Concurrent-safe adjustment of the global level threshold (Note the "alternately" comments in file global.go.) - Documentation Benchmarks demonstrate both run time and allocation reduction for the disallowed/squelching cases compared to the "experimental_level" alternate: name old time/op new time/op delta NopBaseline-4 737ns ± 0% 197ns ± 0% -73.27% NopDisallowedLevel-4 805ns ± 0% 189ns ± 0% -76.52% NopAllowedLevel-4 800ns ± 0% 754ns ± 0% -5.75% JSONBaseline-4 4.00µs ± 0% 4.08µs ± 0% +1.95% JSONDisallowedLevel-4 778ns ± 0% 193ns ± 0% -75.19% JSONAllowedLevel-4 4.24µs ± 0% 4.00µs ± 0% -5.57% LogfmtBaseline-4 1.50µs ± 0% 1.59µs ± 0% +6.28% LogfmtDisallowedLevel-4 795ns ± 0% 186ns ± 0% -76.60% LogfmtAllowedLevel-4 1.63µs ± 0% 1.59µs ± 0% -2.27% name old alloc/op new alloc/op delta NopBaseline-4 288B ± 0% 64B ± 0% -77.78% NopDisallowedLevel-4 288B ± 0% 64B ± 0% -77.78% NopAllowedLevel-4 288B ± 0% 288B ± 0% +0.00% JSONBaseline-4 968B ± 0% 968B ± 0% +0.00% JSONDisallowedLevel-4 288B ± 0% 64B ± 0% -77.78% JSONAllowedLevel-4 968B ± 0% 968B ± 0% +0.00% LogfmtBaseline-4 288B ± 0% 288B ± 0% +0.00% LogfmtDisallowedLevel-4 288B ± 0% 64B ± 0% -77.78% LogfmtAllowedLevel-4 288B ± 0% 288B ± 0% +0.00% name old allocs/op new allocs/op delta NopBaseline-4 9.00 ± 0% 3.00 ± 0% -66.67% NopDisallowedLevel-4 9.00 ± 0% 3.00 ± 0% -66.67% NopAllowedLevel-4 9.00 ± 0% 9.00 ± 0% +0.00% JSONBaseline-4 22.0 ± 0% 22.0 ± 0% +0.00% JSONDisallowedLevel-4 9.00 ± 0% 3.00 ± 0% -66.67% JSONAllowedLevel-4 22.0 ± 0% 22.0 ± 0% +0.00% LogfmtBaseline-4 9.00 ± 0% 9.00 ± 0% +0.00% LogfmtDisallowedLevel-4 9.00 ± 0% 3.00 ± 0% -66.67% LogfmtAllowedLevel-4 9.00 ± 0% 9.00 ± 0% +0.00%
Since these values are only used in one place each, don't bother declaring manifest constants for them.
|
Also worth considering: functions to check whether a given level is currently allowed. This allows callers to decide whether to avoid running some costly operation to produce log values if the event won't be recorded anyway. These would look something like the following, both on the
That's eight more identifiers that we'd export from the package. Another idea—though easy enough for callers to implement—is a narrowing
or, turning it around,
|
|
@seh Thanks for submitting your ideas. This way of handling leveled logging is certainly an interesting alternative to other approaches we've tried. I have similar concerns with this variation as with the experiment in #357. Specifically the use of global state, although convenient, seems to almost always end with regret in the long run for widely reusable components. |
|
@ChrisHines Using the global functions here is optional, much like the @peterbourgon, I'd like to hear your thoughts as well. Is it worth proceeding with documentation? |
|
Unfortunately, I am pretty categorically opposed to package-global state. |
|
What's the chief difference or advantage to this approach over the existing one in your view, @seh? Performance, friendlier API, both, something else? |
|
The main advantage is CPU efficiency, avoiding inspecting log records to figure out whether their level is allowed to pass through, and avoiding collecting of records (their key/value pairs) that will get filtered due to their level. Providing the global functions turned out to be a distraction here. I have (yet) another approach that I'd like to try, which will fall somewhere in between what I proposed here (minus the global functions) and your "experimental_level" package. I was out on vacation for a week, but now that I'm back I'll endeavor to flesh out the idea this week. |
|
Looking forward to it. |
|
@seh So I'd like to cut a Go kit 1.0.0 in the next few days, and I'd like for the current package experimental_level to be promoted to package level. I've been using it in anger on a couple of projects, and I'm happy with the experience. But if you have something cooking, I'd like to give that proper consideration first. Any update? Also, @ChrisHines, would you have any objection to the above? |
|
@peterbourgon I don't object to promoting experimental_level. I do suggest that you create an issue or milestone for doing a 1.0.0. It would be good to give contributors a chance to coordinate with you and prompt people like me to submit any work they have been procrastinating. |
|
Please see a related proposal in #444, which removes the global state and embeds the level threshold in a |
As a foil to #357, demonstrate an alternate approach to the "level" package offered via the "github.com/go-kit/kit/log/experimental_package" import path, allowing use of either a global log event level filtering threshold or local filtering via instances of the
level.Levelerinterface.Features not accommodated here for now:
log.Logwhen squelching an eventlog.Logfor events that lack a level(This feature doesn't make much sense with this approach.)
(This feature doesn't make much sense with this approach.)
(Note the "alternately" comments in file global.go for one proposed solution.)
(We could adapt much of this from the sibling package.)
See the commit message for then benchstat comparison.
This is a proof of concept. If others like this approach, we could refine whether to handle global configuration changes more safely (whether via
atomic.Value, or guarding it with async.RWMutex), and whether to accommodate returning an error fromlog.Log, which wouldn't be too hard, whether via a singular sentinelerrorinstance or with a caller-suppliederrorinstance captured at configuration time.Accommodating adjustment of the global level threshold safely imposes a performance tax on every subsequent event-recording call. I'd prefer to just document that callers should only change the threshold in an
initfunction.