| 383 | #endif |
| 384 | |
| 385 | std::unique_ptr<ILogger> log_logger_stdout() |
| 386 | { |
| 387 | #if !defined(CONF_FAMILY_WINDOWS) |
| 388 | // TODO: Only enable true color when COLORTERM contains "truecolor". |
| 389 | // https://github.com/termstandard/colors/tree/65bf0cd1ece7c15fa33a17c17528b02c99f1ae0b#checking-for-colorterm |
| 390 | const bool Colors = getenv("NO_COLOR") == nullptr && isatty(STDOUT_FILENO); |
| 391 | return std::make_unique<CLoggerAsync>(io_stdout(), Colors, false); |
| 392 | #else |
| 393 | // If we currently have no stdout (console, file, pipe), |
| 394 | // try to attach to the console of the parent process. |
| 395 | if(GetFileType(GetStdHandle(STD_OUTPUT_HANDLE)) == FILE_TYPE_UNKNOWN) |
| 396 | { |
| 397 | AttachConsole(ATTACH_PARENT_PROCESS); |
| 398 | } |
| 399 | |
| 400 | HANDLE pOutput = GetStdHandle(STD_OUTPUT_HANDLE); |
| 401 | if(pOutput == nullptr) |
| 402 | { |
| 403 | // There is no console, file or pipe that we can output to. |
| 404 | return nullptr; |
| 405 | } |
| 406 | dbg_assert(pOutput != INVALID_HANDLE_VALUE, "GetStdHandle failure"); |
| 407 | |
| 408 | const DWORD OutputType = GetFileType(pOutput); |
| 409 | if(OutputType == FILE_TYPE_CHAR) |
| 410 | { |
| 411 | DWORD OldConsoleMode = 0; |
| 412 | if(!GetConsoleMode(pOutput, &OldConsoleMode)) |
| 413 | { |
| 414 | // GetConsoleMode can fail with ERROR_INVALID_HANDLE when redirecting output to "nul", |
| 415 | // which is considered a character file but cannot be used as a console. |
| 416 | dbg_assert(GetLastError() == ERROR_INVALID_HANDLE, "GetConsoleMode failure"); |
| 417 | return nullptr; |
| 418 | } |
| 419 | |
| 420 | const bool Colors = _wgetenv(L"NO_COLOR") == nullptr; |
| 421 | |
| 422 | // Try to enable virtual terminal processing in the Windows console. |
| 423 | // See https://learn.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences |
| 424 | if(!SetConsoleMode(pOutput, OldConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN)) |
| 425 | { |
| 426 | // Try to downgrade mode gracefully when failing to set both. |
| 427 | if(!SetConsoleMode(pOutput, OldConsoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING)) |
| 428 | { |
| 429 | // Fallback to old, slower Windows logging API, when failing to enable virtual terminal processing. |
| 430 | return std::make_unique<CWindowsConsoleLogger>(pOutput, Colors); |
| 431 | } |
| 432 | } |
| 433 | |
| 434 | // Virtual terminal processing was enabled successfully. We can |
| 435 | // use the async logger with ANSI escape codes for colors now. |
| 436 | // We need to set the output encoding to UTF-8 manually and |
| 437 | // convert the HANDLE to an IOHANDLE to use the async logger. |
| 438 | // We assume UTF-8 is available when virtual terminal processing is. |
| 439 | dbg_assert(SetConsoleOutputCP(CP_UTF8) != 0, "SetConsoleOutputCP failure"); |
| 440 | return std::make_unique<CLoggerAsync>(ConvertWindowsHandle(pOutput, _O_TEXT), Colors, false); |
| 441 | } |
| 442 | else if(OutputType == FILE_TYPE_DISK || OutputType == FILE_TYPE_PIPE) |
no test coverage detected