Skip to content

Commit

Permalink
Document everything
Browse files Browse the repository at this point in the history
  • Loading branch information
hrxi committed Mar 22, 2022
1 parent e586290 commit 9706c03
Show file tree
Hide file tree
Showing 7 changed files with 164 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,12 @@ members = [

[package]
name = "tracing-loki"
description = "A tracing layer for shipping logs to Grafana Loki"
authors = ["hrxi <[email protected]>"]
repository = "https://github.com/hrxi/tracing-loki"
keywords = ["tracing", "loki"]
version = "0.0.1"
license = "MIT/Apache-2.0"
edition = "2021"

[dependencies]
Expand Down
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
tracing-loki
============

A [tracing](https://github.com/tokio-rs/tracing) layer for [Grafana
Loki](https://grafana.com/oss/loki/).

Documentation
-------------

https://docs.rs/tracing-loki

Usage
-----

Add this to your `Cargo.toml`:
```toml
[dependencies]
tracing-loki = "0.1"
```

Example
-------

```rust
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
use url::Url;

#[tokio::main]
async fn main() -> Result<(), tracing_loki::Error> {
let (layer, task) = tracing_loki::layer(
Url::parse("http://127.0.0.1:3100").unwrap(),
vec![("host".into(), "mine".into())].into_iter().collect(),
vec![].into_iter().collect(),
)?;

// We need to register our layer with `tracing`.
tracing_subscriber::registry()
.with(layer)
// One could add more layers here, for example logging to stdout:
// .with(tracing_subscriber::fmt::Layer::new())
.init();

// The background task needs to be spawned so the logs actually get
// delivered.
tokio::spawn(task);

tracing::info!(
task = "tracing_setup",
result = "success",
"tracing successfully set up",
);

Ok(())
}
```
2 changes: 1 addition & 1 deletion examples/example.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
);
});

tokio::time::sleep(Duration::from_secs(600)).await;
tokio::time::sleep(Duration::from_secs(1)).await;

Ok(())
}
5 changes: 5 additions & 0 deletions loki-api/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
[package]
name = "loki-api"
description = "Protobuf types used by the Grafana Loki HTTP API"
authors = ["hrxi <[email protected]>"]
repository = "https://github.com/hrxi/tracing-loki"
keywords = ["tracing", "loki"]
version = "0.0.1"
license = "MIT/Apache-2.0"
edition = "2021"

[dependencies]
Expand Down
7 changes: 6 additions & 1 deletion loki-api/generate/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
[package]
name = "loki-api-generate"
version = "0.1.0"
description = "Code generation for loki-api"
authors = ["hrxi <[email protected]>"]
repository = "https://github.com/hrxi/tracing-loki"
keywords = ["tracing", "loki"]
version = "0.0.1"
license = "MIT/Apache-2.0"
edition = "2021"

[build-dependencies]
Expand Down
100 changes: 90 additions & 10 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,48 @@
//! A [`tracing`] layer for shipping logs to [Grafana
//! Loki](https://grafana.com/oss/loki/).
//!
//! Usage
//! =====
//!
//! ```rust
//! use tracing_subscriber::layer::SubscriberExt;
//! use tracing_subscriber::util::SubscriberInitExt;
//! use url::Url;
//!
//! #[tokio::main]
//! async fn main() -> Result<(), tracing_loki::Error> {
//! let (layer, task) = tracing_loki::layer(
//! Url::parse("http://127.0.0.1:3100").unwrap(),
//! vec![("host".into(), "mine".into())].into_iter().collect(),
//! vec![].into_iter().collect(),
//! )?;
//!
//! // We need to register our layer with `tracing`.
//! tracing_subscriber::registry()
//! .with(layer)
//! // One could add more layers here, for example logging to stdout:
//! // .with(tracing_subscriber::fmt::Layer::new())
//! .init();
//!
//! // The background task needs to be spawned so the logs actually get
//! // delivered.
//! tokio::spawn(task);
//!
//! tracing::info!(
//! task = "tracing_setup",
//! result = "success",
//! "tracing successfully set up",
//! );
//!
//! Ok(())
//! }
//! ```
#![allow(clippy::or_fun_call)]
#![allow(clippy::type_complexity)]
#![deny(missing_docs)]

pub extern crate url;

use loki_api::logproto as loki;
use loki_api::prost;
Expand Down Expand Up @@ -33,6 +76,7 @@ use tracing_subscriber::layer::Context as TracingContext;
use tracing_subscriber::registry::LookupSpan;
use url::Url;

use ErrorInner as ErrorI;
use level_map::LevelMap;
use log_support::SerializeEventFieldMapStrippingLog;
use no_subscriber::NoSubscriber;
Expand All @@ -41,25 +85,53 @@ mod level_map;
mod log_support;
mod no_subscriber;

#[cfg(doctest)]
#[doc = include_str!("../README.md")]
struct ReadmeDoctests;

/// The error type for constructing a [`Layer`].
///
/// Nothing except for the [`std::error::Error`] (and [`std::fmt::Debug`] and
/// [`std::fmt::Display`]) implementation of this type is exposed.
pub struct Error(ErrorInner);

impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl error::Error for Error {}

#[derive(Debug)]
pub enum Error {
enum ErrorInner {
ReservedLabelLevel,
InvalidLabelCharacter(char),
InvalidLokiUrl,
}

impl fmt::Display for Error {
impl fmt::Display for ErrorInner {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::Error::*;
use self::ErrorInner::*;
match self {
ReservedLabelLevel => write!(f, "cannot add custom label for `level`"),
InvalidLabelCharacter(c) => write!(f, "invalid label character: {:?}", c),
InvalidLokiUrl => write!(f, "invalid Loki URL"),
}
}
}
impl error::Error for Error {}

/// Construct a [`Layer`] and its corresponding [`BackgroundTask`].
///
/// The [`Layer`] needs to be registered with a
/// [`tracing_subscriber::Registry`], and the [`BackgroundTask`] needs to be
/// [`tokio::spawn`]ed.
///
/// The the crate's root documentation for an example.
pub fn layer(
loki_url: Url,
mut labels: HashMap<String, String>,
Expand All @@ -75,6 +147,9 @@ pub fn layer(
))
}

/// The [`tracing_subscriber::Layer`] implementation for the Loki backend.
///
/// The the crate's root documentation for an example.
pub struct Layer {
extra_fields: HashMap<String, String>,
sender: mpsc::Sender<LokiEvent>,
Expand Down Expand Up @@ -278,6 +353,10 @@ impl fmt::Display for BadRedirect {

impl error::Error for BadRedirect {}

/// The background task that ships logs to Loki. It must be [`tokio::spawn`]ed
/// by the calling application.
///
/// The the crate's root documentation for an example.
pub struct BackgroundTask {
loki_url: Url,
receiver: ReceiverStream<LokiEvent>,
Expand Down Expand Up @@ -307,13 +386,13 @@ impl BackgroundTask {
}

if labels.contains_key("label") {
return Err(Error::ReservedLabelLevel);
return Err(Error(ErrorI::ReservedLabelLevel));
}
Ok(BackgroundTask {
receiver: ReceiverStream::new(receiver),
loki_url: loki_url
.join("/loki/api/v1/push")
.map_err(|_| Error::InvalidLokiUrl)?,
.map_err(|_| Error(ErrorI::InvalidLokiUrl))?,
queues: LevelMap::try_from_fn(|level| {
labels.insert("level".into(), level_str(level).into());
let labels_encoded = labels_to_string(labels)?;
Expand Down Expand Up @@ -474,17 +553,18 @@ fn labels_to_string(labels: &HashMap<String, String>) -> Result<String, Error> {
// Couldn't find documentation except for the promtail source code:
// https://github.com/grafana/loki/blob/8c06c546ab15a568f255461f10318dae37e022d3/vendor/github.com/prometheus/prometheus/promql/parser/generated_parser.y#L597-L598
//
// Apparently labels that confirm to yacc's "IDENTIFIER" are okay. I couldn't find which
// those are. Let's be conservative and allow `[A-Za-z_]*`.
// Apparently labels that confirm to yacc's "IDENTIFIER" are okay. I
// couldn't find which those are. Let's be conservative and allow
// `[A-Za-z_]*`.
for (i, b) in label.bytes().enumerate() {
match b {
b'A'..=b'Z' | b'a'..=b'z' | b'_' => {}
// The first byte outside of the above range must start a UTF-8
// character.
_ => {
return Err(Error::InvalidLabelCharacter(
return Err(Error(ErrorI::InvalidLabelCharacter(
label[i..].chars().next().unwrap(),
))
)))
}
}
}
Expand Down

0 comments on commit 9706c03

Please sign in to comment.