2026-01-01 21:05:54 +00:00

122 lines
3.7 KiB
Rust

/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
mod alerts;
mod api;
mod config;
mod db;
use std::net::SocketAddr;
use std::sync::Arc;
use axum::{
Router,
routing::{get, post},
};
use tower_http::cors::{Any, CorsLayer};
use tower_http::trace::TraceLayer;
use tracing::info;
use tracing_subscriber::{EnvFilter, fmt, prelude::*};
use api::ingest::{
AppState, ingest_batch, ingest_counter, ingest_crash, ingest_gauge, ingest_histogram,
};
use api::query::{
health_check, query_aggregate, query_crashes, query_metrics, query_percentiles, query_top,
};
use config::{Config, MetricsMode};
use db::{ClickHouseStorage, NoOpStorage, Storage};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let _ = dotenvy::dotenv();
tracing_subscriber::registry()
.with(fmt::layer().with_target(true))
.with(
EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info,tonbo=warn")),
)
.init();
info!("Starting fluxer_metrics service");
let config = Config::from_env().unwrap_or_else(|e| {
tracing::error!("Configuration error: {}", e);
std::process::exit(1);
});
let mode_str = match config.metrics_mode {
MetricsMode::ClickHouse => "clickhouse",
MetricsMode::NoOp => "noop",
};
info!(
"Configuration loaded: port={}, mode={}, alert_webhook={}",
config.port,
mode_str,
config.alert_webhook_url.is_some()
);
if config.metrics_mode == MetricsMode::ClickHouse {
info!(
"ClickHouse config: url={}, database={}",
config.clickhouse_url, config.clickhouse_database
);
}
let storage: Box<dyn Storage> = match config.metrics_mode {
MetricsMode::ClickHouse => Box::new(ClickHouseStorage::new(&config).await?),
MetricsMode::NoOp => Box::new(NoOpStorage::new()),
};
let state = Arc::new(AppState {
storage,
config: config.clone(),
});
let app = Router::new()
.route("/metrics/counter", post(ingest_counter))
.route("/metrics/gauge", post(ingest_gauge))
.route("/metrics/histogram", post(ingest_histogram))
.route("/metrics/crash", post(ingest_crash))
.route("/metrics/batch", post(ingest_batch))
.route("/query", get(query_metrics))
.route("/query/aggregate", get(query_aggregate))
.route("/query/percentiles", get(query_percentiles))
.route("/query/top", get(query_top))
.route("/query/crashes", get(query_crashes))
.route("/_health", get(health_check))
.layer(
CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any),
)
.layer(TraceLayer::new_for_http())
.with_state(state);
let addr = SocketAddr::from(([0, 0, 0, 0], config.port));
info!("Listening on {}", addr);
let listener = tokio::net::TcpListener::bind(addr).await?;
axum::serve(listener, app).await?;
Ok(())
}