add configurable log level to errors plugin (#4718)
Automatically submitted.
This commit is contained in:
parent
a6a7e73813
commit
70b51a73d3
5 changed files with 151 additions and 24 deletions
|
@ -22,12 +22,12 @@ Extra knobs are available with an expanded syntax:
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
errors {
|
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
|
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
|
forward . 8.8.8.8
|
||||||
errors {
|
errors {
|
||||||
consolidate 5m ".* i/o timeout$"
|
consolidate 5m ".* i/o timeout$" warn
|
||||||
consolidate 30s "^Failed to .+"
|
consolidate 30s "^Failed to .+"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,10 +18,11 @@ import (
|
||||||
var log = clog.NewWithPlugin("errors")
|
var log = clog.NewWithPlugin("errors")
|
||||||
|
|
||||||
type pattern struct {
|
type pattern struct {
|
||||||
ptimer unsafe.Pointer
|
ptimer unsafe.Pointer
|
||||||
count uint32
|
count uint32
|
||||||
period time.Duration
|
period time.Duration
|
||||||
pattern *regexp.Regexp
|
pattern *regexp.Regexp
|
||||||
|
logCallback func(format string, v ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *pattern) timer() *time.Timer {
|
func (p *pattern) timer() *time.Timer {
|
||||||
|
@ -46,7 +47,7 @@ func newErrorHandler() *errorHandler {
|
||||||
func (h *errorHandler) logPattern(i int) {
|
func (h *errorHandler) logPattern(i int) {
|
||||||
cnt := atomic.SwapUint32(&h.patterns[i].count, 0)
|
cnt := atomic.SwapUint32(&h.patterns[i].count, 0)
|
||||||
if cnt > 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)
|
cnt, h.patterns[i].pattern.String(), h.patterns[i].period)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
"github.com/coredns/coredns/plugin/pkg/dnstest"
|
||||||
|
clog "github.com/coredns/coredns/plugin/pkg/log"
|
||||||
"github.com/coredns/coredns/plugin/test"
|
"github.com/coredns/coredns/plugin/test"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
@ -71,21 +72,56 @@ func TestErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLogPattern(t *testing.T) {
|
func TestLogPattern(t *testing.T) {
|
||||||
buf := bytes.Buffer{}
|
type args struct {
|
||||||
golog.SetOutput(&buf)
|
logCallback func(format string, v ...interface{})
|
||||||
|
}
|
||||||
h := &errorHandler{
|
tests := []struct {
|
||||||
patterns: []*pattern{{
|
name string
|
||||||
count: 4,
|
args args
|
||||||
period: 2 * time.Second,
|
want string
|
||||||
pattern: regexp.MustCompile("^error.*!$"),
|
}{
|
||||||
}},
|
{
|
||||||
|
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"
|
for _, tt := range tests {
|
||||||
if log := buf.String(); !strings.Contains(log, expLog) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
t.Errorf("Expected log %q, but got %q", expLog, log)
|
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{{
|
patterns: []*pattern{{
|
||||||
period: 2 * time.Second,
|
period: 2 * time.Second,
|
||||||
pattern: regexp.MustCompile("^error.*!$"),
|
pattern: regexp.MustCompile("^error.*!$"),
|
||||||
|
logCallback: log.Errorf,
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -66,7 +66,7 @@ func parseBlock(c *caddy.Controller, h *errorHandler) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
args := c.RemainingArgs()
|
args := c.RemainingArgs()
|
||||||
if len(args) != 2 {
|
if len(args) < 2 || len(args) > 3 {
|
||||||
return c.ArgErr()
|
return c.ArgErr()
|
||||||
}
|
}
|
||||||
p, err := time.ParseDuration(args[0])
|
p, err := time.ParseDuration(args[0])
|
||||||
|
@ -77,7 +77,30 @@ func parseBlock(c *caddy.Controller, h *errorHandler) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Err(err.Error())
|
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
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
package errors
|
package errors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
golog "log"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/coredns/caddy"
|
"github.com/coredns/caddy"
|
||||||
|
clog "github.com/coredns/coredns/plugin/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestErrorsParse(t *testing.T) {
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue