Skip to content

Commit

Permalink
Add tracing-journald crate implementing a simple journald layer
Browse files Browse the repository at this point in the history
  • Loading branch information
Ralith committed Apr 26, 2020
1 parent 0c01c0d commit 5e3efe2
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 0 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ members = [
"tracing-subscriber",
"tracing-serde",
"tracing-appender",
"tracing-journald",
"examples"
]
1 change: 1 addition & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ tracing-futures = { version = "0.2.1", path = "../tracing-futures", features = [
tracing-attributes = { path = "../tracing-attributes", version = "0.1.2"}
tracing-log = { path = "../tracing-log", version = "0.1.1", features = ["env_logger"] }
tracing-serde = { path = "../tracing-serde" }
tracing-journald = { path = "../tracing-journald" }

# serde example
serde_json = "1.0"
Expand Down
2 changes: 2 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ This directory contains a collection of examples that demonstrate the use of the
implementation.
+ `tower-load`: Demonstrates how dynamically reloadable filters can be used to
debug a server under load in production.
+ `journald`: Demonstrates how to use `fmt` and `journald` layers to output to
both the terminal and the system journal.
- **tracing-futures**:
+ `futures-proxy-server`: Demonstrates the use of `tracing-futures` by
implementing a simple proxy server, based on [this example][tokio-proxy]
Expand Down
24 changes: 24 additions & 0 deletions examples/examples/journald.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#![deny(rust_2018_idioms)]
use tracing::info;
use tracing_journald;
use tracing_subscriber::prelude::*;

#[path = "fmt/yak_shave.rs"]
mod yak_shave;

fn main() {
tracing_subscriber::registry()
.with(tracing_subscriber::fmt::layer().with_target(false))
.with(tracing_journald::layer().unwrap())
.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."
);
}
13 changes: 13 additions & 0 deletions tracing-journald/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "tracing-journald"
version = "0.1.0"
authors = ["Benjamin Saunders <ben.e.saunders@gmail.com>"]
edition = "2018"
license = "MIT"
repository = "https://github.com/tokio-rs/tracing"
homepage = "https://tokio.rs"
description = "rich journald subscriber for `tracing`"

[dependencies]
tracing-core = { path = "../tracing-core", version = "0.1.10" }
tracing-subscriber = { path = "../tracing-subscriber", version = "0.2.5" }
95 changes: 95 additions & 0 deletions tracing-journald/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
use std::{fmt, io, io::Write, os::unix::net::UnixDatagram};

use tracing_core::{event::Event, field::Visit, Field, Level, Subscriber};
use tracing_subscriber::layer::Context;

pub struct Layer {
socket: UnixDatagram,
}

impl Layer {
pub fn new() -> io::Result<Self> {
let socket = UnixDatagram::unbound()?;
socket.connect(JOURNALD_SOCKET_PATH)?;
Ok(Self { socket })
}
}

pub fn layer() -> io::Result<Layer> {
Layer::new()
}

impl<S: Subscriber> tracing_subscriber::Layer<S> for Layer {
fn on_event(&self, event: &Event, _ctx: Context<S>) {
let mut visitor = Visitor::new();
let meta = event.metadata();
visitor.put(
"PRIORITY",
match *meta.level() {
Level::ERROR => "3",
Level::WARN => "4",
Level::INFO => "6",
Level::DEBUG => "7",
Level::TRACE => "7",
},
);
visitor.put("TARGET", meta.target());
if let Some(file) = meta.file() {
visitor.put("CODE_FILE", file);
}
if let Some(line) = meta.line() {
writeln!(visitor.buf, "CODE_LINE={}", line).unwrap();
}
event.record(&mut visitor);
// What could we possibly do on error?
let _ = self.socket.send(&visitor.buf);
}
}

const JOURNALD_SOCKET_PATH: &str = "/run/systemd/journal/socket";

/// Helper for generating the journal export format, which is consumed by journald:
/// https://www.freedesktop.org/wiki/Software/systemd/export/
struct Visitor {
buf: Vec<u8>,
}

impl Visitor {
fn new() -> Self {
Self {
buf: Vec::with_capacity(256),
}
}

/// Text form; `value` must not contain newlines
fn put(&mut self, name: &str, value: &str) {
self.buf.extend_from_slice(name.as_bytes());
self.buf.push(b'=');
self.buf.extend_from_slice(value.as_bytes());
self.buf.push(b'\n');
}
}

impl Visit for Visitor {
fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) {
sanitize_name(field.name(), &mut self.buf);
self.buf.push(b'\n');
self.buf.extend_from_slice(&[0; 8]); // Length tag, to be populated
let start = self.buf.len();
write!(self.buf, "{:?}", value).unwrap();
let end = self.buf.len();
self.buf[start - 8..start].copy_from_slice(&(end - start).to_le_bytes());
self.buf.push(b'\n');
}
}

/// Mangle a name into journald-compliant form
fn sanitize_name(name: &str, buf: &mut Vec<u8>) {
buf.extend(
name.bytes()
.map(|c| if c == b'.' { b'_' } else { c })
.skip_while(|&c| c == b'_')
.filter(|&c| c == b'_' || char::from(c).is_ascii_alphanumeric())
.map(|c| char::from(c).to_ascii_uppercase() as u8),
);
}

0 comments on commit 5e3efe2

Please sign in to comment.