blob: 0e0624ebef35646e89b02798131af7ec674ee26a [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
use super::ListenerError;
use diagnostics_data::{LegacySeverity, LogsData};
use diagnostics_message::fx_log_severity_t;
use fidl_fuchsia_logger::{LogFilterOptions, LogLevelFilter};
use std::collections::HashSet;
/// Controls whether messages are seen by a given `Listener`. Created from
/// `fidl_fuchsia_logger::LogFilterOptions`.
#[derive(Default)]
pub(super) struct MessageFilter {
/// Only send messages of greater or equal severity to this value.
min_severity: Option<LegacySeverity>,
/// Only send messages that purport to come from this PID.
pid: Option<u64>,
/// Only send messages that purport to come from this TID.
tid: Option<u64>,
/// Only send messages whose tags match one or more of those provided.
tags: HashSet<String>,
}
impl MessageFilter {
/// Constructs a new `MessageFilter` from the filter options provided to the methods
/// `fuchsia.logger.Log.{Listen,DumpLogs}`.
pub fn new(options: Option<Box<LogFilterOptions>>) -> Result<Self, ListenerError> {
let mut this = Self::default();
if let Some(mut options) = options {
this.tags = options.tags.drain(..).collect();
let count = this.tags.len();
if count > fidl_fuchsia_logger::MAX_TAGS as usize {
return Err(ListenerError::TooManyTags { count });
}
for (index, tag) in this.tags.iter().enumerate() {
if tag.len() > fidl_fuchsia_logger::MAX_TAG_LEN_BYTES as usize {
return Err(ListenerError::TagTooLong { index });
}
}
if options.filter_by_pid {
this.pid = Some(options.pid)
}
if options.filter_by_tid {
this.tid = Some(options.tid)
}
// TODO(b/322552971): Rename to raw_severity with LSC process.
// This is defined in FIDL and will be renamed in its own change.
if options.verbosity > 0 {
// verbosity scale sits in the interstitial space between
// INFO and DEBUG
let raw_level: i32 = std::cmp::max(
fidl_fuchsia_logger::LogLevelFilter::Debug as i32 + 1,
fidl_fuchsia_logger::LogLevelFilter::Info as i32
- (options.verbosity as i32
* fidl_fuchsia_logger::LOG_VERBOSITY_STEP_SIZE as i32),
);
this.min_severity = Some(LegacySeverity::try_from(raw_level)?);
} else if options.min_severity != LogLevelFilter::None {
this.min_severity = Some(LegacySeverity::try_from(options.min_severity as i32)?);
}
}
Ok(this)
}
/// This filter defaults to open, allowing messages through. If multiple portions of the filter
/// are specified, they are additive, only allowing messages through that pass all criteria.
pub fn should_send(&self, log_message: &LogsData) -> bool {
let reject_pid = self.pid.map(|p| log_message.pid() != Some(p)).unwrap_or(false);
let reject_tid = self.tid.map(|t| log_message.tid() != Some(t)).unwrap_or(false);
let reject_severity = self
.min_severity
.map(|m| {
fx_log_severity_t::from(m) > fx_log_severity_t::from(log_message.legacy_severity())
})
.unwrap_or(false);
let reject_tags = if self.tags.is_empty() {
false
} else if log_message.tags().map(|t| t.is_empty()).unwrap_or(true) {
!self.tags.contains(log_message.component_name())
} else {
!log_message
.tags()
.map(|tags| {
tags.iter().any(|tag| self.tags.contains(tag) || self.include_tag_prefix(tag))
})
.unwrap_or(false)
};
!(reject_pid || reject_tid || reject_severity || reject_tags)
}
// Rust uses tags of the form "<foo>::<bar>" so if we have a filter for "<foo>" we should
// include messages that have "<foo>" as a prefix.
fn include_tag_prefix(&self, tag: &str) -> bool {
if tag.contains("::") {
self.tags.iter().any(|t| {
tag.len() > t.len() + 2 && &tag[t.len()..t.len() + 2] == "::" && tag.starts_with(t)
})
} else {
false
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::identity::ComponentIdentity;
use diagnostics_data::Severity;
use moniker::ExtendedMoniker;
fn test_message_with_tag(tag: Option<&str>) -> LogsData {
let identity = ComponentIdentity::new(
ExtendedMoniker::parse_str("./bogus/specious-at-best").unwrap(),
"fuchsia-pkg://not-a-package",
);
let mut builder = diagnostics_data::LogsDataBuilder::new(diagnostics_data::BuilderArgs {
timestamp_nanos: fuchsia_zircon::Time::from_nanos(1).into(),
component_url: Some(identity.url.to_string()),
moniker: identity.to_string(),
severity: Severity::Info,
});
if let Some(tag) = tag {
builder = builder.add_tag(tag);
}
builder.build()
}
fn test_message() -> LogsData {
test_message_with_tag(None)
}
#[fuchsia::test]
fn should_send_verbose() {
let mut message = test_message();
let mut filter = MessageFilter {
min_severity: Some(LegacySeverity::RawSeverity(15)),
..MessageFilter::default()
};
for raw_severity in 1..15 {
message.set_raw_severity(raw_severity);
assert!(filter.should_send(&message));
}
filter.min_severity = Some(LegacySeverity::Debug);
message.set_raw_severity(1);
assert!(filter.should_send(&message));
}
#[fuchsia::test]
fn should_reject_verbose() {
let mut message = test_message();
let mut filter = MessageFilter {
min_severity: Some(LegacySeverity::RawSeverity(1)),
..MessageFilter::default()
};
for raw_severity in 2..15 {
message.set_raw_severity(raw_severity);
assert!(!filter.should_send(&message));
}
filter.min_severity = Some(LegacySeverity::Info);
message.set_raw_severity(1);
assert!(!filter.should_send(&message));
}
#[fuchsia::test]
fn should_send_info() {
let mut message = test_message();
let mut filter =
MessageFilter { min_severity: Some(LegacySeverity::Info), ..MessageFilter::default() };
message.metadata.severity = Severity::Info;
assert!(filter.should_send(&message));
filter.min_severity = Some(LegacySeverity::Debug);
message.metadata.severity = Severity::Info;
assert!(filter.should_send(&message));
}
#[fuchsia::test]
fn should_reject_info() {
let mut message = test_message();
let filter =
MessageFilter { min_severity: Some(LegacySeverity::Warn), ..MessageFilter::default() };
message.metadata.severity = Severity::Info;
assert!(!filter.should_send(&message));
}
#[fuchsia::test]
fn should_send_warn() {
let mut message = test_message();
let mut filter =
MessageFilter { min_severity: Some(LegacySeverity::Warn), ..MessageFilter::default() };
message.metadata.severity = Severity::Warn;
assert!(filter.should_send(&message));
filter.min_severity = Some(LegacySeverity::Info);
message.metadata.severity = Severity::Warn;
assert!(filter.should_send(&message));
}
#[fuchsia::test]
fn should_reject_warn() {
let mut message = test_message();
let filter =
MessageFilter { min_severity: Some(LegacySeverity::Error), ..MessageFilter::default() };
message.metadata.severity = Severity::Warn;
assert!(!filter.should_send(&message));
}
#[fuchsia::test]
fn should_send_error() {
let mut message = test_message();
let mut filter =
MessageFilter { min_severity: Some(LegacySeverity::Error), ..MessageFilter::default() };
message.metadata.severity = Severity::Error;
assert!(filter.should_send(&message));
filter.min_severity = Some(LegacySeverity::Warn);
message.metadata.severity = Severity::Error;
assert!(filter.should_send(&message));
}
#[fuchsia::test]
fn should_reject_error() {
let mut message = test_message();
let filter =
MessageFilter { min_severity: Some(LegacySeverity::Fatal), ..MessageFilter::default() };
message.metadata.severity = Severity::Error;
assert!(!filter.should_send(&message));
}
#[fuchsia::test]
fn should_send_debug() {
let mut message = test_message();
let mut filter =
MessageFilter { min_severity: Some(LegacySeverity::Debug), ..MessageFilter::default() };
message.metadata.severity = Severity::Debug;
assert!(filter.should_send(&message));
filter.min_severity = Some(LegacySeverity::Trace);
message.metadata.severity = Severity::Debug;
assert!(filter.should_send(&message));
}
#[fuchsia::test]
fn should_reject_debug() {
let mut message = test_message();
let filter =
MessageFilter { min_severity: Some(LegacySeverity::Info), ..MessageFilter::default() };
message.metadata.severity = Severity::Debug;
assert!(!filter.should_send(&message));
}
#[fuchsia::test]
fn should_send_trace() {
let mut message = test_message();
let filter =
MessageFilter { min_severity: Some(LegacySeverity::Trace), ..MessageFilter::default() };
message.metadata.severity = Severity::Trace;
assert!(filter.should_send(&message));
}
#[fuchsia::test]
fn should_reject_trace() {
let mut message = test_message();
let filter =
MessageFilter { min_severity: Some(LegacySeverity::Debug), ..MessageFilter::default() };
message.metadata.severity = Severity::Trace;
assert!(!filter.should_send(&message));
}
#[fuchsia::test]
fn should_send_attributed_tag() {
let message = test_message();
let filter = MessageFilter {
tags: vec!["specious-at-best".to_string()].into_iter().collect(),
..MessageFilter::default()
};
assert!(filter.should_send(&message), "the filter should have sent {message:#?}");
}
#[fuchsia::test]
fn should_send_prefix_tag() {
let message = test_message_with_tag(Some("foo::bar::baz"));
let filter = MessageFilter {
tags: vec!["foo".to_string()].into_iter().collect(),
..MessageFilter::default()
};
assert!(filter.should_send(&message), "the filter should have sent {message:#?}");
let message2 = test_message_with_tag(Some("foobar"));
assert!(!filter.should_send(&message2), "the filter should not have sent {message2:#?}");
let filter = MessageFilter {
tags: vec!["foo::bar".to_string()].into_iter().collect(),
..MessageFilter::default()
};
assert!(filter.should_send(&message), "the filter should have sent {message:#?}");
let filter = MessageFilter {
tags: vec!["foo:ba".to_string()].into_iter().collect(),
..MessageFilter::default()
};
assert!(!filter.should_send(&message), "the filter should not have sent {message:#?}");
}
}