improving logging

This commit is contained in:
Ara Sadoyan
2026-09-23 15:28:41 +02:00
parent 54ac35a4c9
commit 71ebc6376e
4 changed files with 105 additions and 63 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
use crate::core::proxyhttp::LB; use crate::core::proxyhttp::LB;
use crate::ingress::{APIUpstreamProvider, ConsulProvider, Discovery, FromFileProvider, KubernetesProvider}; use crate::ingress::{APIUpstreamProvider, ConsulProvider, Discovery, FromFileProvider, KubernetesProvider};
use crate::logging::core::init_logging; use crate::logging::core::init_access_logging;
use crate::tls::acme::order::refresh_order; use crate::tls::acme::order::refresh_order;
use crate::utils::metrics::calc_cache_metrics; use crate::utils::metrics::calc_cache_metrics;
use crate::utils::parceyaml::load_configuration; use crate::utils::parceyaml::load_configuration;
@@ -80,7 +80,7 @@ impl BackgroundService for LB {
})); }));
drop(tokio::spawn(async move { calc_cache_metrics().await })); drop(tokio::spawn(async move { calc_cache_metrics().await }));
drop(tokio::spawn(async move { refresh_order(certdir, confdir).await })); drop(tokio::spawn(async move { refresh_order(certdir, confdir).await }));
init_logging(self.config.access_log.clone()); init_access_logging(self.config.access_log.clone());
loop { loop {
tokio::select! { tokio::select! {
_ = shutdown.changed() => { _ = shutdown.changed() => {
+73 -32
View File
@@ -18,6 +18,8 @@ use pingora_cache::CachePhase;
use pingora_http::Version; use pingora_http::Version;
use pingora_proxy::Session; use pingora_proxy::Session;
use serde::Serialize; use serde::Serialize;
use std::cell::RefCell;
use std::fmt::Write;
use std::net::{IpAddr, Ipv4Addr}; use std::net::{IpAddr, Ipv4Addr};
use std::sync::OnceLock; use std::sync::OnceLock;
use tokio::sync::mpsc; use tokio::sync::mpsc;
@@ -34,17 +36,21 @@ pub struct LogMessage {
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct StructuredSystemLog { pub struct StructuredSystemLog {
pub target: String, pub target: &'static str,
pub level: log::Level, pub level: log::Level,
pub message: String, pub message: String,
} }
thread_local! {
static LOG_BUF: RefCell<String> = RefCell::new(String::with_capacity(512));
}
static LOG_SENDER: OnceLock<mpsc::Sender<LogMessage>> = OnceLock::new(); static LOG_SENDER: OnceLock<mpsc::Sender<LogMessage>> = OnceLock::new();
pub(crate) static PINGORA_LOG_SENDER: OnceLock<mpsc::Sender<StructuredSystemLog>> = OnceLock::new(); pub(crate) static PINGORA_LOG_SENDER: OnceLock<mpsc::Sender<StructuredSystemLog>> = OnceLock::new();
static ACCESS_LOG: OnceLock<LogLevel> = OnceLock::new(); static ACCESS_LOG: OnceLock<LogLevel> = OnceLock::new();
const LOG_BUFFER: usize = 16384; const LOG_BUFFER: usize = 16384;
static IS_STRUCTURED: OnceLock<bool> = OnceLock::new();
static LOG_BACKEND: OnceLock<&'static str> = OnceLock::new(); static LOG_BACKEND: OnceLock<&'static str> = OnceLock::new();
pub fn set_backend(key: String) -> Result<(), &'static str> { pub fn set_backend(key: String) -> Result<(), &'static str> {
let static_key: &'static str = Box::leak(key.into_boxed_str()); let static_key: &'static str = Box::leak(key.into_boxed_str());
LOG_BACKEND.set(static_key).map_err(|_| "LOG_BACKEND was already initialized!") LOG_BACKEND.set(static_key).map_err(|_| "LOG_BACKEND was already initialized!")
@@ -55,23 +61,33 @@ pub fn get_backend() -> &'static str {
} }
#[derive(Debug)] #[derive(Debug)]
pub struct StructiredChannelAppender { pub struct StructuredChannelAppender {
sender: mpsc::Sender<StructuredSystemLog>, sender: mpsc::Sender<StructuredSystemLog>,
} }
impl StructiredChannelAppender { impl StructuredChannelAppender {
pub fn new(sender: mpsc::Sender<StructuredSystemLog>) -> Self { pub fn new(sender: mpsc::Sender<StructuredSystemLog>) -> Self {
Self { sender } Self { sender }
} }
} }
impl Append for StructiredChannelAppender { impl Append for StructuredChannelAppender {
fn append(&self, record: &Record) -> Result<()> { fn append(&self, record: &Record) -> Result<()> {
let message = LOG_BUF.with(|buf| {
let mut b = buf.borrow_mut();
b.clear();
let _ = write!(&mut *b, "{}", record.args());
b.clone()
});
let target: &'static str = unsafe { std::mem::transmute(record.target()) };
let msg = StructuredSystemLog { let msg = StructuredSystemLog {
target: record.target().to_string(), target,
level: record.level(), level: record.level(),
message: format!("{}", record.args()), message,
}; };
let _ = self.sender.try_send(msg); let _ = self.sender.try_send(msg);
Ok(()) Ok(())
} }
@@ -99,16 +115,15 @@ pub fn log_builder(conf: &AppConfig, location: &Option<String>) {
} }
let pattern = pat.as_str(); let pattern = pat.as_str();
if let Some(backend) = conf.log_structired.clone() { if let Some(backend) = conf.log_structured.as_deref() {
let b = backend.split_whitespace().collect::<Vec<&str>>(); if let Some(backend_name) = backend.split_whitespace().next() {
let s = b[0].to_string(); let _ = set_backend(backend_name.to_string());
let _ = set_backend(s); init_structured_log();
init_pingora_syslog(); }
let pingora_appender: Option<Box<dyn Append>> = PINGORA_LOG_SENDER let pingora_appender: Option<Box<dyn Append>> = PINGORA_LOG_SENDER
.get() .get()
.cloned() .cloned()
.map(|sender| Box::new(StructiredChannelAppender::new(sender)) as Box<dyn Append>); .map(|sender| Box::new(StructuredChannelAppender::new(sender)) as Box<dyn Append>);
let stdout = ConsoleAppender::builder().encoder(Box::new(PatternEncoder::new(pattern))).build(); let stdout = ConsoleAppender::builder().encoder(Box::new(PatternEncoder::new(pattern))).build();
@@ -136,14 +151,26 @@ pub fn log_builder(conf: &AppConfig, location: &Option<String>) {
let config = config_builder.build(Root::builder().appender("stdout").build(log_level)).unwrap(); let config = config_builder.build(Root::builder().appender("stdout").build(log_level)).unwrap();
log4rs::init_config(config).unwrap(); if let Err(e) = log4rs::init_config(config) {
eprintln!("Warning: log4rs logger already initialized: {:?}", e);
}
info!("Enabling structured logging with backend : {}", backend); info!("Enabling structured logging with backend : {}", backend);
let _ = IS_STRUCTURED.set(true);
return; return;
} }
if let Some(location) = location { if let Some(location) = location {
let parts: Vec<&str> = location.splitn(4, ',').map(|s| s.trim()).collect(); // let parts: Vec<&str> = location.splitn(4, ',').map(|s| s.trim()).collect();
// let path = parts.get(0).expect("Syntax error, could not get path for log files");
let parts: Vec<&str> = location.split(',').map(|s| s.trim()).collect();
let path = match parts.get(0).filter(|s| !s.is_empty()) {
Some(p) => p,
None => {
error!("Invalid log location string provided; falling back to stdout");
return;
}
};
let path = parts.get(0).expect("Syntax error, could not get path for log files");
let compress = parts.get(3).unwrap_or(&"No"); let compress = parts.get(3).unwrap_or(&"No");
let size_mb: u64 = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(100); let size_mb: u64 = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(100);
let keep: u32 = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(5); let keep: u32 = parts.get(2).and_then(|s| s.parse().ok()).unwrap_or(5);
@@ -157,17 +184,22 @@ pub fn log_builder(conf: &AppConfig, location: &Option<String>) {
let trigger = SizeTrigger::new(size_mb * 1024 * 1024); let trigger = SizeTrigger::new(size_mb * 1024 * 1024);
let policy = CompoundPolicy::new(Box::new(trigger), Box::new(roller)); let policy = CompoundPolicy::new(Box::new(trigger), Box::new(roller));
let file = RollingFileAppender::builder() let file = match RollingFileAppender::builder().encoder(Box::new(PatternEncoder::new(pattern))).build(path, Box::new(policy)) {
.encoder(Box::new(PatternEncoder::new(pattern))) Ok(f) => f,
.build(path, Box::new(policy)) Err(e) => {
.unwrap(); error!("Failed to create log file at {}: {}. Falling back to stdout.", path, e);
return;
}
};
let config = Log4rsConfig::builder() let config = Log4rsConfig::builder()
.appender(Appender::builder().build("file", Box::new(file))) .appender(Appender::builder().build("file", Box::new(file)))
.logger(Logger::builder().build("pingora_proxy", LevelFilter::Off)) .logger(Logger::builder().build("pingora_proxy", LevelFilter::Off))
.build(Root::builder().appender("file").build(log_level)) .build(Root::builder().appender("file").build(log_level))
.unwrap(); .unwrap();
log4rs::init_config(config).unwrap(); if let Err(e) = log4rs::init_config(config) {
eprintln!("Warning: log4rs logger already initialized: {:?}", e);
}
info!("Logging to: {}, Max file size: {}mb, Files to keep: {}, compression: {} ", path, size_mb, keep, compress); info!("Logging to: {}, Max file size: {}mb, Files to keep: {}, compression: {} ", path, size_mb, keep, compress);
} else { } else {
let stdout = ConsoleAppender::builder().encoder(Box::new(PatternEncoder::new(pattern))).build(); let stdout = ConsoleAppender::builder().encoder(Box::new(PatternEncoder::new(pattern))).build();
@@ -176,7 +208,9 @@ pub fn log_builder(conf: &AppConfig, location: &Option<String>) {
.logger(Logger::builder().build("pingora_proxy", LevelFilter::Off)) .logger(Logger::builder().build("pingora_proxy", LevelFilter::Off))
.build(Root::builder().appender("stdout").build(log_level)) .build(Root::builder().appender("stdout").build(log_level))
.unwrap(); .unwrap();
log4rs::init_config(config).unwrap(); if let Err(e) = log4rs::init_config(config) {
eprintln!("Warning: log4rs logger already initialized: {:?}", e);
}
info!("No files are configured, logging to stdout"); info!("No files are configured, logging to stdout");
} }
} }
@@ -228,7 +262,6 @@ pub fn access_log(response_code: u16, summary: &str, session: &Session) {
let should_log = match level { let should_log = match level {
LogLevel::Access => true, LogLevel::Access => true,
LogLevel::None => false, LogLevel::None => false,
// Captures all 4xx and 5xx errors when level is set to Error
LogLevel::Error => matches!(status, MatchStatus::Er5xx), LogLevel::Error => matches!(status, MatchStatus::Er5xx),
}; };
@@ -244,7 +277,7 @@ pub fn access_log(response_code: u16, summary: &str, session: &Session) {
let user_agent = session.req_header().headers.get("user-agent").and_then(|v| v.to_str().ok()).unwrap_or("-"); let user_agent = session.req_header().headers.get("user-agent").and_then(|v| v.to_str().ok()).unwrap_or("-");
let log = LogMessage { let msg = LogMessage {
response_code, response_code,
summary: summary.to_owned(), summary: summary.to_owned(),
client_ip: ip, client_ip: ip,
@@ -253,24 +286,28 @@ pub fn access_log(response_code: u16, summary: &str, session: &Session) {
cache_status: session.cache.phase(), cache_status: session.cache.phase(),
}; };
if IS_STRUCTURED.get().is_some() {
write_access_log(&msg);
return;
}
if let Some(sender) = LOG_SENDER.get() { if let Some(sender) = LOG_SENDER.get() {
if let Err(_) = sender.try_send(log) { if let Err(_) = sender.try_send(msg) {
LOGGING_ERRORS.inc(); LOGGING_ERRORS.inc();
} }
} }
} }
pub fn init_logging(enabled: Option<String>) { pub fn init_access_logging(enabled: Option<String>) {
if enabled.is_some() { if enabled.is_some() {
LOGGING_ERRORS.set(0); LOGGING_ERRORS.set(0);
info!("Enabling {:?} log, with buffer of {} messages", ACCESS_LOG.get().unwrap_or(&LogLevel::None), LOG_BUFFER); info!("Enabling {:?} log, with buffer of {} messages", ACCESS_LOG.get().unwrap_or(&LogLevel::None), LOG_BUFFER);
let (ltx, lrx) = mpsc::channel(LOG_BUFFER); let (ltx, lrx) = mpsc::channel(LOG_BUFFER);
LOG_SENDER.set(ltx).unwrap(); let _ = LOG_SENDER.set(ltx);
std::thread::spawn(move || log_receiver(lrx)); std::thread::spawn(move || access_log_receiver(lrx));
} }
} }
pub fn init_pingora_syslog() { pub fn init_structured_log() {
let (tx, mut rx) = mpsc::channel::<StructuredSystemLog>(LOG_BUFFER); let (tx, mut rx) = mpsc::channel::<StructuredSystemLog>(LOG_BUFFER);
let _ = PINGORA_LOG_SENDER.set(tx); let _ = PINGORA_LOG_SENDER.set(tx);
let backend = get_backend(); let backend = get_backend();
@@ -284,8 +321,13 @@ pub fn init_pingora_syslog() {
.expect("Failed to spawn log thread"); .expect("Failed to spawn log thread");
} }
pub fn log_receiver(mut receiver: mpsc::Receiver<LogMessage>) { pub fn access_log_receiver(mut receiver: mpsc::Receiver<LogMessage>) {
while let Some(msg) = receiver.blocking_recv() { while let Some(msg) = receiver.blocking_recv() {
write_access_log(&msg);
}
}
fn write_access_log(msg: &LogMessage) {
match MatchStatus::from_code(msg.response_code) { match MatchStatus::from_code(msg.response_code) {
MatchStatus::Ok2xx => info!( MatchStatus::Ok2xx => info!(
"{}, {}, {}, client: {}, version: {:?}, useragent: {}", "{}, {}, {}, client: {}, version: {:?}, useragent: {}",
@@ -316,4 +358,3 @@ pub fn log_receiver(mut receiver: mpsc::Receiver<LogMessage>) {
), ),
} }
} }
}
+1
View File
@@ -30,6 +30,7 @@ impl WriteLog for ElasticSearch {
if let Ok(jsonmsg) = serde_json::to_string(&msg) { if let Ok(jsonmsg) = serde_json::to_string(&msg) {
// ElasticSearch logic is not yet implemented. // ElasticSearch logic is not yet implemented.
println!("{} - {}:{} => {}", ELASTIC.hosts, ELASTIC.user, ELASTIC.password, jsonmsg); println!("{} - {}:{} => {}", ELASTIC.hosts, ELASTIC.user, ELASTIC.password, jsonmsg);
// let resp = httpclient(method, tls, host, path, upstream.address.as_ref(), upstream.port, link).await;
} }
} }
} }
+1 -1
View File
@@ -132,7 +132,7 @@ pub struct AppConfig {
pub rungroup: Option<String>, pub rungroup: Option<String>,
pub log_file: Option<String>, pub log_file: Option<String>,
pub log_pattern: Option<String>, pub log_pattern: Option<String>,
pub log_structired: Option<String>, pub log_structured: Option<String>,
pub tcp_keepalive_idle: Option<u64>, pub tcp_keepalive_idle: Option<u64>,
pub tcp_keepalive_interval: Option<u64>, pub tcp_keepalive_interval: Option<u64>,
pub tcp_keepalive_count: Option<usize>, pub tcp_keepalive_count: Option<usize>,