pubky_homeserver/metrics_server/
app.rs1use std::net::SocketAddr;
2use std::time::Duration;
3
4use crate::metrics_server::routes::metrics::Metrics;
5use crate::AppContext;
6use crate::AppContextConversionError;
7use axum::routing::get;
8use axum::Router;
9use axum::{extract::State, http::StatusCode, response::IntoResponse};
10use axum_server::Handle;
11use tokio::task::JoinHandle;
12
13fn create_app(metrics: Metrics) -> axum::routing::IntoMakeService<Router> {
14 Router::new()
15 .route("/metrics", get(metrics_handler))
16 .with_state(metrics)
17 .into_make_service()
18}
19
20#[derive(thiserror::Error, Debug)]
22pub enum MetricsServerBuildError {
23 #[error("Failed to create metrics server: {0}")]
25 Server(anyhow::Error),
26
27 #[error("Failed to bootstrap from the data directory: {0}")]
29 DataDir(AppContextConversionError),
30}
31
32pub struct MetricsServer {
39 http_handle: Handle<SocketAddr>,
40 join_handle: JoinHandle<()>,
41 socket: SocketAddr,
42}
43
44impl MetricsServer {
45 pub async fn start(context: &AppContext) -> Result<Self, MetricsServerBuildError> {
47 let metrics = context.metrics.clone();
48 let socket = context.config_toml.metrics.listen_socket;
49 let app = create_app(metrics);
50 let listener = std::net::TcpListener::bind(socket)
51 .map_err(|e| MetricsServerBuildError::Server(e.into()))?;
52 listener
53 .set_nonblocking(true)
54 .map_err(|e| MetricsServerBuildError::Server(e.into()))?;
55 let socket = listener
56 .local_addr()
57 .map_err(|e| MetricsServerBuildError::Server(e.into()))?;
58 let http_handle = Handle::new();
59 let inner_http_handle = http_handle.clone();
60 let server = axum_server::from_tcp(listener)
61 .map_err(|e| MetricsServerBuildError::Server(e.into()))?;
62 let join_handle = tokio::spawn(async move {
63 server
64 .handle(inner_http_handle)
65 .serve(app)
66 .await
67 .unwrap_or_else(|e| tracing::error!("Metrics server error: {}", e));
68 });
69 Ok(Self {
70 http_handle,
71 socket,
72 join_handle,
73 })
74 }
75
76 pub fn listen_socket(&self) -> SocketAddr {
78 self.socket
79 }
80}
81
82impl Drop for MetricsServer {
83 fn drop(&mut self) {
84 self.http_handle
85 .graceful_shutdown(Some(Duration::from_secs(5)));
86 self.join_handle.abort();
87 }
88}
89
90pub async fn metrics_handler(State(metrics): State<Metrics>) -> impl IntoResponse {
92 match metrics.render() {
93 Ok(body) => (StatusCode::OK, body).into_response(),
94 Err(e) => (
95 StatusCode::INTERNAL_SERVER_ERROR,
96 format!("# Failed to render metrics: {}\n", e),
97 )
98 .into_response(),
99 }
100}