std::format instead of snprintf in logger.cpp#4767
std::format instead of snprintf in logger.cpp#4767AlanGriffiths merged 13 commits intocanonical:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates Mir’s core logger timestamp formatting to use C++ std::format/std::chrono instead of snprintf-based char buffers, aligning with the direction in issue #4660.
Changes:
- Replace manual
timespec/strftime/snprintftimestamp construction withstd::chrono+std::format. - Add a fallback timestamp path when time zone formatting via
zoned_time/current_zone()fails. - Build a preformatted
"[timestamp] <severity>"prefix usingstd::format_to_n.
AlanGriffiths
left a comment
There was a problem hiding this comment.
I know this idea originates from one of my comments, but I don't think that should be applied without regard to context.
Specifically, I'm not sure the change is well motivated here. The proposed code is harder to follow and adds complexity to widely used functionality. Is there anything it makes better?
Revert to UTC if current_zone() throws exception.
AlanGriffiths
left a comment
There was a problem hiding this comment.
I'm not sure the change is well motivated here:
-
A significant part of the motivation to move away from
snprintf()is the fragile nature of passingchar*, size_tpairs. But with this proposal we still end up doing the same thing forstd::format_to_n(). -
The proposed code is harder to follow and adds complexity to widely used functionality.
Is there anything it makes better?
In my recent commit I tried to avoid explicit passing the size-limit If we want to move to modern C++ API as reasonably possible, Or, would you suggest to give up on eliminating snprintf |
Yes, we've explored alternatives: None of these appear to be a clear improvement to the existing code.
We seem to have different objectives: I want to improve the code, you want to "move to modern C++ API as reasonably possible". While new APIs and features are, in general, good options they are not always an improvement.
I'm open to being convinced some variant of this is an improvement. But:
|
|
Before coding. While at it. When I tested the try/catch section in "my" format_timestamp(...) How about breaking backward compatibility, and just use: |
robert-ancell
left a comment
There was a problem hiding this comment.
I've no further issues with this PR but I'll leave it to @AlanGriffiths for the final approval as this is using C++ APIs I'm not familiar with.
That would avoid both allocating a
Maybe that points to a design issue with void format_timestamp(...)
try
{
...
}
catch (std::runtime_error const& e)
{
mir::fatal_error_abort("Cannot format timestamp: %s", e.what());
} |
AlanGriffiths
left a comment
There was a problem hiding this comment.
Looks promising. Just needs some tidy-up. Something like:
try
{
std::ostreambuf_iterator<char> iter{out};
auto const now = std::chrono::system_clock::now();
auto const now_us = std::chrono::time_point_cast<std::chrono::microseconds>(now);
iter = std::format_to(iter, "[{:%F %T}]{}{}: {}\n",
std::chrono::zoned_time{std::chrono::current_zone(), now_us},
lut[static_cast<int>(severity)], component, message);
out.flush();
}
catch (std::runtime_error const& e)
{
mir::fatal_error_abort("Cannot format log message: %s", e.what());
}| #include <cstdio> | ||
| #include <ctime> |
There was a problem hiding this comment.
I don't think we need cstdio or ctime anymore
There was a problem hiding this comment.
We still need , since logger.cpp still uses vsnprintf(...)
that is not addressed in this task.
| namespace { | ||
|
|
||
| using OutIter = std::ostreambuf_iterator<char>; | ||
|
|
||
| OutIter format_timestamp(OutIter iter) | ||
| { | ||
| auto now = std::chrono::system_clock::now(); | ||
| auto now_us = std::chrono::time_point_cast<std::chrono::microseconds>(now); | ||
| try { | ||
| auto local = | ||
| std::chrono::zoned_time{std::chrono::current_zone(), now_us}; | ||
| iter = std::format_to(iter, "[{:%F %T}]", local); | ||
| } catch (std::runtime_error const& e) { | ||
| mir::fatal_error_abort("Cannot format timestamp: %s", e.what()); | ||
| } | ||
| return iter; | ||
| } | ||
| } |
There was a problem hiding this comment.
I think this can all be inlined - it is simple enough to be part of ml::format_message()
| iter = format_timestamp(iter); | ||
| format_to(iter, "{}{}: {}\n", | ||
| lut[static_cast<int>(severity)], component, message); |
There was a problem hiding this comment.
After inlining format_timestamp(), the format_to() statements can be combined into a single call.
There was a problem hiding this comment.
single call now.
| try { | ||
| auto local = | ||
| std::chrono::zoned_time{std::chrono::current_zone(), now_us}; | ||
| iter = std::format_to(iter, "[{:%F %T}]", local); | ||
| } catch (std::runtime_error const& e) { | ||
| mir::fatal_error_abort("Cannot format timestamp: %s", e.what()); | ||
| } |
There was a problem hiding this comment.
According to the style guide the braces should be on their own lines.
There was a problem hiding this comment.
In recent commit:
c9461d2 yotam.m inline format_timestamp, single std::format_to call(...)
I tried to follow your suggection.
Two remarks.
- My earlier separation of format_timestamp(...) was following
Robert's suggesion from March 11. - In general, don't we want to localize try/catch block
to the smallest possible code, for which exception may be thrown?
In this case: std::chrono::current_zone().
There was a problem hiding this comment.
- It is simpler now than when Robert suggested separating it
- That depends on how it affects the complexity of the code. We could write:
auto const local = []
{
try
{
return std::chrono::zoned_time{std::chrono::current_zone(), now_us};
}
catch (std::runtime_error const& e)
{
mir::fatal_error_abort("Cannot format log timestamp: %s", e.what());
}
}();But that's an absurd extreme.
|
BTW, how can I run a single failed CI test. For example: the current single failed: |
AlanGriffiths
left a comment
There was a problem hiding this comment.
A little tweaking, but looks good.
Maybe a strategic using namespace std::chrono; would allow us to reduce unnecessary verbosity?
| std::ostreambuf_iterator<char> iter{out}; | ||
| auto now = std::chrono::system_clock::now(); | ||
| auto now_us = | ||
| std::chrono::time_point_cast<std::chrono::microseconds>(now); | ||
| auto local = | ||
| std::chrono::zoned_time{std::chrono::current_zone(), now_us}; | ||
| std::format_to(iter, "[{:%F %T}] {}{}: {}\n", | ||
| local, lut[static_cast<int>(severity)], component, message); | ||
| out.flush(); |
There was a problem hiding this comment.
We don't really need a name for iter:
| std::ostreambuf_iterator<char> iter{out}; | |
| auto now = std::chrono::system_clock::now(); | |
| auto now_us = | |
| std::chrono::time_point_cast<std::chrono::microseconds>(now); | |
| auto local = | |
| std::chrono::zoned_time{std::chrono::current_zone(), now_us}; | |
| std::format_to(iter, "[{:%F %T}] {}{}: {}\n", | |
| local, lut[static_cast<int>(severity)], component, message); | |
| out.flush(); | |
| auto now = std::chrono::system_clock::now(); | |
| auto now_us = | |
| std::chrono::time_point_cast<std::chrono::microseconds>(now); | |
| auto local = | |
| std::chrono::zoned_time{std::chrono::current_zone(), now_us}; | |
| std::format_to(std::ostreambuf_iterator{out}, "[{:%F %T}] {}{}: {}\n", | |
| local, lut[static_cast<int>(severity)], component, message); | |
| out.flush(); |
| } | ||
| catch (std::runtime_error const& e) | ||
| { | ||
| mir::fatal_error_abort("Cannot format message: %s", e.what()); |
There was a problem hiding this comment.
This message is a little too generic: we ought to mention logging so that the reader has some idea where it comes from:
| mir::fatal_error_abort("Cannot format message: %s", e.what()); | |
| mir::fatal_error_abort("Cannot format log message: %s", e.what()); |
| * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| */ | ||
|
|
||
| #include <mir/fatal.h> |
There was a problem hiding this comment.
Project standard is to begin a .cpp file with the corresponding .h (to ensure the .h has no hidden dependencies).
I doubt you have the permissions to re-run CI tasks. In this case it looks like a network timeout, nothing to do with your changes. Team members (like me) could re-run it, but as I've requested changes there's no point. |
AlanGriffiths
left a comment
There was a problem hiding this comment.
I still think we can inline iter, but ok
| auto now = system_clock::now(); | ||
| auto now_us = time_point_cast<microseconds>(now); | ||
| auto local = zoned_time{current_zone(), now_us}; | ||
| std::format_to(std::ostreambuf_iterator<char>(out), |
There was a problem hiding this comment.
It's only six characters, but the compiler can deduce the template parameter.
| std::format_to(std::ostreambuf_iterator<char>(out), | |
| std::format_to(std::ostreambuf_iterator(out), |
AlanGriffiths
left a comment
There was a problem hiding this comment.
The doc check is failing because www.akkadia.org has dropped off the internet. (So non-blocking on this PR)
|
documentation-checks ... failed with: Do I need to do anything to have this PR merged ? |
No, I'm dealing with it |
Head branch was pushed to by a user without write access
Closes nothing
Related: #4660
Related to #4736
What's new?
std::format instead of snprintf in logger.cpp
How to test
Checklist