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

> Learn to log to the Visual Studio debug output window with spdlog using the msvc_sink. Route messages instantly for efficient debugging and enhance your development workflow.

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

---

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

```cpp
#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`](https://github.com/gabime/spdlog/blob/main/include/spdlog/sinks/stdout_color_sinks.h).

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