# Using spdlog Stopwatch for Timing Measurements: A Complete Guide

> Master spdlog stopwatch for precise timing measurements. This guide shows how to leverage fmt integration and std::chrono for accurate log message timing.

- Repository: [Gabi Melman/spdlog](https://github.com/gabime/spdlog)
- Tags: how-to-guide
- Published: 2026-07-25

---

**TLDR:** spdlog provides a lightweight, header-only `spdlog::stopwatch` class that captures timing via `std::chrono::steady_clock` and formats elapsed seconds directly into log messages through built-in fmt integration.

The gabime/spdlog repository includes a purpose-built stopwatch utility designed for zero-overhead timing instrumentation. Using spdlog stopwatch for timing measurements requires no additional linking, as the entire implementation resides in [`include/spdlog/stopwatch.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/stopwatch.h). This tool integrates seamlessly with the library's formatting engine, allowing you to stream elapsed time directly into log records without manual string conversion.

## How spdlog::stopwatch Works Internally

### Monotonic Clock Selection

According to the source code in [`include/spdlog/stopwatch.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/stopwatch.h) (line 32), the stopwatch uses `std::chrono::steady_clock` as its underlying time source. This clock is monotonic, meaning it always moves forward and is not subject to system-time adjustments or daylight saving changes, guaranteeing reliable interval measurement even if the system clock is modified during execution.

### State Management and Construction

The class stores a single private member `start_tp_` representing the start time point. The constructor (lines 36-38) captures the current time via `std::chrono::steady_clock::now()`, establishing the baseline for all subsequent elapsed calculations.

### Elapsed Time Accessors

The stopwatch provides two primary methods for retrieving elapsed duration:

- **`elapsed()`** (lines 39-41): Returns a `std::chrono::duration<double>` representing the elapsed time in seconds as a floating-point value.
- **`elapsed_ms()`** (lines 43-45): Returns a `std::chrono::milliseconds` value via `duration_cast`, useful when you need whole-millisecond precision without floating-point overhead.

### Reset Capability

To begin a new measurement without destroying the object, the `reset()` method (line 47) re-captures the current time into `start_tp_`, effectively zeroing the timer while preserving the object instance.

## Automatic Formatter Integration

The stopwatch leverages a template specialization of `fmt::formatter` (or `std::formatter` when `SPDLOG_USE_STD_FORMAT` is defined) to enable direct insertion into log macros. This formatter specialization (lines 60-66) forwards the stopwatch's elapsed seconds to the underlying double formatter. Consequently, you can pass the stopwatch object directly to any spdlog function:

```cpp
spdlog::stopwatch sw;
// ... perform work ...
spdlog::info("Operation completed in {}", sw);

```

The output automatically renders as a floating-point number of seconds (e.g., `0.005116733`), respecting any format specifiers you provide (such as `{:.6}` for six-digit precision).

## Practical Code Examples

### Basic Usage: Logging Elapsed Seconds

Instantiate the stopwatch at the scope entry point and log it when work completes. The object formats itself as seconds by default:

```cpp
#include <spdlog/spdlog.h>
#include <spdlog/stopwatch.h>

void process_data() {
    spdlog::stopwatch sw;  // starts timing immediately
    perform_computation();
    spdlog::debug("Processing took {} seconds", sw);
}

```

*Example output:* `Processing took 0.005116733 seconds`

### Custom Precision Formatting

Control the floating-point precision using standard fmt format specifiers:

```cpp
spdlog::stopwatch sw;
std::this_thread::sleep_for(std::chrono::milliseconds(12));
spdlog::info("Elapsed: {:.6} seconds", sw);   // limits to six digits

```

*Example output:* `Elapsed: 0.012345 seconds`

### Measuring Milliseconds and Other Units

For discrete units like milliseconds, use `elapsed_ms()` or cast the duration manually. Include `<spdlog/fmt/chrono.h>` to enable chrono-aware formatting helpers:

```cpp
#include <spdlog/fmt/chrono.h>   // enables fmt::chrono formatting

spdlog::stopwatch sw;
std::this_thread::sleep_for(std::chrono::milliseconds(150));
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(sw.elapsed());
spdlog::info("Elapsed: {} ms", ms);

```

*Example output:* `Elapsed: 150 ms`

The comment block at lines 21-28 of [`stopwatch.h`](https://github.com/gabime/spdlog/blob/main/stopwatch.h) explicitly documents this pattern for users requiring unit flexibility beyond the default seconds formatting.

### Resetting for Multiple Intervals

Measure distinct phases of execution by resetting the stopwatch between operations:

```cpp
spdlog::stopwatch sw;

phase_one();
spdlog::info("Phase 1 duration: {}", sw);

sw.reset();  // restart timer from now (line 47)

phase_two();
spdlog::info("Phase 2 duration: {}", sw);

```

### Integration with Custom Loggers

The stopwatch works with any spdlog sink or logger instance, not just the default registry:

```cpp
auto sink = std::make_shared<spdlog::sinks::test_sink_st>();
spdlog::logger logger("perf_logger", sink);
logger.set_pattern("%v");   // output value only

spdlog::stopwatch sw;
std::this_thread::sleep_for(std::chrono::milliseconds(500));
logger.info("{}", sw);      // formats automatically via formatter specialization

```

## Advanced Formatting with spdlog/fmt/chrono.h

While the built-in formatter outputs seconds, the header `<spdlog/fmt/chrono.h>` provides comprehensive chrono formatting support. This allows you to format raw durations using time-specific specifiers (e.g., `%S` for seconds, `%M` for minutes) when you need human-readable timestamps rather than raw floating-point intervals.

## Summary

- **`spdlog::stopwatch`** is a header-only utility in [`include/spdlog/stopwatch.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/stopwatch.h) requiring no additional compilation steps.
- It uses **`std::chrono::steady_clock`** (line 32) to guarantee monotonic, system-adjustment-proof timing.
- The class provides **`elapsed()`** for floating-point seconds and **`elapsed_ms()`** for integer milliseconds (lines 39-45).
- A specialized **`fmt::formatter`** (lines 60-66) enables direct insertion into log messages without manual conversion.
- The **`reset()`** method (line 47) allows object reuse across multiple measurement phases.
- For custom unit display, combine **`duration_cast`** with the chrono formatting helpers in `<spdlog/fmt/chrono.h>`.

## Frequently Asked Questions

### What clock source does spdlog::stopwatch use?

The stopwatch uses `std::chrono::steady_clock` as implemented on line 32 of [`include/spdlog/stopwatch.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/stopwatch.h). This clock is monotonic and not subject to system time adjustments, ensuring that timing measurements remain accurate even if the system clock is modified during program execution.

### How do I reset the stopwatch to measure a new interval?

Call the **`reset()`** method (line 47), which re-captures the current time into the internal `start_tp_` member. This allows you to reuse the same stopwatch instance for consecutive timing measurements without reconstruction overhead.

### Can I display the stopwatch value in milliseconds instead of seconds?

Yes. While the default formatter outputs seconds, you can call **`elapsed_ms()`** (lines 43-45) to retrieve a `std::chrono::milliseconds` value, or manually cast the result of `elapsed()` using `std::chrono::duration_cast`. Include `<spdlog/fmt/chrono.h>` to format these duration types directly in log messages.

### Is spdlog::stopwatch thread-safe?

The stopwatch itself is a lightweight value type storing only a single time point. Reading elapsed time via `elapsed()` or `elapsed_ms()` is safe from multiple threads concurrently, but calling `reset()` (which mutates the internal state) requires external synchronization if the same instance is shared across threads. For per-thread timing, simply create a local stopwatch instance on the stack.