github.com/honeycombio/honeytail@v1.9.0/parsers/postgresql/postgresql_test.go (about)

     1  package postgresql
     2  
     3  import (
     4  	"strings"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/honeycombio/honeytail/event"
    10  	"github.com/stretchr/testify/assert"
    11  )
    12  
    13  // Test parsing individual log statements with different prefix formats.
    14  func TestSingleQueryParsing(t *testing.T) {
    15  	testcases := []struct {
    16  		description  string
    17  		in           string
    18  		prefixFormat string
    19  		expected     event.Event
    20  	}{
    21  		{
    22  			description: "parse a multiline log statement from a default postgres 9.5 log",
    23  			in: `2017-11-07 23:05:16 UTC [3053-3] postgres@postgres LOG:  duration: 0.681 ms  statement: SELECT d.datname as "Name",
    24  	       pg_catalog.pg_get_userbyid(d.datdba) as "Owner",
    25  	       pg_catalog.pg_encoding_to_char(d.encoding) as "Encoding",
    26  	       d.datcollate as "Collate",
    27  	       d.datctype as "Ctype",
    28  	       pg_catalog.array_to_string(d.datacl, E'\n') AS "Access privileges"
    29  	FROM pg_catalog.pg_database d
    30  	ORDER BY 1;`,
    31  			prefixFormat: "%t [%p-%l] %u@%d",
    32  			expected: event.Event{
    33  				Timestamp: time.Date(2017, 11, 7, 23, 5, 16, 0, time.UTC),
    34  				Data: map[string]interface{}{
    35  					"user":                "postgres",
    36  					"database":            "postgres",
    37  					"duration":            0.681,
    38  					"duration_ms":         0.681,
    39  					"pid":                 3053,
    40  					"session_line_number": 3,
    41  					"query":               "SELECT d.datname as \"Name\", pg_catalog.pg_get_userbyid(d.datdba) as \"Owner\", pg_catalog.pg_encoding_to_char(d.encoding) as \"Encoding\", d.datcollate as \"Collate\", d.datctype as \"Ctype\", pg_catalog.array_to_string(d.datacl, E'\\n') AS \"Access privileges\" FROM pg_catalog.pg_database d ORDER BY 1;",
    42  					"normalized_query":    "select d.datname as ?, pg_catalog.pg_get_userbyid(d.datdba) as ?, pg_catalog.pg_encoding_to_char(d.encoding) as ?, d.datcollate as ?, d.datctype as ?, pg_catalog.array_to_string(d.datacl, e?) as ? from pg_catalog.pg_database d order by ?;",
    43  				},
    44  			},
    45  		},
    46  		{
    47  			description:  "extract everything you can put in a line prefix",
    48  			in:           `2017-11-08 03:02:49.314 UTC [8544-1] postgres@test (3/0) (0) (00000) (2017-11-08 03:02:38 UTC) (psql)LOG:  duration: 2.753 ms  statement: select * from test;`,
    49  			prefixFormat: `%m [%p-%l] %q%u@%d (%v) (%x) (%e) (%s) (%a)`,
    50  			expected: event.Event{
    51  				Timestamp: time.Date(2017, 11, 8, 3, 2, 49, 314000000, time.UTC),
    52  				Data: map[string]interface{}{
    53  					"user":                   "postgres",
    54  					"database":               "test",
    55  					"duration":               2.753,
    56  					"duration_ms":            2.753,
    57  					"pid":                    8544,
    58  					"session_line_number":    1,
    59  					"virtual_transaction_id": "3/0",
    60  					"transaction_id":         "0",
    61  					"sql_state":              "00000",
    62  					"session_start":          "2017-11-08 03:02:38 UTC",
    63  					"application":            "psql",
    64  					"query":                  "select * from test;",
    65  					"normalized_query":       "select * from test;",
    66  				},
    67  			},
    68  		},
    69  		{
    70  			description:  "extract the event timestamp from a unix time",
    71  			in:           `1510258541402 [8544-1] postgres@test LOG:  duration: 2.753 ms  statement: select * from test;`,
    72  			prefixFormat: "%n [%p-%l] %u@%d",
    73  			expected: event.Event{
    74  				Timestamp: time.Date(2017, 11, 9, 20, 15, 41, 402000000, time.UTC),
    75  				Data: map[string]interface{}{
    76  					"user":                "postgres",
    77  					"database":            "test",
    78  					"duration":            2.753,
    79  					"duration_ms":         2.753,
    80  					"pid":                 8544,
    81  					"session_line_number": 1,
    82  					"query":               "select * from test;",
    83  					"normalized_query":    "select * from test;",
    84  				},
    85  			},
    86  		},
    87  		{
    88  			description:  "parse a prepared statement",
    89  			in:           `2017-11-07 23:05:16 UTC [3053-3] postgres@postgres LOG:  duration: 0.681 ms  execute sbstmt-1751784287-397260381: SELECT c FROM sbtest1 WHERE id=$1`,
    90  			prefixFormat: "%t [%p-%l] %u@%d",
    91  			expected: event.Event{
    92  				Timestamp: time.Date(2017, 11, 7, 23, 5, 16, 0, time.UTC),
    93  				Data: map[string]interface{}{
    94  					"user":                "postgres",
    95  					"database":            "postgres",
    96  					"duration":            0.681,
    97  					"duration_ms":         0.681,
    98  					"pid":                 3053,
    99  					"session_line_number": 3,
   100  					"query":               "SELECT c FROM sbtest1 WHERE id=$1",
   101  					"normalized_query":    "select c from sbtest1 where id=$?",
   102  				},
   103  			},
   104  		},
   105  		{
   106  			description:  "extract the trace id and parent id from a flat comment",
   107  			in:           `1510258541402 [8544-1] postgres@test LOG:  duration: 2.753 ms  statement: select * from test /* trace_id='5bd66ef5095369c7b0d1f8f4bd33716a', parent_id='c532cb4098ac3dd2' */`,
   108  			prefixFormat: "%n [%p-%l] %u@%d",
   109  			expected: event.Event{
   110  				Timestamp: time.Date(2017, 11, 9, 20, 15, 41, 402000000, time.UTC),
   111  				Data: map[string]interface{}{
   112  					"user":                "postgres",
   113  					"database":            "test",
   114  					"duration":            2.753,
   115  					"duration_ms":         2.753,
   116  					"pid":                 8544,
   117  					"session_line_number": 1,
   118  					"query":               "select * from test /* trace_id='5bd66ef5095369c7b0d1f8f4bd33716a', parent_id='c532cb4098ac3dd2' */",
   119  					"normalized_query":    "select * from test",
   120  					"tables":              "test",
   121  					"trace.trace_id":      "5bd66ef5095369c7b0d1f8f4bd33716a",
   122  					"trace.parent_id":     "c532cb4098ac3dd2",
   123  				},
   124  			},
   125  		},
   126  		{
   127  			description:  "extract the trace id and parent id from a flat comment with different quotes",
   128  			in:           `1510258541402 [8544-1] postgres@test LOG:  duration: 2.753 ms  statement: select * from test /* trace_id="5bd66ef5095369c7b0d1f8f4bd33716a", parent_id="c532cb4098ac3dd2" */`,
   129  			prefixFormat: "%n [%p-%l] %u@%d",
   130  			expected: event.Event{
   131  				Timestamp: time.Date(2017, 11, 9, 20, 15, 41, 402000000, time.UTC),
   132  				Data: map[string]interface{}{
   133  					"user":                "postgres",
   134  					"database":            "test",
   135  					"duration":            2.753,
   136  					"duration_ms":         2.753,
   137  					"pid":                 8544,
   138  					"session_line_number": 1,
   139  					"query":               "select * from test /* trace_id=\"5bd66ef5095369c7b0d1f8f4bd33716a\", parent_id=\"c532cb4098ac3dd2\" */",
   140  					"normalized_query":    "select * from test",
   141  					"tables":              "test",
   142  					"trace.trace_id":      "5bd66ef5095369c7b0d1f8f4bd33716a",
   143  					"trace.parent_id":     "c532cb4098ac3dd2",
   144  				},
   145  			},
   146  		},
   147  		{
   148  			description:  "extract the trace id and parent id from a flat comment in different order",
   149  			in:           `1510258541402 [8544-1] postgres@test LOG:  duration: 2.753 ms  statement: select * from test /* parent_id='c532cb4098ac3dd2' trace_id='5bd66ef5095369c7b0d1f8f4bd33716a' */`,
   150  			prefixFormat: "%n [%p-%l] %u@%d",
   151  			expected: event.Event{
   152  				Timestamp: time.Date(2017, 11, 9, 20, 15, 41, 402000000, time.UTC),
   153  				Data: map[string]interface{}{
   154  					"user":                "postgres",
   155  					"database":            "test",
   156  					"duration":            2.753,
   157  					"duration_ms":         2.753,
   158  					"pid":                 8544,
   159  					"session_line_number": 1,
   160  					"query":               "select * from test /* parent_id='c532cb4098ac3dd2' trace_id='5bd66ef5095369c7b0d1f8f4bd33716a' */",
   161  					"normalized_query":    "select * from test",
   162  					"tables":              "test",
   163  					"trace.trace_id":      "5bd66ef5095369c7b0d1f8f4bd33716a",
   164  					"trace.parent_id":     "c532cb4098ac3dd2",
   165  				},
   166  			},
   167  		},
   168  		{
   169  			description: "extract the trace id and parent id from a deep comment",
   170  			in: `2017-11-07 23:05:16 UTC [3053-3] postgres@postgres LOG:  duration: 0.681 ms  statement: SELECT d.datname as "Name",
   171  			pg_catalog.pg_get_userbyid(d.datdba) as "Owner",
   172  			pg_catalog.pg_encoding_to_char(d.encoding) as "Encoding",
   173  			d.datcollate as "Collate",
   174  			d.datctype as "Ctype",
   175  			pg_catalog.array_to_string(d.datacl, E'\n') AS "Access privileges"
   176  	 FROM pg_catalog.pg_database d
   177  	 ORDER BY 1 /* trace.trace_id='5bd66ef5095369c7b0d1f8f4bd33716a', trace.parent_id='c532cb4098ac3dd2' */;`,
   178  			prefixFormat: "%t [%p-%l] %u@%d",
   179  			expected: event.Event{
   180  				Timestamp: time.Date(2017, 11, 7, 23, 5, 16, 0, time.UTC),
   181  				Data: map[string]interface{}{
   182  					"user":                "postgres",
   183  					"database":            "postgres",
   184  					"duration":            0.681,
   185  					"duration_ms":         0.681,
   186  					"pid":                 3053,
   187  					"session_line_number": 3,
   188  					"query":               "SELECT d.datname as \"Name\", pg_catalog.pg_get_userbyid(d.datdba) as \"Owner\", pg_catalog.pg_encoding_to_char(d.encoding) as \"Encoding\", d.datcollate as \"Collate\", d.datctype as \"Ctype\", pg_catalog.array_to_string(d.datacl, E'\\n') AS \"Access privileges\" FROM pg_catalog.pg_database d ORDER BY 1 /* trace.trace_id='5bd66ef5095369c7b0d1f8f4bd33716a', trace.parent_id='c532cb4098ac3dd2' */;",
   189  					"normalized_query":    "select d.datname as ?, pg_catalog.pg_get_userbyid(d.datdba) as ?, pg_catalog.pg_encoding_to_char(d.encoding) as ?, d.datcollate as ?, d.datctype as ?, pg_catalog.array_to_string(d.datacl, e?) as ? from pg_catalog.pg_database d order by ? /* trace.trace_id=?, trace.parent_id=? */;",
   190  					"trace.trace_id":      "5bd66ef5095369c7b0d1f8f4bd33716a",
   191  					"trace.parent_id":     "c532cb4098ac3dd2",
   192  				},
   193  			},
   194  		},
   195  		{
   196  			description:  "extract the trace id and parent id from a traceparent",
   197  			in:           `1510258541402 [8544-1] postgres@test LOG:  duration: 2.753 ms  statement: select * from test /* traceparent='00-5bd66ef5095369c7b0d1f8f4bd33716a-c532cb4098ac3dd2-01', */`,
   198  			prefixFormat: "%n [%p-%l] %u@%d",
   199  			expected: event.Event{
   200  				Timestamp: time.Date(2017, 11, 9, 20, 15, 41, 402000000, time.UTC),
   201  				Data: map[string]interface{}{
   202  					"user":                "postgres",
   203  					"database":            "test",
   204  					"duration":            2.753,
   205  					"duration_ms":         2.753,
   206  					"pid":                 8544,
   207  					"session_line_number": 1,
   208  					"query":               "select * from test /* traceparent='00-5bd66ef5095369c7b0d1f8f4bd33716a-c532cb4098ac3dd2-01', */",
   209  					"normalized_query":    "select * from test",
   210  					"tables":              "test",
   211  					"trace.trace_id":      "5bd66ef5095369c7b0d1f8f4bd33716a",
   212  					"trace.parent_id":     "c532cb4098ac3dd2",
   213  				},
   214  			},
   215  		},
   216  	}
   217  
   218  	for _, tc := range testcases {
   219  		t.Run(tc.description, func(t *testing.T) {
   220  			in := make(chan []string)
   221  			out := make(chan event.Event)
   222  			p := Parser{}
   223  			p.Init(&Options{LogLinePrefix: tc.prefixFormat})
   224  			wg := &sync.WaitGroup{}
   225  			wg.Add(1)
   226  			go p.handleEvents(in, out, wg)
   227  			in <- strings.Split(tc.in, "\n")
   228  			close(in)
   229  			got := <-out
   230  			assert.Equal(t, tc.expected, got)
   231  		})
   232  	}
   233  }
   234  
   235  // Test grouping and parsing multiple log statements.
   236  func TestMultipleQueryParsing(t *testing.T) {
   237  	in := `
   238  2017-11-07 01:43:18 UTC [3542-5] postgres@test LOG:  duration: 9.263 ms  statement: INSERT INTO test (id, name, value) VALUES (1, 'Alice', 'foo');
   239  2017-11-07 01:43:27 UTC [3542-6] postgres@test LOG:  duration: 0.841 ms  statement: INSERT INTO test (id, name, value) VALUES (2, 'Bob', 'bar');
   240  2017-11-07 01:43:39 UTC [3542-7] postgres@test LOG:  duration: 15.577 ms  statement: SELECT * FROM test
   241  	WHERE id=1;
   242  2017-11-07 01:43:42 UTC [3542-8] postgres@test LOG:  duration: 0.501 ms  statement: SELECT * FROM test
   243  	WHERE id=2;
   244  `
   245  	out := []event.Event{
   246  		{
   247  			Timestamp: time.Date(2017, 11, 7, 1, 43, 18, 0, time.UTC),
   248  			Data: map[string]interface{}{
   249  				"user":                "postgres",
   250  				"database":            "test",
   251  				"duration":            9.263,
   252  				"duration_ms":         9.263,
   253  				"pid":                 3542,
   254  				"session_line_number": 5,
   255  				"query":               "INSERT INTO test (id, name, value) VALUES (1, 'Alice', 'foo');",
   256  				"normalized_query":    "insert into test (id, name, value) values (?, ?, ?);",
   257  			},
   258  		},
   259  		{
   260  			Timestamp: time.Date(2017, 11, 7, 1, 43, 27, 0, time.UTC),
   261  			Data: map[string]interface{}{
   262  				"user":                "postgres",
   263  				"database":            "test",
   264  				"duration":            0.841,
   265  				"duration_ms":         0.841,
   266  				"pid":                 3542,
   267  				"session_line_number": 6,
   268  				"query":               "INSERT INTO test (id, name, value) VALUES (2, 'Bob', 'bar');",
   269  				"normalized_query":    "insert into test (id, name, value) values (?, ?, ?);",
   270  			},
   271  		},
   272  		{
   273  			Timestamp: time.Date(2017, 11, 7, 1, 43, 39, 0, time.UTC),
   274  			Data: map[string]interface{}{
   275  				"user":                "postgres",
   276  				"database":            "test",
   277  				"duration":            15.577,
   278  				"duration_ms":         15.577,
   279  				"pid":                 3542,
   280  				"session_line_number": 7,
   281  				"query":               "SELECT * FROM test WHERE id=1;",
   282  				"normalized_query":    "select * from test where id=?;",
   283  			},
   284  		},
   285  		{
   286  			Timestamp: time.Date(2017, 11, 7, 1, 43, 42, 0, time.UTC),
   287  			Data: map[string]interface{}{
   288  				"user":                "postgres",
   289  				"database":            "test",
   290  				"duration":            0.501,
   291  				"duration_ms":         0.501,
   292  				"pid":                 3542,
   293  				"session_line_number": 8,
   294  				"query":               "SELECT * FROM test WHERE id=2;",
   295  				"normalized_query":    "select * from test where id=?;",
   296  			},
   297  		},
   298  	}
   299  
   300  	parser := Parser{}
   301  	parser.Init(nil)
   302  	inChan := make(chan string)
   303  	sendChan := make(chan event.Event, 4)
   304  	go parser.ProcessLines(inChan, sendChan, nil)
   305  	for _, line := range strings.Split(in, "\n") {
   306  		inChan <- line
   307  	}
   308  	close(inChan)
   309  	for _, expected := range out {
   310  		got := <-sendChan
   311  		assert.Equal(t, expected, got)
   312  	}
   313  }
   314  
   315  // Test handling log statements that aren't slow query logs
   316  func TestSkipNonQueryLogLines(t *testing.T) {
   317  	parser := Parser{}
   318  	parser.Init(nil)
   319  	testcases := []string{
   320  		"la la la",
   321  		"2017-11-06 19:20:32 UTC [11534-2] LOG:  autovacuum launcher shutting down",
   322  		"2017-11-07 01:43:39 UTC [3542-7] postgres@test ERROR: relation \"test\" does not exist at character 15",
   323  	}
   324  
   325  	for _, tc := range testcases {
   326  		lineGroup := []string{tc}
   327  		ev := parser.handleEvent(lineGroup)
   328  		assert.Nil(t, ev)
   329  	}
   330  }
   331  
   332  func TestEnsureRegexMatchesBeginningOfLine(t *testing.T) {
   333  	parser := Parser{}
   334  	parser.Init(&Options{
   335  		// missing "[PUCE] " prefix
   336  		LogLinePrefix: "[%p-%l]  sql_error_code = %e",
   337  	})
   338  	line := "[PUCE] [200-1]  sql_error_code = 00000 LOG:  duration: 1050.729 ms  execute <unnamed>: UPDATE \"repositories\" SET \"current_build_id\" = 341933279, \"updated_at\" = '2018-02-15 15:21:55.174858' WHERE \"repositories\".\"id\" = 16235973"
   339  
   340  	lineGroup := []string{line}
   341  	got := parser.handleEvent(lineGroup)
   342  	assert.Nil(t, got)
   343  }
   344  
   345  func TestCustomLogLinePrefix(t *testing.T) {
   346  	parser := Parser{}
   347  	parser.Init(&Options{
   348  		LogLinePrefix: "[PUCE] [%p-%l]  sql_error_code = %e",
   349  	})
   350  	line := "[PUCE] [200-1]  sql_error_code = 00000 LOG:  duration: 1050.729 ms  execute <unnamed>: UPDATE \"repositories\" SET \"current_build_id\" = 341933279, \"updated_at\" = '2018-02-15 15:21:55.174858' WHERE \"repositories\".\"id\" = 16235973"
   351  	expected := &event.Event{
   352  		Timestamp: time.Date(0001, 1, 1, 0, 0, 0, 0, time.UTC),
   353  		Data: map[string]interface{}{
   354  			"duration":            1050.729,
   355  			"duration_ms":         1050.729,
   356  			"pid":                 200,
   357  			"session_line_number": 1,
   358  			"query":               "UPDATE \"repositories\" SET \"current_build_id\" = 341933279, \"updated_at\" = '2018-02-15 15:21:55.174858' WHERE \"repositories\".\"id\" = 16235973",
   359  			"normalized_query":    "update ? set ? = ?, ? = ? where ?.? = ?",
   360  			"sql_state":           "00000",
   361  		},
   362  	}
   363  
   364  	lineGroup := []string{line}
   365  	got := parser.handleEvent(lineGroup)
   366  	assert.Equal(t, expected, got)
   367  }