blocky/querylog/file_writer.go

130 lines
3.4 KiB
Go

package querylog
import (
"encoding/csv"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"time"
"github.com/0xERR0R/blocky/log"
"github.com/0xERR0R/blocky/util"
"github.com/miekg/dns"
"github.com/sirupsen/logrus"
)
const loggerPrefixFileWriter = "fileQueryLogWriter"
var validFilePattern = regexp.MustCompile("[^a-zA-Z0-9-_]+")
type FileWriter struct {
target string
perClient bool
logRetentionDays uint64
}
func NewCSVWriter(target string, perClient bool, logRetentionDays uint64) (*FileWriter, error) {
if _, err := os.Stat(target); target != "" && err != nil && os.IsNotExist(err) {
return nil, fmt.Errorf("query log directory '%s' does not exist or is not writable", target)
}
return &FileWriter{
target: target,
perClient: perClient,
logRetentionDays: logRetentionDays,
}, nil
}
func (d *FileWriter) Write(entry *LogEntry) {
var clientPrefix string
dateString := entry.Start.Format("2006-01-02")
if d.perClient {
clientPrefix = strings.Join(entry.Request.ClientNames, "-")
} else {
clientPrefix = "ALL"
}
fileName := fmt.Sprintf("%s_%s.log", dateString, escape(clientPrefix))
writePath := filepath.Join(d.target, fileName)
file, err := os.OpenFile(writePath, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666)
util.LogOnErrorWithEntry(log.PrefixedLog(loggerPrefixFileWriter).WithField("file_name", writePath),
"can't create/open file", err)
if err == nil {
defer file.Close()
writer := createCsvWriter(file)
err := writer.Write(createQueryLogRow(entry))
util.LogOnErrorWithEntry(log.PrefixedLog(loggerPrefixFileWriter).WithField("file_name", writePath),
"can't write to file", err)
writer.Flush()
}
}
// CleanUp deletes old log files
func (d *FileWriter) CleanUp() {
const hoursPerDay = 24
logger := log.PrefixedLog(loggerPrefixFileWriter)
logger.Trace("starting clean up")
files, err := os.ReadDir(d.target)
util.LogOnErrorWithEntry(logger.WithField("target", d.target), "can't list log directory: ", err)
// search for log files, which names starts with date
for _, f := range files {
if strings.HasSuffix(f.Name(), ".log") && len(f.Name()) > 10 {
t, err := time.Parse("2006-01-02", f.Name()[:10])
if err == nil {
differenceDays := uint64(time.Since(t).Hours() / hoursPerDay)
if d.logRetentionDays > 0 && differenceDays > d.logRetentionDays {
logger.WithFields(logrus.Fields{
"file": f.Name(),
"ageInDays": differenceDays,
"logRetentionDays": d.logRetentionDays,
}).Info("existing log file is older than retention time and will be deleted")
err := os.Remove(filepath.Join(d.target, f.Name()))
util.LogOnErrorWithEntry(logger.WithField("file", f.Name()), "can't remove file: ", err)
}
}
}
}
}
func createQueryLogRow(logEntry *LogEntry) []string {
request := logEntry.Request
response := logEntry.Response
return []string{
logEntry.Start.Format("2006-01-02 15:04:05"),
request.ClientIP.String(),
strings.Join(request.ClientNames, "; "),
fmt.Sprintf("%d", logEntry.DurationMs),
response.Reason,
util.QuestionToString(request.Req.Question),
util.AnswerToString(response.Res.Answer),
dns.RcodeToString[response.Res.Rcode],
}
}
func createCsvWriter(file io.Writer) *csv.Writer {
writer := csv.NewWriter(file)
writer.Comma = '\t'
return writer
}
func escape(file string) string {
return validFilePattern.ReplaceAllString(file, "_")
}