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 *_mt loggers)

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::stopwatch provides zero-overhead elapsed time measurement using std::chrono::steady_clock
  • Header-only implementation in 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. 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →