# How to Use spdlog's Stopwatch for Measuring Elapsed Time in Logging

> Learn how to use spdlog stopwatch to measure elapsed time in your logs. This header-only utility integrates seamlessly with fmt for efficient timing.

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

---

**`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`](https://github.com/gabime/spdlog/blob/main/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](https://github.com/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:

```cpp
// 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:

```cpp
#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:

```cpp
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:

```cpp
#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:

```cpp
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`](https://github.com/gabime/spdlog/blob/main/spdlog/fmt/chrono.h) and use `std::chrono` casts:

```cpp
#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`](https://github.com/gabime/spdlog/blob/main/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 `*_mt` loggers)

The underlying `steady_clock` access is thread-safe per C++ standard guarantees.

## Complete Working Example

From [`example/example.cpp`](https://github.com/gabime/spdlog/blob/main/example/example.cpp) in the [gabime/spdlog](https://github.com/gabime/spdlog) repository:

```cpp
#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`](https://github.com/gabime/spdlog/blob/main/tests/test_stopwatch.cpp) validate these precision behaviors and formatter integration across platforms.

## Summary

- **`spdlog::stopwatch`** provides zero-overhead elapsed time measurement using `std::chrono::steady_clock`
- **Header-only** implementation in [`include/spdlog/stopwatch.h`](https://github.com/gabime/spdlog/blob/main/include/spdlog/stopwatch.h) requires no linking
- **Automatic formatting** via `fmt` integration 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`](https://github.com/gabime/spdlog/blob/main/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`](https://github.com/gabime/spdlog/blob/main/spdlog/fmt/chrono.h) and use `std::chrono::duration_cast` with custom chrono formatting for flexible unit display.