2019-08-26 09:54:38 +01:00
package querylog
2018-12-05 11:03:41 +00:00
import (
"bytes"
"encoding/json"
2021-10-29 11:43:08 +01:00
"fmt"
2018-12-05 11:03:41 +00:00
"os"
"time"
2024-10-02 19:00:15 +01:00
"github.com/AdguardTeam/AdGuardHome/internal/aghos"
2021-05-24 15:28:11 +01:00
"github.com/AdguardTeam/golibs/errors"
2019-02-25 13:44:22 +00:00
"github.com/AdguardTeam/golibs/log"
2018-12-05 11:03:41 +00:00
)
2023-03-31 15:39:04 +01:00
// flushLogBuffer flushes the current buffer to file and resets the current
// buffer.
2023-04-03 14:29:07 +01:00
func ( l * queryLog ) flushLogBuffer ( ) ( err error ) {
2023-10-03 18:50:43 +01:00
defer func ( ) { err = errors . Annotate ( err , "flushing log buffer: %w" ) } ( )
2019-05-15 11:11:36 +01:00
l . fileFlushLock . Lock ( )
defer l . fileFlushLock . Unlock ( )
2023-10-03 18:50:43 +01:00
b , err := l . encodeEntries ( )
if err != nil {
// Don't wrap the error since it's informative enough as is.
return err
}
2023-03-31 15:39:04 +01:00
2023-10-03 18:50:43 +01:00
return l . flushToFile ( b )
2019-02-10 17:47:43 +00:00
}
2023-10-03 18:50:43 +01:00
// encodeEntries returns JSON encoded log entries, logs estimated time, clears
// the log buffer.
func ( l * queryLog ) encodeEntries ( ) ( b * bytes . Buffer , err error ) {
l . bufferLock . Lock ( )
defer l . bufferLock . Unlock ( )
2023-03-31 15:39:04 +01:00
2023-10-03 18:50:43 +01:00
bufLen := l . buffer . Len ( )
if bufLen == 0 {
return nil , errors . Error ( "nothing to write to a file" )
2018-12-05 11:03:41 +00:00
}
2023-03-31 15:39:04 +01:00
2018-12-05 11:03:41 +00:00
start := time . Now ( )
2023-10-03 18:50:43 +01:00
b = & bytes . Buffer { }
e := json . NewEncoder ( b )
l . buffer . Range ( func ( entry * logEntry ) ( cont bool ) {
2021-05-24 15:28:11 +01:00
err = e . Encode ( entry )
2023-10-03 18:50:43 +01:00
return err == nil
} )
if err != nil {
// Don't wrap the error since it's informative enough as is.
return nil , err
2018-12-05 11:03:41 +00:00
}
elapsed := time . Since ( start )
2023-10-03 18:50:43 +01:00
log . Debug ( "%d elements serialized via json in %v: %d kB, %v/entry, %v/entry" , bufLen , elapsed , b . Len ( ) / 1024 , float64 ( b . Len ( ) ) / float64 ( bufLen ) , elapsed / time . Duration ( bufLen ) )
2018-12-05 11:03:41 +00:00
2023-10-03 18:50:43 +01:00
l . buffer . Clear ( )
l . flushPending = false
2018-12-05 11:03:41 +00:00
2023-10-03 18:50:43 +01:00
return b , nil
}
// flushToFile saves the encoded log entries to the query log file.
func ( l * queryLog ) flushToFile ( b * bytes . Buffer ) ( err error ) {
2019-09-27 16:58:57 +01:00
l . fileWriteLock . Lock ( )
defer l . fileWriteLock . Unlock ( )
2023-10-03 18:50:43 +01:00
filename := l . logFile
2024-10-02 19:00:15 +01:00
f , err := os . OpenFile ( filename , os . O_WRONLY | os . O_CREATE | os . O_APPEND , aghos . DefaultPermFile )
2018-12-05 11:03:41 +00:00
if err != nil {
2023-10-03 18:50:43 +01:00
return fmt . Errorf ( "creating file %q: %w" , filename , err )
2018-12-05 11:03:41 +00:00
}
2023-10-03 18:50:43 +01:00
2021-05-24 15:28:11 +01:00
defer func ( ) { err = errors . WithDeferred ( err , f . Close ( ) ) } ( )
2018-12-05 11:03:41 +00:00
2023-10-03 18:50:43 +01:00
n , err := f . Write ( b . Bytes ( ) )
2018-12-05 11:03:41 +00:00
if err != nil {
2023-10-03 18:50:43 +01:00
return fmt . Errorf ( "writing to file %q: %w" , filename , err )
2018-12-05 11:03:41 +00:00
}
2023-10-03 18:50:43 +01:00
log . Debug ( "querylog: ok %q: %v bytes written" , filename , n )
2018-12-05 11:03:41 +00:00
return nil
}
2019-09-27 16:58:57 +01:00
func ( l * queryLog ) rotate ( ) error {
2019-02-10 17:47:43 +00:00
from := l . logFile
to := l . logFile + ".1"
2018-12-05 11:03:41 +00:00
err := os . Rename ( from , to )
if err != nil {
2021-01-27 15:32:13 +00:00
if errors . Is ( err , os . ErrNotExist ) {
2021-10-29 11:43:08 +01:00
log . Debug ( "querylog: no log to rotate" )
2021-01-27 15:32:13 +00:00
return nil
}
2021-10-29 11:43:08 +01:00
return fmt . Errorf ( "failed to rename old file: %w" , err )
2018-12-05 11:03:41 +00:00
}
2021-10-29 11:43:08 +01:00
log . Debug ( "querylog: renamed %s into %s" , from , to )
2021-01-27 15:32:13 +00:00
2018-12-05 11:03:41 +00:00
return nil
}
2021-07-01 16:50:28 +01:00
func ( l * queryLog ) readFileFirstTimeValue ( ) ( first time . Time , err error ) {
var f * os . File
f , err = os . Open ( l . logFile )
2020-09-02 17:42:26 +01:00
if err != nil {
2021-07-01 16:50:28 +01:00
return time . Time { } , err
2020-09-02 17:42:26 +01:00
}
2021-07-01 16:50:28 +01:00
defer func ( ) { err = errors . WithDeferred ( err , f . Close ( ) ) } ( )
buf := make ( [ ] byte , 512 )
var r int
r , err = f . Read ( buf )
2020-09-02 17:42:26 +01:00
if err != nil {
2021-07-01 16:50:28 +01:00
return time . Time { } , err
2020-09-02 17:42:26 +01:00
}
2021-07-01 16:50:28 +01:00
val := readJSONValue ( string ( buf [ : r ] ) , ` "T":" ` )
2020-09-02 17:42:26 +01:00
t , err := time . Parse ( time . RFC3339Nano , val )
if err != nil {
2021-07-01 16:50:28 +01:00
return time . Time { } , err
2020-09-02 17:42:26 +01:00
}
log . Debug ( "querylog: the oldest log entry: %s" , val )
2021-07-01 16:50:28 +01:00
return t , nil
2020-09-02 17:42:26 +01:00
}
2019-09-27 16:58:57 +01:00
func ( l * queryLog ) periodicRotate ( ) {
2021-07-01 16:50:28 +01:00
defer log . OnPanic ( "querylog: rotating" )
2021-11-09 14:17:57 +00:00
l . checkAndRotate ( )
// rotationCheckIvl is the period of time between checking the need for
// rotating log files. It's smaller of any available rotation interval to
// increase time accuracy.
//
// See https://github.com/AdguardTeam/AdGuardHome/issues/3823.
const rotationCheckIvl = 1 * time . Hour
rotations := time . NewTicker ( rotationCheckIvl )
2021-10-29 11:43:08 +01:00
defer rotations . Stop ( )
for range rotations . C {
2021-11-09 14:17:57 +00:00
l . checkAndRotate ( )
}
}
2021-10-29 11:43:08 +01:00
2021-11-09 14:17:57 +00:00
// checkAndRotate rotates log files if those are older than the specified
// rotation interval.
func ( l * queryLog ) checkAndRotate ( ) {
2023-04-03 14:29:07 +01:00
var rotationIvl time . Duration
func ( ) {
l . confMu . RLock ( )
defer l . confMu . RUnlock ( )
rotationIvl = l . conf . RotationIvl
} ( )
2023-02-13 15:15:33 +00:00
2021-11-09 14:17:57 +00:00
oldest , err := l . readFileFirstTimeValue ( )
if err != nil && ! errors . Is ( err , os . ErrNotExist ) {
log . Error ( "querylog: reading oldest record for rotation: %s" , err )
2021-07-01 16:50:28 +01:00
2021-11-09 14:17:57 +00:00
return
}
2021-10-29 11:43:08 +01:00
2023-04-03 14:29:07 +01:00
if rotTime , now := oldest . Add ( rotationIvl ) , time . Now ( ) ; rotTime . After ( now ) {
2021-11-09 14:17:57 +00:00
log . Debug (
"querylog: %s <= %s, not rotating" ,
now . Format ( time . RFC3339 ) ,
2023-04-03 14:29:07 +01:00
rotTime . Format ( time . RFC3339 ) ,
2021-11-09 14:17:57 +00:00
)
2021-10-29 11:43:08 +01:00
2021-11-09 14:17:57 +00:00
return
}
2021-10-29 11:43:08 +01:00
2021-11-09 14:17:57 +00:00
err = l . rotate ( )
if err != nil {
log . Error ( "querylog: rotating: %s" , err )
2020-09-02 17:42:26 +01:00
2021-11-09 14:17:57 +00:00
return
2018-12-05 11:03:41 +00:00
}
2021-11-09 14:17:57 +00:00
log . Debug ( "querylog: rotated successfully" )
2018-12-05 11:03:41 +00:00
}