@@ -19,21 +19,41 @@ pub struct ErrorConfig {
19
19
}
20
20
21
21
impl ErrorConfig {
22
- pub fn request (
23
- & self ,
24
- timestamp : & mut impl Timestamp ,
25
- exit : & ExitStatus ,
26
- lines : impl IntoIterator < Item = String > ,
27
- ) -> Result < reqwest:: Request , reqwest:: Error > {
22
+ pub fn request ( & self , body : impl Into < Body > ) -> Result < reqwest:: Request , reqwest:: Error > {
28
23
let url = format ! ( "{}/errors" , self . endpoint) ;
29
24
30
25
client ( )
31
26
. post ( url)
32
27
. query ( & [ ( "api_key" , & self . api_key ) ] )
33
28
. header ( "Content-Type" , "application/json" )
34
- . body ( ErrorBody :: from_config ( self , timestamp , exit , lines ) )
29
+ . body ( body )
35
30
. build ( )
36
31
}
32
+
33
+ pub fn request_from_spawn (
34
+ & self ,
35
+ timestamp : & mut impl Timestamp ,
36
+ error : & std:: io:: Error ,
37
+ ) -> Result < reqwest:: Request , reqwest:: Error > {
38
+ self . request ( ErrorBody :: from_spawn ( self , timestamp, error) )
39
+ }
40
+
41
+ pub fn request_from_exit (
42
+ & self ,
43
+ timestamp : & mut impl Timestamp ,
44
+ exit : & ExitStatus ,
45
+ lines : impl IntoIterator < Item = String > ,
46
+ ) -> Result < reqwest:: Request , reqwest:: Error > {
47
+ self . request ( ErrorBody :: from_exit ( self , timestamp, exit, lines) )
48
+ }
49
+
50
+ fn tags ( & self ) -> BTreeMap < String , String > {
51
+ [
52
+ ( "hostname" . to_string ( ) , self . hostname . clone ( ) ) ,
53
+ ( format ! ( "{}-digest" , NAME ) , self . digest . clone ( ) ) ,
54
+ ]
55
+ . into ( )
56
+ }
37
57
}
38
58
39
59
#[ derive( Serialize ) ]
@@ -46,26 +66,42 @@ pub struct ErrorBody {
46
66
}
47
67
48
68
impl ErrorBody {
49
- pub fn from_config (
69
+ pub fn new (
50
70
config : & ErrorConfig ,
51
71
timestamp : & mut impl Timestamp ,
52
- exit : & ExitStatus ,
53
- lines : impl IntoIterator < Item = String > ,
72
+ error : ErrorBodyError ,
73
+ tags : impl IntoIterator < Item = ( String , String ) > ,
54
74
) -> Self {
55
75
ErrorBody {
56
76
timestamp : timestamp. as_secs ( ) ,
57
77
action : config. action . clone ( ) ,
58
78
namespace : "process" . to_string ( ) ,
59
- error : ErrorBodyError :: new ( exit, lines) ,
60
- tags : exit_tags ( exit)
61
- . into_iter ( )
62
- . chain ( [
63
- ( "hostname" . to_string ( ) , config. hostname . clone ( ) ) ,
64
- ( format ! ( "{}-digest" , NAME ) , config. digest . clone ( ) ) ,
65
- ] )
66
- . collect ( ) ,
79
+ error,
80
+ tags : tags. into_iter ( ) . chain ( config. tags ( ) ) . collect ( ) ,
67
81
}
68
82
}
83
+
84
+ pub fn from_spawn (
85
+ config : & ErrorConfig ,
86
+ timestamp : & mut impl Timestamp ,
87
+ error : & std:: io:: Error ,
88
+ ) -> Self {
89
+ Self :: new ( config, timestamp, ErrorBodyError :: from_spawn ( error) , vec ! [ ] )
90
+ }
91
+
92
+ pub fn from_exit (
93
+ config : & ErrorConfig ,
94
+ timestamp : & mut impl Timestamp ,
95
+ exit : & ExitStatus ,
96
+ lines : impl IntoIterator < Item = String > ,
97
+ ) -> Self {
98
+ Self :: new (
99
+ config,
100
+ timestamp,
101
+ ErrorBodyError :: from_exit ( exit, lines) ,
102
+ exit_tags ( exit) ,
103
+ )
104
+ }
69
105
}
70
106
71
107
impl From < ErrorBody > for Body {
@@ -81,7 +117,14 @@ pub struct ErrorBodyError {
81
117
}
82
118
83
119
impl ErrorBodyError {
84
- pub fn new ( exit : & ExitStatus , lines : impl IntoIterator < Item = String > ) -> Self {
120
+ pub fn from_spawn ( error : & std:: io:: Error ) -> Self {
121
+ ErrorBodyError {
122
+ name : "StartError" . to_string ( ) ,
123
+ message : format ! ( "[Error starting process: {}]" , error) ,
124
+ }
125
+ }
126
+
127
+ pub fn from_exit ( exit : & ExitStatus , lines : impl IntoIterator < Item = String > ) -> Self {
85
128
let ( name, exit_context) = if let Some ( code) = exit. code ( ) {
86
129
( "NonZeroExit" . to_string ( ) , format ! ( "code {}" , code) )
87
130
} else if let Some ( signal) = exit. signal ( ) {
@@ -136,15 +179,59 @@ mod tests {
136
179
}
137
180
138
181
#[ test]
139
- fn error_config_request ( ) {
182
+ fn error_config_request_from_spawn ( ) {
183
+ let config = error_config ( ) ;
184
+ let error = std:: io:: Error :: new (
185
+ std:: io:: ErrorKind :: NotFound ,
186
+ "No such file or directory (os error 2)" ,
187
+ ) ;
188
+
189
+ let request = config. request_from_spawn ( & mut timestamp ( ) , & error) . unwrap ( ) ;
190
+
191
+ assert_eq ! ( request. method( ) . as_str( ) , "POST" ) ;
192
+ assert_eq ! (
193
+ request. url( ) . as_str( ) ,
194
+ "https://some-endpoint.com/errors?api_key=some_api_key"
195
+ ) ;
196
+ assert_eq ! (
197
+ request. headers( ) . get( "Content-Type" ) . unwrap( ) ,
198
+ "application/json"
199
+ ) ;
200
+ assert_eq ! (
201
+ String :: from_utf8_lossy( request. body( ) . unwrap( ) . as_bytes( ) . unwrap( ) ) ,
202
+ format!(
203
+ concat!(
204
+ "{{" ,
205
+ r#""timestamp":{},"# ,
206
+ r#""action":"some-action","# ,
207
+ r#""namespace":"process","# ,
208
+ r#""error":{{"# ,
209
+ r#""name":"StartError","# ,
210
+ r#""message":"[Error starting process: No such file or directory (os error 2)]""# ,
211
+ r#"}},"# ,
212
+ r#""tags":{{"# ,
213
+ r#""{}-digest":"some-digest","# ,
214
+ r#""hostname":"some-hostname""# ,
215
+ r#"}}"# ,
216
+ "}}"
217
+ ) ,
218
+ EXPECTED_SECS , NAME
219
+ )
220
+ ) ;
221
+ }
222
+
223
+ #[ test]
224
+ fn error_config_request_from_exit ( ) {
140
225
let config = error_config ( ) ;
141
226
// `ExitStatus::from_raw` expects a wait status, not an exit status.
142
227
// The wait status for exit code `n` is represented by `n << 8`.
143
228
// See `__WEXITSTATUS` in `glibc/bits/waitstatus.h` for reference.
144
229
let exit = ExitStatus :: from_raw ( 42 << 8 ) ;
145
230
let lines = vec ! [ "line 1" . to_string( ) , "line 2" . to_string( ) ] ;
146
231
147
- let request = config. request ( & mut timestamp ( ) , & exit, lines) . unwrap ( ) ;
232
+ let request = config
233
+ . request_from_exit ( & mut timestamp ( ) , & exit, lines)
234
+ . unwrap ( ) ;
148
235
149
236
assert_eq ! ( request. method( ) . as_str( ) , "POST" ) ;
150
237
assert_eq ! (
0 commit comments