mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
refactor: 拆分 gateway 单体为独立 crate,新增 systemd 部署方案
将 gateway 内部的 model-fetch、provider-transport、scheduler-core、 usage-runtime、video-tasks-core 模块提取为独立 crate;重构 gateway 内部模块结构(state/router/cache/data/query 等);移除大量遗留模块 文件;新增 systemd 二进制部署骨架及相关文档;更新前端 usage 相关 API 和组件。
This commit is contained in:
@@ -5,8 +5,8 @@ use std::sync::{Arc, RwLock};
|
||||
use std::time::Duration;
|
||||
|
||||
use aether_runtime::{
|
||||
init_reloadable_tracing, wait_for_shutdown_signal, ConcurrencyGate, DistributedConcurrencyGate,
|
||||
LogFormat, RedisDistributedConcurrencyConfig,
|
||||
init_reloadable_service_tracing, wait_for_shutdown_signal, ConcurrencyGate,
|
||||
DistributedConcurrencyGate, RedisDistributedConcurrencyConfig,
|
||||
};
|
||||
use arc_swap::ArcSwap;
|
||||
use tokio::sync::{watch, Mutex};
|
||||
@@ -338,14 +338,13 @@ async fn retry_failed_registrations(
|
||||
}
|
||||
|
||||
fn init_tracing(config: &Config) {
|
||||
let format = if config.log_json {
|
||||
LogFormat::Json
|
||||
} else {
|
||||
LogFormat::Pretty
|
||||
};
|
||||
|
||||
let reloader = init_reloadable_tracing(&config.log_level, format)
|
||||
.expect("proxy tracing should initialize");
|
||||
let reloader = init_reloadable_service_tracing(
|
||||
&config.log_level,
|
||||
config
|
||||
.service_runtime_config()
|
||||
.expect("proxy service runtime config should be valid"),
|
||||
)
|
||||
.expect("proxy tracing should initialize");
|
||||
runtime::set_log_reloader(reloader);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use std::path::Path;
|
||||
|
||||
use aether_runtime::{
|
||||
FileLoggingConfig, LogDestination, LogFormat, LogRotation, ServiceRuntimeConfig,
|
||||
};
|
||||
use clap::Parser;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
@@ -33,6 +36,40 @@ const DELEGATE_TO_UPSTREAM: &[(&str, &str)] = &[
|
||||
("delegate_tcp_nodelay", "upstream_tcp_nodelay"),
|
||||
];
|
||||
|
||||
#[derive(clap::ValueEnum, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ProxyLogDestinationArg {
|
||||
Stdout,
|
||||
File,
|
||||
Both,
|
||||
}
|
||||
|
||||
impl From<ProxyLogDestinationArg> for LogDestination {
|
||||
fn from(value: ProxyLogDestinationArg) -> Self {
|
||||
match value {
|
||||
ProxyLogDestinationArg::Stdout => LogDestination::Stdout,
|
||||
ProxyLogDestinationArg::File => LogDestination::File,
|
||||
ProxyLogDestinationArg::Both => LogDestination::Both,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(clap::ValueEnum, Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ProxyLogRotationArg {
|
||||
Hourly,
|
||||
Daily,
|
||||
}
|
||||
|
||||
impl From<ProxyLogRotationArg> for LogRotation {
|
||||
fn from(value: ProxyLogRotationArg) -> Self {
|
||||
match value {
|
||||
ProxyLogRotationArg::Hourly => LogRotation::Hourly,
|
||||
ProxyLogRotationArg::Daily => LogRotation::Daily,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Aether tunnel proxy.
|
||||
///
|
||||
/// Deployed on overseas VPS to relay API traffic for Aether instances
|
||||
@@ -242,6 +279,36 @@ pub struct Config {
|
||||
#[arg(long, env = "AETHER_PROXY_LOG_JSON", default_value_t = false)]
|
||||
pub log_json: bool,
|
||||
|
||||
/// Log destination (stdout, file, both)
|
||||
#[arg(
|
||||
long,
|
||||
env = "AETHER_PROXY_LOG_DESTINATION",
|
||||
value_enum,
|
||||
default_value = "stdout"
|
||||
)]
|
||||
pub log_destination: ProxyLogDestinationArg,
|
||||
|
||||
/// Log directory when file logging is enabled
|
||||
#[arg(long, env = "AETHER_PROXY_LOG_DIR")]
|
||||
pub log_dir: Option<String>,
|
||||
|
||||
/// Log rotation schedule for file logging
|
||||
#[arg(
|
||||
long,
|
||||
env = "AETHER_PROXY_LOG_ROTATION",
|
||||
value_enum,
|
||||
default_value = "daily"
|
||||
)]
|
||||
pub log_rotation: ProxyLogRotationArg,
|
||||
|
||||
/// Log file retention days for file logging
|
||||
#[arg(long, env = "AETHER_PROXY_LOG_RETENTION_DAYS", default_value_t = 7)]
|
||||
pub log_retention_days: u64,
|
||||
|
||||
/// Maximum number of retained rolled log files
|
||||
#[arg(long, env = "AETHER_PROXY_LOG_MAX_FILES", default_value_t = 30)]
|
||||
pub log_max_files: usize,
|
||||
|
||||
/// Tunnel reconnect base delay in milliseconds (used by exponential backoff)
|
||||
#[arg(
|
||||
long,
|
||||
@@ -356,8 +423,49 @@ impl Config {
|
||||
if self.distributed_stream_command_timeout_ms == 0 {
|
||||
anyhow::bail!("distributed_stream_command_timeout_ms must be > 0");
|
||||
}
|
||||
if matches!(
|
||||
self.log_destination,
|
||||
ProxyLogDestinationArg::File | ProxyLogDestinationArg::Both
|
||||
) && self
|
||||
.log_dir
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.is_none_or(|value| value.is_empty())
|
||||
{
|
||||
anyhow::bail!("log_dir must be set when AETHER_PROXY_LOG_DESTINATION is file or both");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn service_runtime_config(&self) -> anyhow::Result<ServiceRuntimeConfig> {
|
||||
let mut config = ServiceRuntimeConfig::new("aether-proxy", "aether_proxy=info")
|
||||
.with_log_format(if self.log_json {
|
||||
LogFormat::Json
|
||||
} else {
|
||||
LogFormat::Pretty
|
||||
})
|
||||
.with_log_destination(self.log_destination.into())
|
||||
.with_node_role("proxy")
|
||||
.with_instance_id(self.node_name.trim().to_string());
|
||||
if matches!(
|
||||
self.log_destination,
|
||||
ProxyLogDestinationArg::File | ProxyLogDestinationArg::Both
|
||||
) {
|
||||
let log_dir = self
|
||||
.log_dir
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.ok_or_else(|| anyhow::anyhow!("log_dir must be configured for file logging"))?;
|
||||
config = config.with_file_logging(FileLoggingConfig::new(
|
||||
log_dir,
|
||||
self.log_rotation.into(),
|
||||
self.log_retention_days,
|
||||
self.log_max_files,
|
||||
));
|
||||
}
|
||||
Ok(config)
|
||||
}
|
||||
}
|
||||
|
||||
/// Per-server connection config (used in multi-server TOML `[[servers]]`).
|
||||
@@ -432,6 +540,16 @@ pub struct ConfigFile {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub log_json: Option<bool>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub log_destination: Option<ProxyLogDestinationArg>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub log_dir: Option<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub log_rotation: Option<ProxyLogRotationArg>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub log_retention_days: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub log_max_files: Option<usize>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tunnel_reconnect_base_ms: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub tunnel_reconnect_max_ms: Option<u64>,
|
||||
@@ -678,6 +796,24 @@ impl ConfigFile {
|
||||
);
|
||||
set!("AETHER_PROXY_LOG_LEVEL", self.log_level);
|
||||
set!("AETHER_PROXY_LOG_JSON", self.log_json);
|
||||
set!(
|
||||
"AETHER_PROXY_LOG_DESTINATION",
|
||||
self.log_destination.map(|v| match v {
|
||||
ProxyLogDestinationArg::Stdout => "stdout",
|
||||
ProxyLogDestinationArg::File => "file",
|
||||
ProxyLogDestinationArg::Both => "both",
|
||||
})
|
||||
);
|
||||
set!("AETHER_PROXY_LOG_DIR", self.log_dir.as_deref());
|
||||
set!(
|
||||
"AETHER_PROXY_LOG_ROTATION",
|
||||
self.log_rotation.map(|v| match v {
|
||||
ProxyLogRotationArg::Hourly => "hourly",
|
||||
ProxyLogRotationArg::Daily => "daily",
|
||||
})
|
||||
);
|
||||
set!("AETHER_PROXY_LOG_RETENTION_DAYS", self.log_retention_days);
|
||||
set!("AETHER_PROXY_LOG_MAX_FILES", self.log_max_files);
|
||||
set!(
|
||||
"AETHER_PROXY_TUNNEL_RECONNECT_BASE_MS",
|
||||
self.tunnel_reconnect_base_ms
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use aether_http::{build_http_client, jittered_delay_for_retry, HttpClientConfig, HttpRetryConfig};
|
||||
use aether_runtime::summarize_text_payload;
|
||||
use reqwest::{Client, StatusCode};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tokio::time::sleep;
|
||||
@@ -132,7 +133,13 @@ impl AetherClient {
|
||||
let status = resp.status();
|
||||
if !status.is_success() {
|
||||
let text = resp.text().await.unwrap_or_default();
|
||||
anyhow::bail!("register failed (HTTP {}): {}", status, text);
|
||||
let summary = summarize_text_payload(&text);
|
||||
anyhow::bail!(
|
||||
"register failed (HTTP {}): response body redacted (bytes={}, sha256={})",
|
||||
status,
|
||||
summary.bytes,
|
||||
summary.sha256
|
||||
);
|
||||
}
|
||||
|
||||
let data: RegisterResponse = resp.json().await?;
|
||||
@@ -167,9 +174,21 @@ impl AetherClient {
|
||||
Ok(())
|
||||
}
|
||||
Ok(r) => {
|
||||
let status = r.status();
|
||||
let text = r.text().await.unwrap_or_default();
|
||||
error!(body = %text, "unregister failed");
|
||||
anyhow::bail!("unregister failed: {}", text);
|
||||
let summary = summarize_text_payload(&text);
|
||||
error!(
|
||||
status = %status,
|
||||
body_bytes = summary.bytes,
|
||||
body_sha256 = %summary.sha256,
|
||||
"unregister failed"
|
||||
);
|
||||
anyhow::bail!(
|
||||
"unregister failed (HTTP {}): response body redacted (bytes={}, sha256={})",
|
||||
status,
|
||||
summary.bytes,
|
||||
summary.sha256
|
||||
);
|
||||
}
|
||||
Err(e) => {
|
||||
// Best-effort during shutdown
|
||||
|
||||
@@ -63,11 +63,15 @@ pub fn install_service(config_path: &Path) -> anyhow::Result<()> {
|
||||
Type=simple\n\
|
||||
WorkingDirectory={working_dir}\n\
|
||||
Environment=AETHER_PROXY_CONFIG={config_str}\n\
|
||||
Environment=AETHER_PROXY_LOG_DESTINATION=both\n\
|
||||
Environment=AETHER_PROXY_LOG_DIR=/var/log/aether-proxy\n\
|
||||
ExecStart={exe_str}\n\
|
||||
Restart=on-failure\n\
|
||||
RestartSec=5\n\
|
||||
LimitNOFILE=65535\n\
|
||||
UMask=0077\n\
|
||||
LogsDirectory=aether-proxy\n\
|
||||
LogsDirectoryMode=0750\n\
|
||||
\n\
|
||||
[Install]\n\
|
||||
WantedBy=multi-user.target\n",
|
||||
|
||||
@@ -358,8 +358,7 @@ mod tests {
|
||||
async fn start_gateway_on_port(
|
||||
port: u16,
|
||||
) -> Result<(GatewayAppState, tokio::task::JoinHandle<()>), std::io::Error> {
|
||||
let state =
|
||||
GatewayAppState::new("http://127.0.0.1:9").expect("gateway test state should build");
|
||||
let state = GatewayAppState::new().expect("gateway test state should build");
|
||||
let router = build_router_with_state(state.clone());
|
||||
let handle = spawn_router_on_port(port, router).await?;
|
||||
Ok((state, handle))
|
||||
@@ -475,6 +474,11 @@ mod tests {
|
||||
upstream_tcp_nodelay: true,
|
||||
log_level: "info".to_string(),
|
||||
log_json: false,
|
||||
log_destination: crate::config::ProxyLogDestinationArg::Stdout,
|
||||
log_dir: None,
|
||||
log_rotation: crate::config::ProxyLogRotationArg::Daily,
|
||||
log_retention_days: 7,
|
||||
log_max_files: 30,
|
||||
tunnel_reconnect_base_ms: 50,
|
||||
tunnel_reconnect_max_ms: 250,
|
||||
tunnel_ping_interval_secs: 1,
|
||||
|
||||
@@ -690,6 +690,11 @@ mod tests {
|
||||
upstream_tcp_nodelay: true,
|
||||
log_level: "info".to_string(),
|
||||
log_json: false,
|
||||
log_destination: crate::config::ProxyLogDestinationArg::Stdout,
|
||||
log_dir: None,
|
||||
log_rotation: crate::config::ProxyLogRotationArg::Daily,
|
||||
log_retention_days: 7,
|
||||
log_max_files: 30,
|
||||
tunnel_reconnect_base_ms: 500,
|
||||
tunnel_reconnect_max_ms: 30_000,
|
||||
tunnel_ping_interval_secs: 15,
|
||||
|
||||
Reference in New Issue
Block a user