Pull request: querylog bug fix

Merge in DNS/adguard-home from 2324-querylog-bug-fix to master

Closes #2324.

Squashed commit of the following:

commit fdd584a218e1edc3e45ab5b00ceed0a3be681e32
Merge: 8103f9e42 f2eda6d19
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Wed Nov 18 15:35:42 2020 +0300

    Merge branch 'master' into 2324-querylog-bug-fix

commit 8103f9e42a398f43682ee30d09b3afdab0e9e177
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Wed Nov 18 14:28:29 2020 +0300

    querylog: fix the file ordering bug

commit 2c4e8fcc5b8593be1614480508dfd600fc676e64
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Tue Nov 17 20:57:45 2020 +0300

    querylog: wrap errors to clarify error trace

commit 3733062b494817696e4443f153774bb01cea1b06
Author: Eugene Burkov <e.burkov@adguard.com>
Date:   Tue Nov 17 18:55:17 2020 +0300

    querylog: fix logger output bug
This commit is contained in:
Eugene Burkov 2020-11-18 15:43:28 +03:00
parent f2eda6d192
commit 40614d9a7b
4 changed files with 132 additions and 19 deletions

View File

@ -48,7 +48,7 @@ func TestError_Unwrap(t *testing.T) {
) )
errs := []error{ errs := []error{
errSimple: errors.New("a"), errSimple: errors.New("a"),
errWrapped: fmt.Errorf("%w", errors.New("nested")), errWrapped: fmt.Errorf("err: %w", errors.New("nested")),
errNil: nil, errNil: nil,
} }
testCases := []struct { testCases := []struct {

View File

@ -1,6 +1,7 @@
package querylog package querylog
import ( import (
"fmt"
"io" "io"
"os" "os"
"sync" "sync"
@ -102,13 +103,14 @@ func (q *QLogFile) Seek(timestamp int64) (int64, int, error) {
if err != nil { if err != nil {
return 0, depth, err return 0, depth, err
} }
if lineIdx < start || lineEndIdx > end || lineIdx == lastProbeLineIdx { if lineIdx < start || lineEndIdx > end || lineIdx == lastProbeLineIdx {
// If we're testing the same line twice then most likely // If we're testing the same line twice then most likely
// the scope is too narrow and we won't find anything anymore // the scope is too narrow and we won't find anything
log.Error("querylog: didn't find timestamp:%v", timestamp) // anymore in any other file.
return 0, depth, ErrSeekNotFound return 0, depth, fmt.Errorf("couldn't find timestamp %v: %w", timestamp, ErrSeekNotFound)
} else if lineIdx == end && lineEndIdx == end { } else if lineIdx == end && lineEndIdx == end {
// If both line beginning and line ending indices point
// at the end of the file, we apparently reached it.
return 0, depth, ErrEndOfLog return 0, depth, ErrEndOfLog
} }
@ -119,7 +121,7 @@ func (q *QLogFile) Seek(timestamp int64) (int64, int, error) {
ts := readQLogTimestamp(line) ts := readQLogTimestamp(line)
if ts == 0 { if ts == 0 {
return 0, depth, ErrSeekNotFound return 0, depth, fmt.Errorf("couldn't get timestamp: %w", ErrSeekNotFound)
} }
if ts == timestamp { if ts == timestamp {
@ -141,8 +143,7 @@ func (q *QLogFile) Seek(timestamp int64) (int64, int, error) {
depth++ depth++
if depth >= 100 { if depth >= 100 {
log.Error("Seek depth is too high, aborting. File %s, ts %v", q.file.Name(), timestamp) return 0, depth, fmt.Errorf("seek depth is too high, aborting. File %s, timestamp %v: %w", q.file.Name(), timestamp, ErrSeekNotFound)
return 0, depth, ErrSeekNotFound
} }
} }

View File

@ -2,6 +2,7 @@ package querylog
import ( import (
"errors" "errors"
"fmt"
"io" "io"
"github.com/AdguardTeam/AdGuardHome/internal/agherr" "github.com/AdguardTeam/AdGuardHome/internal/agherr"
@ -49,22 +50,22 @@ func NewQLogReader(files []string) (*QLogReader, error) {
// //
// Returns nil if the record is successfully found. // Returns nil if the record is successfully found.
// Returns an error if for some reason we could not find a record with the specified timestamp. // Returns an error if for some reason we could not find a record with the specified timestamp.
func (r *QLogReader) Seek(timestamp int64) error { func (r *QLogReader) Seek(timestamp int64) (err error) {
for i := len(r.qFiles) - 1; i >= 0; i-- { for i, q := range r.qFiles {
q := r.qFiles[i] _, _, err = q.Seek(timestamp)
_, _, err := q.Seek(timestamp) if err == nil {
if err == nil || errors.Is(err, ErrEndOfLog) { // Search is finished, and the searched element have
// Our search is finished, and we either found the // been found. Update currentFile only, position is
// element we were looking for or reached the end of the // already set properly in QLogFile.
// log. Update currentFile only, position is already
// set properly in QLogFile.
r.currentFile = i r.currentFile = i
return err return err
} }
if errors.Is(err, ErrSeekNotFound) {
break
}
} }
return ErrSeekNotFound return fmt.Errorf("querylog: %w", err)
} }
// SeekStart changes the current position to the end of the newest file // SeekStart changes the current position to the end of the newest file

View File

@ -1,6 +1,7 @@
package querylog package querylog
import ( import (
"errors"
"io" "io"
"os" "os"
"testing" "testing"
@ -90,6 +91,116 @@ func TestQLogReaderMultipleFiles(t *testing.T) {
assert.Equal(t, io.EOF, err) assert.Equal(t, io.EOF, err)
} }
func TestQLogReader_Seek(t *testing.T) {
count := 10000
filesCount := 2
testDir := prepareTestDir()
t.Cleanup(func() {
_ = os.RemoveAll(testDir)
})
testFiles := prepareTestFiles(testDir, filesCount, count)
r, err := NewQLogReader(testFiles)
assert.Nil(t, err)
assert.NotNil(t, r)
t.Cleanup(func() {
_ = r.Close()
})
testCases := []struct {
name string
time string
want error
}{{
name: "not_too_old",
time: "2020-02-19T04:04:56.920973+03:00",
want: nil,
}, {
name: "old",
time: "2020-02-19T01:28:16.920973+03:00",
want: nil,
}, {
name: "first",
time: "2020-02-19T04:09:55.920973+03:00",
want: nil,
}, {
name: "last",
time: "2020-02-19T01:23:16.920973+03:00",
want: nil,
}, {
name: "non-existent_long_ago",
time: "2000-02-19T01:23:16.920973+03:00",
want: ErrSeekNotFound,
}, {
name: "non-existent_far_ahead",
time: "2100-02-19T01:23:16.920973+03:00",
want: ErrEndOfLog,
}, {
name: "non-existent_but_could",
time: "2020-02-19T03:00:00.000000+03:00",
want: ErrSeekNotFound,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
timestamp, err := time.Parse(time.RFC3339Nano, tc.time)
assert.Nil(t, err)
err = r.Seek(timestamp.UnixNano())
assert.True(t, errors.Is(err, tc.want), err)
})
}
}
func TestQLogReader_ReadNext(t *testing.T) {
count := 10
filesCount := 1
testDir := prepareTestDir()
t.Cleanup(func() {
_ = os.RemoveAll(testDir)
})
testFiles := prepareTestFiles(testDir, filesCount, count)
r, err := NewQLogReader(testFiles)
assert.Nil(t, err)
assert.NotNil(t, r)
t.Cleanup(func() {
_ = r.Close()
})
testCases := []struct {
name string
start int
want error
}{{
name: "ok",
start: 0,
want: nil,
}, {
name: "too_big",
start: count + 1,
want: io.EOF,
}}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := r.SeekStart()
assert.Nil(t, err, err)
for i := 1; i < tc.start; i++ {
_, err := r.ReadNext()
assert.Nil(t, err)
}
_, err = r.ReadNext()
assert.Equal(t, tc.want, err)
})
}
}
// TODO(e.burkov): Remove the tests below. Make tests above more compelling.
func TestQLogReaderSeek(t *testing.T) { func TestQLogReaderSeek(t *testing.T) {
// more or less big file // more or less big file
count := 10000 count := 10000