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  }