Make Tracing Great Again.
cargo add actaThe default feature set enables Unicode console output and file logging.
cargo add acta --features serde,compress,nerd,asyncImport the types you need directly:
use acta::{init, Config, Result};
fn main() -> Result<()> {
let _guard = init(Config::default())?;
tracing::info!("Hello, acta!");
tracing::debug!(user = "alice", "User logged in");
Ok(())
}Or use the prelude for convenience:
use acta::prelude::*;
fn main() -> Result<()> {
let _guard = init(Config::default())?;
tracing::info!("Hello, acta!");
tracing::debug!(user = "alice", "User logged in");
Ok(())
}Keep the returned guard alive for as long as logging is needed. Dropping it stops file logging.
| Feature | Enabled by default | Description |
|---|---|---|
unicode |
Yes | Uses the Unicode icon set unless nerd selects Nerd Font icons. |
file |
Yes | Enables init, TracingGuard, build_file_layer, and file logging through tracing-appender. |
compress |
No | Enables Rotation::Compress for gzip-compressing old log files. |
serde |
No | Adds Serialize / Deserialize support for config types. |
nerd |
No | Enables Nerd Font icons through Icons::NERD and uses them by default. |
custom-async |
No | Enables Tokio-backed async console writers and exports AsyncWriter helpers. |
native-async |
No | Enables non-blocking console writers backed by tracing-appender. |
async |
No | Enables both custom-async and native-async. |
If you disable default features, init is unavailable unless the file feature is enabled.
Config::default() uses:
- Level:
Level::Info - Format:
Format::Compact(custom formatter with themes) - Writer:
Writer::Stdoutwith ANSI colors enabled - Path and span display: enabled
- File logging: disabled
use acta::{
init, Format, Level, Config, Result, Writer,
};
fn main() -> Result<()> {
let config = Config {
level: Level::Debug,
writers: vec![Writer {
format: Format::Compact,
ansi: true,
show_path: true,
show_spans: true,
time_format: Some("%Y-%m-%d %H:%M:%S".to_string()),
..Default::default()
}],
..Default::default()
};
let _guard = init(config)?;
Ok(())
}| Format | Description |
|---|---|
Format::Compact |
Default themed formatter with optional path and span display. |
Format::Pretty |
tracing-subscriber pretty formatter with file and line metadata. |
Format::Json |
Flattened JSON events without ANSI colors. |
File logging is available with the file feature, which is enabled by default. File logs are written as flattened JSON
events.
use acta::{
init, Level, Config, Result, Rotation, Writer, WriterTarget,
};
use std::path::PathBuf;
fn main() -> Result<()> {
let config = Config {
level: Level::Info,
writers: vec![Writer {
format: Format::Compact,
target: WriterTarget::File {
path: PathBuf::from("logs/app.log"),
rotation: Rotation::Rename,
},
..Default::default()
}],
..Default::default()
};
let _guard = init(config)?;
Ok(())
}Supported rotation modes:
| Mode | Description |
|---|---|
Rotation::None |
Keeps the existing log file. |
Rotation::Rename |
Renames the existing log file with a timestamp before opening a new one. |
Rotation::Compress |
Compresses the existing log file to gzip before opening a new one. Requires compress. |
acta uses tracing-subscriber EnvFilter directive syntax for startup filters and runtime reloads.
use acta::{Level, Config};
let config = Config {
level: Level::Custom("info,my_crate=debug,my_crate::db=trace".to_owned()),
..Default::default()
};You can change filters after initialization directly through TracingGuard.
use acta::{init, Filter, Level, Config, Result};
fn main() -> Result<()> {
let mut guard = init(Config::default())?;
guard.set_level(Level::Debug)?;
guard.set_target_level("my_crate", Level::Trace)?;
guard.remove_target_level("my_crate")?;
guard.reload("info,my_crate=trace")?;
guard.set_filter(
Filter::new(Level::Warn).with_target("my_crate", Level::Debug),
)?;
Ok(())
}RUST_LOG is not read automatically. If you want to use it, pass its value into Level::Custom.
use acta::{Level, Config};
let directive = std::env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string());
let config = Config {
level: Level::Custom(directive),
..Default::default()
};Formatter powers Format::Compact and can be customized through builder methods.
use acta::{Formatter, Icons, LevelLabels, Theme};
let formatter = Formatter::new()
.with_theme(Theme::tokyo_night())
.with_icons(Icons::UNICODE)
.with_labels(LevelLabels::long())
.with_time_format("%H:%M:%S")
.with_show_path(true)
.with_show_spans(true);The default path width is generated at build time.
| Theme | Description |
|---|---|
Theme::acta() |
Default |
Theme::monokai() |
Monokai |
Theme::dracula() |
Dracula |
Theme::nord() |
Nord |
Theme::catppuccin_mocha() |
Catppuccin Mocha |
Theme::gruvbox() |
Gruvbox |
Theme::one_dark() |
One Dark |
Theme::tokyo_night() |
Tokyo Night |
Create custom themes from RGB values:
use acta::Theme;
let custom = Theme::new(
(91, 206, 250), // accent
(245, 169, 184), // secondary
(255, 255, 255), // text
(255, 85, 85), // error
(255, 200, 60), // warn
(91, 206, 250), // info
(245, 169, 184), // debug
(240, 240, 240), // trace
);use acta::{Icons, LevelLabels};
let unicode_icons = Icons::UNICODE;
let short_labels = LevelLabels::short();
let long_labels = LevelLabels::long();With the nerd feature enabled:
use acta::Icons;
let nerd_icons = Icons::NERD;Custom icons and labels:
use acta::{Icons, LevelLabels};
let custom_icons = Icons::custom("custom", "[", "]", "{", "}", "|", ">", "->", "·");
let custom_labels = LevelLabels::custom("ERR", "WRN", "INF", "DBG", "TRC");TracingGuard can reload themes, icons, and labels via with_style directly on the returned guard:
use acta::{init, Config, Theme, Result};
fn main() -> Result<()> {
let mut guard = init(Config::default())?;
// Reload the theme dynamically at runtime!
guard.with_style(|s| s.theme = Theme::dracula());
Ok(())
}If you build the subscriber manually, build_reload_filter returns a TracingGuard that also supports style reloading:
use acta::{
build_reload_filter, Formatter, Level, Result,
Theme, Style, Writer,
};
use tracing_subscriber::prelude::*;
fn main() -> Result<()> {
let formatter = Formatter::new().with_theme(Theme::monokai());
let style = *formatter.style_config();
let console_layer = tracing_subscriber::fmt::Layer::default()
.with_writer(std::io::stdout)
.event_format(formatter)
.boxed();
let (filter_layer, mut guard) = build_reload_filter(Level::Info, style);
let subscriber = tracing_subscriber::registry()
.with(console_layer)
.with(filter_layer);
tracing::subscriber::set_global_default(subscriber)?;
guard.with_style(|s| s.theme = Theme::dracula());
Ok(())
}This low-level setup requires adding tracing-subscriber as a direct dependency.
With custom-async, native-async, or async, Writer gains async stdout and stderr variants.
AsyncMode::Custom uses Tokio, so your application must run inside a Tokio runtime. If you use #[tokio::main],
add Tokio as a direct dependency with the required runtime and macro features.
use acta::{
init, AsyncMode, Config, Result, Writer, WriterTarget,
};
#[tokio::main]
async fn main() -> Result<()> {
let config = Config {
writers: vec![Writer {
target: WriterTarget::AsyncStdout(AsyncMode::Custom),
..Default::default()
}],
..Default::default()
};
let _guard = init(config)?;
Ok(())
}AsyncMode::Native uses tracing-appender non-blocking writers.