// Structured log queries over in-memory records.
///|
pub(all) struct LogQuery {
minimum_level : Level?
maximum_level : Level?
message_fragment : String?
required_field_key : String?
required_field_value : String?
start_timestamp : Int?
end_timestamp : Int?
result_limit : Int?
}
///|
pub fn LogQuery::new() -> LogQuery {
LogQuery::{
minimum_level: None,
maximum_level: None,
message_fragment: None,
required_field_key: None,
required_field_value: None,
start_timestamp: None,
end_timestamp: None,
result_limit: None,
}
}
///|
pub fn LogQuery::min_level(self : LogQuery, level : Level) -> LogQuery {
LogQuery::{ ..self, minimum_level: Some(level) }
}
///|
pub fn LogQuery::max_level(self : LogQuery, level : Level) -> LogQuery {
LogQuery::{ ..self, maximum_level: Some(level) }
}
///|
pub fn LogQuery::message_contains(
self : LogQuery,
fragment : String,
) -> LogQuery {
LogQuery::{ ..self, message_fragment: Some(fragment) }
}
///|
pub fn LogQuery::field_exists(self : LogQuery, key : String) -> LogQuery {
LogQuery::{
..self,
required_field_key: Some(key),
required_field_value: None,
}
}
///|
pub fn LogQuery::field_equals(
self : LogQuery,
key : String,
value : String,
) -> LogQuery {
LogQuery::{
..self,
required_field_key: Some(key),
required_field_value: Some(value),
}
}
///|
pub fn LogQuery::since(self : LogQuery, timestamp : Int) -> LogQuery {
LogQuery::{ ..self, start_timestamp: Some(timestamp) }
}
///|
pub fn LogQuery::until(self : LogQuery, timestamp : Int) -> LogQuery {
LogQuery::{ ..self, end_timestamp: Some(timestamp) }
}
///|
pub fn LogQuery::limit(self : LogQuery, count : Int) -> LogQuery {
let normalized = if count < 0 { 0 } else { count }
LogQuery::{ ..self, result_limit: Some(normalized) }
}
///|
pub fn LogQuery::matches(self : LogQuery, record : LogRecord) -> Bool {
query_matches_minimum(self.minimum_level, record.level) &&
query_matches_maximum(self.maximum_level, record.level) &&
query_matches_message(self.message_fragment, record.message) &&
query_matches_field(
self.required_field_key,
self.required_field_value,
record.fields,
) &&
query_matches_start(self.start_timestamp, record.timestamp) &&
query_matches_end(self.end_timestamp, record.timestamp)
}
///|
fn query_matches_minimum(minimum : Level?, actual : Level) -> Bool {
match minimum {
None => true
Some(level) => level_ordinal(actual) >= level_ordinal(level)
}
}
///|
fn query_matches_maximum(maximum : Level?, actual : Level) -> Bool {
match maximum {
None => true
Some(level) => level_ordinal(actual) <= level_ordinal(level)
}
}
///|
fn query_matches_message(fragment : String?, message : String) -> Bool {
match fragment {
None => true
Some(value) => message.contains(value)
}
}
///|
fn query_matches_field(
key : String?,
value : String?,
fields : Array[(String, String)],
) -> Bool {
match key {
None => true
Some(required_key) => query_find_field(fields, required_key, value, 0)
}
}
///|
fn query_find_field(
fields : Array[(String, String)],
required_key : String,
required_value : String?,
index : Int,
) -> Bool {
if index >= fields.length() {
false
} else {
let (key, value) = fields[index]
if key == required_key {
let matched = match required_value {
None => true
Some(expected) => value == expected
}
matched ||
query_find_field(fields, required_key, required_value, index + 1)
} else {
query_find_field(fields, required_key, required_value, index + 1)
}
}
}
///|
fn query_matches_start(start : Int?, timestamp : Int) -> Bool {
match start {
None => true
Some(value) => timestamp >= value
}
}
///|
fn query_matches_end(finish : Int?, timestamp : Int) -> Bool {
match finish {
None => true
Some(value) => timestamp <= value
}
}
///|
pub fn LogQuery::execute(
self : LogQuery,
records : Array[LogRecord],
) -> Array[LogRecord] {
execute_query(self, records, 0, [])
}
///|
fn execute_query(
query : LogQuery,
records : Array[LogRecord],
index : Int,
result : Array[LogRecord],
) -> Array[LogRecord] {
if index >= records.length() ||
query_limit_reached(query.result_limit, result) {
result
} else {
if query.matches(records[index]) {
result.push(records[index].copy())
}
execute_query(query, records, index + 1, result)
}
}
///|
fn query_limit_reached(limit : Int?, result : Array[LogRecord]) -> Bool {
match limit {
None => false
Some(value) => result.length() >= value
}
}
///|
pub fn LogQuery::count(self : LogQuery, records : Array[LogRecord]) -> Int {
self.execute(records).length()
}
///|
pub fn LogQuery::first(
self : LogQuery,
records : Array[LogRecord],
) -> LogRecord? {
let matches = self.limit(1).execute(records)
if matches.length() == 0 {
None
} else {
Some(matches[0])
}
}
///|
pub fn Logger::query(self : Logger, query : LogQuery) -> Array[LogRecord] {
query.execute(self.records())
}