3
3
4
4
use super :: prelude:: * ;
5
5
use crate :: util:: request_header;
6
- use conduit:: { header, RequestExt , StatusCode } ;
6
+ use conduit:: { header, Host , RequestExt , Scheme , StatusCode } ;
7
+ use sentry:: protocol:: IpAddress ;
8
+ use sentry:: Level ;
7
9
use std:: fmt:: { self , Display , Formatter } ;
10
+ use std:: net:: IpAddr ;
8
11
use std:: time:: Instant ;
9
12
10
13
const SLOW_REQUEST_THRESHOLD_MS : u64 = 1000 ;
@@ -40,6 +43,8 @@ impl Middleware for LogRequests {
40
43
}
41
44
) ;
42
45
46
+ report_to_sentry ( req, & res, response_time) ;
47
+
43
48
res
44
49
}
45
50
}
@@ -60,6 +65,104 @@ pub fn add_custom_metadata<V: Display>(req: &mut dyn RequestExt, key: &'static s
60
65
}
61
66
}
62
67
68
+ fn report_to_sentry ( req : & dyn RequestExt , res : & AfterResult , response_time : u64 ) {
69
+ let ( message, level) = match res {
70
+ Err ( e) => ( e. to_string ( ) , Level :: Error ) ,
71
+ Ok ( _) => {
72
+ if response_time <= SLOW_REQUEST_THRESHOLD_MS {
73
+ return ;
74
+ }
75
+
76
+ let message = format ! ( "Slow Request: {} {}" , req. method( ) , req. path( ) ) ;
77
+ ( message, Level :: Info )
78
+ }
79
+ } ;
80
+
81
+ let config = |scope : & mut sentry:: Scope | {
82
+ let method = Some ( req. method ( ) . as_str ( ) . to_owned ( ) ) ;
83
+
84
+ let scheme = match req. scheme ( ) {
85
+ Scheme :: Http => "http" ,
86
+ Scheme :: Https => "https" ,
87
+ } ;
88
+
89
+ let host = match req. host ( ) {
90
+ Host :: Name ( name) => name. to_owned ( ) ,
91
+ Host :: Socket ( addr) => addr. to_string ( ) ,
92
+ } ;
93
+
94
+ let path = & req. extensions ( ) . find :: < OriginalPath > ( ) . unwrap ( ) . 0 ;
95
+
96
+ let url = format ! ( "{}://{}{}" , scheme, host, path) . parse ( ) . ok ( ) ;
97
+
98
+ {
99
+ let ip_addr = match req. headers ( ) . get ( "x-real-ip" ) {
100
+ Some ( value) => value
101
+ . to_str ( )
102
+ . ok ( )
103
+ . and_then ( |str| str. parse :: < IpAddr > ( ) . ok ( ) ) ,
104
+ None => Some ( req. remote_addr ( ) . ip ( ) ) ,
105
+ } ;
106
+
107
+ let user = sentry:: User {
108
+ ip_address : ip_addr. map ( IpAddress :: Exact ) ,
109
+ ..Default :: default ( )
110
+ } ;
111
+
112
+ scope. set_user ( Some ( user) ) ;
113
+ }
114
+
115
+ {
116
+ let headers = req
117
+ . headers ( )
118
+ . iter ( )
119
+ . map ( |( k, v) | ( k. to_string ( ) , v. to_str ( ) . unwrap_or_default ( ) . to_string ( ) ) )
120
+ . collect ( ) ;
121
+
122
+ let sentry_req = sentry:: protocol:: Request {
123
+ method,
124
+ url,
125
+ headers,
126
+ ..Default :: default ( )
127
+ } ;
128
+
129
+ scope. add_event_processor ( Box :: new ( move |mut event| {
130
+ if event. request . is_none ( ) {
131
+ event. request = Some ( sentry_req. clone ( ) ) ;
132
+ }
133
+ Some ( event)
134
+ } ) ) ;
135
+ }
136
+
137
+ if let Some ( request_id) = req
138
+ . headers ( )
139
+ . get ( "x-request-id" )
140
+ . and_then ( |value| value. to_str ( ) . ok ( ) )
141
+ {
142
+ scope. set_tag ( "request.id" , request_id) ;
143
+ }
144
+
145
+ {
146
+ let status = res
147
+ . as_ref ( )
148
+ . map ( |resp| resp. status ( ) )
149
+ . unwrap_or ( StatusCode :: INTERNAL_SERVER_ERROR ) ;
150
+
151
+ scope. set_tag ( "response.status" , status. as_str ( ) ) ;
152
+ }
153
+
154
+ scope. set_extra ( "Response time [ms]" , response_time. into ( ) ) ;
155
+
156
+ if let Some ( metadata) = req. extensions ( ) . find :: < CustomMetadata > ( ) {
157
+ for ( key, value) in & metadata. entries {
158
+ scope. set_extra ( key, value. to_string ( ) . into ( ) ) ;
159
+ }
160
+ }
161
+ } ;
162
+
163
+ sentry:: with_scope ( config, || sentry:: capture_message ( & message, level) ) ;
164
+ }
165
+
63
166
#[ cfg( test) ]
64
167
pub ( crate ) fn get_log_message ( req : & dyn RequestExt , key : & ' static str ) -> String {
65
168
// Unwrap shouldn't panic as no other code has access to the private struct to remove it
0 commit comments