13 个版本
0.3.1 | 2024 年 3 月 5 日 |
---|---|
0.3.0 | 2023 年 1 月 13 日 |
0.2.1 | 2023 年 1 月 5 日 |
0.1.8 | 2023 年 1 月 1 日 |
0.1.7 | 2022 年 12 月 29 日 |
#113 in 调试
298 每月下载量
135KB
797 行
Traceon - trace on
一个易于使用的日志和跟踪格式化工具,具有扁平化 JSON 或美观输出。
它建立在并简化了 tracing
crate 上,该 crate 为日志消息添加上下文,以增强可观察性,尤其是在异步或多线程环境中,它侧重于以扁平化 JSON 或美观格式打印每个事件的关联上下文,并且不关注跨度时间 这可以在单独的层中完成,例如 opentelemetry,如果需要的话
您可以这样编写您的第一个跟踪
traceon::on();
traceon::info!("a simple message");
12:12:37 INFO a simple message
以捕获函数中使用的参数
#[traceon::instrument]
fn add(a: i32, b: i32) {
traceon::info!("result = {}", a + b);
}
fn main() {
traceon::on();
add(5, 10);
}
输出
09:52:33 INFO result = 15
a: 5
b: 10
span: add
要使用 JSON 输出
traceon::json();
每个事件输出一行扁平化 JSON,此示例已美化
{
"time": "2023-01-01T05:28:13.447Z",
"level": "INFO",
"message": "result: 15",
"a": 5,
"b": 10,
"span": "add"
}
如果您想更改任何默认设置,可以通过构建器进行配置
use traceon::{Case, JoinFields, LevelFormat, SecondsFormat, SpanFormat, TimeFormat, TimeZone, info};
fn main() {
traceon::builder()
// Add field with source code filename and line number e.g. src/main.rs:10
.file()
// Add field with target and module path e.g. mybinary::mymodule::submodule
.module()
// Turn off field with joined span name where the event occured e.g. parentspan::childspan
.span(SpanFormat::None)
// If the time is recorded in local system timezone or UTC
.timezone(TimeZone::UTC)
// Change the formatting of the time to RFC3339 with Seconds and Zulu
.time(TimeFormat::RFC3339Options(SecondsFormat::Secs, true))
// Change the casing of all the key names e.g. `camelCase` to `snake_case`
.case(Case::Snake)
// The characters used to concatenate field values that repeat in nested spans. Defaults to overwrite.
.join_fields(JoinFields::All("::"))
// Turn on json formatting instead of pretty output
.json()
// Change level value formatting to numbers for easier filtering
// trace: 10
// debug: 20
// info: 30
// warn: 40
// error: 50
.level(LevelFormat::Number)
// Put anything that implements `Write` here to redirect output
.writer(std::io::stderr())
// on() activates it globally on all threads and panic if a global subcriber is already set
// try_on() will return an error if a global subscriber is already set
// on_thread() will return a guard so the subscriber will only be active in the current scope and thread
.on();
info!("a simple message");
}
输出
{
"timestamp": "2023-01-01T03:26:48Z",
"level": 30,
"module": "builder",
"file": "examples/builder.rs:27",
"message": "a simple message"
}
env-filter
默认在 info
级别使用,用于在 debug
或 trace
级别过滤任何消息,要更改级别,您可以设置环境变量,例如 RUST_LOG=warn
,这将过滤掉 info
级别的消息,或者 RUST_LOG=trace
以显示所有事件。有关详细信息,请参阅 此处
示例
#[instrument] 宏
您可以使用 traceon::instrument
宏,在异步函数和普通函数中捕获每个函数调用所使用的参数。
use traceon::{instrument, info};
#[instrument]
async fn add(a: i32, b: i32) {
info!("result: {}", a + b);
}
#[tokio::main]
async fn main() {
traceon::builder().on();
add(5, 10).await;
}
06:16:30 INFO result: 15
a: 5
b: 10
span: add
仪器特质
如果您需要向异步函数添加额外的上下文,可以创建一个跨度并对其进行仪器化。
use tracing::{Instrument, info};
async fn add(a: i32, b: i32) {
tracing::info!("result: {}", a + b);
}
#[tokio::main]
async fn main() {
traceon::builder().on();
let span = tracing::info_span!("math_functions", package_name = env!("CARGO_PKG_NAME",));
add(5, 10).instrument(span).await;
}
06:18:55 INFO result: 15
package_name: traceon
span: math_functions
上面的 package_name
来自编译时在 Cargo.toml
中,并在运行时保存到二进制文件中。
[package]
name = "traceon"
进入的跨度
这会创建一个跨度并返回一个保护器,只要这个保护器在作用域内,跨度就会处于活动状态。
fn add(a: i32, b: i32) {
tracing::info!("result: {}", a + b);
}
fn main() {
traceon::builder().on();
let _guard = tracing::info_span!("math", package_name = env!("CARGO_PKG_NAME")).entered();
add(5, 10);
}
06:26:21 INFO result: 15
package_name: traceon
span: math
警告 如果
add()
是一个async fn
,保持保护器会导致内存泄漏和信息丢失,您必须使用上面的traceon::instrument
宏或特质,有关详细信息,请参阅 此处
注意 记住不要在保持保护器时调用
.await
嵌套跨度
默认情况下,跨度名称将以字符 ::
连接,用于嵌套跨度。
use traceon::{instrument, info};
#[instrument]
fn add(a: i32, b: i32) {
tracing::info!("result: {}", a + b);
}
fn main() {
traceon::builder().on();
let _guard = tracing::info_span!("math", package_name = env!("CARGO_PKG_NAME")).entered();
add(5, 10);
}
06:33:57 INFO result: 15
a: 5
b: 10
package_name: traceon
span: math::add
您可以将其设置为覆盖,如果您愿意的话。
use traceon::SpanFormat;
traceon::builder().span(SpanFormat::Overwrite).on();
06:36:00 INFO result: 15
a: 5
b: 10
package_name: traceon
span: add
默认情况下,如果嵌套跨度具有相同的字段名称,则所有其他字段都会覆盖,您可以连接所有字段或特定字段,如果您愿意的话(这仅影响文本值)。
use traceon::{JoinFields, info, info_span};
traceon::builder()
.join_fields(JoinFields::Some("||", &["field_b"]))
.on();
let _span_1 = info_span!("span_1", field_a = "original", field_b = "original").entered();
let _span_2 = info_span!("span_2", field_a = "changed", field_b = "changed").entered();
info!("testing field join");
输出
12:44:12 INFO testing field join
field_a: changed
field_b: original||changed
span: span_1::span_1
更改键的大小写
通常,您会消费不同的 crate,这些 crate 实现了自己的跟踪,并且您需要它们的键匹配某种格式,此示例还演示了如何使用 on_thread()
使用 traceon::instrument
,它返回一个保护器,以便订阅者仅在当前线程上运行,当保护器释放时将关闭。
use traceon::{Case, Level, event};
fn main() {
let _guard = traceon::builder().case(Case::Pascal).on_thread();
event!(
Level::INFO,
message = "PascalCase",
PascalCase = "test",
camelCase = "test",
snake_case = "test",
SCREAMING_SNAKE_CASE = "test",
);
let _guard = traceon::builder().case(Case::Camel).on_thread();
event!(
Level::INFO,
message = "camelCase",
PascalCase = "test",
camelCase = "test",
snake_case = "test",
SCREAMING_SNAKE_CASE = "test",
);
let _guard = traceon::builder().case(Case::Snake).on_thread();
event!(
Level::INFO,
message = "snake_case",
PascalCase = "test",
camelCase = "test",
snake_case = "test",
SCREAMING_SNAKE_CASE = "test",
);
}
输出
10:06:38 INFO PascalCase
CamelCase: test
PascalCase: test
ScreamingSnakeCase: test
SnakeCase: test
10:06:38 INFO camelCase
camelCase: test
pascalCase: test
screamingSnakeCase: test
snakeCase: test
10:06:38 INFO snake_case
camel_case: test
pascal_case: test
screaming_snake_case: test
snake_case: test
事件
tracing::event!
允许您在不创建跨度的情况下向消息添加字段,只需记住将级别(例如 tracing::Level::INFO
)作为第一个参数即可,这也展示了如何在事件中创建自定义消息,以及如何输出 Debug
实现。
use tracing::{Level, event};
fn main() {
traceon::builder().on();
event!(
Level::INFO,
event_example = "add field and log it without a span"
);
let vector = vec![10, 15, 20];
event!(
Level::WARN,
message = "overwrite message, and debug a vector",
?vector,
);
}
06:47:00 INFO event triggered
event_example: add field and log it without a span
06:47:00 WARN overwrite message, and debug a vector
vector: [10, 15, 20]
写入文件
如果您想将日志写入文件而不是标准输出,只需将依赖项添加到 Cargo.toml
。
[dependencies]
tracing-appender = "0.2.2"
并通过构建器初始化它
use traceon::info;
let file_appender = tracing_appender::rolling::hourly("./", "test.log");
traceon::builder().json().writer(file_appender).on();
info!("wow cool!");
写入器接受实现 Write
特质的任何内容,如果您想保持一个包裹在 Arc
和 Mutex
中的缓冲区,构建器上有 buffer()
方法。
与其他层组合
您还可以使用格式化层与其他跟踪层一起使用,因为您越来越熟悉跟踪生态系统,例如,添加 opentelemetry。
use opentelemetry::trace::TracerProvider as _;
use opentelemetry_sdk::trace::TracerProvider;
use opentelemetry_stdout as stdout;
use tracing::{info, span};
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::Registry;
fn main() {
let provider = TracerProvider::builder()
.with_simple_exporter(stdout::SpanExporter::default())
.build();
let tracer = provider.tracer("readme_example");
let telemetry = tracing_opentelemetry::layer().with_tracer(tracer);
// Compose opentelemetry with traceon
let subscriber = Registry::default().with(telemetry).with(traceon::builder());
tracing::subscriber::with_default(subscriber, || {
let root = span!(tracing::Level::TRACE, "app_start", work_units = 2);
let _enter = root.enter();
info!(
"This will log the full span data to stdout via opentelemetry \
along with the simplified and flattened data using traceon"
);
});
}
性能
此 crate 使用来自: LukeMathWalker/tracing-bunyan-formatter 的想法,将访问过的跨度中的字段存储在 HashMap
中,而不是更适合扁平化字段的 BTreeMap
,这导致与 tracing-subscriber
中的 json 格式化器非常相似的性能。
将日志记录到接收器
单位 = 纳秒或秒的十亿分之一
将日志记录到标准输出
单位 = 微秒或百万分之一秒
嵌套三层并合并字段的span
依赖项
~6–18MB
~193K SLoC