mirror of
https://github.com/coredns/coredns.git
synced 2025-12-12 05:15:12 -05:00
Add optional show_first flag to consolidate directive that logs the first error immediately and then consolidates subsequent errors. When show_first is enabled: - The first matching error is logged immediately with full details (rcode, domain, type, error message) using the configured log level - Subsequent matching errors are consolidated during the period - At period end: - If only one error occurred, no summary is printed (already logged) - If multiple errors occurred, summary shows the total count Syntax: consolidate DURATION REGEXP [LEVEL] [show_first] Example with 3 errors: [WARNING] 2 example.org. A: read udp 10.0.0.1:53->8.8.8.8:53: i/o timeout [WARNING] 3 errors like '^read udp .* i/o timeout$' occurred in last 30s Example with 1 error: [WARNING] 2 example.org. A: read udp 10.0.0.1:53->8.8.8.8:53: i/o timeout Implementation details: - Add showFirst bool to pattern struct - Rename inc() to consolidateError(), return false for showFirst case - Use function pointer in ServeDNS to unify log calls with proper level - Simplify logPattern() with single condition (cnt > 1 || !showFirst) - Refactor parseLogLevel() to parseOptionalParams() with map-based dispatch - Validate parameter order: log level must come before show_first - Update README.md with show_first documentation and examples - Add comprehensive test cases for show_first functionality Signed-off-by: cangming <cangming@cangming.app>
253 lines
5.8 KiB
Go
253 lines
5.8 KiB
Go
package errors
|
|
|
|
import (
|
|
"bytes"
|
|
golog "log"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/coredns/caddy"
|
|
"github.com/coredns/coredns/core/dnsserver"
|
|
clog "github.com/coredns/coredns/plugin/pkg/log"
|
|
)
|
|
|
|
func TestErrorsParse(t *testing.T) {
|
|
tests := []struct {
|
|
inputErrorsRules string
|
|
shouldErr bool
|
|
optCount int
|
|
stacktrace bool
|
|
}{
|
|
{`errors`, false, 0, false},
|
|
{`errors stdout`, false, 0, false},
|
|
{`errors errors.txt`, true, 0, false},
|
|
{`errors visible`, true, 0, false},
|
|
{`errors { log visible }`, true, 0, false},
|
|
{`errors
|
|
errors `, true, 0, false},
|
|
{`errors a b`, true, 0, false},
|
|
|
|
{`errors {
|
|
consolidate
|
|
}`, true, 0, false},
|
|
{`errors {
|
|
consolidate 1m
|
|
}`, true, 0, false},
|
|
{`errors {
|
|
consolidate 1m .* extra
|
|
}`, true, 0, false},
|
|
{`errors {
|
|
consolidate abc .*
|
|
}`, true, 0, false},
|
|
{`errors {
|
|
consolidate 1 .*
|
|
}`, true, 0, false},
|
|
{`errors {
|
|
consolidate 1m ())
|
|
}`, true, 0, false},
|
|
{`errors {
|
|
stacktrace
|
|
}`, false, 0, true},
|
|
{`errors {
|
|
stacktrace
|
|
consolidate 1m ^exact$
|
|
}`, false, 1, true},
|
|
{`errors {
|
|
consolidate 1m ^exact$
|
|
}`, false, 1, false},
|
|
{`errors {
|
|
consolidate 1m error
|
|
}`, false, 1, false},
|
|
{`errors {
|
|
consolidate 1m "format error"
|
|
}`, false, 1, false},
|
|
{`errors {
|
|
consolidate 1m error1
|
|
consolidate 5s error2
|
|
}`, false, 2, false},
|
|
{`errors {
|
|
consolidate 1m error show_first
|
|
}`, false, 1, false},
|
|
{`errors {
|
|
consolidate 1m error warning show_first
|
|
}`, false, 1, false},
|
|
{`errors {
|
|
consolidate 1m error invalid_option
|
|
}`, true, 0, false},
|
|
{`errors {
|
|
consolidate 1m error warning error
|
|
}`, true, 0, false},
|
|
{`errors {
|
|
consolidate 1m error info debug
|
|
}`, true, 0, false},
|
|
{`errors {
|
|
consolidate 1m error show_first warning
|
|
}`, true, 0, false},
|
|
{`errors {
|
|
consolidate 1m error show_first info
|
|
}`, true, 0, false},
|
|
}
|
|
for i, test := range tests {
|
|
c := caddy.NewTestController("dns", test.inputErrorsRules)
|
|
h, err := errorsParse(c)
|
|
|
|
if err == nil && test.shouldErr {
|
|
t.Errorf("Test %d didn't error, but it should have", i)
|
|
} else if err != nil && !test.shouldErr {
|
|
t.Errorf("Test %d errored, but it shouldn't have; got '%v'", i, err)
|
|
} else if h != nil && len(h.patterns) != test.optCount {
|
|
t.Errorf("Test %d: pattern count mismatch, expected %d, got %d",
|
|
i, test.optCount, len(h.patterns))
|
|
}
|
|
if dnsserver.GetConfig(c).Stacktrace != test.stacktrace {
|
|
t.Errorf("Test %d: stacktrace, expected %t, got %t",
|
|
i, test.stacktrace, dnsserver.GetConfig(c).Stacktrace)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestProperLogCallbackIsSet(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
inputErrorsRules string
|
|
wantLogLevel string
|
|
}{
|
|
{
|
|
name: "warning is parsed properly",
|
|
inputErrorsRules: `errors {
|
|
consolidate 1m .* warning
|
|
}`,
|
|
wantLogLevel: "[WARNING]",
|
|
},
|
|
{
|
|
name: "error is parsed properly",
|
|
inputErrorsRules: `errors {
|
|
consolidate 1m .* error
|
|
}`,
|
|
wantLogLevel: "[ERROR]",
|
|
},
|
|
{
|
|
name: "info is parsed properly",
|
|
inputErrorsRules: `errors {
|
|
consolidate 1m .* info
|
|
}`,
|
|
wantLogLevel: "[INFO]",
|
|
},
|
|
{
|
|
name: "debug is parsed properly",
|
|
inputErrorsRules: `errors {
|
|
consolidate 1m .* debug
|
|
}`,
|
|
wantLogLevel: "[DEBUG]",
|
|
},
|
|
{
|
|
name: "default is error",
|
|
inputErrorsRules: `errors {
|
|
consolidate 1m .*
|
|
}`,
|
|
wantLogLevel: "[ERROR]",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
buf := bytes.Buffer{}
|
|
golog.SetOutput(&buf)
|
|
clog.D.Set()
|
|
|
|
c := caddy.NewTestController("dns", tt.inputErrorsRules)
|
|
h, _ := errorsParse(c)
|
|
|
|
l := h.patterns[0].logCallback
|
|
l("some error happened")
|
|
|
|
if log := buf.String(); !strings.Contains(log, tt.wantLogLevel) {
|
|
t.Errorf("Expected log %q, but got %q", tt.wantLogLevel, log)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestShowFirstOption(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
inputErrorsRules string
|
|
wantShowFirst bool
|
|
wantLogLevel string
|
|
}{
|
|
{
|
|
name: "show_first without log level",
|
|
inputErrorsRules: `errors {
|
|
consolidate 1m .* show_first
|
|
}`,
|
|
wantShowFirst: true,
|
|
wantLogLevel: "[ERROR]", // default
|
|
},
|
|
{
|
|
name: "show_first with warning log level",
|
|
inputErrorsRules: `errors {
|
|
consolidate 1m .* warning show_first
|
|
}`,
|
|
wantShowFirst: true,
|
|
wantLogLevel: "[WARNING]",
|
|
},
|
|
{
|
|
name: "show_first with error log level",
|
|
inputErrorsRules: `errors {
|
|
consolidate 1m .* error show_first
|
|
}`,
|
|
wantShowFirst: true,
|
|
wantLogLevel: "[ERROR]",
|
|
},
|
|
{
|
|
name: "no show_first",
|
|
inputErrorsRules: `errors {
|
|
consolidate 1m .*
|
|
}`,
|
|
wantShowFirst: false,
|
|
wantLogLevel: "[ERROR]",
|
|
},
|
|
{
|
|
name: "no show_first with log level",
|
|
inputErrorsRules: `errors {
|
|
consolidate 1m .* info
|
|
}`,
|
|
wantShowFirst: false,
|
|
wantLogLevel: "[INFO]",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
buf := bytes.Buffer{}
|
|
golog.SetOutput(&buf)
|
|
clog.D.Set()
|
|
|
|
c := caddy.NewTestController("dns", tt.inputErrorsRules)
|
|
h, err := errorsParse(c)
|
|
|
|
if err != nil {
|
|
t.Errorf("Failed to parse: %v", err)
|
|
return
|
|
}
|
|
|
|
if len(h.patterns) != 1 {
|
|
t.Errorf("Expected 1 pattern, got %d", len(h.patterns))
|
|
return
|
|
}
|
|
|
|
if h.patterns[0].showFirst != tt.wantShowFirst {
|
|
t.Errorf("Expected showFirst=%v, got %v", tt.wantShowFirst, h.patterns[0].showFirst)
|
|
}
|
|
|
|
// Test log level
|
|
l := h.patterns[0].logCallback
|
|
l("test log")
|
|
|
|
if log := buf.String(); !strings.Contains(log, tt.wantLogLevel) {
|
|
t.Errorf("Expected log level %q, but got %q", tt.wantLogLevel, log)
|
|
}
|
|
})
|
|
}
|
|
}
|