fix(gateway): 禁用 Windows 控制台 QuickEdit 并改为非阻塞日志,修复终端输出卡住需按回车的问题
This commit is contained in:
parent
b0c24d64f0
commit
accdfeed3b
@ -1,4 +1,5 @@
|
|||||||
pub fn initialize_process_runtime() {
|
pub fn initialize_process_runtime() {
|
||||||
|
crate::platform::disable_console_quick_edit();
|
||||||
let _ = rustls::crypto::ring::default_provider().install_default();
|
let _ = rustls::crypto::ring::default_provider().install_default();
|
||||||
|
|
||||||
// Install a global panic hook so that any panic in a spawned task
|
// Install a global panic hook so that any panic in a spawned task
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use chrono_tz::Tz;
|
use chrono_tz::Tz;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
use tracing_appender::non_blocking::WorkerGuard;
|
||||||
use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
use tracing_appender::rolling::{RollingFileAppender, Rotation};
|
||||||
use tracing_subscriber::{
|
use tracing_subscriber::{
|
||||||
EnvFilter, Layer, fmt, fmt::time::FormatTime, layer::SubscriberExt, util::SubscriberInitExt,
|
EnvFilter, Layer, fmt, fmt::time::FormatTime, layer::SubscriberExt, util::SubscriberInitExt,
|
||||||
@ -8,6 +10,13 @@ use tracing_subscriber::{
|
|||||||
|
|
||||||
use crate::config::LogFormat;
|
use crate::config::LogFormat;
|
||||||
|
|
||||||
|
/// Keeps the non-blocking writer worker threads alive for the process lifetime.
|
||||||
|
///
|
||||||
|
/// Dropping a [`WorkerGuard`] shuts down its worker, after which all log
|
||||||
|
/// writes are silently discarded. `init_logging` runs exactly once per
|
||||||
|
/// process, so the guards are parked in a process-wide static.
|
||||||
|
static NON_BLOCKING_GUARDS: OnceLock<Vec<WorkerGuard>> = OnceLock::new();
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
struct ConfiguredTimestamp {
|
struct ConfiguredTimestamp {
|
||||||
timezone: Tz,
|
timezone: Tz,
|
||||||
@ -74,13 +83,24 @@ pub fn init_logging(timezone: Tz, log_format: LogFormat) {
|
|||||||
// Create file appender with daily rotation
|
// Create file appender with daily rotation
|
||||||
let file_appender = RollingFileAppender::new(Rotation::DAILY, &log_dir, "picobot.log");
|
let file_appender = RollingFileAppender::new(Rotation::DAILY, &log_dir, "picobot.log");
|
||||||
|
|
||||||
|
// Wrap both writers in non-blocking mode: the actual console/file writes
|
||||||
|
// happen on dedicated worker threads. On Windows the console can freeze
|
||||||
|
// (QuickEdit/mark mode, or a selection in Windows Terminal blocks every
|
||||||
|
// console write until Enter is pressed); with synchronous writers that
|
||||||
|
// would stall whichever tokio worker thread emits a log and cascade into
|
||||||
|
// a full gateway freeze. Here only the log worker thread blocks while
|
||||||
|
// the runtime keeps serving requests.
|
||||||
|
let (file_writer, file_guard) = tracing_appender::non_blocking(file_appender);
|
||||||
|
let (console_writer, console_guard) = tracing_appender::non_blocking(std::io::stdout());
|
||||||
|
let _ = NON_BLOCKING_GUARDS.set(vec![file_guard, console_guard]);
|
||||||
|
|
||||||
// Build subscriber with both console and file output
|
// Build subscriber with both console and file output
|
||||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
||||||
|
|
||||||
// 文件层:根据 log_format 选择 text 或 json
|
// 文件层:根据 log_format 选择 text 或 json
|
||||||
let file_layer = match log_format {
|
let file_layer = match log_format {
|
||||||
LogFormat::Json => fmt::layer()
|
LogFormat::Json => fmt::layer()
|
||||||
.with_writer(file_appender)
|
.with_writer(file_writer)
|
||||||
.with_timer(ConfiguredTimestamp { timezone })
|
.with_timer(ConfiguredTimestamp { timezone })
|
||||||
.with_ansi(false)
|
.with_ansi(false)
|
||||||
.with_target(true)
|
.with_target(true)
|
||||||
@ -89,7 +109,7 @@ pub fn init_logging(timezone: Tz, log_format: LogFormat) {
|
|||||||
.json()
|
.json()
|
||||||
.boxed(),
|
.boxed(),
|
||||||
LogFormat::Text => fmt::layer()
|
LogFormat::Text => fmt::layer()
|
||||||
.with_writer(file_appender)
|
.with_writer(file_writer)
|
||||||
.with_timer(ConfiguredTimestamp { timezone })
|
.with_timer(ConfiguredTimestamp { timezone })
|
||||||
.with_ansi(false)
|
.with_ansi(false)
|
||||||
.with_target(true)
|
.with_target(true)
|
||||||
@ -100,6 +120,7 @@ pub fn init_logging(timezone: Tz, log_format: LogFormat) {
|
|||||||
|
|
||||||
// 控制台层:始终文本格式
|
// 控制台层:始终文本格式
|
||||||
let console_layer = fmt::layer()
|
let console_layer = fmt::layer()
|
||||||
|
.with_writer(console_writer)
|
||||||
.with_timer(ConfiguredTimestamp { timezone })
|
.with_timer(ConfiguredTimestamp { timezone })
|
||||||
.with_target(true)
|
.with_target(true)
|
||||||
.with_level(true);
|
.with_level(true);
|
||||||
@ -121,7 +142,13 @@ pub fn init_logging(timezone: Tz, log_format: LogFormat) {
|
|||||||
pub fn init_logging_console_only(timezone: Tz) {
|
pub fn init_logging_console_only(timezone: Tz) {
|
||||||
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
|
||||||
|
|
||||||
|
// See init_logging: non-blocking writes keep a frozen Windows console
|
||||||
|
// from stalling the runtime.
|
||||||
|
let (console_writer, console_guard) = tracing_appender::non_blocking(std::io::stdout());
|
||||||
|
let _ = NON_BLOCKING_GUARDS.set(vec![console_guard]);
|
||||||
|
|
||||||
let console_layer = fmt::layer()
|
let console_layer = fmt::layer()
|
||||||
|
.with_writer(console_writer)
|
||||||
.with_timer(ConfiguredTimestamp { timezone })
|
.with_timer(ConfiguredTimestamp { timezone })
|
||||||
.with_target(true)
|
.with_target(true)
|
||||||
.with_level(true);
|
.with_level(true);
|
||||||
|
|||||||
@ -329,6 +329,99 @@ fn windows_is_process_waiting_on_stdin(pid: u32) -> Option<bool> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Disable Windows console QuickEdit mode.
|
||||||
|
///
|
||||||
|
/// With QuickEdit enabled (the default), clicking the console window enters
|
||||||
|
/// mark/selection mode and silently blocks every console write from this
|
||||||
|
/// process until the user presses Enter — making the gateway look frozen.
|
||||||
|
/// Disabling it keeps keyboard input working while preventing mouse-click
|
||||||
|
/// freezes. No-op on non-Windows platforms or when no console is attached.
|
||||||
|
///
|
||||||
|
/// If stdin is redirected (so `GetStdHandle(STD_INPUT_HANDLE)` is not a
|
||||||
|
/// console handle), falls back to opening `CONIN$` to reach the attached
|
||||||
|
/// console directly. Note that a manual "Edit → Mark" from the title-bar
|
||||||
|
/// menu can still freeze console writes; non-blocking log writers in
|
||||||
|
/// `logging.rs` keep the runtime alive in that case.
|
||||||
|
pub fn disable_console_quick_edit() {
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
windows_disable_console_quick_edit();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "windows")]
|
||||||
|
fn windows_disable_console_quick_edit() {
|
||||||
|
const STD_INPUT_HANDLE: i32 = -10;
|
||||||
|
const ENABLE_QUICK_EDIT_MODE: u32 = 0x0040;
|
||||||
|
const ENABLE_EXTENDED_FLAGS: u32 = 0x0080;
|
||||||
|
const GENERIC_READ: u32 = 0x8000_0000;
|
||||||
|
const GENERIC_WRITE: u32 = 0x4000_0000;
|
||||||
|
const FILE_SHARE_READ: u32 = 0x0000_0001;
|
||||||
|
const FILE_SHARE_WRITE: u32 = 0x0000_0002;
|
||||||
|
const OPEN_EXISTING: u32 = 3;
|
||||||
|
const INVALID_HANDLE_VALUE: isize = -1;
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
unsafe extern "system" {
|
||||||
|
fn GetStdHandle(n_std_handle: i32) -> isize;
|
||||||
|
fn GetConsoleMode(h_console_handle: isize, lp_mode: *mut u32) -> i32;
|
||||||
|
fn SetConsoleMode(h_console_handle: isize, dw_mode: u32) -> i32;
|
||||||
|
fn CreateFileW(
|
||||||
|
lp_file_name: *const u16,
|
||||||
|
dw_desired_access: u32,
|
||||||
|
dw_share_mode: u32,
|
||||||
|
lp_security_attributes: *const std::ffi::c_void,
|
||||||
|
dw_creation_disposition: u32,
|
||||||
|
dw_flags_and_attributes: u32,
|
||||||
|
h_template_file: isize,
|
||||||
|
) -> isize;
|
||||||
|
fn CloseHandle(h_object: isize) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut mode: u32 = 0;
|
||||||
|
let mut opened_conin = false;
|
||||||
|
|
||||||
|
let stdin = GetStdHandle(STD_INPUT_HANDLE);
|
||||||
|
let console =
|
||||||
|
if stdin != 0 && stdin != INVALID_HANDLE_VALUE && GetConsoleMode(stdin, &mut mode) != 0
|
||||||
|
{
|
||||||
|
stdin
|
||||||
|
} else {
|
||||||
|
// stdin is redirected/closed but a console may still be attached
|
||||||
|
// (e.g. output shown in a window started via `start /B`). Open
|
||||||
|
// CONIN$ to reach the console input buffer directly.
|
||||||
|
let name: Vec<u16> = "CONIN$\0".encode_utf16().collect();
|
||||||
|
let conin = CreateFileW(
|
||||||
|
name.as_ptr(),
|
||||||
|
GENERIC_READ | GENERIC_WRITE,
|
||||||
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||||
|
std::ptr::null(),
|
||||||
|
OPEN_EXISTING,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
if conin == INVALID_HANDLE_VALUE || GetConsoleMode(conin, &mut mode) == 0 {
|
||||||
|
if conin != INVALID_HANDLE_VALUE {
|
||||||
|
let _ = CloseHandle(conin);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
opened_conin = true;
|
||||||
|
conin
|
||||||
|
};
|
||||||
|
|
||||||
|
if mode & ENABLE_QUICK_EDIT_MODE != 0 {
|
||||||
|
let new_mode = (mode & !ENABLE_QUICK_EDIT_MODE) | ENABLE_EXTENDED_FLAGS;
|
||||||
|
let _ = SetConsoleMode(console, new_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The mode change is a property of the console itself, so the
|
||||||
|
// CONIN$ handle can be released immediately.
|
||||||
|
if opened_conin {
|
||||||
|
let _ = CloseHandle(console);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the user's home directory.
|
/// Get the user's home directory.
|
||||||
///
|
///
|
||||||
/// Supports environment variable overrides for testing:
|
/// Supports environment variable overrides for testing:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user