-
Notifications
You must be signed in to change notification settings - Fork 19
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
Excessive overhead in logging methods #69
Comments
The proxy and the adapter have different goals. The new features added change the proxy's goal more: The proxy does useful things on its own. So, it can check But, if the user of the Proxy knows absolutely that they don't care about those extra things and only want to pass things to the Adapter, we could speed (in theory) it up a bit. A new proxy class would work for that. You could write a proxy ( We could also change the I would accept either of these solutions, though the new proxy class would be less of a change. As for a protocol for Adapter objects to talk back to Proxy objects, I'm not sure how that will work. Do enough of the log adapters support knowing such a thing?
There's also this project, which claims to be 1.5x faster than Log-Any: https://metacpan.org/pod/Log::ger. |
What I really would like most of all is a way to return to the efficiency of Log::Any 0.15 API on an opt-in basis for module authors. All of my code is 0.15 compliant anyway, so I'd just change the I have a lot invested in Log::Any so I wouldn't really want to change logging systems, though it looks like perlancar put a lot of effort into Log::ger already, unfortunately. I think it's better not to fragment the logging ecosystem further, if possible. For the protocol, I haven't really investigated how difficult it would be for other logging back-ends to take advantage of this, though my main concern is to fix mine, and leave a path that other back-ends can use if they decide to. Sort of a "hey, if you want to optimize things, here's how to officially do it". You can see the mechanism I use between my Daemontools adapter and its Config objects, and portions of that functionality that could even be moved to this new Proxy so that other modules don't have to re-implement it. I would basically declare the main class passing through to the adapter:
and then write out subclasses with various levels disabled:
and then add an optional hook between the adapter and the proxy:
Presuming the adapter knew when its log level was changing, it would just call "_set_squelch" on each proxy that it knew about, if the proxy supports that method. So, the Adapter opts-in to know about proxies using it, and the Proxy opts-in to the ability to change squelch level, and the producer module opts-in to wanting this new Proxy API. |
Michael, Does this satisfy your goal? Does anyone see a downside in doing this optimization? |
That optimization seems fine to me (especially if it passes the test suite, which is our best guess at "working") and can probably be applied whether or not this solves @nrdvana's problem satisfactorily. As for the proxy keeping track of its adapter's log level (the adapter calling a method on each proxy when the log level changes), I'm still not convinced it can be done well, and I really don't want to have you do it and then have me not like how it's done and have you have wasted your time on it. So, keeping that in mind: Whether reblessing or having the proxy have its own log level before delegating to the adapter, it's the proxy keeping some state that has previously been only in the adapter. This state must also always be in the adapter: The adapter is the source of truth for the consumer's log level for the given category. There's all kinds of fun bugs that result from trying (and failing) to keep multiple copies of the same state in sync (since each adapter instance and each proxy instance will need to know the state). That's assuming that knowing the state at all times is even possible (which is likely only those adapters that directly write logs and so keep track of the log level themselves, not adapters for other logging systems). Having an API for adapters to talk to proxies would also be a new thing: Right now a proxy can read an adapter's state, but the adapter itself can never talk to the proxy, cannot even know a proxy exists (except that it's getting messages from something). There's nothing really preventing it, but no proxy method exists that an adapter would find useful. Adding one would turn a unidirectional message-passing API ( There's nothing wrong with doing these things provided it gains enough to justify the complexity. But, I don't think adding this feature would improve enough over alternatives like just writing a proxy that always does the level checks and doesn't do any processing to the logged strings (so, basically, the 1.040 version of Log::Any::Proxy). Another option is constant-folding away the logging entirely using something like |
Yeah I think that's a good idea. It satisfies my actual production needs, so the ticket can be closed on that. But, it doesn't scratch my itch that makes me want to write a Proxy ;-) so I'd like to continue the conversation if you don't mind. The real problem is that there's a lot of different sensible APIs for the producer that people have proposed, and you can't roll them all into the same class without a whole lot of In order to get there, the only two possibilities I see are to make the Adapter double as a Proxy API like it used to, or to provide a linkage between them so that the adapter and proxy can cooperate. I described the linkage scenario above, but here's a proposal for the first one (which again, I could publish separate on CPAN, but would like some consensus ...)
|
Just to give a little more background on my use cases, any time I write something complex I try to add a lot of ->trace and ->debug methods. Later on, when I've forgotten how it all works and something in production has suddenly broken on me, I just enable trace logging and now I can see the deep behavior. Sometimes I only want to enable trace on a single category. Other times I only want to enable trace on a single web request. (I actually have this in production, where I set a special browser cookie and then all requests from that browser produce debug or trace level of logging in the server logs) If the log level is enabled, then I don't really care how efficient or how many levels it has to hop through to be seen. But if it's disabled, I want to get performance as if it doesn't exist. But I also don't want to have to type a lot when I'm writing it, or restart the process to change log levels. I also like to push the limits of what can be done with perl. I'm the DeLorean digital dashboard guy, and yes I do actually have Log::Any trace messages in the middle of my graphics rendering loop. So maybe I should be using a special-purpose XS logging system for that stuff (though calls into XS aren't really much less overhead than calling |
As a sidenote, the internal check for wantarray still leaves me with a possible problem where I might have written
which after an upgrade of Log::Any could severely impact page-serving times. I don't usually write logging statements that way though, but it is something I have to be careful of now. |
I mean, like I said, I'd totally accept into core a minimalist proxy that did only the bare minimum to forward requests to its adapter (the proxy object as it was in 1.040). Producers are allowed to choose their proxy, and all proxies know how to talk to all adapters. The structured logging feature basically does what you just proposed: If the adapter supports a There's also nothing stopping anyone from using an adapter directly except that using a proxy means being able to reliably support more than one adapter. If there are a set of adapters that all have the same API, then it's possible to use those without a proxy. If it's not easy to use adapters directly, I'd support adding method like But for the main Log::Any workflow, the proxy is a necessary component: Not all adapters support the same API and/or logging concepts (categories, contexts, or even log levels). There's no real way to make the project like it was before the proxy object as it was in 1.040 (before the proxy returned the formatted string that was logged) still have it work like it does. The thinner the proxy is, the more tightly-coupled a proxy is to a specific adapter's API, the less "any" the proxy is. If the proxy doesn't support the alias methods that a producer expects it to (for example, |
IIRC, the original contract for the producer API was "logging methods take exactly one string", "if you call the So for Log4perl, the adapter inherits from ::Base and then iterates ->logging_methods and applies the level mapping where needed, since it doesn't have all the same names on its own loggers. I just checked around (random sampling on cpan) and they all still inherit from base, and of course they all still have to accept a single string argument to the main logging method. So, I think the only thing that prevents the adapter from being used like the original API is the lack of the "*f" methods and aliases on Log::Any::Adapter::Base. Looking back, it appears David Golden removed them ( 8647691, f6a91e9 ) just as cleanup since they weren't needed anymore. It probably wouldn't hurt anything to add them back. |
If you want to use an individual adapter's methods directly, you are free to, but the I again reiterate that there can be a proxy that is exactly the proxy that was in 1.040 and I will accept it into the core Log::Any distribution. A proxy with those features could probably even be made faster with expanded application of level detection. We could even do other things to improve performance on a new proxy that could minimally break the proxy API (such small breaks could be made with a new proxy, since it doesn't require backwards compatibility). Additional performance improvements will be judged based on their effectiveness and added complexity. This project already has quite a lot of indirection (I didn't even know about the Manager class until I tried adding the Null proxy), and I would be wary of adding even more complexity. The project still needs to have maintainers, and using clever tricks that only the most experienced Perl devs will understand is a sure route towards stagnation and rot. |
How about just making get_adapter($category) public? Then other cpan modules could make whatever front-end they wanted and continue using all the log::any routing to the back-end, including the adapter configs that the consumer sets up . Modules that care about performance could just directly log to it, and problem solved. The only hesitation would be if you were planning any future compatibility breaks on the adapter API, or planning to implement category routing in the Proxy rather than via the adapter like is done now. re: the I might submit a minimalist Proxy as well, but I'm actually toying with some different ideas and might not decide exactly what I want right away. Having get_adapter officially available would get me back to identical performance of 0.15 without needing to finish those ideas first. |
Pull request #70 created. @nrdvana regarding your sidenote in #69 (comment) - I think you're mistaken: if you check my example code in https://gist.github.com/mephinet/fbdeff254e00d3fa4a8df6beb90f0064 you can see that |
@mephinet ah nice, perl is smarter than i gave it credit for. In that case there's probably a negligable number of cases where this changed on anyone. (would have to be last statement in a function) |
Making the get_adapter method public allows other logging systems to build on Log::Any's multiplexing capability without reaching into the inner workings.
Making the get_adapter method public allows other logging systems to build on Log::Any's multiplexing capability without reaching into the inner workings.
Back in the pre-0.9 versions of Log::Any, there was no Proxy and methods called on
$log
would go directly to the adapter, who could efficiently squelch them. It meant that things likecould run extremely quickly if the back-end adapter was well optimized and the log level disabled. I did implement such back-ends, like Log::Any::Adapter::TAP and Log::Any::Adapter::Daemontools which use dynamic-re-blessing to replace methods with empty subs, for the best theoretical performance possible with Log::Any.
Then, version 0.9 came out and introduced the 'Proxy', which added a layer. It wasn't that big of a deal though, since it still checked the
is_
method before evaluating any strings, and it was still pretty efficient, though it made my effort less relevant.I see now that the latest version has quite a few new features. But, while they all look useful, it is no longer remotely as efficient as the old versions when dealing with disabled log levels. I can of course write
$log->foo(...) if $log->is_foo
but this is very tedious and not really practical for normal use.At this point, I'm considering writing my own Proxy for Log::Any so I can count on it to remain minimal, and probably a new front-end module to go with it, like
I'm wondering if you would be interested in having it contributed to this package, or if I should release it separately on CPAN.
I would also be interested in establishing a protocol between the proxy and the adapter where the proxy could register itself with the adapter so that the adapter can tell it when logging levels change. Not anything elaborate, just a callback or something and the adapter would be responsible for maintaining the weak references and etc. (Of course, my use case would be to have the adapter re-bless the proxy any time the logging levels changed, thus restoring my original optimization.)
I could also implement that independently on CPAN, since I'd be writing both the proxy and the adapter, but if you're interested in making this a part of Log::Any itself, or just documenting the protocol for other adapters to follow, I'd be happy to collaborate, and submit a patch.
The text was updated successfully, but these errors were encountered: