task-4660 std::format instead of snprintf(buffer...) in 9 files#4736
task-4660 std::format instead of snprintf(buffer...) in 9 files#4736yotam-medini wants to merge 4 commits intocanonical:mainfrom
Conversation
src/core/logging/logger.cpp
Outdated
| 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::string fmt_now = std::format("{:%F %T}", local); |
There was a problem hiding this comment.
I'm fairly certain this is missing the old 6-digit nsec field?
There was a problem hiding this comment.
now_ft.cpp
yotam@zorani:ym-misc 2503:$ g++ -Wall -g --std=c++20 -o /tmp/now_ft now_ft.cpp
yotam@zorani:ym-misc 2504:$ /tmp/now_ft
fmt_now=2026-03-04 12:01:07.958230
RAOF
left a comment
There was a problem hiding this comment.
This looks reasonable, with the exception of the log timestamp format that I'm not sure about. (I shall check!)
AlanGriffiths
left a comment
There was a problem hiding this comment.
While there are advantages to std::format() and std::print() they are not always appropriate. Which means every change site needs to be examined carefully: Is it noexcept? Is it async-signal-safe? Is it performance critical?
AFAICS there's only one place where this is an issue here, but I would err to the cautious side: not improving things where there is any doubt.
There are a couple of places where the narrow window of changes proposed could be expanded to incorporate improvements to the surrounding code.
src/core/logging/logger.cpp
Outdated
| 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::string fmt_now = std::format("{:%F %T}", local); |
There was a problem hiding this comment.
Rather than creating a std::string via std::format() it would be more efficient and clearer to format directly to out using std::print() (replacing the current streaming operator useage).
There was a problem hiding this comment.
Did most of this, except that std::print does not work with std::ostream
There was a problem hiding this comment.
std::print does not work with std::ostream
It should: https://en.cppreference.com/w/cpp/io/basic_ostream/print.html
I guess it was missing with g++-13:
Formatted output P2093R14 14.1 __cpp_lib_print >= 202207L
A pity you're looking just now: In a couple of months we will be able to use it as we will switch our baseline support to Ubuntu 26.04 (g++-15)
There was a problem hiding this comment.
I missed that. I was looking at:
print
This std::print(ostream, ...) is indeed in C++-23 standard but current g++ does not support it
| std::string path; | ||
| path = std::format(x11_lock_fmt, xdisplay); | ||
| unlink(path.c_str()); | ||
| path = std::format(x11_socket_fmt, xdisplay); | ||
| unlink(path.c_str()); |
There was a problem hiding this comment.
I know it is paranoia: but I feel uncomfortable allocating memory in noexcept code (like a destructor).
("Paranoia" because on any system this code is likely to run on we won't get bad_alloc - memory will be overallocated and using it will kill the processes.)
I know it would mean restructuring the code somewhat, but couldn't we cache the paths in the constructor so that they are only formatted in one place?
For example: introducing a class that formats (and remembers) these paths while providing the Fd would consolidate this logic.
There was a problem hiding this comment.
Use automatic buffer, and format_to_n
| constexpr std::string_view x11_lock_fmt = "/tmp/.X{}-lock"; | ||
| constexpr std::string_view const x11_socket_fmt = "/tmp/.X11-unix/X{}"; | ||
|
|
||
| // TODO this can be written with more modern c++ |
There was a problem hiding this comment.
Can we now remove the TODO?
There was a problem hiding this comment.
@AlanGriffiths - Is there still something left to be addressed ?
There was a problem hiding this comment.
Pull request overview
This PR modernizes string formatting across Mir by replacing fixed-size snprintf(buffer, ...) usage with std::format, aligning with issue #4660 and reducing manual buffer management in multiple components (tests, examples, server logging).
Changes:
- Replace multiple
snprintf()-built paths/messages withstd::format()strings. - Update call sites that require C strings (
printf,popen,open,unlink, etc.) to use.c_str(). - Refactor a few call sites to keep formatted strings alive where a
const char*is stored/passed later.
Reviewed changes
Copilot reviewed 9 out of 9 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/performance-tests/test_glmark2-es2.cpp | Switch test log filenames to std::format, adjust printf() usage, keep host log filename alive via std::string. |
| tests/performance-tests/test_compositor.cpp | Switch server log filename to std::format. |
| tests/mir_test_framework/test_wlcs_display_server.cpp | Switch WAYLAND_DISPLAY value construction to std::format. |
| src/server/report/logging/compositor_report.cpp | Switch compositor report log messages to std::format. |
| src/server/frontend_xwayland/xwayland_spawner.cpp | Switch X11 lock/socket path construction to std::format / format_to_n. |
| src/core/logging/logger.cpp | Switch timestamp formatting to chrono + std::format. |
| src/common/logging/input_timestamp.cpp | Switch input timestamp formatting to std::format. |
| examples/mir_demo_server/server_example_test_client.cpp | Switch client-status messages to std::format. |
| examples/mir_demo_server/server_example.cpp | Switch apport command construction to std::format. |
| constexpr std::string_view x11_lock_fmt = "/tmp/.X{}-lock"; | ||
| constexpr std::string_view const x11_socket_fmt = "/tmp/.X11-unix/X{}"; | ||
|
|
||
| // TODO this can be written with more modern c++ | ||
| int create_lockfile(int xdisplay) | ||
| { | ||
| char lockfile[256]; | ||
| auto const lockfile = std::format(x11_lock_fmt, xdisplay); | ||
|
|
||
| snprintf(lockfile, sizeof lockfile, x11_lock_fmt, xdisplay); | ||
|
|
||
| mir::Fd const fd{open(lockfile, O_WRONLY | O_CLOEXEC | O_CREAT | O_EXCL, 0444)}; | ||
| mir::Fd const fd{open(lockfile.c_str(), O_WRONLY | O_CLOEXEC | O_CREAT | O_EXCL, 0444)}; |
There was a problem hiding this comment.
std::format()'s first parameter is a std::format_string<...> (intended for string literals) and generally won't accept a std::string_view variable like x11_lock_fmt. This is likely a compile error with standard library implementations. Use a std::format_string<int> constant (constructed from the literal) or inline the string literal in the std::format() call.
There was a problem hiding this comment.
While it is probably better to use std::format_string<> constants over std::string_view I don't think this will fail to compile.
There was a problem hiding this comment.
Each of these format strings are is used twice.
Once with: int xdisplay, once with int xdisplay.
So we cannot have one flexible definition like std::format_string<int | int&>'
| auto eoln = std::format_to_n(addr.sun_path + 1, sizeof(addr.sun_path) - 2, | ||
| x11_socket_fmt, xdisplay).out; | ||
| *eoln = '\0'; | ||
| path_size = eoln - (addr.sun_path + 1); | ||
| create_socket(result, &addr, path_size); | ||
|
|
||
| path_size = snprintf(addr.sun_path, sizeof(addr.sun_path), x11_socket_fmt, xdisplay); | ||
| eoln = std::format_to_n(addr.sun_path, sizeof(addr.sun_path) - 1, | ||
| x11_socket_fmt, xdisplay).out; | ||
| *eoln = '\0'; | ||
| path_size = eoln - addr.sun_path; |
There was a problem hiding this comment.
std::format_to_n() also expects a compile-time checked std::format_string<...>; passing x11_socket_fmt (a std::string_view) is likely to fail to compile. Consider making x11_socket_fmt a std::format_string<int> (or inline the literal) so format_to_n() can be instantiated.
src/core/logging/logger.cpp
Outdated
| 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::string fmt_now = std::format("{:%F %T}", local); |
There was a problem hiding this comment.
This timestamp construction can throw (e.g., std::chrono::current_zone()/zoned_time when the tzdb is missing/corrupt, or std::format on allocation failure). Since format_message() is used by the logging path without exception handling, an exception here can terminate the process. Please make this path non-throwing (e.g., wrap in try/catch and fall back to a simpler timestamp such as UTC, or revert to the previous clock_gettime/strftime approach).
| 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::string fmt_now = std::format("{:%F %T}", local); | |
| std::string fmt_now; | |
| try | |
| { | |
| 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}; | |
| fmt_now = std::format("{:%F %T}", local); | |
| } | |
| catch (...) | |
| { | |
| auto now = std::chrono::system_clock::now(); | |
| auto now_time_t = std::chrono::system_clock::to_time_t(now); | |
| char buf[32]{}; | |
| std::tm tm{}; | |
| if (gmtime_r(&now_time_t, &tm) != nullptr && | |
| std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm)) | |
| { | |
| fmt_now = buf; | |
| } | |
| else | |
| { | |
| fmt_now = "unknown-time"; | |
| } | |
| } |
There was a problem hiding this comment.
done as suggested.
| char buffer[sizeof format]; | ||
| snprintf(buffer, sizeof buffer, format, signal); | ||
| ml::log(ml::Severity::informational, buffer, component); | ||
| auto const msg = std::format(" Client terminated by signal {}", signal); |
There was a problem hiding this comment.
The log message has a leading space (" Client..."), which looks unintentional and changes the output compared to the previous message text. Remove the leading whitespace.
| auto const msg = std::format(" Client terminated by signal {}", signal); | |
| auto const msg = std::format("Client terminated by signal {}", signal); |
src/core/logging/logger.cpp
Outdated
| char buf[32]{}; | ||
| std::tm tm{}; | ||
| if (gmtime_r(&now_time_t, &tm) != nullptr && | ||
| std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm)) |
There was a problem hiding this comment.
microseconds added in recent commit
| when_ns, milliseconds, sub_milliseconds, sign_suffix); | ||
|
|
||
| return std::string(str); | ||
| return fmt_now; |
There was a problem hiding this comment.
No need for fmt_now - just return directly.
| "/tmp/%s_%s_host.log", | ||
| s_host_output_filename = std::format("/tmp/{}_{}_host.log", | ||
| test_info->test_case_name(), test_info->name()); | ||
| host_output_filename = s_host_output_filename.c_str(); |
There was a problem hiding this comment.
This doesn't seem necessary anymore - can't we just use s_host_output_filename.c_str() in all cases below?
| << std::endl; | ||
|
|
||
| out << std::format("[{}] {}{}: {}\n", | ||
| fmt_now, lut[static_cast<int>(severity)], component, message); |
There was a problem hiding this comment.
By removing the use of std::endl we are no longer flushing the output stream - is that intentional?
robert-ancell
left a comment
There was a problem hiding this comment.
There's a mix of simple and more complicated changes in here. As these changes are independent of each other I suggest you split this into a number of PRs. Some changes can then land quickly and others that require more thought can take longer.
|
@robert-ancell @RAOF @AlanGriffiths This is my first PR for MIR. I would like to proceed efficiently |
|
The easiest way to split is to grab a simple case like in |
|
Followed Robert Ancell advice, I converted this PR to draft for now. |
Closes nothing. <!-- Mention the issue this closes if applicable --> Related: #4660 <!-- List any PRs, issues, and links that might benefit reviewers build context around your work --> Related to PR #4736 <!-- List the major changes of this PR --> std::format instead of snprintf in server_example.cpp ## How to test <!-- List how reviewers can poke at the addition in this PR --> ## Checklist - [ ] Tests added and pass - [ ] Adequate documentation added - [ ] (optional) Added Screenshots or videos
Closes nothing. <!-- Mention the issue this closes if applicable --> Related: #4660 <!-- List any PRs, issues, and links that might benefit reviewers build context around your work --> ## What's new? Related to PR #4736 <!-- List the major changes of this PR --> std::format instead of snprintf in TestWlcsDisplayServer(...) ## How to test <!-- List how reviewers can poke at the addition in this PR --> ## Checklist - [ ] Tests added and pass - [ ] Adequate documentation added - [ ] (optional) Added Screenshots or videos
Closes nothing. <!-- Mention the issue this closes if applicable --> Related: #4660 <!-- List any PRs, issues, and links that might benefit reviewers build context around your work --> ## What's new? Related to PR #4736 std::format instead of snprintf in test_compositor.cpp <!-- List the major changes of this PR --> ## How to test <!-- List how reviewers can poke at the addition in this PR --> ## Checklist - [ ] Tests added and pass - [ ] Adequate documentation added - [ ] (optional) Added Screenshots or videos
Closes nothing. <!-- Mention the issue this closes if applicable --> Related: #4660 <!-- List any PRs, issues, and links that might benefit reviewers build context around your work --> ## What's new? Related to PR #4736 std::format instead of snprintf in test_glmark2-es2.cpp <!-- List the major changes of this PR --> ## How to test <!-- List how reviewers can poke at the addition in this PR --> ## Checklist - [ ] Tests added and pass - [ ] Adequate documentation added - [ ] (optional) Added Screenshots or videos
) Closes nothing <!-- Mention the issue this closes if applicable --> Related: to PR #4736 std::format instead of snprintf in server_example_test_client.cpp <!-- List any PRs, issues, and links that might benefit reviewers build context around your work --> ## What's new? <!-- List the major changes of this PR --> ## How to test <!-- List how reviewers can poke at the addition in this PR --> ## Checklist - [ ] Tests added and pass - [ ] Adequate documentation added - [ ] (optional) Added Screenshots or videos
Closes nothing <!-- Mention the issue this closes if applicable --> Related: #4660 <!-- List any PRs, issues, and links that might benefit reviewers build context around your work --> Related to PR #4736 ## What's new? std::format instead of snprintf in input_timestamp.cpp <!-- List the major changes of this PR --> ## How to test <!-- List how reviewers can poke at the addition in this PR --> ## Checklist - [ ] Tests added and pass - [ ] Adequate documentation added - [ ] (optional) Added Screenshots or videos
Closes nothing <!-- Mention the issue this closes if applicable --> Related: #4660 <!-- List any PRs, issues, and links that might benefit reviewers build context around your work --> Related to #4736 ## What's new? std::format instead of snprintf in compositor_report.cpp <!-- List the major changes of this PR --> ## How to test <!-- List how reviewers can poke at the addition in this PR --> ## Checklist - [ ] Tests added and pass - [ ] Adequate documentation added - [ ] (optional) Added Screenshots or videos
It may close #4660.
#4660
What's new?
This commit replaces all calls of snprintf(buffer...) with automatic buffer with std::format.
Use of vsnprintf(...) is not changed. Moving from vsnprinf to std::format requires design decisions.
How to test
Under build directory, I ran:
ctest --exclude-regex wlcs --output-on-failure --stop-on-failure
And it ran successfully. Last message lines:
Checklist