How to Use fmtlib for Logging in C++: A Complete Guide
fmtlib provides type-safe, high-performance logging through fmt::print, fmt::println, and fmt::format with compile-time format string checking and zero-allocation formatting.
The fmtlib/fmt library (commonly called fmt) replaces C's printf/fprintf and C++ streams with a modern, thread-safe formatting engine. This guide covers the core APIs, implementation details, and practical patterns for building robust logging systems using fmtlib.
Core Logging APIs
fmt::print and fmt::println
The simplest way to write log messages is through fmt::print and fmt::println. These functions reside in [include/fmt/ostream.h](https://github.com/fmtlib/fmt/blob/main/include/fmt/ostream.h) and [include/fmt/os.h](https://github.com/fmtlib/fmt/blob/main/include/fmt/os.h).
#include <fmt/core.h>
#include <fmt/ostream.h>
int main() {
// Basic stdout logging
fmt::print("Server started on port {}\n", 8080);
// Automatic newline - cleaner syntax
fmt::println("User {} logged in", "alice");
// Logging to stderr for errors
fmt::print(stderr, "Error: {} (code {})\n", "connection lost", -1);
}
fmt::println appends a newline automatically, eliminating common bugs from missing \n characters. The stderr overload ensures error messages separate from standard output.
fmt::format for Buffered Logging
When you need to construct a message before sending it to a destination (file, network, or syslog), use fmt::format from [include/fmt/format.h](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h):
#include <fmt/format.h>
#include <fstream>
void log_to_file(const std::string& level, const std::string& message) {
std::string formatted = fmt::format("[{}] {}", level, message);
std::ofstream logfile("app.log", std::ios::app);
logfile << formatted << std::endl;
}
// Usage
log_to_file("INFO", "All systems nominal");
log_to_file("WARN", "High memory usage detected");
This pattern decouples message formatting from I/O operations, enabling async logging or multiple output destinations.
Printf-Style Compatibility
For codebases migrating from C-style logging, fmt::printf and fmt::fprintf in [include/fmt/printf.h](https://github.com/fmtlib/fmt/blob/main/include/fmt/printf.h) provide familiar syntax with type safety:
#include <fmt/printf.h>
int main() {
// Familiar % syntax, but compile-time checked
fmt::printf("Processed %d items in %.2f seconds\n", 1024, 0.37);
// Direct to file descriptor
FILE* log = fopen("legacy.log", "a");
fmt::fprintf(log, "Event at %s: %s\n", "14:32:01", "backup complete");
fclose(log);
}
Unlike C printf, format string mismatches trigger compile-time errors rather than runtime crashes.
Thread Safety in fmtlib Logging
Per the source in [include/fmt/os.h](https://github.com/fmtlib/fmt/blob/main/include/fmt/os.h), fmt::print variants lock a global mutex only when writing to streams that lack native thread safety. This means:
- Log lines are never interleaved from multiple threads
stdout/stderroperations acquirefmt::detail::mutexinternally- User-provided
FILE*handles receive the same protection
#include <fmt/core.h>
#include <thread>
#include <vector>
void worker(int id) {
for (int i = 0; i < 100; ++i) {
fmt::println("Thread {}: iteration {}", id, i);
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 4; ++i) {
threads.emplace_back(worker, i);
}
for (auto& t : threads) t.join();
// Output lines remain intact, never mixed
}
Custom Type Formatting
You can log user-defined types by specializing fmt::formatter<T>. This extends fmtlib logging to domain-specific objects:
#include <fmt/core.h>
struct Point {
int x, y;
};
template<>
struct fmt::formatter<Point> {
constexpr auto parse(format_parse_context& ctx) { return ctx.begin(); }
auto format(const Point& p, format_context& ctx) const {
return fmt::format_to(ctx.out(), "({}, {})", p.x, p.y);
}
};
int main() {
Point pos{3, 4};
fmt::println("Position: {}", pos); // Output: Position: (3, 4)
}
Place formatter specializations in headers alongside your types for reusable logging.
Performance Characteristics
The fmtlib formatting engine in [include/fmt/core.h](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h) achieves zero-allocation formatting through:
- Single-pass parsing –
fmt::vformatandfmt::detail::vformat_implparse the format string once - Compact argument storage –
fmt::format_argspacks arguments without heap allocation - Direct output – No intermediate
std::stringconstruction unless explicitly requested
This makes fmtlib suitable for high-throughput logging where std::ostringstream overhead is prohibitive.
Building a Complete Logger
Combine these APIs into a reusable logging component:
#include <fmt/core.h>
#include <fmt/ostream.h>
#include <fstream>
#include <mutex>
#include <string>
class Logger {
std::ofstream file_;
std::mutex mutex_;
std::string name_;
public:
explicit Logger(const std::string& filename, const std::string& name = "App")
: file_(filename, std::ios::app), name_(name) {}
template<typename... Args>
void info(fmt::format_string<Args...> fmt, Args&&... args) {
log("INFO", fmt, std::forward<Args>(args)...);
}
template<typename... Args>
void error(fmt::format_string<Args...> fmt, Args&&... args) {
log("ERROR", fmt, std::forward<Args>(args)...);
}
private:
template<typename... Args>
void log(const std::string& level, fmt::format_string<Args...> fmt, Args&&... args) {
std::lock_guard<std::mutex> lock(mutex_);
auto msg = fmt::format(fmt, std::forward<Args>(args)...);
fmt::println(file_, "[{}] [{}] {}",
fmt::format("{:%Y-%m-%d %H:%M:%S}",
std::chrono::system_clock::now()),
level, msg);
file_.flush();
}
};
// Usage
int main() {
Logger logger("app.log", "Server");
logger.info("Listening on port {}", 8080);
logger.error("Failed to connect to {}", "database.example.com");
}
Key Source Files in fmtlib
| File | Purpose |
|---|---|
[include/fmt/core.h](https://github.com/fmtlib/fmt/blob/main/include/fmt/core.h) |
Core engine: vformat, format_args, format_parse_context |
[include/fmt/format.h](https://github.com/fmtlib/fmt/blob/main/include/fmt/format.h) |
fmt::format, fmt::format_to, and formatting utilities |
[include/fmt/ostream.h](https://github.com/fmtlib/fmt/blob/main/include/fmt/ostream.h) |
fmt::print, fmt::println for C++ streams |
[include/fmt/os.h](https://github.com/fmtlib/fmt/blob/main/include/fmt/os.h) |
FILE* output and platform-specific I/O with mutex handling |
[include/fmt/printf.h](https://github.com/fmtlib/fmt/blob/main/include/fmt/printf.h) |
fmt::printf, fmt::fprintf compatibility layer |
src/format.cc |
Implementation of the core formatting engine |
Summary
fmt::printandfmt::printlnprovide immediate stdout/stderr logging with automatic thread safetyfmt::formatenables buffered, multi-destination logging architectures- Compile-time checking prevents format string vulnerabilities common in C-style logging
- Zero-allocation paths in
fmt::detail::vformat_impldeliver superior performance for high-frequency logs - Custom formatters integrate domain types directly into log statements
Frequently Asked Questions
Does fmtlib support asynchronous logging?
fmtlib itself provides synchronous APIs. For async logging, use fmt::format to build messages, then pass them to a thread pool or lock-free queue for background I/O. The zero-allocation formatting keeps producer threads unblocked.
How do I add timestamps to fmtlib log messages?
Use std::chrono integration with format specifiers: fmt::format("{:%Y-%m-%d %H:%M:%S}", std::chrono::system_clock::now()). Wrap this in a logging function for automatic timestamp injection.
Can I use fmtlib with existing logging frameworks like spdlog?
Yes—spdlog itself uses fmtlib as its formatting backend. You can also bridge fmtlib to other frameworks by using fmt::format to construct messages before passing them to framework APIs.
Is fmtlib's print output atomic on all platforms?
Yes. According to the source in include/fmt/os.h, all stream output acquires fmt::detail::mutex when the destination lacks native thread safety. This guarantees line-level atomicity across platforms.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →