fix(app): PrometheusHandle must be Data-wrapped before Fn closure capture

PrometheusHandle was moved into the HttpServer Fn closure but Fn
closures require Clone (not FnOnce). Wrap in web::Data before
cloning into the closure.
This commit is contained in:
ZhenYi 2026-04-21 23:05:54 +08:00
parent beae9bdea0
commit 4aaee59fa4

View File

@ -1,19 +1,19 @@
use actix_cors::Cors;
use actix_web::cookie::time::Duration;
use actix_web::middleware::Logger;
use actix_web::{App, HttpResponse, HttpServer, cookie::Key, web};
use actix_web::{cookie::Key, web, App, HttpResponse, HttpServer};
use clap::Parser;
use db::cache::AppCache;
use db::database::AppDatabase;
use sea_orm::ConnectionTrait;
use service::AppService;
use session::SessionMiddleware;
use session::config::{PersistentSession, SessionLifecycle, TtlExtensionPolicy};
use session::storage::RedisClusterSessionStore;
use observability::{
init_tracing_subscriber, install_recorder, prometheus_handler, spawn_http_metrics_poller,
MetricsMiddleware, HttpMetrics, TracingSpanMiddleware, HttpSnapshotGuard,
HttpMetrics, HttpSnapshotGuard, MetricsMiddleware, TracingSpanMiddleware,
};
use sea_orm::ConnectionTrait;
use service::AppService;
use session::config::{PersistentSession, SessionLifecycle, TtlExtensionPolicy};
use session::storage::RedisClusterSessionStore;
use session::SessionMiddleware;
mod args;
@ -57,48 +57,40 @@ async fn main() -> anyhow::Result<()> {
let args = ServerArgs::parse();
let service = AppService::new(cfg.clone()).await?;
tracing::info!("AppService initialized");
// Spawn background task: sync OpenRouter models immediately on startup,
// then every 10 minutes.
let _model_sync_handle = service.clone().start_sync_task();
// Spawn background task: check workspace billing alerts every 30 minutes.
let _billing_alert_handle = service.clone().start_billing_alert_task();
let (shutdown_tx, shutdown_rx) = tokio::sync::broadcast::channel::<()>(1);
let worker_service = service.clone();
let worker_handle = tokio::spawn(async move {
worker_service
.start_room_workers(shutdown_rx)
.await
});
let worker_handle =
tokio::spawn(async move { worker_service.start_room_workers(shutdown_rx).await });
// ── Phase 6: OTLP tracing ──────────────────────────────────────────────
let _otel_guard = if cfg.otel_enabled().unwrap_or(false) {
let endpoint = cfg.otel_endpoint().unwrap_or_else(|_| "http://localhost:4317".to_string());
let service_name = cfg.otel_service_name().unwrap_or_else(|_| "app".to_string());
let service_version = cfg.otel_service_version().unwrap_or_else(|_| "0.1.0".to_string());
let endpoint = cfg
.otel_endpoint()
.unwrap_or_else(|_| "http://localhost:4317".to_string());
let service_name = cfg
.otel_service_name()
.unwrap_or_else(|_| "app".to_string());
let service_version = cfg
.otel_service_version()
.unwrap_or_else(|_| "0.1.0".to_string());
tracing::info!(endpoint = %endpoint, service = %service_name, "OTLP tracing enabled");
let guard = observability::init_otlp(
&endpoint,
&service_name,
&service_version,
&log_level,
)
.map_err(|e| anyhow::anyhow!("OTLP init failed: {}", e))?;
let guard =
observability::init_otlp(&endpoint, &service_name, &service_version, &log_level)
.map_err(|e| anyhow::anyhow!("OTLP init failed: {}", e))?;
guard
} else {
None
};
// ── Phase 6: Prometheus metrics ─────────────────────────────────────────
install_recorder();
let prometheus_handle = install_recorder();
let prometheus_handle_data = web::Data::new(prometheus_handle);
let http_metrics = std::sync::Arc::new(HttpMetrics::new());
let http_snapshot: HttpSnapshotGuard =
std::sync::Arc::new(std::sync::RwLock::new(
observability::HttpMetricsSnapshot::default(),
));
let http_snapshot: HttpSnapshotGuard = std::sync::Arc::new(std::sync::RwLock::new(
observability::HttpMetricsSnapshot::default(),
));
let http_snapshot_for_poller = http_snapshot.clone();
spawn_http_metrics_poller(
http_metrics.clone(),
@ -147,6 +139,7 @@ async fn main() -> anyhow::Result<()> {
.app_data(web::Data::new(db.clone()))
.app_data(web::Data::new(cache.clone()))
.app_data(http_snapshot_data.clone())
.app_data(prometheus_handle_data.clone())
.route("/health", web::get().to(health_check))
.route("/metrics", web::get().to(prometheus_handler))
.configure(api::route::init_routes)