-
Notifications
You must be signed in to change notification settings - Fork 260
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
Add an optional logger param #664
base: master
Are you sure you want to change the base?
Conversation
Signed-off-by: tison <wander4096@gmail.com>
Signed-off-by: tison <wander4096@gmail.com>
155a28d
to
2851f2b
Compare
Signed-off-by: tison <wander4096@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a good target to test our new logger
param.
Previously, the global logger state is shared with all the macros
tests and causes the result unstable.
Signed-off-by: tison <wander4096@gmail.com>
I'm not convinced this is a common enough use case that it has to be in facade, couldn't it be implemented in the logging implementation? |
spdlog-rs implements this, with their own So, the logging implementation must implement the same functionality by bypassing our macro, which loses the value of a facade. If you have an idea how to implement and use it with our abstraction and a custom impl crate, I'm glad to know. |
I my crate I used the |
Thanks for your information! I'm considering the difference between target and a logger field also. In the description:
Switching targets still use the same (global & singleton) logger instance, while setting a new logger instance can avoid meta logging cycle - #645. |
The global logging implementation can be set up to call different loggers depending on |
Personally, I think being able to target a different logger entirely using an argument is useful in a few scenarios:
From a maintenance perspective it also keeps us a bit honest in not being able to assume that everything is shared by a single global logger. If @Thomasdezeeuw isn't convinced it's worth adding though then we'll leave it for the time being. |
I'm not convinced the additional complexity is worth the use case that is already possible via the log implementation. @tisonkun do you have any argument why it should be done this way, and not via the log implementation? |
It's included in the issue description of #645:
That said, passing a logger instance is determinate for what is installed in the log implementation (pipeline), while target is a loose contract (convention). If you have a single log instance with a Kafka appender installed, you may first filter out logs with target "kafka". But what if a user application uses the same target for their actions over kafka? Then you need to agree on a reserved target and every user should learn about it. Instead, a pass-in logger can be an explicit contract that the certain logger is used by kafka client internally (if a client library adopts this) and thus you can fine tune the manner and be sure that it would be used in that scenario.
This is also a good point that when you have different log requirements for different components, instead of making a global instance and dispatching the application logic (rather than barely appending to multiple destinations), it would be easier to keep the logger instance separated. Finally, if we support logging with a pass-in instance, it's possible to use a global instance everywhere. But the reverse direction is not true. slog's macros always has a logger param, while there is slog-global to use a global instance when it's satisfied. |
This PR reminds me of
Sounds like you actually want something like this? |
@NobodyXu No. This is a thread-local default, which can be even worse than a global default to tune with. The |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, I'm the author of spdlog-rs
, and I'd like to thank @tisonkun for mentioning my crate and nominating to merge this feature into log
crate, and @KodrAus for supporting this feature in the issue. I'll give my two cents here.
The introduction of the logger
parameter (and replaceable global logger) in spdlog-rs
(as well as the same concept in C++ spdlog
) was primarily to address the inconvenience of singleton - can only be set once at startup.
Imagine you have a huge project with many components. Some components just print to standard streams, some components need to report logs to a specific network endpoint (maybe even this endpoint needs to be dynamically obtained and unknown at startup). In the singleton model, the initial setup needs to prepare all components for their needs, rather than being managed by the components themselves. And since the discriminator target
is just a string, ambiguities may be uncommon but are by no means absent especially when match rules like starts_with
are involved.
The addition of the logger
parameter is not killing the singleton pattern, which is still the easiest way for lightweight projects, but larger projects can benefit from this harmless addition.
Merge master branch and resolve conflicts. May we have another round to review this patch? |
// tests to ensure logs with a level beneath 'max_level' are filtered out | ||
log::set_max_level(filter); | ||
error!(""); | ||
error!(logger: logger, ""); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we'd need to document this behavior. We have a hierarchy of level controls; at compile time, at runtime, and whatever the logger itself applies. We should be clear that these are features of the macros, so apply equally regardless of the logger you plug in.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please elaborate a bit what to be documented and where I should add the docs?
It seems that you'd like to emphasize that these macros can be compiled to nop if the related flags (e.g., max_level_error
, release_max_level_error
, etc.) enabled.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me @tisonkun 👍
@Thomasdezeeuw are you happy if we move forwards with this feature? I think pushing back on additions is a good position that keeps us from accumulating bloat, but feel like this one specifically is a reasonable enhancement aligned with the existing macro capabilities.
I still think the use case can be solved without this change, hence I don't think it's worth the additional complexity to be honest. However, if we go forward with it have we looked at the code size changes? Work has been done in the past to reduce the compiler code size, is that affected at all? /cc @EFanZh in case you're still interested in this |
Some of them can, but some can be done more easily without needing to write dispatching logic based on targets. Being able to plug a logger in to the macro calls would let you use local loggers for contextual logging, without needing a centrally synchronized place to store them. That could also potentially be solved with the key-value support though if we let you plug in an additional Maybe we can think of it more like |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I won't block this (any further) if code size isn't a concern.
I do think we should add some more documentation for this change as I don't see any. I would at least expect the logger
argument to be documented and stating that by default it will use the global logger.
Thanks for your suggestions. Then please leave me a few days so that I can pick up this patch :D |
Signed-off-by: tison <wander4096@gmail.com>
80e04d9
to
cde2585
Compare
src/macros.rs
Outdated
|
||
// log!(target: "my_target", Level::Info, "a log event") | ||
(target: $target:expr, $lvl:expr, $($arg:tt)+) => ({ | ||
$crate::log!(logger: $crate::logger(), target: $target, $lvl, $($arg)+) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default logger should be a zero sized type, otherwise caller needs pay the cost of passing the logger value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh. I get your point now. Let me update it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please check if this is what you mean 2bb47d6
@@ -53,31 +53,31 @@ fn main() { | |||
last_log_location: Mutex::new(None), | |||
}); | |||
let a = me.clone(); | |||
set_boxed_logger(Box::new(Logger(me))).unwrap(); | |||
let logger = Logger(me); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if we call a log macro with logger
param here? The macro would expand a let logger = $logger;
, which seems to move rather than borrow the logger variable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC it would be debug!(logger: &logger, ...)
then. That is, use ref from the user size.
One of the rationales is @EFanZh's comment about "binary size", see:
Signed-off-by: tison <wander4096@gmail.com>
impl Log for GlobalLogger { | ||
fn enabled(&self, metadata: &Metadata) -> bool { | ||
logger().enabled(metadata) | ||
} | ||
|
||
fn log(&self, record: &Record) { | ||
logger().log(record) | ||
} | ||
|
||
fn flush(&self) { | ||
logger().flush() | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shall we #[inline]
here? In my application development conventions, I trust the compiler to do the inlining based on its analysis.
struct NopLogger; | ||
|
||
impl Log for NopLogger { | ||
fn enabled(&self, _: &Metadata) -> bool { | ||
false | ||
} | ||
fn log(&self, _: &Record) {} | ||
fn flush(&self) {} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This inspires me to consider whether we should export the NopLogger ..
Another thought: with the current implementation, user specified logger will always be passed by reference, which might not be ideal for binary size. Do we want to support the case where user can specify a zero-sized logger to reduce binary size? |
What does it mean? We already accept Does 2bb47d6 implements what you mean for passing a zero-sized logger? If not, why. |
@tisonkun: Oh, I thought the implementation still passes reference for user specified loggers. Now the implementation passes logger argument as is, then I have no further questions. |
@EFanZh OK. Good to know the clarification. Then just leave me some time to figure out how to improve the docs :D |
This closes #645
@KodrAus I noticed that we previously introduced
target
for similar usage, but switching to a different logger is different.This patch is for preview. I'm not quite sure how we pass the customized logger. The current implementation seems good. I think the
log
crate itself isn't suitable to hold a globalname -> logger
registry: that would be too heavy and too specific.