# Setting Up spdlog Logging in C++ PowerToys Modules: A Complete Implementation Guide

> Implement spdlog logging in C++ PowerToys modules. Learn how to initialize and use the central logger for efficient debugging and error tracking throughout your PowerToys development.

- Repository: [Microsoft/PowerToys](https://github.com/microsoft/PowerToys)
- Tags: how-to-guide
- Published: 2026-02-25

---

**PowerToys centralizes C++ logging through a static wrapper around spdlog located in `src/common/logger`, which modules initialize via `Logger::init()` and call using static methods like `Logger::info()` without managing logger instances.**

Setting up spdlog logging within C++ PowerToys modules requires understanding the centralized logging architecture in the microsoft/PowerToys repository. The project provides a thin abstraction layer over spdlog that handles sink configuration, log level management, and thread-safe output across all C++ components.

## PowerToys spdlog Architecture Overview

The logging system separates **initialization** (handled once per process) from the **static logging API** that can be called from any module. This design prevents multiple logger instances while allowing universal access.

The core implementation resides in two files:

- **[`src/common/logger/logger.h`](https://github.com/microsoft/PowerToys/blob/main/src/common/logger/logger.h)** – Declares the static `Logger` class with a private `std::shared_ptr<spdlog::logger>` and templated convenience methods (`trace`, `debug`, `info`, `warn`, `error`, `critical`).
- **[`src/common/logger/logger.cpp`](https://github.com/microsoft/PowerToys/blob/main/src/common/logger/logger.cpp)** – Implements spdlog integration, including sink setup, log level mapping from JSON settings, and fallback handling.

All logging calls resolve to the underlying spdlog instance, ensuring consistent formatting and output behavior across the application.

## Initializing spdlog in Your Module

Every executable or DLL that wishes to log must call `Logger::init` early in its startup sequence. The initialization signature used by most modules is:

```cpp
static void init(std::string loggerName,
                 std::wstring logFilePath,
                 std::wstring_view logSettingsPath);

```

The parameters serve distinct purposes:

- **`loggerName`** – Logical name that appears in the log prefix (e.g., `[p-P]`).
- **`logFilePath`** – Full path to the daily log file (e.g., `%LOCALAPPDATA%/PowerToys/PowerToys-Runner.log`).
- **`logSettingsPath`** – Path to the JSON file that stores the user-selected log level.

### Runner Process Setup

The main PowerToys process initializes logging in [`src/runner/main.cpp`](https://github.com/microsoft/PowerToys/blob/main/src/runner/main.cpp). It obtains the log directory from `PTSettingsHelper::get_root_save_folder_location()`, appends `LogSettings::runnerLogPath`, and invokes:

```cpp
Logger::init(LogSettings::runnerLoggerName,
             logFilePath.wstring(),
             PTSettingsHelper::get_log_settings_file_location());

```

### DLL Module Setup

Each module follows the same pattern in its [`dllmain.cpp`](https://github.com/microsoft/PowerToys/blob/main/dllmain.cpp). For example, the Launcher module ([`src/modules/launcher/Microsoft.Launcher/dllmain.cpp`](https://github.com/microsoft/PowerToys/blob/main/src/modules/launcher/Microsoft.Launcher/dllmain.cpp)) calls:

```cpp
Logger::init(LogSettings::launcherLoggerName,
             logFilePath.wstring(),
             PTSettingsHelper::get_log_settings_file_location());

```

## Configuring Log Levels and Sinks

The PowerToys logger reads user-defined settings from a JSON configuration file via `get_log_settings`. A static `unordered_map<std::wstring, level_enum>` maps string values (`"trace"`, `"debug"`, `"info"`, `"warn"`, `"err"`, `"critical"`, `"off"`) to spdlog’s `level_enum`.

If the user-defined value is missing, the system falls back to `LogSettings::defaultLogLevel`.

### Sink Configuration

The implementation in [`src/common/logger/logger.cpp`](https://github.com/microsoft/PowerToys/blob/main/src/common/logger/logger.cpp) configures two primary sinks:

1. **Daily File Sink** – Rotates log files each day, writing to the path specified during initialization.
2. **MSVC Sink** – When a debugger is attached, this sink writes to the Visual Studio output window for real-time debugging.

If logger creation fails, the system falls back to a **null logger** and displays a one-time message box to prevent crashes while maintaining API compatibility.

## Writing Log Messages

Because the logger methods are static templated wrappers, they can be called without an object instance. The format strings follow spdlog’s `{}` placeholder syntax, supporting any type that implements `operator<<` or provides a formatter.

Example usage from [`src/runner/main.cpp`](https://github.com/microsoft/PowerToys/blob/main/src/runner/main.cpp):

```cpp
Logger::info("Running powertoys with cmd args: {}", cmdLine);
Logger::error("Failed to get or save OOBE state with an exception: {}", e.what());

```

Available severity methods include:

- `Logger::trace()`
- `Logger::debug()`
- `Logger::info()`
- `Logger::warn()`
- `Logger::error()`
- `Logger::critical()`

## Advanced spdlog Patterns

### Custom Sink Initialization

For command-line utilities requiring bespoke output configurations, `Logger::init(std::vector<spdlog::sink_ptr>)` creates a logger from supplied sinks. Both **StylesReportTool** and **MonitorReportTool** use this overload in [`tools/StylesReportTool/StylesReportTool.cpp`](https://github.com/microsoft/PowerToys/blob/main/tools/StylesReportTool/StylesReportTool.cpp):

```cpp
#include "common/logger/logger.h"
#include <spdlog/sinks/stdout_color_sinks.h>

int main()
{
    auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    console_sink->set_pattern("[%Y-%m-%d %H:%M:%S] [%l] %v");
    Logger::init({ console_sink });          // creates a logger with only the console sink
    Logger::info("Tool started");
}

```

### Thread Safety and Flushing

All spdlog loggers are thread-safe by default. The PowerToys wrapper forwards `flush()` to the underlying spdlog instance, and `Logger::init` configures `flush_on` with the current log level, ensuring immediate write operations for messages meeting the severity threshold.

## Summary

- PowerToys provides a centralized spdlog wrapper in `src/common/logger` that separates initialization from static API calls.
- Modules initialize logging via `Logger::init()` with a logger name, file path, and settings path, typically called in `DllMain` or `main()`.
- Log levels are controlled via JSON settings mapped to spdlog’s `level_enum`, with automatic fallback to default levels.
- The implementation uses daily file sinks and optional MSVC debug sinks, falling back to null loggers on failure.
- Static methods like `Logger::info()` and `Logger::error()` support spdlog format strings and are thread-safe.

## Frequently Asked Questions

### How do I add logging to a new PowerToys C++ module?

Include the header `#include "common/logger/logger.h"` in your source files, add a logger name constant to `LogSettings`, and call `Logger::init()` during your module's entry point (typically `DllMain` for DLLs or `main()` for executables). Use the static methods like `Logger::info()` to write messages.

### What log levels are available in PowerToys spdlog configuration?

The system supports `"trace"`, `"debug"`, `"info"`, `"warn"`, `"err"`, `"critical"`, and `"off"`, mapped to spdlog's `level_enum`. These values are configured in the JSON settings file read at initialization, with a default level defined in `LogSettings::defaultLogLevel` if the setting is missing.

### Where are PowerToys log files stored and how do they rotate?

Log files are stored in the path returned by `PTSettingsHelper::get_root_save_folder_location()` (typically `%LOCALAPPDATA%/PowerToys`), with filenames defined in `LogSettings` constants like `runnerLogPath`. The logger uses spdlog's daily file sink, which automatically rotates files each day based on the system date.

### How does PowerToys handle logging errors or missing configuration files?

If logger creation fails during `Logger::init()`, the system falls back to a null logger to prevent application crashes while maintaining API compatibility. It also displays a one-time message box to alert the user of the logging failure. If the JSON settings file is missing or contains invalid log levels, the system uses `LogSettings::defaultLogLevel` as a fallback.