github.com/honeycombio/honeytail@v1.9.0/parsers/nginx/nginx_test.go (about) 1 package nginx 2 3 import ( 4 "log" 5 "reflect" 6 "regexp" 7 "testing" 8 "time" 9 10 "github.com/honeycombio/gonx" 11 "github.com/honeycombio/honeytail/event" 12 "github.com/honeycombio/honeytail/httime" 13 "github.com/honeycombio/honeytail/httime/httimetest" 14 "github.com/honeycombio/honeytail/parsers" 15 ) 16 17 func init() { 18 fakeNow, err := time.ParseInLocation(commonLogFormatTimeLayout, "02/Jan/2010:12:34:56 -0000", time.UTC) 19 if err != nil { 20 log.Fatal(err) 21 } 22 httime.DefaultNower = &httimetest.FakeNower{fakeNow} 23 } 24 25 type testLineMaps struct { 26 line string 27 trimmedLine string 28 ev event.Event 29 } 30 31 func TestProcessLines(t *testing.T) { 32 t1, _ := time.ParseInLocation(commonLogFormatTimeLayout, "08/Oct/2015:00:26:26 -0000", time.UTC) 33 preReg := &parsers.ExtRegexp{regexp.MustCompile("^.*:..:.. (?P<pre_hostname>[a-zA-Z-.]+): ")} 34 tlm := []testLineMaps{ 35 { 36 line: "Nov 05 10:23:45 myhost: https - 10.252.4.24 - - [08/Oct/2015:00:26:26 +0000] 200 174 0.099 a873d74c-4588-4a25-b3ed-77d23fe6275a 17d22fa7-796e-85f6-3d58-2d6e693da860", 37 trimmedLine: "https - 10.252.4.24 - - [08/Oct/2015:00:26:26 +0000] 200 174 0.099 a873d74c-4588-4a25-b3ed-77d23fe6275a 17d22fa7-796e-85f6-3d58-2d6e693da860", 38 ev: event.Event{ 39 Timestamp: t1, 40 Data: map[string]interface{}{ 41 "pre_hostname": "myhost", 42 "body_bytes_sent": int64(174), 43 "http_x_forwarded_proto": "https", 44 "remote_addr": "10.252.4.24", 45 "request_time": 0.099, 46 "status": int64(200), 47 "traceId": "a873d74c-4588-4a25-b3ed-77d23fe6275a", 48 "id": "17d22fa7-796e-85f6-3d58-2d6e693da860", 49 }, 50 }, 51 }, 52 } 53 p := &Parser{ 54 conf: Options{ 55 NumParsers: 5, 56 }, 57 lineParser: &GonxLineParser{ 58 parser: gonx.NewParser("$http_x_forwarded_proto - $remote_addr - $remote_user [$time_local] $status $body_bytes_sent $request_time $traceId $id"), 59 }, 60 } 61 lines := make(chan string) 62 send := make(chan event.Event) 63 go func() { 64 for _, pair := range tlm { 65 lines <- pair.line 66 } 67 close(lines) 68 }() 69 go p.ProcessLines(lines, send, preReg) 70 for _, pair := range tlm { 71 resp := <-send 72 if !reflect.DeepEqual(resp, pair.ev) { 73 t.Fatalf("line resp didn't match up for %s. Expected: %+v, actual: %+v", 74 pair.line, pair.ev, resp) 75 } 76 } 77 } 78 79 func TestProcessLinesNoPreReg(t *testing.T) { 80 t1, _ := time.ParseInLocation(commonLogFormatTimeLayout, "08/Oct/2015:00:26:26 +0000", time.UTC) 81 tlm := []testLineMaps{ 82 { 83 line: "https - 10.252.4.24 - - [08/Oct/2015:00:26:26 +0000] 200 174 0.099", 84 trimmedLine: "https - 10.252.4.24 - - [08/Oct/2015:00:26:26 +0000] 200 174 0.099", 85 ev: event.Event{ 86 Timestamp: t1, 87 Data: map[string]interface{}{ 88 "body_bytes_sent": int64(174), 89 "http_x_forwarded_proto": "https", 90 "remote_addr": "10.252.4.24", 91 "request_time": 0.099, 92 "status": int64(200), 93 }, 94 }, 95 }, 96 } 97 p := &Parser{ 98 conf: Options{ 99 NumParsers: 5, 100 }, 101 lineParser: &GonxLineParser{ 102 parser: gonx.NewParser("$http_x_forwarded_proto - $remote_addr - $remote_user [$time_local] $status $body_bytes_sent $request_time"), 103 }, 104 } 105 lines := make(chan string) 106 send := make(chan event.Event) 107 go func() { 108 for _, pair := range tlm { 109 lines <- pair.line 110 } 111 close(lines) 112 }() 113 go p.ProcessLines(lines, send, nil) 114 for _, pair := range tlm { 115 resp := <-send 116 if !reflect.DeepEqual(resp, pair.ev) { 117 t.Fatalf("line resp didn't match up for %s. Expected: %v, actual: %v", 118 pair.line, pair.ev.Data, resp.Data) 119 } 120 } 121 } 122 123 type typeifyTestCase struct { 124 untyped map[string]string 125 typed map[string]interface{} 126 } 127 128 func TestTypeifyParsedLine(t *testing.T) { 129 tc := typeifyTestCase{ 130 untyped: map[string]string{ 131 "str": "str", // should stay string 132 "space": "str with space", // should stay string 133 "ver": "5.1.0", // should stay string 134 "dash": "-", // should vanish 135 "float": "4.134", // should become float 136 "int": "987", // should become int 137 "negint": "-5", // should become int 138 }, 139 typed: map[string]interface{}{ 140 "str": "str", 141 "space": "str with space", 142 "ver": "5.1.0", 143 "float": float64(4.134), 144 "int": int64(987), 145 "negint": int64(-5), 146 }, 147 } 148 res := typeifyParsedLine(tc.untyped) 149 if !reflect.DeepEqual(res, tc.typed) { 150 t.Fatalf("Comparison failed. Expected: %v, Actual: %v", tc.typed, res) 151 } 152 } 153 154 func TestGetTimestamp(t *testing.T) { 155 t1, _ := time.ParseInLocation(commonLogFormatTimeLayout, "08/Oct/2015:00:26:26 +0000", time.UTC) 156 t2, _ := time.ParseInLocation(commonLogFormatTimeLayout, "02/Jan/2010:12:34:56 -0000", time.UTC) 157 userDefinedTimeFormat := "2006-01-02T15:04:05.9999Z" 158 exampleCustomFormatTimestamp := "2017-07-31T20:40:57.980264Z" 159 t3, _ := time.ParseInLocation(userDefinedTimeFormat, exampleCustomFormatTimestamp, time.UTC) 160 t4 := time.Unix(1444263986, 250000000) 161 msec := 1444263986.25 162 testCases := []struct { 163 desc string 164 conf Options 165 input map[string]interface{} 166 postMunge map[string]interface{} 167 retval time.Time 168 }{ 169 { 170 desc: "well formatted time_local", 171 conf: Options{}, 172 input: map[string]interface{}{ 173 "foo": "bar", 174 "time_local": "08/Oct/2015:00:26:26 +0000", 175 }, 176 postMunge: map[string]interface{}{ 177 "foo": "bar", 178 }, 179 retval: t1, 180 }, 181 { 182 desc: "well formatted time_iso", 183 conf: Options{}, 184 input: map[string]interface{}{ 185 "foo": "bar", 186 "time_iso8601": "2015-10-08T00:26:26-00:00", 187 }, 188 postMunge: map[string]interface{}{ 189 "foo": "bar", 190 }, 191 retval: t1, 192 }, 193 { 194 desc: "broken formatted time_local", 195 conf: Options{}, 196 input: map[string]interface{}{ 197 "foo": "bar", 198 "time_local": "08aoeu00:26:26 +0000", 199 }, 200 postMunge: map[string]interface{}{ 201 "foo": "bar", 202 }, 203 retval: t2, 204 }, 205 { 206 desc: "broken formatted time_iso", 207 conf: Options{}, 208 input: map[string]interface{}{ 209 "foo": "bar", 210 "time_iso8601": "2015-aoeu00:00", 211 }, 212 postMunge: map[string]interface{}{ 213 "foo": "bar", 214 }, 215 retval: t2, 216 }, 217 { 218 desc: "non-string formatted time_local", 219 conf: Options{}, 220 input: map[string]interface{}{ 221 "foo": "bar", 222 "time_local": 1234, 223 }, 224 postMunge: map[string]interface{}{ 225 "foo": "bar", 226 }, 227 retval: t2, 228 }, 229 { 230 desc: "non-string formatted time_iso", 231 conf: Options{}, 232 input: map[string]interface{}{ 233 "foo": "bar", 234 "time_iso8601": 1234, 235 }, 236 postMunge: map[string]interface{}{ 237 "foo": "bar", 238 }, 239 retval: t2, 240 }, 241 { 242 desc: "missing time field", 243 conf: Options{}, 244 input: map[string]interface{}{ 245 "foo": "bar", 246 }, 247 postMunge: map[string]interface{}{ 248 "foo": "bar", 249 }, 250 retval: t2, 251 }, 252 { 253 desc: "timestamp in user-defined format", 254 conf: Options{ 255 TimeFieldFormat: userDefinedTimeFormat, 256 TimeFieldName: "timestamp", 257 }, 258 input: map[string]interface{}{ 259 "foo": "bar", 260 "timestamp": exampleCustomFormatTimestamp, 261 }, 262 postMunge: map[string]interface{}{ 263 "foo": "bar", 264 }, 265 retval: t3, 266 }, 267 { 268 desc: "timestamp in milliseconds", 269 conf: Options{}, 270 input: map[string]interface{}{ 271 "foo": "bar", 272 "msec": msec, 273 }, 274 postMunge: map[string]interface{}{ 275 "foo": "bar", 276 }, 277 retval: t4, 278 }, 279 } 280 281 for _, tc := range testCases { 282 parser := &Parser{ 283 conf: tc.conf, 284 } 285 res := parser.getTimestamp(tc.input) 286 if !reflect.DeepEqual(tc.input, tc.postMunge) { 287 t.Errorf("didn't remove time field in %q: %v", tc.desc, tc.input) 288 } 289 if !reflect.DeepEqual(res, tc.retval) { 290 t.Errorf("got wrong time in %q. expected %v got %v", tc.desc, tc.retval, res) 291 } 292 } 293 }