From d0ed79a868daa1dc26005922fb5f6ed1a1a3f9f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Ml=C3=A1dek?= Date: Thu, 13 Jun 2024 23:22:18 +0200 Subject: [PATCH] finish docs and hide some things not ready to be made public --- .github/workflows/ci.yaml | 2 +- Cargo.lock | 4 +- README.md | 27 +++++++++++ src/fields.rs | 2 +- src/fmt/builder.rs | 2 + src/fmt/layer.rs | 30 ++++++++++-- src/fmt/mod.rs | 15 ++++++ src/layer/mod.rs | 11 ++++- src/lib.rs | 98 ++++++++++++++++++++++++++++++++++++++- 9 files changed, 181 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 71f13a3..e7e8f79 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -35,7 +35,7 @@ jobs: - uses: Swatinem/rust-cache@v2 - name: cargo doc env: - RUSTDOCFLAGS: "-D rustdoc::all -A rustdoc::private-doc-tests" + RUSTDOCFLAGS: "-D missing_docs -D rustdoc::all -A rustdoc::private-doc-tests" run: cargo doc --all-features --no-deps cargo-hack: diff --git a/Cargo.lock b/Cargo.lock index 510bc06..a8ce065 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -362,9 +362,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "6d0d8b92cd8358e8d229c11df9358decae64d137c5be540952c5ca7b25aea768" [[package]] name = "nu-ansi-term" diff --git a/README.md b/README.md index bf0571f..f89be58 100644 --- a/README.md +++ b/README.md @@ -95,6 +95,33 @@ This will produce log lines like for example this (without the formatting): See the `readme-opentelemetry` example for full code. +### Custom + +You can also specify custom static fields to be added to each log line, or serialize extensions provided by other `Layer`s: + +```rust +#[derive(Serialize)] +struct Foo(String); + +impl LookupSpan<'lookup>> Layer for FooLayer { + fn on_new_span(&self, attrs: &Attributes<'_>, id: &Id, ctx: Context<'_, S>) { + let span = ctx.span(id).unwrap(); + let mut extensions = span.extensions_mut(); + let foo = Foo("hello".to_owned()); + extensions.insert(foo); + } +} + +fn main() { + let foo_layer = FooLayer; + + let mut layer = json_subscriber::JsonLayer::stdout(); + layer.serialize_extension::("foo"); + + registry().with(foo_layer).with(layer); +} +``` + ## Supported Rust Versions `json-subscriber` is built against the latest stable release. The minimum supported version is 1.65. diff --git a/src/fields.rs b/src/fields.rs index e7fb3af..a5db7f1 100644 --- a/src/fields.rs +++ b/src/fields.rs @@ -1,7 +1,7 @@ use std::{collections::BTreeMap, sync::Arc}; #[derive(Debug, Default)] -pub struct JsonFields { +pub(crate) struct JsonFields { pub(crate) fields: BTreeMap<&'static str, serde_json::Value>, pub(crate) version: usize, pub(crate) serialized: Option>, diff --git a/src/fmt/builder.rs b/src/fmt/builder.rs index ed53796..bafc2a0 100644 --- a/src/fmt/builder.rs +++ b/src/fmt/builder.rs @@ -568,6 +568,8 @@ impl SubscriberBuilder { /// Sets whether or not [OpenTelemetry] trace ID and span ID is displayed when formatting /// events. + /// + /// [OpenTelemetry]: https://opentelemetry.io #[cfg(feature = "opentelemetry")] #[cfg_attr(docsrs, doc(cfg(feature = "opentelemetry")))] pub fn with_opentelemetry_ids(self, display_opentelemetry_ids: bool) -> Self { diff --git a/src/fmt/layer.rs b/src/fmt/layer.rs index 568a5cd..1f6909d 100644 --- a/src/fmt/layer.rs +++ b/src/fmt/layer.rs @@ -198,7 +198,7 @@ where /// Updates the [`MakeWriter`] by applying a function to the existing [`MakeWriter`]. /// - /// This sets the [`MakeWriter`] that the subscriber being built will use to write events. + /// This sets the [`MakeWriter`] that the layer being built will use to write events. /// /// # Examples /// @@ -224,7 +224,7 @@ where } } - /// Configures the subscriber to support [`libtest`'s output capturing][capturing] when used in + /// Configures the layer to support [`libtest`'s output capturing][capturing] when used in /// unit tests. /// /// See [`TestWriter`] for additional details. @@ -252,14 +252,14 @@ where } } - /// Borrows the [writer] for this subscriber. + /// Borrows the [writer] for this layer. /// /// [writer]: MakeWriter pub fn writer(&self) -> &W { self.inner.writer() } - /// Mutably borrows the [writer] for this subscriber. + /// Mutably borrows the [writer] for this layer. /// /// This method is primarily expected to be used with the [`reload::Handle::modify`] method. /// @@ -290,6 +290,28 @@ where self.inner.writer_mut() } + /// Mutably borrows the [`JsonLayer`] inside of this layer. This can be useful to add more + /// information to the output or to change the output with the [`reload::Handle::modify`] + /// method. + /// + /// # Examples + /// ```rust + /// let mut layer = tracing_json::layer(); + /// let mut inner = layer.inner_layer_mut(); + /// + /// inner.add_static_field( + /// "hostname", + /// serde_json::json!({ + /// "hostname": get_hostname(), + /// }), + /// ); + /// ``` + /// + /// [`reload::Handle::modify`]: tracing_subscriber::reload::Handle::modify + pub fn inner_layer_mut(&mut self) -> &mut JsonLayer { + &mut self.inner + } + /// Sets whether to write errors from [`FormatEvent`] to the writer. /// Defaults to true. /// diff --git a/src/fmt/mod.rs b/src/fmt/mod.rs index 71e1c97..5820eca 100644 --- a/src/fmt/mod.rs +++ b/src/fmt/mod.rs @@ -94,9 +94,24 @@ where Layer::default() } +/// A type that can be used to create a [`Subscriber`](tracing::Subscriber) which collects tracing +/// spans and events and emits them in JSON. +/// +/// This type is actually ZST and is only useful to call [`builder`](fn@Self::builder) to configure +/// a subscriber. pub struct Subscriber; impl Subscriber { + /// Creates a [`SubscriberBuilder`] which can be used to configure a [`Subscriber`]. + /// + /// # Examples + /// + /// ```rust + /// let subscriber = Subscriber::builder() + /// .with_max_level(tracing::Level::INFO) + /// .with_target(false) + /// .finish(); + /// ``` pub fn builder() -> SubscriberBuilder { SubscriberBuilder::default() } diff --git a/src/layer/mod.rs b/src/layer/mod.rs index 150a171..2fdb50b 100644 --- a/src/layer/mod.rs +++ b/src/layer/mod.rs @@ -21,6 +21,11 @@ use event::EventRef; use crate::{cached::Cached, fields::JsonFields, visitor::JsonVisitor}; +/// Layer that implements logging JSON to a configured output. This is a lower-level API that may +/// change a bit in next versions. +/// +/// See [`fmt::Layer`](crate::fmt::Layer) for an alternative especially if you're migrating from +/// `tracing_subscriber`. pub struct JsonLayer io::Stdout> { make_writer: W, log_internal_errors: bool, @@ -28,7 +33,7 @@ pub struct JsonLayer io::Stdout> { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum SchemaKey { +enum SchemaKey { Static(Cow<'static, str>), } @@ -183,14 +188,18 @@ impl JsonLayer where S: Subscriber + for<'lookup> LookupSpan<'lookup>, { + /// Creates an empty [`JsonLayer`] which will output logs to stdout. pub fn stdout() -> JsonLayer io::Stdout> { JsonLayer::new(io::stdout) } + /// Creates an empty [`JsonLayer`] which will output logs to stderr. pub fn stderr() -> JsonLayer io::Stderr> { JsonLayer::new(io::stderr) } + /// Creates an empty [`JsonLayer`] which will output logs to the configured + /// [`Writer`](io::Write). pub fn new(make_writer: W) -> JsonLayer where W: for<'writer> MakeWriter<'writer> + 'static, diff --git a/src/lib.rs b/src/lib.rs index 0cc0961..5a039f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,102 @@ +//! # `json-subscriber` +//! +//! `json-subscriber` is (mostly) a drop-in replacement for `tracing_subscriber::fmt().json()`. +//! +//! It provides helpers to be as compatible as possible with `tracing_subscriber` while also +//! allowing for simple extensions to the format to include custom data in the log lines. +//! +//! The end goal is for each user to be able to define the structure of their JSON log lines as they +//! wish. Currently, the library only allows what `tracing-subscriber` plus OpenTelemetry trace and +//! span IDs. +//! +//! ## Compatibility +//! +//! However you created your `FmtSubscriber` or `fmt::Layer`, the same thing should work in this +//! crate. +//! +//! For example in `README.md` in Tracing, you can see an yak-shaving example where if you just +//! change `tracing_subscriber` to `json_subscriber`, everything will work the same, except the logs +//! will be in JSON. +//! +//! ```rust +//! use tracing::info; +//! use json_subscriber; +//! +//! fn main() { +//! // install global collector configured based on RUST_LOG env var. +//! json_subscriber::fmt::init(); +//! +//! let number_of_yaks = 3; +//! // this creates a new event, outside of any spans. +//! info!(number_of_yaks, "preparing to shave yaks"); +//! +//! let number_shaved = yak_shave::shave_all(number_of_yaks); +//! info!( +//! all_yaks_shaved = number_shaved == number_of_yaks, +//! "yak shaving completed." +//! ); +//! } +//! ``` +//! +//! Most configuration under `tracing_subscriber::fmt` should work equivalently. For example one can +//! create a layer like this: +//! +//! ```rust +//! json_subscriber::fmt() +//! // .json() +//! .with_max_level(tracing::Level::TRACE) +//! .with_current_span(false) +//! .init(); +//! ``` +//! +//! Calling `.json()` is not needed and the method does nothing and is marked as deprecated. It is +//! kept around for simpler migration from `tracing-subscriber` though. +//! +//! Trying to call `.pretty()` or `.compact()` will however result in an error. `json-tracing` does +//! not support any output other than JSON. +//! +//! ## Extensions +//! +//! ### OpenTelemetry +//! +//! To include trace ID and span ID from opentelemetry in log lines, simply call +//! `with_opentelemetry_ids`. This will have no effect if you don't also configure a +//! `tracing-opentelemetry` layer. +//! +//! ```rust +//! let tracer = todo!(); +//! let opentelemetry = tracing_opentelemetry::layer().with_tracer(tracer); +//! let json = json_subscriber::layer() +//! .with_current_span(false) +//! .with_span_list(false) +//! .with_opentelemetry_ids(true); +//! +//! tracing_subscriber::registry() +//! .with(opentelemetry) +//! .with(json) +//! .init(); +//! ``` +//! +//! This will produce log lines like for example this (without the formatting): +//! +//! ```json +//! { +//! "fields": { +//! "message": "shaving yaks" +//! }, +//! "level": "INFO", +//! "openTelemetry": { +//! "spanId": "35249d86bfbcf774", +//! "traceId": "fb4b6ae1fa52d4aaf56fa9bda541095f" +//! }, +//! "target": "readme_opentelemetry::yak_shave", +//! "timestamp": "2024-06-06T23:09:07.620167Z" +//! } +//! ``` + mod cached; mod cursor; -pub mod fields; +mod fields; pub mod fmt; mod layer; mod serde;