Skip to content

Upgrade to libfmt 12#30171

Merged
StephanDollberg merged 14 commits into
devfrom
stephan/remove-fmt-deprecated-ostream
Apr 17, 2026
Merged

Upgrade to libfmt 12#30171
StephanDollberg merged 14 commits into
devfrom
stephan/remove-fmt-deprecated-ostream

Conversation

@StephanDollberg
Copy link
Copy Markdown
Member

@StephanDollberg StephanDollberg commented Apr 15, 2026

Upgrade to libfmt 12. Remove no longer supported FMT_DEPRECATED_OSTREAM (so all types need explicit libfmt support now).

Reviewed by multiple rounds of:

  • Opus 4.6 max
  • GPT-5.4 xhigh
  • Gemini 3.1 Pro high
  • Deepseek 3.2 reasoning

Besides mechanical changes from operator<< to format_to there is also some explicit formatting changes from for example using built-in libfmt formatters for std types instead of our custom ones (e.g.: bool changes from 0/1 to false/true, optional goes from {val} to optional(val)).

Backports Required

  • none - not a bug fix
  • none - this is a backport
  • none - issue does not exist in previous branches
  • none - papercut/not impactful enough to backport
  • v26.1.x
  • v25.3.x
  • v25.2.x

Release Notes

  • none

…d operator<<

Add HasFormatToFreeFunction concept for types that provide a free function
format_to(const T&, fmt::iterator) -> fmt::iterator, found via ADL. This
is the counterpart of HasFormatToMethod for types that cannot have member
functions (e.g. enums). Add auto-generated fmt::formatter and operator<<
for both concepts. Add std::unique_ptr<T> formatter.
@StephanDollberg StephanDollberg requested a review from a team as a code owner April 15, 2026 08:41
@StephanDollberg StephanDollberg requested review from BenPope and Copilot and removed request for a team April 15, 2026 08:41
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@StephanDollberg StephanDollberg force-pushed the stephan/remove-fmt-deprecated-ostream branch 3 times, most recently from c898e4b to 9f687b5 Compare April 15, 2026 15:15
@redpanda-data redpanda-data deleted a comment from vbotbuildovich Apr 15, 2026
@redpanda-data redpanda-data deleted a comment from vbotbuildovich Apr 15, 2026
@redpanda-data redpanda-data deleted a comment from vbotbuildovich Apr 15, 2026
@vbotbuildovich
Copy link
Copy Markdown
Collaborator

vbotbuildovich commented Apr 15, 2026

CI test results

test results on build#83185
test_status test_class test_method test_arguments test_kind job_url passed reason test_history
FLAKY(PASS) WriteCachingFailureInjectionE2ETest test_crash_all {"use_transactions": false} integration https://buildkite.com/redpanda/redpanda/builds/83185#019d91c3-a7b9-465a-bf6e-7eae0c13e861 17/21 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0683, p0=0.1529, reject_threshold=0.0100. adj_baseline=0.1913, p1=0.4504, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=WriteCachingFailureInjectionE2ETest&test_method=test_crash_all
FLAKY(PASS) TxAtomicProduceConsumeTest test_basic_tx_consumer_transform_produce {"with_failures": true} integration https://buildkite.com/redpanda/redpanda/builds/83185#019d91c3-a7b8-4970-ac2d-0923fc8ccccd 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0035, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=TxAtomicProduceConsumeTest&test_method=test_basic_tx_consumer_transform_produce
test results on build#83304
test_status test_class test_method test_arguments test_kind job_url passed reason test_history
FLAKY(FAIL) CloudStorageScrubberTest test_scrubber {"cloud_storage_type": 1} integration https://buildkite.com/redpanda/redpanda/builds/83304#019d9b12-df07-40cb-87d5-643da2400b96 9/11 Test FAILS after retries.Significant increase in flaky rate(baseline=0.0000, p0=0.0000, reject_threshold=0.0100) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=CloudStorageScrubberTest&test_method=test_scrubber
FLAKY(PASS) RedpandaNodeOperationsSmokeTest test_node_ops_smoke_test {"cloud_storage_type": 1, "mixed_versions": false} integration https://buildkite.com/redpanda/redpanda/builds/83304#019d9b0e-2417-4bfe-bd38-bec15560cae1 10/11 Test PASSES after retries.No significant increase in flaky rate(baseline=0.0037, p0=1.0000, reject_threshold=0.0100. adj_baseline=0.1000, p1=0.3487, trust_threshold=0.5000) https://redpanda.metabaseapp.com/dashboard/87-tests?tab=142-dt-individual-test-history&test_class=RedpandaNodeOperationsSmokeTest&test_method=test_node_ops_smoke_test

@StephanDollberg
Copy link
Copy Markdown
Member Author

/microbench

@vbotbuildovich
Copy link
Copy Markdown
Collaborator

Performance change detected in https://buildkite.com/redpanda/redpanda/builds/83187#019d9223-c89f-429d-9c8b-e2fa3e73a815:

Performance changes detected in 51 tests
pipeline_rpbench.read_pipeline_bench.propagation_latency: inst -> -9.29pct
pipeline_rpbench.write_pipeline_bench.propagation_latency: inst -> +0.64pct
ssx_bench_rpbench.ss_ss_fmt_1K.join: allocs -> +0.06pct
ssx_bench_rpbench.ss_ss_fmt_1K.join: inst -> -40.59pct
ssx_bench_rpbench.ss_ss_ssx_1K.join: allocs -> +0.10pct
ssx_bench_rpbench.ss_ss_ssx_1K.join: inst -> -49.79pct
ssx_bench_rpbench.std_std_fmt_1K.join: allocs -> +0.10pct
ssx_bench_rpbench.std_std_fmt_1K.join: inst -> -1.87pct
crypto_bench_rpbench.openssl_perf_test.hmac_sha256_1k: inst -> +0.18pct
crypto_bench_rpbench.openssl_perf_test.hmac_sha512_1k: inst -> +0.14pct
wasm_transform_rpbench.WasmBenchTest_BatchSize1_RecordSize1_KiB.IdentityTransform: inst -> -0.11pct
async_algorithm_rpbench.algo_bench.maybe_yield_loop_big: allocs -> +0.03pct
role_store_bench_rpbench.role_store_bench.get_member_roles: inst -> +0.03pct
role_store_bench_rpbench.role_store_bench.get_member_roles_bare_query: inst -> +0.03pct
role_store_bench_rpbench.role_store_bench.get_member_roles_bare_query_mixed: inst -> -0.07pct
role_store_bench_rpbench.role_store_bench.get_member_roles_mixed: inst -> -0.06pct
role_store_bench_rpbench.role_store_bench.range_query_bare_query_mixed: inst -> -0.05pct
role_store_bench_rpbench.role_store_bench.range_query_mixed: inst -> -0.05pct
partition_balancer_rpbench.partition_balancer_planner_fixture.unavailable_nodes: inst -> -0.65pct
record_multiplexer_rpbench.record_multiplexer_bench_fixture.avro_linear_1_field_large: inst -> -0.01pct
record_multiplexer_rpbench.record_multiplexer_bench_fixture.avro_linear_1_field_small: inst -> -0.02pct
record_multiplexer_rpbench.record_multiplexer_bench_fixture.avro_linear_1_field_small_zstd: inst -> -0.01pct
record_multiplexer_rpbench.record_multiplexer_bench_fixture.avro_linear_40_fields_small: inst -> -0.02pct
record_multiplexer_rpbench.record_multiplexer_bench_fixture.avro_linear_40_fields_small_zstd: inst -> -0.01pct
record_multiplexer_rpbench.record_multiplexer_bench_fixture.avro_linear_80_fields_small: inst -> -0.02pct
record_multiplexer_rpbench.record_multiplexer_bench_fixture.avro_linear_80_fields_small_zstd: inst -> -0.01pct
record_multiplexer_rpbench.record_multiplexer_bench_fixture.json_linear_1_field_small: inst -> -0.01pct
record_multiplexer_rpbench.record_multiplexer_bench_fixture.protobuf_linear_1_field_small: inst -> -0.01pct
record_multiplexer_rpbench.record_multiplexer_bench_fixture.protobuf_linear_1_field_small_zstd: inst -> -0.01pct
leader_balancer_bench_rpbench.lb.full_simulation_random: inst -> -1.90pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.CollectProto100k: allocs -> +0.02pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.CollectProto100k: inst -> +0.01pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.CollectProto250k: allocs -> +0.02pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.CollectProto250k: inst -> +0.01pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.E2EOrderedJson100k: inst -> -0.04pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.E2EOrderedProto100k: inst -> -0.12pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.E2EOrderedProto10k: inst -> -0.12pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.E2EUnorderedJson100k: inst -> +0.05pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.E2EUnorderedJson100of100k: allocs -> +0.02pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.E2EUnorderedJson10k: inst -> +0.05pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.E2EUnorderedProto100of100k: allocs -> +0.02pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.E2EUnorderedProto100of100k: inst -> +0.01pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.OrderedExtract100k: inst -> -0.18pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.OrderedExtract10k: inst -> -0.18pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.OrderedExtract250k: inst -> -0.19pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.ToJson100k: inst -> +0.05pct
list_kafka_connections_rpbench.ListKafkaConnectionsTest.UnorderedInsertProto100of100k: inst -> +0.04pct
read_fanout_rpbench.read_fanout_bench.concurrent: inst -> -1.32pct
read_fanout_rpbench.read_fanout_bench.vectorized_noop: inst -> -7.95pct
read_merge_rpbench.read_merge_bench.baseline: inst -> -6.81pct
read_merge_rpbench.read_merge_bench.serial_unique_objects: inst -> -7.12pct

See https://redpandadata.atlassian.net/wiki/x/LQAqLg for docs

Comment thread src/v/base/format_to.h
Comment thread src/v/bytes/bytes.h
Comment thread src/v/transform/rpc/client.cc
Comment thread src/v/storage/segment_reader.cc Outdated
Comment thread src/v/storage/fs_utils.cc
Comment on lines -130 to -133
std::ostream& operator<<(std::ostream& o, const partition_path& p) {
o << ss::format("{}_{}", p.ntp.path(), p.revision_id);
return o;
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this one might be wrong? it looks like itw as replace din the header with make_string() but that has an extra prefix like {}/{}_{}?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes good catch

dotnwat
dotnwat previously approved these changes Apr 16, 2026
Copy link
Copy Markdown
Member

@dotnwat dotnwat left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it all looks ok, and it's hard to review this with super high confidence, but the tests pass which is a good sign.

i flagged a few things that looked like they might actually be problems, but i also would have expected a test failure if they were problematic, still worth taking a closer look.

Comment thread src/v/datalake/translation/translation_probe.h
Comment on lines -284 to 288
"exception thrown while converting AVRO {} schema of type {} to "
"exception thrown while converting AVRO schema of type {} to "
"iceberg - {}",
*node,
node->type(),
static_cast<int>(node->type()),
std::current_exception()));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ahh, mismatched argument count? nice catch

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This actually looks like a regression (node removed). Let me check what's going on here. Could be third party type issues.

Comment thread src/v/container/chunked_vector.h
fmt::format,
"topic manifest version {} is not supported",
handler._version));
handler._version.value_or(-1)));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where did the value_or come from?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a workaround (probably for some failing test) for the builtin optional formatting.

Previously our formatter used {value/nullopt} but the builtin one does optional(value/none).

No strong opinion on this. Happy to just adjust the test also.

Comment on lines +2727 to +2726
return os;
return fmt::format_to(out, "");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeh, ok

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what it was smoking in this file. Fixed to just return out.

Comment thread src/v/model/model.cc
Comment on lines -91 to -98
* We need to use specific string representations of timestamp_type as this
* is related with protocol correctness
*/
switch (ts) {
case timestamp_type::append_time:
return os << "LogAppendTime";
case timestamp_type::create_time:
return os << "CreateTime";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to use specific string representations of timestamp_type as this
is related with protocol correctness

Something to double check?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks fine, not sure I like how it moved all the formatters into different places though.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll move things back.

Comment on lines -85 to -86
case proto_incompatibility_type::field_kind_changed:
return os << "FIELD_KIND_CHANGED";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the capitalization here makes me think these might be used in more than just logging

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks translated fine. Seems to be used in proto_incompatibility.

Comment thread src/v/cluster_link/model/types.h Outdated
};

static constexpr std::string_view to_string_view(filter_pattern_type f) {
static inline fmt::iterator
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably shouldn't be static in header here... (also like all the others in the file... but it was like this before so nbd...)

Comment thread src/v/security/audit/types.h Outdated
Comment on lines +51 to +52
case event_type::num_elements:
return fmt::format_to(out, "invalid");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i can't find the original, but this one looked sus

Copy link
Copy Markdown
Member Author

@StephanDollberg StephanDollberg Apr 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting, this is actually a bug in the current code. num_elements is not listed but there is a default case (so no compiler error).

The translation is hence correct as num_elements will print "invalid" already. Crazy intelligence at work.

I'll fix.

@@ -269,14 +269,14 @@ actor result_to_actor(const security::auth_result& result) {
} else if (result.acl.has_value() || result.resource_pattern.has_value()) {
ss::sstring desc;
if (result.acl.has_value()) {
desc += fmt::format("acl: {}", result.acl.value());
desc += fmt::format("acl: {}", result.acl.value().get());
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.get?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

value() is a reference wrapper so need get(). Don't actually see how this worked before but being more explicit seems fine.

@StephanDollberg StephanDollberg force-pushed the stephan/remove-fmt-deprecated-ostream branch from 9f687b5 to ddf7e9a Compare April 17, 2026 10:17
@vbotbuildovich
Copy link
Copy Markdown
Collaborator

Retry command for Build#83304

please wait until all jobs are finished before running the slash command

/ci-repeat 1
skip-redpanda-build
skip-units
skip-rebase
tests/rptest/tests/cloud_storage_scrubber_test.py::CloudStorageScrubberTest.test_scrubber@{"cloud_storage_type":1}

Add base/external_fmt.h with fmt::formatter specializations for
boost::beast::http::status/verb, boost::system::error_code, and
boost::filesystem::path. Patch redpanda-data/avro fork to add const
to format() methods required by fmt v11.
fmt::streamed(x) formats x via operator<<. But the blanket operator<<
in format_to.h delegates back to fmt for types with format_to, creating
infinite recursion:

  format_to() → streamed(x) → operator<< → fmt::print → format_to()

Add fmt_streamed(), a checked wrapper that static_asserts the argument
does not satisfy HasFormatToMethod / HasFormatToFreeFunction. Types with
format_to are already directly formattable via {} — there is no reason
to go through streamed().

Migrate all fmt::streamed() call sites (except the auto_fmt.h fallback,
which is already guarded by !is_formattable) to fmt_streamed().

Also serves a reminder to remove streamed usages when implementing
format_to (for better perf).
Remove FMT_DEPRECATED_OSTREAM from redpanda_copts.
@StephanDollberg StephanDollberg force-pushed the stephan/remove-fmt-deprecated-ostream branch from ddf7e9a to 79c81fd Compare April 17, 2026 13:05
@StephanDollberg StephanDollberg merged commit 133c860 into dev Apr 17, 2026
19 checks passed
@StephanDollberg StephanDollberg deleted the stephan/remove-fmt-deprecated-ostream branch April 17, 2026 16:39
randomizedcoder added a commit to randomizedcoder/redpanda that referenced this pull request Apr 28, 2026
Picks up the fmt 12 upgrade (PR redpanda-data#30171), liburing 2.14, rules_cc
0.2.17, Go SDK 1.26, and other upstream changes. Adapts the UDS
broker_authn_endpoint formatting to use the new format_to() pattern
required by fmt 12, unwrapping std::optional<broker_authn_method>
explicitly.

Co-Authored-By: Claude Opus 4.6 <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants