1
- use sentry_core:: protocol:: Event ;
1
+ use sentry_core:: protocol:: { Event , Value } ;
2
2
#[ cfg( feature = "logs" ) ]
3
3
use sentry_core:: protocol:: { Log , LogAttribute , LogLevel } ;
4
4
use sentry_core:: { Breadcrumb , Level } ;
5
+ use std:: collections:: BTreeMap ;
5
6
#[ cfg( feature = "logs" ) ]
6
- use std:: { collections :: BTreeMap , time:: SystemTime } ;
7
+ use std:: time:: SystemTime ;
7
8
8
9
/// Converts a [`log::Level`] to a Sentry [`Level`], used for [`Event`] and [`Breadcrumb`].
9
10
pub fn convert_log_level ( level : log:: Level ) -> Level {
@@ -27,23 +28,97 @@ pub fn convert_log_level_to_sentry_log_level(level: log::Level) -> LogLevel {
27
28
}
28
29
}
29
30
31
+ /// Visitor to extract key-value pairs from log records
32
+ #[ derive( Default ) ]
33
+ struct AttributeVisitor {
34
+ json_values : BTreeMap < String , Value > ,
35
+ }
36
+
37
+ impl AttributeVisitor {
38
+ fn record < T : Into < Value > > ( & mut self , key : & str , value : T ) {
39
+ self . json_values . insert ( key. to_owned ( ) , value. into ( ) ) ;
40
+ }
41
+ }
42
+
43
+ impl log:: kv:: VisitSource < ' _ > for AttributeVisitor {
44
+ fn visit_pair (
45
+ & mut self ,
46
+ key : log:: kv:: Key ,
47
+ value : log:: kv:: Value ,
48
+ ) -> Result < ( ) , log:: kv:: Error > {
49
+ let key = key. as_str ( ) ;
50
+
51
+ if let Some ( value) = value. to_borrowed_str ( ) {
52
+ self . record ( key, value) ;
53
+ } else if let Some ( value) = value. to_u64 ( ) {
54
+ self . record ( key, value) ;
55
+ } else if let Some ( value) = value. to_f64 ( ) {
56
+ self . record ( key, value) ;
57
+ } else if let Some ( value) = value. to_bool ( ) {
58
+ self . record ( key, value) ;
59
+ } else {
60
+ self . record ( key, format ! ( "{:?}" , value) ) ;
61
+ } ;
62
+
63
+ Ok ( ( ) )
64
+ }
65
+ }
66
+
67
+ fn extract_record_attributes ( record : & log:: Record < ' _ > ) -> AttributeVisitor {
68
+ let mut visitor = AttributeVisitor :: default ( ) ;
69
+ let _ = record. key_values ( ) . visit ( & mut visitor) ;
70
+ visitor
71
+ }
72
+
30
73
/// Creates a [`Breadcrumb`] from a given [`log::Record`].
31
74
pub fn breadcrumb_from_record ( record : & log:: Record < ' _ > ) -> Breadcrumb {
75
+ let visitor = extract_record_attributes ( record) ;
76
+
32
77
Breadcrumb {
33
78
ty : "log" . into ( ) ,
34
79
level : convert_log_level ( record. level ( ) ) ,
35
80
category : Some ( record. target ( ) . into ( ) ) ,
36
81
message : Some ( record. args ( ) . to_string ( ) ) ,
82
+ data : visitor. json_values ,
37
83
..Default :: default ( )
38
84
}
39
85
}
40
86
41
87
/// Creates an [`Event`] from a given [`log::Record`].
42
88
pub fn event_from_record ( record : & log:: Record < ' _ > ) -> Event < ' static > {
89
+ let visitor = extract_record_attributes ( record) ;
90
+ let attributes = visitor. json_values ;
91
+
92
+ let mut contexts = BTreeMap :: new ( ) ;
93
+
94
+ let mut metadata_map = BTreeMap :: new ( ) ;
95
+ metadata_map. insert ( "logger.target" . into ( ) , record. target ( ) . into ( ) ) ;
96
+ if let Some ( module_path) = record. module_path ( ) {
97
+ metadata_map. insert ( "logger.module_path" . into ( ) , module_path. into ( ) ) ;
98
+ }
99
+ if let Some ( file) = record. file ( ) {
100
+ metadata_map. insert ( "logger.file" . into ( ) , file. into ( ) ) ;
101
+ }
102
+ if let Some ( line) = record. line ( ) {
103
+ metadata_map. insert ( "logger.line" . into ( ) , line. into ( ) ) ;
104
+ }
105
+ contexts. insert (
106
+ "Rust Log Metadata" . to_string ( ) ,
107
+ sentry_core:: protocol:: Context :: Other ( metadata_map) ,
108
+ ) ;
109
+
110
+ if !attributes. is_empty ( ) {
111
+ contexts. insert (
112
+ "Rust Log Attributes" . to_string ( ) ,
113
+ sentry_core:: protocol:: Context :: Other ( attributes) ,
114
+ ) ;
115
+ }
116
+
43
117
Event {
44
118
logger : Some ( record. target ( ) . into ( ) ) ,
45
119
level : convert_log_level ( record. level ( ) ) ,
46
120
message : Some ( record. args ( ) . to_string ( ) ) ,
121
+ contexts,
47
122
..Default :: default ( )
48
123
}
49
124
}
@@ -60,7 +135,13 @@ pub fn exception_from_record(record: &log::Record<'_>) -> Event<'static> {
60
135
/// Creates a [`Log`] from a given [`log::Record`].
61
136
#[ cfg( feature = "logs" ) ]
62
137
pub fn log_from_record ( record : & log:: Record < ' _ > ) -> Log {
63
- let mut attributes: BTreeMap < String , LogAttribute > = BTreeMap :: new ( ) ;
138
+ let visitor = extract_record_attributes ( record) ;
139
+
140
+ let mut attributes: BTreeMap < String , LogAttribute > = visitor
141
+ . json_values
142
+ . into_iter ( )
143
+ . map ( |( key, val) | ( key, val. into ( ) ) )
144
+ . collect ( ) ;
64
145
65
146
attributes. insert ( "logger.target" . into ( ) , record. target ( ) . into ( ) ) ;
66
147
if let Some ( module_path) = record. module_path ( ) {
@@ -75,8 +156,6 @@ pub fn log_from_record(record: &log::Record<'_>) -> Log {
75
156
76
157
attributes. insert ( "sentry.origin" . into ( ) , "auto.logger.log" . into ( ) ) ;
77
158
78
- // TODO: support the `kv` feature and store key value pairs as attributes
79
-
80
159
Log {
81
160
level : convert_log_level_to_sentry_log_level ( record. level ( ) ) ,
82
161
body : format ! ( "{}" , record. args( ) ) ,
0 commit comments