pub mod argument;
pub mod c;
pub mod config;
pub mod proxy;
pub mod util;
use std::path::PathBuf;
use std::time::Duration;
use argument::Argument;
use clap::Parser;
use pingora::{proxy::http_proxy_service, server::Server};
use pingora::lb::LoadBalancer;
use pingora::prelude::{background_service, RoundRobin, TcpHealthCheck};
use tracing::{Level, debug, info};
use tracing_appender::rolling::daily;
use tracing_subscriber::{
EnvFilter,
fmt::{self, time::OffsetTime, writer::MakeWriterExt},
layer::SubscriberExt,
util::SubscriberInitExt,
};
use util::Util;
use crate::proxy::Proxy;
fn init_log(log_dir: &PathBuf) {
let time_fmt = time::format_description::parse(
"[year]-[month]-[day] [hour]:[minute]:[second].[subsecond digits:3]",
)
.expect("Failed to parse time format");
let time_offset = time::UtcOffset::current_local_offset().unwrap_or(time::UtcOffset::UTC);
let timer = OffsetTime::new(time_offset, time_fmt);
let file_appender = daily(log_dir, "err.log")
.with_max_level(Level::WARN)
.or_else(daily(log_dir, "out.log"));
let file_layer = fmt::layer()
.with_writer(file_appender)
.with_ansi(false) .with_timer(timer.clone())
.with_target(true);
let std_appender = std::io::stderr
.with_max_level(Level::WARN)
.or_else(std::io::stdout);
let console_layer = fmt::layer()
.with_writer(std_appender)
.with_ansi(true) .with_timer(timer)
.with_target(true);
tracing_subscriber::registry()
.with(EnvFilter::from_default_env())
.with(file_layer)
.with(console_layer)
.init()
}
fn main() {
let log_dir = Util::app_dirs().data_dir().join("log");
init_log(&log_dir);
debug!(?log_dir);
let argument = Argument::parse();
info!(?argument);
let config = config::parse(&argument)
.expect("Failed to parse configuration file");
info!(?config);
let mut bg_services = Vec::new();
let mut proxy_entries = Vec::new();
for (k, setting) in config.proxies() {
let mut lb = LoadBalancer::<RoundRobin>::try_from_iter(setting.upstreams())
.expect("Failed to build load balancer");
let hc = TcpHealthCheck::new();
lb.set_health_check(hc);
lb.health_check_frequency = Some(Duration::from_secs(1));
let name = format!("health check for {}", k.to_string());
let background = background_service(&name, lb);
let lb = background.task();
bg_services.push(background);
proxy_entries.push((k.clone(), lb));
}
let proxy = Proxy::new(proxy_entries.into_iter());
let mut server = Server::new(None).expect("Failed to create server");
server.bootstrap();
let addr = format!("0.0.0.0:{}", config.port());
let mut proxy_service = http_proxy_service(&server.configuration, proxy);
if let Some((cert_path, key_path)) = config.tls_config() {
let cert_path = cert_path.to_str().expect("Invalid cert path");
let key_path = key_path.to_str().expect("Invalid key path");
proxy_service.add_tls(&addr, cert_path, key_path)
.expect("Failed to add TLS to proxy service");
info!("Listening on {} with tls", addr);
} else {
proxy_service.add_tcp(&format!("0.0.0.0:{}", config.port()));
info!("Listening on {} with tls", addr);
}
server.add_service(proxy_service);
for s in bg_services {
server.add_service(s);
}
server.run_forever();
}