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

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 – 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 – 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:

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. It obtains the log directory from PTSettingsHelper::get_root_save_folder_location(), appends LogSettings::runnerLogPath, and invokes:

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. For example, the Launcher module (src/modules/launcher/Microsoft.Launcher/dllmain.cpp) calls:

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

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:

#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.

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 →