add configurable log level to errors plugin (#4718)

Automatically submitted.
This commit is contained in:
Ondřej Benkovský 2021-07-09 16:23:02 +02:00 committed by GitHub
parent a6a7e73813
commit 70b51a73d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 151 additions and 24 deletions

View file

@ -22,12 +22,12 @@ Extra knobs are available with an expanded syntax:
~~~
errors {
consolidate DURATION REGEXP
consolidate DURATION REGEXP [LEVEL]
}
~~~
Option `consolidate` allows collecting several error messages matching the regular expression **REGEXP** during **DURATION**. After the **DURATION** since receiving the first such message, the consolidated message will be printed to standard output, e.g.
Option `consolidate` allows collecting several error messages matching the regular expression **REGEXP** during **DURATION**. After the **DURATION** since receiving the first such message, the consolidated message will be printed to standard output with
log level, which is configurable by optional option **LEVEL**. Supported options for **LEVEL** option are `warn`,`error`,`info` and `debug`.
~~~
2 errors like '^read udp .* i/o timeout$' occurred in last 30s
~~~
@ -53,7 +53,7 @@ Use the *forward* to resolve queries via 8.8.8.8 and print consolidated error me
. {
forward . 8.8.8.8
errors {
consolidate 5m ".* i/o timeout$"
consolidate 5m ".* i/o timeout$" warn
consolidate 30s "^Failed to .+"
}
}

View file

@ -18,10 +18,11 @@ import (
var log = clog.NewWithPlugin("errors")
type pattern struct {
ptimer unsafe.Pointer
count uint32
period time.Duration
pattern *regexp.Regexp
ptimer unsafe.Pointer
count uint32
period time.Duration
pattern *regexp.Regexp
logCallback func(format string, v ...interface{})
}
func (p *pattern) timer() *time.Timer {
@ -46,7 +47,7 @@ func newErrorHandler() *errorHandler {
func (h *errorHandler) logPattern(i int) {
cnt := atomic.SwapUint32(&h.patterns[i].count, 0)
if cnt > 0 {
log.Errorf("%d errors like '%s' occurred in last %s",
h.patterns[i].logCallback("%d errors like '%s' occurred in last %s",
cnt, h.patterns[i].pattern.String(), h.patterns[i].period)
}
}

View file

@ -14,6 +14,7 @@ import (
"github.com/coredns/coredns/plugin"
"github.com/coredns/coredns/plugin/pkg/dnstest"
clog "github.com/coredns/coredns/plugin/pkg/log"
"github.com/coredns/coredns/plugin/test"
"github.com/miekg/dns"
@ -71,21 +72,56 @@ func TestErrors(t *testing.T) {
}
func TestLogPattern(t *testing.T) {
buf := bytes.Buffer{}
golog.SetOutput(&buf)
h := &errorHandler{
patterns: []*pattern{{
count: 4,
period: 2 * time.Second,
pattern: regexp.MustCompile("^error.*!$"),
}},
type args struct {
logCallback func(format string, v ...interface{})
}
tests := []struct {
name string
args args
want string
}{
{
name: "error log",
args: args{logCallback: log.Errorf},
want: "[ERROR] plugin/errors: 4 errors like '^error.*!$' occurred in last 2s",
},
{
name: "warn log",
args: args{logCallback: log.Warningf},
want: "[WARNING] plugin/errors: 4 errors like '^error.*!$' occurred in last 2s",
},
{
name: "info log",
args: args{logCallback: log.Infof},
want: "[INFO] plugin/errors: 4 errors like '^error.*!$' occurred in last 2s",
},
{
name: "debug log",
args: args{logCallback: log.Debugf},
want: "[DEBUG] plugin/errors: 4 errors like '^error.*!$' occurred in last 2s",
},
}
h.logPattern(0)
expLog := "4 errors like '^error.*!$' occurred in last 2s"
if log := buf.String(); !strings.Contains(log, expLog) {
t.Errorf("Expected log %q, but got %q", expLog, log)
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
buf := bytes.Buffer{}
clog.D.Set()
golog.SetOutput(&buf)
h := &errorHandler{
patterns: []*pattern{{
count: 4,
period: 2 * time.Second,
pattern: regexp.MustCompile("^error.*!$"),
logCallback: tt.args.logCallback,
}},
}
h.logPattern(0)
if log := buf.String(); !strings.Contains(log, tt.want) {
t.Errorf("Expected log %q, but got %q", tt.want, log)
}
})
}
}
@ -154,6 +190,7 @@ func TestStop(t *testing.T) {
patterns: []*pattern{{
period: 2 * time.Second,
pattern: regexp.MustCompile("^error.*!$"),
logCallback: log.Errorf,
}},
}

View file

@ -66,7 +66,7 @@ func parseBlock(c *caddy.Controller, h *errorHandler) error {
}
args := c.RemainingArgs()
if len(args) != 2 {
if len(args) < 2 || len(args) > 3 {
return c.ArgErr()
}
p, err := time.ParseDuration(args[0])
@ -77,7 +77,30 @@ func parseBlock(c *caddy.Controller, h *errorHandler) error {
if err != nil {
return c.Err(err.Error())
}
h.patterns = append(h.patterns, &pattern{period: p, pattern: re})
lc, err := parseLogLevel(c, args)
if err != nil {
return err
}
h.patterns = append(h.patterns, &pattern{period: p, pattern: re, logCallback: lc})
return nil
}
func parseLogLevel(c *caddy.Controller, args []string) (func(format string, v ...interface{}), error) {
if len(args) != 3 {
return log.Errorf, nil
}
switch args[2] {
case "warn":
return log.Warningf, nil
case "error":
return log.Errorf, nil
case "info":
return log.Infof, nil
case "debug":
return log.Debugf, nil
default:
return nil, c.Err("unknown log level argument in consolidate")
}
}

View file

@ -1,9 +1,13 @@
package errors
import (
"bytes"
golog "log"
"strings"
"testing"
"github.com/coredns/caddy"
clog "github.com/coredns/coredns/plugin/pkg/log"
)
func TestErrorsParse(t *testing.T) {
@ -67,3 +71,65 @@ func TestErrorsParse(t *testing.T) {
}
}
}
func TestProperLogCallbackIsSet(t *testing.T) {
tests := []struct {
name string
inputErrorsRules string
wantLogLevel string
}{
{
name: "warn is parsed properly",
inputErrorsRules: `errors {
consolidate 1m .* warn
}`,
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)
}
})
}
}