// Retention policies for bounded in-memory log archives.
///|
pub(all) struct RetentionPolicy {
minimum_level : Level?
maximum_age : Int?
maximum_count : Int?
}
///|
pub(all) struct RetentionResult {
records : Array[LogRecord]
dropped_by_level : Int
dropped_by_age : Int
dropped_by_capacity : Int
}
///|
pub fn RetentionPolicy::new() -> RetentionPolicy {
RetentionPolicy::{
minimum_level: None,
maximum_age: None,
maximum_count: None,
}
}
///|
pub fn RetentionPolicy::min_level(
self : RetentionPolicy,
level : Level,
) -> RetentionPolicy {
RetentionPolicy::{ ..self, minimum_level: Some(level) }
}
///|
pub fn RetentionPolicy::max_age(
self : RetentionPolicy,
age : Int,
) -> RetentionPolicy {
let normalized = if age < 0 { 0 } else { age }
RetentionPolicy::{ ..self, maximum_age: Some(normalized) }
}
///|
pub fn RetentionPolicy::max_count(
self : RetentionPolicy,
count : Int,
) -> RetentionPolicy {
let normalized = if count < 0 { 0 } else { count }
RetentionPolicy::{ ..self, maximum_count: Some(normalized) }
}
///|
pub fn RetentionPolicy::apply(
self : RetentionPolicy,
records : Array[LogRecord],
current_timestamp : Int,
) -> RetentionResult {
let filtered = retain_matching(self, records, current_timestamp, 0, [], 0, 0)
let kept = trim_retained(filtered.0, self.maximum_count)
RetentionResult::{
records: kept.0,
dropped_by_level: filtered.1,
dropped_by_age: filtered.2,
dropped_by_capacity: kept.1,
}
}
///|
fn retain_matching(
policy : RetentionPolicy,
records : Array[LogRecord],
current_timestamp : Int,
index : Int,
kept : Array[LogRecord],
dropped_level : Int,
dropped_age : Int,
) -> (Array[LogRecord], Int, Int) {
if index >= records.length() {
(kept, dropped_level, dropped_age)
} else {
let record = records[index]
if !retention_level_matches(policy.minimum_level, record.level) {
retain_matching(
policy,
records,
current_timestamp,
index + 1,
kept,
dropped_level + 1,
dropped_age,
)
} else if !retention_age_matches(
policy.maximum_age,
current_timestamp,
record.timestamp,
) {
retain_matching(
policy,
records,
current_timestamp,
index + 1,
kept,
dropped_level,
dropped_age + 1,
)
} else {
kept.push(record.copy())
retain_matching(
policy,
records,
current_timestamp,
index + 1,
kept,
dropped_level,
dropped_age,
)
}
}
}
///|
fn retention_level_matches(minimum : Level?, actual : Level) -> Bool {
match minimum {
None => true
Some(level) => level_ordinal(actual) >= level_ordinal(level)
}
}
///|
fn retention_age_matches(
maximum_age : Int?,
current_timestamp : Int,
timestamp : Int,
) -> Bool {
match maximum_age {
None => true
Some(age) => timestamp >= current_timestamp - age
}
}
///|
fn trim_retained(
records : Array[LogRecord],
maximum_count : Int?,
) -> (Array[LogRecord], Int) {
match maximum_count {
None => (records, 0)
Some(count) => {
let dropped = if records.length() > count {
records.length() - count
} else {
0
}
(copy_retained(records, dropped, []), dropped)
}
}
}
///|
fn copy_retained(
records : Array[LogRecord],
index : Int,
result : Array[LogRecord],
) -> Array[LogRecord] {
if index >= records.length() {
result
} else {
result.push(records[index].copy())
copy_retained(records, index + 1, result)
}
}
///|
pub fn RetentionResult::get_records(self : RetentionResult) -> Array[LogRecord] {
copy_retained(self.records, 0, [])
}
///|
pub fn RetentionResult::kept_count(self : RetentionResult) -> Int {
self.records.length()
}
///|
pub fn RetentionResult::dropped_count(self : RetentionResult) -> Int {
self.dropped_by_level + self.dropped_by_age + self.dropped_by_capacity
}
///|
pub fn Logger::apply_retention(
self : Logger,
policy : RetentionPolicy,
current_timestamp : Int,
) -> RetentionResult {
policy.apply(self.records(), current_timestamp)
}