forked from proxmox-mirrors/proxmox
proxmox-log: add tracing infrastructure
Add the `proxmox_log` crate which includes the new logging infra. Export the `init_logger` function, which creates the `tracing` logger that includes the default subscriber and two layer. The first layer comes from the tracing-journald crate and logs everything that does not come from a worker-task/thread to the syslog. The second layer filters the exact opposite and writes the logs into the corresponding task-log file. Reviewed-by: Lukas Wagner <l.wagner@proxmox.com> Tested-by: Lukas Wagner <l.wagner@proxmox.com> Signed-off-by: Gabriel Goller <g.goller@proxmox.com>
This commit is contained in:
parent
f1920d9b94
commit
0550659cd1
@ -19,6 +19,7 @@ members = [
|
|||||||
"proxmox-io",
|
"proxmox-io",
|
||||||
"proxmox-lang",
|
"proxmox-lang",
|
||||||
"proxmox-ldap",
|
"proxmox-ldap",
|
||||||
|
"proxmox-log",
|
||||||
"proxmox-login",
|
"proxmox-login",
|
||||||
"proxmox-metrics",
|
"proxmox-metrics",
|
||||||
"proxmox-network-api",
|
"proxmox-network-api",
|
||||||
@ -104,6 +105,10 @@ tokio = "1.6"
|
|||||||
tokio-openssl = "0.6.1"
|
tokio-openssl = "0.6.1"
|
||||||
tokio-stream = "0.1.0"
|
tokio-stream = "0.1.0"
|
||||||
tower-service = "0.3.0"
|
tower-service = "0.3.0"
|
||||||
|
tracing = "0.1"
|
||||||
|
tracing-journald = "0.3.0"
|
||||||
|
tracing-log = { version = "0.1.3", default-features = false }
|
||||||
|
tracing-subscriber = "0.3.16"
|
||||||
url = "2.2"
|
url = "2.2"
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
webauthn-rs = "0.3"
|
webauthn-rs = "0.3"
|
||||||
@ -121,6 +126,7 @@ proxmox-http-error = { version = "0.1.0", path = "proxmox-http-error" }
|
|||||||
proxmox-human-byte = { version = "0.1.0", path = "proxmox-human-byte" }
|
proxmox-human-byte = { version = "0.1.0", path = "proxmox-human-byte" }
|
||||||
proxmox-io = { version = "1.1.0", path = "proxmox-io" }
|
proxmox-io = { version = "1.1.0", path = "proxmox-io" }
|
||||||
proxmox-lang = { version = "1.3", path = "proxmox-lang" }
|
proxmox-lang = { version = "1.3", path = "proxmox-lang" }
|
||||||
|
proxmox-log= { version = "0.1.0", path = "proxmox-log" }
|
||||||
proxmox-login = { version = "0.1.0", path = "proxmox-login" }
|
proxmox-login = { version = "0.1.0", path = "proxmox-login" }
|
||||||
proxmox-product-config = { version = "0.2.0", path = "proxmox-product-config" }
|
proxmox-product-config = { version = "0.2.0", path = "proxmox-product-config" }
|
||||||
proxmox-config-digest = { version = "0.1.0", path = "proxmox-config-digest" }
|
proxmox-config-digest = { version = "0.1.0", path = "proxmox-config-digest" }
|
||||||
|
23
proxmox-log/Cargo.toml
Normal file
23
proxmox-log/Cargo.toml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
[package]
|
||||||
|
name = "proxmox-log"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
description = "Logging infrastructure for proxmox"
|
||||||
|
|
||||||
|
exclude.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
anyhow.workspace = true
|
||||||
|
nix.workspace = true
|
||||||
|
log.workspace = true
|
||||||
|
tracing.workspace = true
|
||||||
|
tracing-journald.workspace = true
|
||||||
|
tracing-subscriber.workspace = true
|
||||||
|
tracing-log = { workspace = true, features = ["std"] }
|
||||||
|
tokio = { workspace = true, features = ["rt-multi-thread"] }
|
||||||
|
proxmox-time.workspace = true
|
||||||
|
proxmox-sys.workspace = true
|
||||||
|
|
5
proxmox-log/debian/changelog
Normal file
5
proxmox-log/debian/changelog
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
rust-proxmox-log (0.1.0-3) UNRELEASED; urgency=medium
|
||||||
|
|
||||||
|
* Initial release
|
||||||
|
|
||||||
|
-- Gabriel Goller <ggoller@luna.proxmox.com> Wed, 11 Oct 2023 15:13:58 +0200
|
52
proxmox-log/debian/control
Normal file
52
proxmox-log/debian/control
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
Source: rust-proxmox-log
|
||||||
|
Section: rust
|
||||||
|
Priority: optional
|
||||||
|
Build-Depends: debhelper (>= 12),
|
||||||
|
dh-cargo (>= 25),
|
||||||
|
cargo:native <!nocheck>,
|
||||||
|
rustc:native <!nocheck>,
|
||||||
|
libstd-rust-dev <!nocheck>,
|
||||||
|
librust-anyhow-1+default-dev <!nocheck>,
|
||||||
|
librust-log-0.4+default-dev (>= 0.4.17-~~) <!nocheck>,
|
||||||
|
librust-nix-0.26+default-dev (>= 0.26.1-~~) <!nocheck>,
|
||||||
|
librust-proxmox-sys-0.5+default-dev (>= 0.5.1-~~) <!nocheck>,
|
||||||
|
librust-proxmox-time-1+default-dev (>= 1.1.6-~~) <!nocheck>,
|
||||||
|
librust-tracing-journald-0.3+default-dev <!nocheck>
|
||||||
|
librust-tokio-1+default-dev (>= 1.6-~~) <!nocheck>,
|
||||||
|
librust-tokio-1+rt-multi-thread-dev (>= 1.6-~~) <!nocheck>,
|
||||||
|
librust-tracing-0.1+default-dev <!nocheck>,
|
||||||
|
librust-tracing-log-0.1-dev (>= 0.1.3-~~) <!nocheck>,
|
||||||
|
librust-tracing-subscriber-0.3+default-dev (>= 0.3.16-~~) <!nocheck>
|
||||||
|
Maintainer: Proxmox Support Team <support@proxmox.com>
|
||||||
|
Standards-Version: 4.6.2
|
||||||
|
Vcs-Git: git://git.proxmox.com/git/proxmox.git
|
||||||
|
Vcs-Browser: https://git.proxmox.com/?p=proxmox.git
|
||||||
|
X-Cargo-Crate: proxmox-log
|
||||||
|
Rules-Requires-Root: no
|
||||||
|
|
||||||
|
Package: librust-proxmox-log-dev
|
||||||
|
Architecture: any
|
||||||
|
Multi-Arch: same
|
||||||
|
Depends:
|
||||||
|
${misc:Depends},
|
||||||
|
librust-anyhow-1+default-dev,
|
||||||
|
librust-log-0.4+default-dev (>= 0.4.17-~~),
|
||||||
|
librust-nix-0.26+default-dev (>= 0.26.1-~~),
|
||||||
|
librust-proxmox-sys-0.5+default-dev (>= 0.5.1-~~),
|
||||||
|
librust-proxmox-time-1+default-dev (>= 1.1.6-~~),
|
||||||
|
librust-tracing-journald-0.3+default-dev,
|
||||||
|
librust-tokio-1+default-dev (>= 1.6-~~),
|
||||||
|
librust-tokio-1+rt-multi-thread-dev (>= 1.6-~~),
|
||||||
|
librust-tracing-0.1+default-dev,
|
||||||
|
librust-tracing-log-0.1-dev (>= 0.1.3-~~),
|
||||||
|
librust-tracing-subscriber-0.3+default-dev (>= 0.3.16-~~)
|
||||||
|
Provides:
|
||||||
|
librust-proxmox-log+default-dev (= ${binary:Version}),
|
||||||
|
librust-proxmox-log-0-dev (= ${binary:Version}),
|
||||||
|
librust-proxmox-log-0+default-dev (= ${binary:Version}),
|
||||||
|
librust-proxmox-log-0.1-dev (= ${binary:Version}),
|
||||||
|
librust-proxmox-log-0.1+default-dev (= ${binary:Version}),
|
||||||
|
librust-proxmox-log-0.1.0-dev (= ${binary:Version}),
|
||||||
|
librust-proxmox-log-0.1.0+default-dev (= ${binary:Version})
|
||||||
|
Description: Logging infrastructure for proxmox - Rust source code
|
||||||
|
Source code for Debianized Rust crate "proxmox-log"
|
18
proxmox-log/debian/copyright
Normal file
18
proxmox-log/debian/copyright
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
|
||||||
|
|
||||||
|
Files:
|
||||||
|
*
|
||||||
|
Copyright: 2019 - 2024 Proxmox Server Solutions GmbH <support@proxmox.com>
|
||||||
|
License: AGPL-3.0-or-later
|
||||||
|
This program 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.
|
||||||
|
.
|
||||||
|
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
7
proxmox-log/debian/debcargo.toml
Normal file
7
proxmox-log/debian/debcargo.toml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
overlay = "."
|
||||||
|
crate_src_path = ".."
|
||||||
|
maintainer = "Proxmox Support Team <support@proxmox.com>"
|
||||||
|
|
||||||
|
[source]
|
||||||
|
vcs_git = "git://git.proxmox.com/git/proxmox.git"
|
||||||
|
vcs_browser = "https://git.proxmox.com/?p=proxmox.git"
|
151
proxmox-log/src/file_logger.rs
Normal file
151
proxmox-log/src/file_logger.rs
Normal file
@ -0,0 +1,151 @@
|
|||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
use anyhow::Error;
|
||||||
|
use nix::fcntl::OFlag;
|
||||||
|
|
||||||
|
use proxmox_sys::fs::{atomic_open_or_create_file, CreateOptions};
|
||||||
|
|
||||||
|
/// Options to control the behavior of a [FileLogger] instance
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct FileLogOptions {
|
||||||
|
/// Open underlying log file in append mode, useful when multiple concurrent processes
|
||||||
|
/// want to log to the same file (e.g., HTTP access log). Note that it is only atomic
|
||||||
|
/// for writes smaller than the PIPE_BUF (4k on Linux).
|
||||||
|
/// Inside the same process you may need to still use an mutex, for shared access.
|
||||||
|
pub append: bool,
|
||||||
|
/// Open underlying log file as readable
|
||||||
|
pub read: bool,
|
||||||
|
/// If set, ensure that the file is newly created or error out if already existing.
|
||||||
|
pub exclusive: bool,
|
||||||
|
/// Duplicate logged messages to STDOUT, like tee
|
||||||
|
pub to_stdout: bool,
|
||||||
|
/// Prefix messages logged to the file with the current local time as RFC 3339
|
||||||
|
pub prefix_time: bool,
|
||||||
|
/// File owner/group and mode
|
||||||
|
pub file_opts: CreateOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log messages with optional automatically added timestamps into files
|
||||||
|
///
|
||||||
|
/// #### Example:
|
||||||
|
/// ```
|
||||||
|
/// # use anyhow::{bail, format_err, Error};
|
||||||
|
/// use proxmox_log::{flog, FileLogger, FileLogOptions};
|
||||||
|
///
|
||||||
|
/// # std::fs::remove_file("test.log");
|
||||||
|
/// let options = FileLogOptions {
|
||||||
|
/// to_stdout: true,
|
||||||
|
/// exclusive: true,
|
||||||
|
/// ..Default::default()
|
||||||
|
/// };
|
||||||
|
/// let mut log = FileLogger::new("test.log", options).unwrap();
|
||||||
|
/// flog!(log, "A simple log: {}", "Hello!");
|
||||||
|
/// # std::fs::remove_file("test.log");
|
||||||
|
/// ```
|
||||||
|
pub struct FileLogger {
|
||||||
|
file: std::fs::File,
|
||||||
|
file_name: std::path::PathBuf,
|
||||||
|
options: FileLogOptions,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Log messages to [FileLogger] - ``println`` like macro
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! flog {
|
||||||
|
($log:expr, $($arg:tt)*) => ({
|
||||||
|
$log.log(format!($($arg)*));
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileLogger {
|
||||||
|
/// Create a new FileLogger. This opens the specified file and
|
||||||
|
/// stores a file descriptor.
|
||||||
|
pub fn new<P: AsRef<std::path::Path>>(
|
||||||
|
file_name: P,
|
||||||
|
options: FileLogOptions,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let file = Self::open(&file_name, &options)?;
|
||||||
|
|
||||||
|
let file_name: std::path::PathBuf = file_name.as_ref().to_path_buf();
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
file,
|
||||||
|
file_name,
|
||||||
|
options,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reopen logfile.
|
||||||
|
pub fn reopen(&mut self) -> Result<&Self, Error> {
|
||||||
|
let file = Self::open(&self.file_name, &self.options)?;
|
||||||
|
self.file = file;
|
||||||
|
Ok(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn open<P: AsRef<std::path::Path>>(
|
||||||
|
file_name: P,
|
||||||
|
options: &FileLogOptions,
|
||||||
|
) -> Result<std::fs::File, Error> {
|
||||||
|
let mut flags = OFlag::O_CLOEXEC;
|
||||||
|
|
||||||
|
if options.read {
|
||||||
|
flags |= OFlag::O_RDWR;
|
||||||
|
} else {
|
||||||
|
flags |= OFlag::O_WRONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.append {
|
||||||
|
flags |= OFlag::O_APPEND;
|
||||||
|
}
|
||||||
|
if options.exclusive {
|
||||||
|
flags |= OFlag::O_EXCL;
|
||||||
|
}
|
||||||
|
|
||||||
|
let file =
|
||||||
|
atomic_open_or_create_file(&file_name, flags, &[], options.file_opts.clone(), false)?;
|
||||||
|
|
||||||
|
Ok(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Writes `msg` to the logfile with a timestamp and to stdout if
|
||||||
|
/// specified.
|
||||||
|
pub fn log<S: AsRef<str>>(&mut self, msg: S) {
|
||||||
|
let msg = msg.as_ref();
|
||||||
|
|
||||||
|
if self.options.to_stdout {
|
||||||
|
let mut stdout = std::io::stdout();
|
||||||
|
let _ = stdout.write_all(msg.as_bytes());
|
||||||
|
let _ = stdout.write_all(b"\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
let line = if self.options.prefix_time {
|
||||||
|
let now = proxmox_time::epoch_i64();
|
||||||
|
let rfc3339 = match proxmox_time::epoch_to_rfc3339(now) {
|
||||||
|
Ok(rfc3339) => rfc3339,
|
||||||
|
Err(_) => "1970-01-01T00:00:00Z".into(), // for safety, should really not happen!
|
||||||
|
};
|
||||||
|
format!("{rfc3339}: {msg}\n")
|
||||||
|
} else {
|
||||||
|
format!("{msg}\n")
|
||||||
|
};
|
||||||
|
// Note: we ignore the potential error here because log methods
|
||||||
|
// shouldn't panic. We also can't log an error, because that
|
||||||
|
// would lead to recursion.
|
||||||
|
let _ = self.file.write_all(line.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::io::Write for FileLogger {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> Result<usize, std::io::Error> {
|
||||||
|
if self.options.to_stdout {
|
||||||
|
let _ = std::io::stdout().write(buf);
|
||||||
|
}
|
||||||
|
self.file.write(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> Result<(), std::io::Error> {
|
||||||
|
if self.options.to_stdout {
|
||||||
|
let _ = std::io::stdout().flush();
|
||||||
|
}
|
||||||
|
self.file.flush()
|
||||||
|
}
|
||||||
|
}
|
48
proxmox-log/src/lib.rs
Normal file
48
proxmox-log/src/lib.rs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
use std::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
env,
|
||||||
|
};
|
||||||
|
|
||||||
|
use tracing::Level;
|
||||||
|
use tracing_log::{AsLog, LogTracer};
|
||||||
|
use tracing_subscriber::filter::{filter_fn, LevelFilter};
|
||||||
|
use tracing_subscriber::prelude::*;
|
||||||
|
|
||||||
|
use tasklog_layer::TasklogLayer;
|
||||||
|
|
||||||
|
mod file_logger;
|
||||||
|
pub use file_logger::{FileLogOptions, FileLogger};
|
||||||
|
|
||||||
|
mod tasklog_layer;
|
||||||
|
|
||||||
|
tokio::task_local! {
|
||||||
|
pub static LOGGER: RefCell<FileLogger>;
|
||||||
|
pub static WARN_COUNTER: Cell<u64>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init_logger(
|
||||||
|
env_var_name: &str,
|
||||||
|
default_log_level: LevelFilter,
|
||||||
|
_application_name: &str,
|
||||||
|
) -> Result<(), anyhow::Error> {
|
||||||
|
let mut log_level = default_log_level;
|
||||||
|
if let Ok(v) = env::var(env_var_name) {
|
||||||
|
if let Ok(l) = v.parse::<LevelFilter>() {
|
||||||
|
log_level = l;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let registry = tracing_subscriber::registry()
|
||||||
|
.with(
|
||||||
|
tracing_journald::layer()
|
||||||
|
.expect("Unable to open syslog")
|
||||||
|
.with_filter(log_level)
|
||||||
|
.with_filter(filter_fn(|metadata| {
|
||||||
|
LOGGER.try_with(|_| {}).is_err() || *metadata.level() == Level::ERROR
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
.with(TasklogLayer {}.with_filter(log_level));
|
||||||
|
|
||||||
|
tracing::subscriber::set_global_default(registry)?;
|
||||||
|
LogTracer::init_with_filter(log_level.as_log())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
58
proxmox-log/src/tasklog_layer.rs
Normal file
58
proxmox-log/src/tasklog_layer.rs
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
use std::fmt::Write as _;
|
||||||
|
|
||||||
|
use tracing::field::Field;
|
||||||
|
use tracing::field::Visit;
|
||||||
|
use tracing::Event;
|
||||||
|
use tracing::Level;
|
||||||
|
use tracing::Subscriber;
|
||||||
|
use tracing_subscriber::layer::Context;
|
||||||
|
use tracing_subscriber::Layer;
|
||||||
|
|
||||||
|
use crate::FileLogger;
|
||||||
|
use crate::LOGGER;
|
||||||
|
use crate::WARN_COUNTER;
|
||||||
|
|
||||||
|
pub struct TasklogLayer;
|
||||||
|
|
||||||
|
impl<S: Subscriber> Layer<S> for TasklogLayer {
|
||||||
|
fn on_event(&self, event: &Event<'_>, _ctx: Context<'_, S>) {
|
||||||
|
let _result = LOGGER.try_with(|logger| {
|
||||||
|
let mut buf = String::new();
|
||||||
|
event.record(&mut EventVisitor::new(&mut buf));
|
||||||
|
let level = event.metadata().level();
|
||||||
|
log_to_file(&mut logger.borrow_mut(), level, &buf);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log_to_file(logger: &mut FileLogger, level: &Level, buf: &String) {
|
||||||
|
match *level {
|
||||||
|
Level::ERROR | Level::WARN => {
|
||||||
|
WARN_COUNTER.with(|counter| {
|
||||||
|
counter.set(counter.get() + 1);
|
||||||
|
});
|
||||||
|
logger.log(buf);
|
||||||
|
}
|
||||||
|
Level::INFO => logger.log(buf),
|
||||||
|
Level::DEBUG => logger.log(format!("DEBUG: {buf}")),
|
||||||
|
Level::TRACE => logger.log(format!("TRACE: {buf}")),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EventVisitor<'a> {
|
||||||
|
buf: &'a mut String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> EventVisitor<'a> {
|
||||||
|
fn new(buf: &'a mut String) -> Self {
|
||||||
|
Self { buf }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Visit for EventVisitor<'_> {
|
||||||
|
fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
|
||||||
|
if field.name() == "message" {
|
||||||
|
let _ = write!(self.buf, "{value:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user