Skip to content

Commit 102b7bd

Browse files
authored
Merge branch 'master' into lcian/ref/logs-feature-flag
2 parents a4aa065 + 3b78cf8 commit 102b7bd

File tree

10 files changed

+178
-53
lines changed

10 files changed

+178
-53
lines changed

.config/nextest.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[profile.ci]
2+
fail-fast = false
3+
4+
[profile.ci.junit]
5+
path = "junit.xml"

.github/workflows/ci.yml

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,15 +50,26 @@ jobs:
5050

5151
name: Test using Rust stable on ${{ matrix.os }}
5252
runs-on: ${{ matrix.os }}
53+
permissions:
54+
id-token: write # required by `getsentry/prevent-action`
5355

5456
steps:
5557
- name: Checkout sources
5658
uses: actions/checkout@v4
5759

5860
- uses: Swatinem/rust-cache@v2
5961

60-
- name: Run cargo test
61-
run: cargo test --workspace --all-features --all-targets
62+
- uses: taiki-e/install-action@nextest
63+
64+
- name: Run tests with nextest
65+
run: cargo nextest run --profile ci --all-features --all-targets
66+
67+
- name: Upload test results to Sentry Prevent
68+
if: ${{ !cancelled() }}
69+
uses: getsentry/prevent-action@v0
70+
with:
71+
files: target/nextest/ci/junit.xml
72+
disable_search: true
6273

6374
MSRV:
6475
strategy:

.vscode/settings.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
{
2-
"editor.formatOnSave": true
2+
"editor.formatOnSave": true,
3+
"rust-analyzer.cargo.features": "all"
34
}

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
## Unreleased
44

5+
### Breaking changes
6+
7+
- feat(log): support combined LogFilters and RecordMappings ([#914](https://github.com/getsentry/sentry-rust/pull/914)) by @lcian
8+
- Breaking change: `sentry::integrations::log::LogFilter` has been changed to a `bitflags` struct.
9+
- It's now possible to map a `log` record to multiple items in Sentry by combining multiple log filters in the filter, e.g. `log::Level::ERROR => LogFilter::Event | LogFilter::Log`.
10+
- If using a custom `mapper` instead, it's possible to return a `Vec<sentry::integrations::log::RecordMapping>` to map a `log` record to multiple items in Sentry.
11+
512
### Behavioral changes
613

714
- ref(logs): enable logs by default if logs feature flag is used ([#910](https://github.com/getsentry/sentry-rust/pull/910)) by @lcian

Cargo.lock

Lines changed: 15 additions & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sentry-log/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ logs = ["sentry-core/logs"]
1919
[dependencies]
2020
sentry-core = { version = "0.43.0", path = "../sentry-core" }
2121
log = { version = "0.4.8", features = ["std", "kv"] }
22+
bitflags = "2.9.4"
2223

2324
[dev-dependencies]
2425
sentry = { path = "../sentry", default-features = false, features = ["test"] }

sentry-log/src/lib.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,42 @@
4646
//! _ => LogFilter::Ignore,
4747
//! });
4848
//! ```
49+
//!
50+
//! # Sending multiple items to Sentry
51+
//!
52+
//! To map a log record to multiple items in Sentry, you can combine multiple log filters
53+
//! using the bitwise or operator:
54+
//!
55+
//! ```
56+
//! use sentry_log::LogFilter;
57+
//!
58+
//! let logger = sentry_log::SentryLogger::new().filter(|md| match md.level() {
59+
//! log::Level::Error => LogFilter::Event,
60+
//! log::Level::Warn => LogFilter::Breadcrumb | LogFilter::Log,
61+
//! _ => LogFilter::Ignore,
62+
//! });
63+
//! ```
64+
//!
65+
//! If you're using a custom record mapper instead of a filter, you can return a `Vec<RecordMapping>`
66+
//! from your mapper function to send multiple items to Sentry from a single log record:
67+
//!
68+
//! ```
69+
//! use sentry_log::{RecordMapping, SentryLogger, event_from_record, breadcrumb_from_record};
70+
//!
71+
//! let logger = SentryLogger::new().mapper(|record| {
72+
//! match record.level() {
73+
//! log::Level::Error => {
74+
//! // Send both an event and a breadcrumb for errors
75+
//! vec![
76+
//! RecordMapping::Event(event_from_record(record)),
77+
//! RecordMapping::Breadcrumb(breadcrumb_from_record(record)),
78+
//! ]
79+
//! }
80+
//! log::Level::Warn => RecordMapping::Breadcrumb(breadcrumb_from_record(record)).into(),
81+
//! _ => RecordMapping::Ignore.into(),
82+
//! }
83+
//! });
84+
//! ```
4985
5086
#![doc(html_favicon_url = "https://sentry-brand.storage.googleapis.com/favicon.ico")]
5187
#![doc(html_logo_url = "https://sentry-brand.storage.googleapis.com/sentry-glyph-black.png")]

sentry-log/src/logger.rs

Lines changed: 61 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
11
use log::Record;
22
use sentry_core::protocol::{Breadcrumb, Event};
33

4+
use bitflags::bitflags;
5+
46
#[cfg(feature = "logs")]
57
use crate::converters::log_from_record;
68
use crate::converters::{breadcrumb_from_record, event_from_record, exception_from_record};
79

8-
/// The action that Sentry should perform for a [`log::Metadata`].
9-
#[derive(Debug)]
10-
pub enum LogFilter {
11-
/// Ignore the [`Record`].
12-
Ignore,
13-
/// Create a [`Breadcrumb`] from this [`Record`].
14-
Breadcrumb,
15-
/// Create a message [`Event`] from this [`Record`].
16-
Event,
17-
/// Create an exception [`Event`] from this [`Record`].
18-
Exception,
19-
/// Create a [`sentry_core::protocol::Log`] from this [`Record`].
20-
#[cfg(feature = "logs")]
21-
Log,
10+
bitflags! {
11+
/// The action that Sentry should perform for a [`log::Metadata`].
12+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13+
pub struct LogFilter: u32 {
14+
/// Ignore the [`Record`].
15+
const Ignore = 0b0000;
16+
/// Create a [`Breadcrumb`] from this [`Record`].
17+
const Breadcrumb = 0b0001;
18+
/// Create a message [`Event`] from this [`Record`].
19+
const Event = 0b0010;
20+
/// Create an exception [`Event`] from this [`Record`].
21+
const Exception = 0b0100;
22+
/// Create a [`sentry_core::protocol::Log`] from this [`Record`].
23+
#[cfg(feature = "logs")]
24+
const Log = 0b1000;
25+
}
2226
}
2327

2428
/// The type of Data Sentry should ingest for a [`log::Record`].
@@ -36,6 +40,12 @@ pub enum RecordMapping {
3640
Log(sentry_core::protocol::Log),
3741
}
3842

43+
impl From<RecordMapping> for Vec<RecordMapping> {
44+
fn from(mapping: RecordMapping) -> Self {
45+
vec![mapping]
46+
}
47+
}
48+
3949
/// The default log filter.
4050
///
4151
/// By default, an exception event is captured for `error`, a breadcrumb for
@@ -73,7 +83,7 @@ pub struct SentryLogger<L: log::Log> {
7383
dest: L,
7484
filter: Box<dyn Fn(&log::Metadata<'_>) -> LogFilter + Send + Sync>,
7585
#[allow(clippy::type_complexity)]
76-
mapper: Option<Box<dyn Fn(&Record<'_>) -> RecordMapping + Send + Sync>>,
86+
mapper: Option<Box<dyn Fn(&Record<'_>) -> Vec<RecordMapping> + Send + Sync>>,
7787
}
7888

7989
impl Default for SentryLogger<NoopLogger> {
@@ -119,43 +129,59 @@ impl<L: log::Log> SentryLogger<L> {
119129
/// Sets a custom mapper function.
120130
///
121131
/// The mapper is responsible for creating either breadcrumbs or events
122-
/// from [`Record`]s.
132+
/// from [`Record`]s. It can return either a single [`RecordMapping`] or
133+
/// a `Vec<RecordMapping>` to send multiple items to Sentry from one log record.
123134
#[must_use]
124-
pub fn mapper<M>(mut self, mapper: M) -> Self
135+
pub fn mapper<M, T>(mut self, mapper: M) -> Self
125136
where
126-
M: Fn(&Record<'_>) -> RecordMapping + Send + Sync + 'static,
137+
M: Fn(&Record<'_>) -> T + Send + Sync + 'static,
138+
T: Into<Vec<RecordMapping>>,
127139
{
128-
self.mapper = Some(Box::new(mapper));
140+
self.mapper = Some(Box::new(move |record| mapper(record).into()));
129141
self
130142
}
131143
}
132144

133145
impl<L: log::Log> log::Log for SentryLogger<L> {
134146
fn enabled(&self, metadata: &log::Metadata<'_>) -> bool {
135-
self.dest.enabled(metadata) || !matches!((self.filter)(metadata), LogFilter::Ignore)
147+
self.dest.enabled(metadata) || !((self.filter)(metadata) == LogFilter::Ignore)
136148
}
137149

138150
fn log(&self, record: &log::Record<'_>) {
139-
let item: RecordMapping = match &self.mapper {
151+
let items = match &self.mapper {
140152
Some(mapper) => mapper(record),
141-
None => match (self.filter)(record.metadata()) {
142-
LogFilter::Ignore => RecordMapping::Ignore,
143-
LogFilter::Breadcrumb => RecordMapping::Breadcrumb(breadcrumb_from_record(record)),
144-
LogFilter::Event => RecordMapping::Event(event_from_record(record)),
145-
LogFilter::Exception => RecordMapping::Event(exception_from_record(record)),
153+
None => {
154+
let filter = (self.filter)(record.metadata());
155+
let mut items = vec![];
156+
if filter.contains(LogFilter::Breadcrumb) {
157+
items.push(RecordMapping::Breadcrumb(breadcrumb_from_record(record)));
158+
}
159+
if filter.contains(LogFilter::Event) {
160+
items.push(RecordMapping::Event(event_from_record(record)));
161+
}
162+
if filter.contains(LogFilter::Exception) {
163+
items.push(RecordMapping::Event(exception_from_record(record)));
164+
}
146165
#[cfg(feature = "logs")]
147-
LogFilter::Log => RecordMapping::Log(log_from_record(record)),
148-
},
166+
if filter.contains(LogFilter::Log) {
167+
items.push(RecordMapping::Log(log_from_record(record)));
168+
}
169+
items
170+
}
149171
};
150172

151-
match item {
152-
RecordMapping::Ignore => {}
153-
RecordMapping::Breadcrumb(b) => sentry_core::add_breadcrumb(b),
154-
RecordMapping::Event(e) => {
155-
sentry_core::capture_event(e);
173+
for mapping in items {
174+
match mapping {
175+
RecordMapping::Ignore => {}
176+
RecordMapping::Breadcrumb(breadcrumb) => sentry_core::add_breadcrumb(breadcrumb),
177+
RecordMapping::Event(event) => {
178+
sentry_core::capture_event(event);
179+
}
180+
#[cfg(feature = "logs")]
181+
RecordMapping::Log(log) => {
182+
sentry_core::Hub::with_active(|hub| hub.capture_log(log))
183+
}
156184
}
157-
#[cfg(feature = "logs")]
158-
RecordMapping::Log(log) => sentry_core::Hub::with_active(|hub| hub.capture_log(log)),
159185
}
160186

161187
self.dest.log(record)

sentry-tracing/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ tracing-subscriber = { version = "0.3.20", default-features = false, features =
2929
"std",
3030
] }
3131
sentry-backtrace = { version = "0.43.0", path = "../sentry-backtrace", optional = true }
32-
bitflags = "2.0.0"
32+
bitflags = "2.9.4"
3333

3434
[dev-dependencies]
3535
log = "0.4"

0 commit comments

Comments
 (0)