-
Notifications
You must be signed in to change notification settings - Fork 3.5k
Logger docs: recommend using thunks for expensive computations #5655
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
In a mailing list conversation we decided Logger docs should encourage lazily evaluated messages over potentially expensive computations that might not get used. This change aims to do that. [0] https://groups.google.com/d/msg/elixir-lang-core/LNZNyUGTdno/9PpvJ8htCQAJ
option, the argument passed to `Logger` calls is evaluated even if the | ||
level of the call is lower than `:level`. For expensive to compute | ||
messages it's a good idea to wrap them in a zero argument function, so the | ||
computation only occurs when this option demands 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.
The only addition here is the last sentence. I rewrapped it to 80-columns because that's how the rest of the file seemed to be formatted.
I noticed the Writing Documentation page doesn't suggest any column wrapping conventions.
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 let's not wrap here. We don't have a convention of wrapping at a specific column and this block of text doesn't look to wide to me to justify rewrapping it completely.
option, the argument passed to `Logger` calls is evaluated even if the | ||
level of the call is lower than `:level`. For expensive to compute | ||
messages it's a good idea to wrap them in a zero argument function, so the | ||
computation only occurs when this option demands 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 let's not wrap here. We don't have a convention of wrapping at a specific column and this block of text doesn't look to wide to me to justify rewrapping it completely.
too. Note that, unlike what happens with the `:compile_time_purge_level` | ||
option, the argument passed to `Logger` calls is evaluated even if the | ||
level of the call is lower than `:level`. For expensive to compute | ||
messages it's a good idea to wrap them in a zero argument function, so 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.
We could say:
[...] the argument passed to
Logger
calls is evaluated even if the level of the call is lower than:level
. For this reason, messages that are expensive to compute should be wrapped in 0-arity anonymous functions that are evaluated only when the:level
option demands it.
Wdyt?
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.
That's clearer--I'll go with this. 👍
@@ -19,6 +19,27 @@ defmodule Logger do | |||
* Wraps OTP's `error_logger` to prevent it from | |||
overflowing. | |||
|
|||
A common use case is tracking when an important function is called. |
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 don't think we should say this. I don't think "tracking when an important function is called" is a particularly common use case of Logger
(or at least, I think it's the more general "tracking when an event of interest happens in your system").
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.
Good point. How's this?
Logging is useful for tracking when an event of interest happens in
your system. For example, logging incoming synchronous calls in a
GenServer
behaviour can provide valuable insight.
def handle_call(request, from, state) do
Logger.info fn ->
"handle_call/3: #{inspect request} request received " <>
"from #{inspect from} with state #{inspect state}."
end
# ...
end
end | ||
|
||
The `Logger.info/2` macro emits the provided message at the `:info` | ||
level. There are additional macros for other levels. Refer to the `:level` and |
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.
We can omit mentioning :level
and :compile_time_purge_level
IMO because they're really close in the next section, and doing so will bring down the noise a bit here.
`:compile_time_purge_level` configuration options for more details on how | ||
levels work. | ||
|
||
Notice the argument passed to `Logger.info` in the above example is a zero |
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 would put this in the same paragraph as the sentence above ("The Logger.info/2
macro ...").
wrapped in functions, it's not recommended in this case. Calling `"... #{inspect | ||
problem}"` without wrapping it in a zero argument function will hurt | ||
performance, even when then `:info` level is blocked by the configured runtime | ||
logging level. |
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 about this rephrasing:
Although the
Logger
macros accept messages as strings as well as functions, it's recommended to use functions whenever the message is expensive to compute. In the example above, the message will be evaluated (and thus the interpolation inside it) whatever the level is, even if the message will not be actually logged at runtime; the only way of avoiding evaluation of such message is purging the log call at compile-time through the:compile_time_purge_level
option (see below), or using a function that is evaluated to generate the message only if the message needs to be logged according to the runtime level.
I am not sure this is an improvement though, wdyt?
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 like that a lot better. Phrasing what's recommended as a positive statement is clearer. It also concisely draws a connection to :compile_time_purge_level
.
Great job @jmitchell, I left a few comments 💟 |
Thanks for your feedback and suggestions, @whatyouhide! The latest commit incorporates them. |
"from #{inspect from} with state #{inspect state}." | ||
end | ||
# ... | ||
end |
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 would keep this example more abstract (since there are tools to inspect messages and calls in GenServer and other OTP behaviours). What about something very abstract like:
def delete_user(user) do
Logger.info fn ->
"Deleting user from the system: #{inspect(user)}"
end
end
?
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.
Perfect!
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.
Oops, my latest push missed the inspect
part. Fixing now.
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.
Awesome job @jmitchell! 💟
Same to you! It's a lot better thanks to your suggestions. 💖 |
❤️ 💚 💙 💛 💜 |
In a mailing list conversation we decided Logger docs should encourage lazily evaluated messages over potentially expensive computations that might not get used. This change aims to do that.