How to Log to the Visual Studio Debug Output Window Using spdlog

Use spdlog::sinks::msvc_sink to route formatted log messages through the Win32 OutputDebugString API, causing them to appear instantly in the Visual Studio Output window while debugging.

The gabime/spdlog library provides a dedicated sink for Windows developers who need to log to the Visual Studio debug output window. By leveraging the msvc_sink class defined in include/spdlog/sinks/msvc_sink.h, you can direct log traffic to the debugger without blocking or affecting console or file output. This integration works by calling OutputDebugStringA or OutputDebugStringW inside the sink’s protected sink_it_ method, ensuring that every log entry appears in the Debug pane of the Output window.

How the MSVC Sink Works

The msvc_sink implementation inherits from spdlog::sinks::base_sink<Mutex> located in include/spdlog/sinks/base_sink.h. When you invoke a logging method, the logger formats the message and delegates to the sink’s overridden sink_it_ function. This function converts the formatted payload to a system string and invokes OutputDebugString, which Visual Studio captures in real time.

Key characteristics include:

  • Thread-safe variants: msvc_sink_mt uses a mutex for multi-threaded environments, while msvc_sink_st offers a single-threaded version.
  • Convenient aliases: The header also exposes windebug_sink_mt and windebug_sink_st as typedefs for the same functionality.

Creating a Standalone Debug Logger

To send all output exclusively to the Visual Studio debugger, instantiate the sink and attach it to a logger. You can then promote this logger to the global default using spdlog::set_default_logger so that free functions like spdlog::debug() route automatically.

#include <spdlog/spdlog.h>
#include <spdlog/sinks/msvc_sink.h>

int main()
{
    // Create the multi-threaded MSVC sink
    auto debug_sink = std::make_shared<spdlog::sinks::msvc_sink_mt>();

    // Optional: customize the output pattern
    debug_sink->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v");

    // Construct a dedicated logger
    auto logger = std::make_shared<spdlog::logger>("vsdebug", debug_sink);

    // Make this the default for spdlog::info(), spdlog::debug(), etc.
    spdlog::set_default_logger(logger);
    spdlog::set_level(spdlog::level::debug);

    // Messages appear in Output → Debug
    spdlog::debug("Application initialized with value {}", 42);
}

Combining the Debug Sink with Other Outputs

In practice, you often want to see logs both in the console and in the Visual Studio debugger. Because spdlog::logger accepts multiple sinks, you can combine msvc_sink_mt with stdout_color_sink_mt or file sinks defined in include/spdlog/sinks/stdout_color_sinks.h.

#include <spdlog/spdlog.h>
#include <spdlog/sinks/stdout_color_sinks.h>
#include <spdlog/sinks/msvc_sink.h>

int main()
{
    auto console_sink = std::make_shared<spdlog::sinks::stdout_color_sink_mt>();
    auto debug_sink = std::make_shared<spdlog::sinks::msvc_sink_mt>();

    // Initialize logger with both sinks
    std::vector<spdlog::sink_ptr> sinks{console_sink, debug_sink};
    auto logger = std::make_shared<spdlog::logger>("dual", sinks.begin(), sinks.end());

    logger->set_level(spdlog::level::trace);
    logger->info("This line prints to console and appears in VS Debug output");
}

Key Implementation Details

Thread Safety and Mutex Policies

The sink template specializes base_sink with either std::mutex (for msvc_sink_mt) or a null mutex (for msvc_sink_st). Choose mt variants when sharing the logger across threads; use st only when logging from a single thread to minimize overhead.

OutputDebugString Behavior

Internally, sink_it_ calls OutputDebugStringA for narrow character builds or OutputDebugStringW for wide character builds. When Visual Studio runs without a debugger attached, these calls become no-ops with negligible cost, making the sink safe for release builds.

Summary

  • Include spdlog/sinks/msvc_sink.h to access msvc_sink_mt and msvc_sink_st.
  • Instantiate the sink with std::make_shared and attach it to a spdlog::logger.
  • Call OutputDebugString automatically via the sink_it_ override when you log messages.
  • Combine with console or file sinks by passing multiple sinks to the logger constructor.
  • Set the logger as default using spdlog::set_default_logger to enable global logging functions.

Frequently Asked Questions

Does msvc_sink work when Visual Studio is not attached?

Yes. The Win32 OutputDebugString API silently discards messages when no debugger is present, so including the sink in release builds is safe and adds minimal overhead.

Can I filter which severity levels appear in the debug output window?

Yes. Set the sink’s level independently using debug_sink->set_level(spdlog::level::debug) or control levels at the logger level. Messages below the configured threshold are filtered before reaching OutputDebugString.

Is there a performance penalty when logging to the debug window?

While OutputDebugString is not as fast as in-memory logging, it is sufficiently fast for development debug builds. For hot paths in production code, consider compiling the sink out or using spdlog::drop("logger_name") to disable it.

Can I use msvc_sink with the async logger?

Yes. The sink is compatible with spdlog::async_logger. Create the sink normally and pass it to the asynchronous factory along with an async queue, as you would with any other sink type.

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 →