How to Use spdlog's Stopwatch for Measuring Elapsed Time in Logging
spdlog::stopwatch is a header-only utility that measures elapsed time using std::chrono::steady_clock and formats directly into log messages via built-in fmt integration.
The spdlog logging library provides a lightweight, zero-overhead stopwatch for timing code execution without external dependencies. Located in include/spdlog/stopwatch.h, this class is designed specifically for logging workflows where you need to track and report elapsed time with minimal boilerplate.
Core Architecture and Design
Class Implementation
The spdlog::stopwatch class is intentionally minimal—approximately 70 lines of header-only code. It stores a single start_tp_ member representing the construction timestamp and provides three public methods:
| Method | Return Type | Purpose |
|---|---|---|
elapsed() |
std::chrono::duration<double> |
Seconds elapsed as floating-point duration |
elapsed_ms() |
std::chrono::milliseconds |
Whole-millisecond duration |
reset() |
void |
Restart measurement from current time |
The implementation uses std::chrono::steady_clock, guaranteeing monotonic timing unaffected by system clock adjustments. As implemented in gabime/spdlog, this avoids the clock-skew problems that plague wall-clock timing in production systems.
Automatic Formatting Integration
A custom fmt formatter specialization enables direct stopwatch insertion into log messages:
// In stopwatch.h - formatter forwards elapsed().count() as double
template<>
struct fmt::formatter<spdlog::stopwatch> : fmt::formatter<double> {
template<typename FormatContext>
auto format(const spdlog::stopwatch& sw, FormatContext& ctx) {
return fmt::formatter<double>::format(sw.elapsed().count(), ctx);
}
};
This design allows standard fmt format specifiers ({:.3}, {:8.2}) to control precision and width without manual conversion.
Basic Usage Examples
Logging Elapsed Seconds
Include the stopwatch header and pass directly to any spdlog function:
#include "spdlog/stopwatch.h"
#include "spdlog/spdlog.h"
int main() {
spdlog::stopwatch sw; // construction starts timing
performSomeWork();
spdlog::info("Operation completed in {} seconds", sw);
}
Output:
[2026-08-06 12:34:56.789] [info] Operation completed in 0.004321 seconds
Controlling Output Precision
Leverage fmt specifiers for custom formatting:
spdlog::debug("Elapsed {:.6} seconds", sw); // 6 decimal places
spdlog::debug("Elapsed {:8.3} seconds", sw); // width 8, 3 decimals
spdlog::info("Duration: {:.2f}s", sw); // fixed notation, 2 decimals
Accessing Raw Milliseconds
For whole-millisecond precision without floating-point conversion:
#include "spdlog/stopwatch.h"
spdlog::stopwatch sw;
auto ms = sw.elapsed_ms(); // returns std::chrono::milliseconds
spdlog::info("Elapsed {} ms", ms.count());
Resetting for Interval Timing
Use reset() to measure multiple phases without creating new objects:
spdlog::stopwatch sw;
initializeSystem();
spdlog::info("Init took {}", sw);
sw.reset(); // restart from now
processData();
spdlog::info("Processing took {}", sw);
sw.reset();
cleanupResources();
spdlog::info("Cleanup took {}", sw);
Advanced: Custom Duration Formatting
For units beyond seconds or milliseconds, include spdlog/fmt/chrono.h and use std::chrono casts:
#include "spdlog/stopwatch.h"
#include "spdlog/fmt/chrono.h" // enables chrono formatting
#include "spdlog/spdlog.h"
using namespace std::chrono;
int main() {
spdlog::stopwatch sw;
runBenchmark();
// Cast to microseconds for high-resolution reporting
auto us = duration_cast<microseconds>(sw.elapsed());
spdlog::info("Elapsed: {}", us); // outputs: 4321µs
// Or format as HH:MM:SS.ms
spdlog::info("Time: {:%H:%M:%S}", sw.elapsed());
}
This approach keeps stopwatch.h lightweight for common cases while supporting sophisticated timing reports when needed.
Thread Safety Considerations
The spdlog::stopwatch class holds no mutable shared state—only a std::chrono::steady_clock::time_point member. This design ensures:
- Safe to copy across threads
- Safe to use from multiple threads simultaneously (each instance independent)
- Safe to pass to thread-safe logger sinks (default
*_mtloggers)
The underlying steady_clock access is thread-safe per C++ standard guarantees.
Complete Working Example
From example/example.cpp in the gabime/spdlog repository:
#include "spdlog/stopwatch.h"
#include "spdlog/spdlog.h"
#include "spdlog/sinks/stdout_color_sinks.h"
#include <thread>
void stopwatch_example() {
auto logger = spdlog::stdout_color_mt("stopwatch");
spdlog::stopwatch sw;
std::this_thread::sleep_for(std::chrono::milliseconds(123));
logger->info("Elapsed {}", sw);
logger->info("Elapsed {:.3}", sw); // 3 decimal places
logger->info("Elapsed {:.6}", sw); // 6 decimal places
}
Output:
[12:34:56.789] [info] Elapsed 0.123456
[12:34:56.789] [info] Elapsed 0.123
[12:34:56.789] [info] Elapsed 0.123456
Tests in tests/test_stopwatch.cpp validate these precision behaviors and formatter integration across platforms.
Summary
spdlog::stopwatchprovides zero-overhead elapsed time measurement usingstd::chrono::steady_clock- Header-only implementation in
include/spdlog/stopwatch.hrequires no linking - Automatic formatting via
fmtintegration enables direct insertion into log messages with standard specifiers - Three core methods:
elapsed()(seconds),elapsed_ms()(milliseconds),reset()(restart) - Thread-safe by design with no shared mutable state
Frequently Asked Questions
What clock does spdlog::stopwatch use?
spdlog::stopwatch uses std::chrono::steady_clock, as implemented in include/spdlog/stopwatch.h. This guarantees monotonic timing that never decreases, making it suitable for measuring durations even when the system wall clock changes.
How do I format stopwatch output with specific precision?
Use standard fmt format specifiers: {:.3} for three decimal places, {:8.2} for width 8 with two decimals. The formatter<spdlog::stopwatch> specialization forwards to the double formatter, so all numeric formatting options work directly.
Can I use spdlog::stopwatch in multi-threaded code?
Yes. Each spdlog::stopwatch instance maintains only its own start_tp_ member with no shared state. You can safely create, copy, and use stopwatch objects across threads. Ensure your logger sink is thread-safe (use *_mt variants like stdout_color_mt).
How do I measure milliseconds instead of fractional seconds?
Call elapsed_ms() to get std::chrono::milliseconds, or include spdlog/fmt/chrono.h and use std::chrono::duration_cast with custom chrono formatting for flexible unit display.
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 →