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 }