-
Notifications
You must be signed in to change notification settings - Fork 65
/
Copy pathquery-log.go
97 lines (84 loc) · 2.32 KB
/
query-log.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package rdns
import (
"context"
"fmt"
"log/slog"
"os"
"github.com/miekg/dns"
)
// QueryLogResolver logs requests to STDOUT or file.
type QueryLogResolver struct {
id string
resolver Resolver
opt QueryLogResolverOptions
logger *slog.Logger
}
var _ Resolver = &QueryLogResolver{}
type QueryLogResolverOptions struct {
OutputFile string // Output filename, leave blank for STDOUT
OutputFormat LogFormat
}
type LogFormat string
const (
LogFormatText LogFormat = "text"
LogFormatJSON LogFormat = "json"
)
// NewQueryLogResolver returns a new instance of a QueryLogResolver.
func NewQueryLogResolver(id string, resolver Resolver, opt QueryLogResolverOptions) (*QueryLogResolver, error) {
w := os.Stdout
if opt.OutputFile != "" {
f, err := os.OpenFile(opt.OutputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return nil, err
}
w = f
}
handlerOpts := &slog.HandlerOptions{
ReplaceAttr: logReplaceAttr,
}
var logger *slog.Logger
switch opt.OutputFormat {
case "", LogFormatText:
logger = slog.New(slog.NewTextHandler(w, handlerOpts))
case LogFormatJSON:
logger = slog.New(slog.NewJSONHandler(w, handlerOpts))
default:
return nil, fmt.Errorf("invalid output format %q", opt.OutputFormat)
}
return &QueryLogResolver{
resolver: resolver,
logger: logger,
}, nil
}
// Resolve logs the query details and passes the query to the next resolver.
func (r *QueryLogResolver) Resolve(q *dns.Msg, ci ClientInfo) (*dns.Msg, error) {
question := q.Question[0]
attrs := []slog.Attr{
slog.String("source-ip", ci.SourceIP.String()),
slog.String("question-name", question.Name),
slog.String("question-class", dns.Class(question.Qclass).String()),
slog.String("question-type", dns.Type(question.Qtype).String()),
}
// Add ECS attributes if present
edns0 := q.IsEdns0()
if edns0 != nil {
// Find the ECS option
for _, opt := range edns0.Option {
ecs, ok := opt.(*dns.EDNS0_SUBNET)
if ok {
attrs = append(attrs, slog.String("ecs-addr", ecs.Address.String()))
}
}
}
r.logger.LogAttrs(context.Background(), slog.LevelInfo, "", attrs...)
return r.resolver.Resolve(q, ci)
}
func (r *QueryLogResolver) String() string {
return r.id
}
func logReplaceAttr(groups []string, a slog.Attr) slog.Attr {
if a.Key == "msg" || a.Key == "level" {
return slog.Attr{}
}
return a
}