Skip to content

Add tracing support #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# looker

A lens for removing glare and reducing eye strain while looking at Bunyan logs.

## Usage

See `looker --help` for usage options.

## Filtering with RHAI

The `-c` option accepts an [RHAI script](https://rhai.rs) that returns a Boolean
value indicating whether a record should be displayed. Each record is supplied
to the script in a variable named `r`.

The following Bunyan fields are guaranteed to exist for all records: `level`,
`name`, `hostname`, `pid`, `time`, and `msg`. Other fields, including
`component`, are optional and must be followed by the `?` operator for RHAI to
compile a script referring to them. Records that don't have a field referred to
in the script will be elided.

### Examples

- `looker -c 'r.msg.contains("Failed")'` - include all lines with a `msg` that
contains `Failed`
- `looker -c 'r.response_code?.parse_int() >= 500'` - include all lines with a
`response_code` field in the 5XX level
106 changes: 106 additions & 0 deletions src/bunyan.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
use std::collections::BTreeMap;

use chrono::{DateTime, Utc};
use serde::Deserialize;

use crate::{bold, level, Format, Level, Record};

#[derive(Deserialize, Debug)]
pub struct BunyanEntry {
pub v: i64,
pub level: Level,
pub name: String,
pub hostname: String,
pub pid: u64,
pub time: DateTime<Utc>,
pub msg: String,

/*
* This is not a part of the base specification, but is widely used:
*/
pub component: Option<String>,

#[serde(flatten)]
pub extra: BTreeMap<String, serde_json::Value>,
}

impl Record for BunyanEntry {
fn level(&self) -> Level {
self.level
}

fn emit_record(
&self,
colour: crate::Colour,
fmt: crate::Format,
lookups: &Vec<String>,
) -> anyhow::Result<()> {
let l = level(self.level, colour);
let mut n = bold(&self.name, colour);
if matches!(fmt, Format::Long) {
n += &format!("/{}", self.pid);
}
if let Some(c) = &self.component {
if c != &self.name {
n += &format!(" ({})", c);
}
};

/*
* For multi-line messages, indent subsequent lines by 4 spaces, so that
* they are at least somewhat distinguishable from the next log message.
*/
let msg = self
.msg
.lines()
.enumerate()
.map(|(i, l)| {
let mut s = if i > 0 { " " } else { "" }.to_string();
s.push_str(l);
s
})
.collect::<Vec<String>>()
.join("\n");

match fmt {
Format::Short => {
let d = self.time.format("%H:%M:%S%.3fZ").to_string();
println!("{:13} {} {}: {}", d, l, n, msg);
}
Format::Long => {
let d = self.time.format("%Y-%m-%d %H:%M:%S%.3fZ").to_string();
println!("{} {} {} on {}: {}", d, l, n, self.hostname, msg);
}
Format::Bare => unreachable!(),
}

for (k, v) in self.extra.iter() {
if !lookups.is_empty() && !lookups.contains(k) {
continue;
}

print!(" {} = ", bold(k.as_str(), colour));

match v {
serde_json::Value::Null => println!("null"),
serde_json::Value::Bool(v) => println!("{}", v),
serde_json::Value::Number(n) => println!("{}", n),
serde_json::Value::String(s) => {
let mut out = String::new();
for c in s.chars() {
if c != '"' && c != '\'' {
out.push_str(&c.escape_default().to_string());
} else {
out.push(c);
}
}
println!("{}", out);
}
serde_json::Value::Array(a) => println!("{:?}", a),
serde_json::Value::Object(o) => println!("{:?}", o),
}
}

Ok(())
}
}
Loading