Skip to content

Conversation

@NotEvenANeko
Copy link
Member

@NotEvenANeko NotEvenANeko commented Sep 5, 2025

Refactor tracing to spdlog-rs for logging.

This closes #486 .

cc @SpriteOvO

@eatradish
Copy link
Member

eatradish commented Sep 5, 2025

Initially it looks good, but what I'm wondering is whether we should just replace the tracing family directly with spdlog instead of optional?

@NotEvenANeko
Copy link
Member Author

Yeah we should replace tracing with spdlog, current codes is just convenient for debugging. I'll delete the tracing codes after spdlog parts are good to go.

Copy link
Member

@SpriteOvO SpriteOvO left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a brief look and most of it seems to be fine. I'll probably merge SpriteOvO/spdlog-rs#93 in the next couple days. In the meantime, you can try removing the use of tracing from oma entirely.

src/main.rs Outdated
Comment on lines 304 to 321
let rotating_sink = if let Ok(log_file) = &log_file {
Some(
AsyncPoolSink::builder()
.sink(Arc::new(
RotatingFileSink::builder()
.base_path(&log_file)
.formatter(formatter.clone())
.rotation_policy(RotationPolicy::Hourly)
.build()
.unwrap(),
))
.overflow_policy(spdlog::sink::OverflowPolicy::DropIncoming)
.build()
.unwrap(),
)
} else {
None
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RotatingFileSink should not rotate hourly as oma did not do so. Could try RotationPolicy::FileSize(u64::MAX) and set max_files(config.save_log_count()).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, file size is set to 10MB.

src/main.rs Outdated
.join("oma")
};

let log_file = create_log_file(&log_dir);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating variants for filenames has been done internally by spdlog-rs, so oma no longer needs to create names manually.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

@SpriteOvO
Copy link
Member

FYI, SpriteOvO/spdlog-rs#93 is merged.

@NotEvenANeko
Copy link
Member Author

I'll work on this PR tomorrow.

@NotEvenANeko
Copy link
Member Author

spdlog-rs is set to main-dev branch now.

Comment on lines 147 to 148
/// StdStreamSink::builder().formatter(Box::new(OmaLayer::default())).build().unwrap()
/// )).build().unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is preferred that unwrap() not be used inside an example, and some of the error handling components be hidden if they make the example too difficult to follow.

Quote from https://doc.rust-lang.org/rustdoc/write-documentation/what-to-include.html?highlight=unwrap#examples

dest.write_str(&console::strip_ansi_codes(record.payload()))?;
}

dest.write_char('\n')?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we should write out the key-values of the record here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where we place the KV data? Just append it to the end of the log? 🤔

And it looks messy if we output the KV data in stdout.

Copy link
Member

@SpriteOvO SpriteOvO Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let mut visitor = OmaRecorder(BTreeMap::new());
event.record(&mut visitor);
for (k, v) in visitor.0 {
if k == "message" {
if self.with_ansi {
self.writer.writeln(&prefix, &v).ok();
} else {
self.writer
.writeln(&prefix, &console::strip_ansi_codes(&v))
.ok();
}
}
}
}
}
/// OmaRecorder
/// `OmaRecorder` is used for recording oma-style logs.
///
/// # Example:
/// ```ignore
/// let mut visitor = OmaRecorder(BTreeMap::new());
/// event.record(&mut visitor);
/// for (k, v) in visitor.0 {
/// if k == "message" {
/// self.writer.writeln(&prefix, &v).ok();
/// }
/// }
/// ```
struct OmaRecorder<'a>(BTreeMap<&'a str, String>);
impl tracing::field::Visit for OmaRecorder<'_> {
fn record_f64(&mut self, field: &Field, value: f64) {
self.0.insert(field.name(), value.to_string());
}
fn record_i64(&mut self, field: &Field, value: i64) {
self.0.insert(field.name(), value.to_string());
}
fn record_u64(&mut self, field: &Field, value: u64) {
self.0.insert(field.name(), value.to_string());
}
fn record_bool(&mut self, field: &Field, value: bool) {
self.0.insert(field.name(), value.to_string());
}
fn record_str(&mut self, field: &Field, value: &str) {
self.0.insert(field.name(), value.to_string());
}
fn record_error(&mut self, field: &Field, value: &(dyn std::error::Error + 'static)) {
self.0.insert(field.name(), format!("{value:#?}"));
}
fn record_debug(&mut self, field: &Field, value: &dyn std::fmt::Debug) {
self.0.insert(field.name(), format!("{value:#?}"));
}
}

Since before this PR, oma is already used OmaRecorder to write KV to the end of a log record, I think keeping the behavior should be fine.

Some dep crates may completely use KV to recording variables instead of formatting into the payload.

Copy link
Member

@SpriteOvO SpriteOvO Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, seems I understand it incorrectly, oma only cares value with "message" as the key.

My bad, let's just keep the old behavior. You can use KeyValues::get method to get value with "message" as the key.

EDIT: NVM, tracing uses "message" KV to store payload, the current code works the same as before already, so looks good to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll leave the original implementation here, which is not to print KV

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The with_kv field is added to formatter but unused if we need to print KV later.

src/main.rs Outdated
Comment on lines 39 to 41
// FIXME: `spdlog::error` is conflict with `mod error`
use spdlog::{
Level, LevelFilter, Logger, debug, default_logger, error as error2, info, set_default_logger,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is definitely a mistake in spdlog-rs, I haven't figure out the right way to fix it. For now, we can import the error! macro from spdlog::prelude::error.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use spdlog:prelude:error has the same issue

Copy link
Member

@SpriteOvO SpriteOvO Oct 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, so it's not a conflict between spdlog::error mod and spdlog::error! macro? So, if not, then it's probably should not consider as a mistake from spdlog-rs.

Okay, let's just keep the alias here. I'll suggest another name error as err.

src/main.rs Outdated
Comment on lines 286 to 287
.to_string_lossy()
.to_string();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, since spdlog-rs accepts a path type instead of a string type, I think we should place the conversion to the end of the function.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed

let level_filter = LevelFilter::MoreSevereEqual(Level::Info);

if !debug && !dry_run {
let no_i18n_embd: EnvFilter = "i18n_embed=off,info".parse().unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we have not setup the log crate proxy in this PR. The logs from log crate will only be available after we set it up via spdlog::init_log_crate_proxy().

And we really should migrate env filters, but spdlog-rs currently does not support filter logs from log crate, we will probably support this feature in the next minor release.

For now, is it acceptable for not migrate this at the moment? @eatradish

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like we have not setup the log crate proxy in this PR. The logs from log crate will only be available after we set it up via spdlog::init_log_crate_proxy().

And we really should migrate env filters, but spdlog-rs currently does not support filter logs from log crate, we will probably support this feature in the next minor release.

For now, is it acceptable for not migrate this at the moment? @eatradish

Yes, but subsequent releases may include this feature as a breaking

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

init_log_crate_proxy() added.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Env filter for log crate is added in spdlog-rs v0.5.2 (docs), so filter directive i18n_embed=off can now be migrated! :D

@NotEvenANeko Please also adding this in the PR.

@SpriteOvO
Copy link
Member

FYI, we have just released spdlog-rs v0.5.0. You could target the dependency spdlog-rs in this PR to the crates.io registry now.

@NotEvenANeko
Copy link
Member Author

Updated to v0.5.0

@NotEvenANeko NotEvenANeko force-pushed the refactor/spdlog branch 4 times, most recently from 125bbbf to ecaf9a1 Compare October 5, 2025 17:39
@NotEvenANeko
Copy link
Member Author

Doc tests should be fixed now

@NotEvenANeko
Copy link
Member Author

Ok I think clippy check is passed now...

Copy link
Member

@SpriteOvO SpriteOvO left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically LGTM, @eatradish is there anything you are still concerned about?

@eatradish
Copy link
Member

Basically LGTM, @eatradish is there anything you are still concerned about?

LGTM, I'll test the functionality tomorrow

@eatradish
Copy link
Member

image

80col limit feature is broken

.writeln(&prefix, &console::strip_ansi_codes(&v))
.ok();
time
}
Copy link
Member

@SpriteOvO SpriteOvO Nov 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the 80col limit feature was implemented in oma_console::Writer, the original Writer directly writes to the IO handle stdout or stderr, probably we should implement one that writes to a string buf for spdlog-rs.

@NotEvenANeko Could you make time to have a look at it and try to fix?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be fixed now.

@NotEvenANeko
Copy link
Member Author

I'm so sorry for AFK for 3 weeks, I'm busy at study recently so I don't have much time for this, really sorry for this :(

I'm continue this PR now.

After reviewing the code I think that if we can migrate console.rs/src/writer.rs to spdlog-rs? Since the Writer directly use console crate to print the log.

We can migrate all the Writer functions and behavior to OmaFormatter and remove Writer.

Will it be a good idea? 🤔

@SpriteOvO
Copy link
Member

SpriteOvO commented Nov 8, 2025

@NotEvenANeko

We can migrate all the Writer functions and behavior to OmaFormatter and remove Writer.

Will it be a good idea? 🤔

I think putting the behavior "writing to console" into a formatter is not an ideal design. Why don't we just replace the field term: Term in Writer with buf: String, then return it from the formatter?

@NotEvenANeko
Copy link
Member Author

Not the print to console behavior, I mean that we can move the formatting part into OmaFormatter, and the printing part is obviously moved to StdStreamSink. However we may need information against terminal, I think we can make an OmaStdoutFormatter to handle terminal-related formatting instead of leaving these parts mixed together in Writer.

@SpriteOvO
Copy link
Member

Not the print to console behavior, I mean that we can move the formatting part into OmaFormatter, and the printing part is obviously moved to StdStreamSink. However we may need information against terminal, I think we can make an OmaStdoutFormatter to handle terminal-related formatting instead of leaving these parts mixed together in Writer.

I think it's acceptable.

@NotEvenANeko NotEvenANeko force-pushed the refactor/spdlog branch 2 times, most recently from 232f031 to 1ca1cb5 Compare January 12, 2026 15:09
@NotEvenANeko
Copy link
Member Author

image

80col limit feature is broken

Should be fixed now. Column wrap logic is extract to oma_console::terminal::wrap_content to reuse.

@NotEvenANeko
Copy link
Member Author

It should look like this. I don't know if this error is normal for Debian 12 in Docker.

photo_6280735899528138431_w

Copy link
Member

@SpriteOvO SpriteOvO left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left some reviews, though I didn't test it locally, maybe @eatradish wanna check the output's appearance.

Comment on lines 254 to 259
Level::Debug => console::style("DEBUG").dim().to_string(),
Level::Info => console::style("INFO").blue().bold().to_string(),
Level::Warn => console::style("WARNING").yellow().bold().to_string(),
Level::Error => console::style("ERROR").red().bold().to_string(),
Level::Trace => console::style("TRACE").dim().to_string(),
Level::Critical => console::style("CRITICAL").red().bright().bold().to_string(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eatradish Just saying, if you care about performance, here is a good optimization point, by caching these string into a immutable array. However, since the previous also used to_string, so I think this can be done in future PRs, not in this one.

Same thing for the to_string uses below.

let level_filter = LevelFilter::MoreSevereEqual(Level::Info);

if !debug && !dry_run {
let no_i18n_embd: EnvFilter = "i18n_embed=off,info".parse().unwrap();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Env filter for log crate is added in spdlog-rs v0.5.2 (docs), so filter directive i18n_embed=off can now be migrated! :D

@NotEvenANeko Please also adding this in the PR.

(level_filter, formatter)
} else {
let filter = EnvFilter::try_from_default_env()
.unwrap_or_else(|_| "hyper=off,rustls=off,debug".parse().unwrap());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same thing for hyper=off,rustls=off directives.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added. First we look for RUST_LOG environment variable as what tracing_subscriber does, if RUST_LOG is empty we use the hyper=off,rustls=off,debug directive for debug or dry run.

For any case besides debug and dry run we use i18n_embed=off,info

Comment on lines +322 to +341
// 10 MB
.rotation_policy(RotationPolicy::FileSize(10 * 1024 * 1024))
Copy link
Member

@SpriteOvO SpriteOvO Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this PR, oma does not limit the single log file size, is 10MB for a single log file makes sense to you? @eatradish

If not, we could simply change to u64::MAX here :)

Copy link
Member

@eatradish eatradish Jan 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before this PR, oma does not limit the single log file size, is 10MB for a single log file makes sense to you? @eatradish

If not, we could simply change to u64::MAX here :)

Agree 10MiB

.build()
.unwrap(),
))
.overflow_policy(spdlog::sink::OverflowPolicy::DropIncoming)
Copy link
Member

@SpriteOvO SpriteOvO Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tracing also uses the same policy, quoting from their docs:

The queue has a fixed capacity, and if it becomes full, any logs written to it will be dropped until capacity is once again available.

So from the perspective of migration, this is fine. 👍

logger_builder
.level_filter(level_filter)
.sink(Arc::new(stream_sink))
.sink(Arc::new(rotating_sink));
Copy link
Member

@SpriteOvO SpriteOvO Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we have wrapped RotatingFileSink with AsyncPoolSink, writing logs to file will not block the current thread, we can set the flush level filter for the logger to All, for flushing the buffer in time.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

"{}",
color_formatter().color_str(body.trim(), Action::Secondary)
)
// Keep original behavior (?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is fine as long as we can display the same results as before.

/// `OmaLayer` is used for outputting oma-style logs to `tracing`

// '1970-01-01T00:00:00Z' costs 24 chars, and a space costs 1.
const TIME_RFC3339_LEN: u16 = 24 + 1;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So it should be 20 + 1?

Or we could play some fancy feature here.

Suggested change
const TIME_RFC3339_LEN: u16 = 24 + 1;
const TIME_RFC3339_LEN: usize = "1970-01-01T00:00:00Z ".len();

It is a const variable, so it computes at compile-time.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I forgot to count the milliseconds part...

@eatradish
Copy link
Member

LGTM output style:

图片

Copy link
Member

@SpriteOvO SpriteOvO left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. If @eatradish has no further concerns, I feel this PR can be merged.

EDIT: Oh, looks like CI clippy failed, just one more tiny fix and a rebase needed.

@eatradish
Copy link
Member

Thanks!

@eatradish eatradish merged commit 61b3f5f into AOSC-Dev:master Jan 13, 2026
3 checks passed
@SpriteOvO
Copy link
Member

@eatradish Thanks for your reviews and @NotEvenANeko's hard work on this.

If you experience any issues while using spdlog-rs in oma in the future, feel free to CC me and @NotEvenANeko :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace the current logging system with spdlog-rs

4 participants