2023-05-31 10:40:28 +03:00
|
|
|
package querylog
|
|
|
|
|
|
|
|
import (
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/AdguardTeam/golibs/log"
|
|
|
|
"github.com/miekg/dns"
|
|
|
|
)
|
|
|
|
|
2023-06-02 15:26:04 +03:00
|
|
|
// csvRow is an alias type for csv rows.
|
|
|
|
type csvRow = [18]string
|
|
|
|
|
2023-06-02 15:15:37 +03:00
|
|
|
// csvHeaderRow is a slice of strings with column names for CSV header row.
|
2023-06-02 15:26:04 +03:00
|
|
|
var csvHeaderRow = csvRow{
|
2023-05-31 10:40:28 +03:00
|
|
|
"ans_dnssec",
|
|
|
|
"ans_rcode",
|
|
|
|
"ans_type",
|
|
|
|
"ans_value",
|
|
|
|
"cached",
|
|
|
|
"client_ip",
|
|
|
|
"client_id",
|
|
|
|
"ecs",
|
|
|
|
"elapsed",
|
|
|
|
"filter_id",
|
|
|
|
"filter_rule",
|
|
|
|
"proto",
|
|
|
|
"qclass",
|
|
|
|
"qname",
|
|
|
|
"qtype",
|
|
|
|
"reason",
|
|
|
|
"time",
|
|
|
|
"upstream",
|
|
|
|
}
|
|
|
|
|
|
|
|
// toCSV returns a slice of strings with entry fields according to the
|
|
|
|
// csvHeaderRow slice.
|
2023-06-13 12:08:15 +03:00
|
|
|
func (e *logEntry) toCSV() (out *csvRow) {
|
2023-05-31 10:40:28 +03:00
|
|
|
var filterID, filterRule string
|
|
|
|
|
|
|
|
if e.Result.IsFiltered && len(e.Result.Rules) > 0 {
|
|
|
|
rule := e.Result.Rules[0]
|
|
|
|
filterID = strconv.FormatInt(rule.FilterListID, 10)
|
|
|
|
filterRule = rule.Text
|
|
|
|
}
|
|
|
|
|
|
|
|
aData := ansData(e)
|
|
|
|
|
2023-06-13 12:08:15 +03:00
|
|
|
return &csvRow{
|
2023-06-02 12:09:50 +03:00
|
|
|
strconv.FormatBool(e.AuthenticatedData),
|
2023-05-31 10:40:28 +03:00
|
|
|
aData.rCode,
|
|
|
|
aData.typ,
|
|
|
|
aData.value,
|
|
|
|
strconv.FormatBool(e.Cached),
|
|
|
|
e.IP.String(),
|
|
|
|
e.ClientID,
|
|
|
|
e.ReqECS,
|
|
|
|
strconv.FormatFloat(e.Elapsed.Seconds()*1000, 'f', -1, 64),
|
|
|
|
filterID,
|
|
|
|
filterRule,
|
|
|
|
string(e.ClientProto),
|
|
|
|
e.QClass,
|
|
|
|
e.QHost,
|
|
|
|
e.QType,
|
|
|
|
e.Result.Reason.String(),
|
|
|
|
e.Time.Format(time.RFC3339Nano),
|
|
|
|
e.Upstream,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// csvAnswer is a helper struct for csv row answer fields.
|
|
|
|
type csvAnswer struct {
|
2023-06-02 12:09:50 +03:00
|
|
|
rCode string
|
|
|
|
typ string
|
|
|
|
value string
|
2023-05-31 10:40:28 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// ansData returns a map with message answer data.
|
|
|
|
func ansData(entry *logEntry) (out csvAnswer) {
|
|
|
|
if len(entry.Answer) == 0 {
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
msg := &dns.Msg{}
|
|
|
|
if err := msg.Unpack(entry.Answer); err != nil {
|
|
|
|
log.Debug("querylog: failed to unpack dns msg answer: %v: %s", entry.Answer, err)
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
out.rCode = dns.RcodeToString[msg.Rcode]
|
|
|
|
|
|
|
|
if len(msg.Answer) == 0 {
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
|
|
|
rr := msg.Answer[0]
|
|
|
|
header := rr.Header()
|
|
|
|
|
|
|
|
out.typ = dns.TypeToString[header.Rrtype]
|
|
|
|
|
|
|
|
// Remove the header string from the answer value since it's mostly
|
|
|
|
// unnecessary in the log.
|
|
|
|
out.value = strings.TrimPrefix(rr.String(), header.String())
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|