logging: unify pkg/log and plugin/log (#2245)
Default to using pkg/log for all logging and use a fixed time prefix which is RFC3339Millli (doesn't exist in time, so we just extended RFC3339), i.e. Nano might be pushing it. Logs go from: 2018/10/30 19:14:55 [INFO] CoreDNS-1.2.5 2018/10/30 19:14:55 [INFO] linux/amd64, go1.11, to: 2018-10-30T19:10:07.547Z [INFO] CoreDNS-1.2.5 2018-10-30T19:10:07.547Z [INFO] linux/amd64, go1.11, Which includes the timezone - which oddly the std log package doesn't natively do. Signed-off-by: Miek Gieben <miek@miek.nl>
This commit is contained in:
parent
cfbfa5c00e
commit
2456416444
7 changed files with 45 additions and 49 deletions
|
@ -62,7 +62,7 @@ func Run() {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.SetOutput(os.Stdout)
|
log.SetOutput(os.Stdout)
|
||||||
log.SetFlags(log.LstdFlags)
|
log.SetFlags(0) // Set to 0 because we're doing our own time, with timezone
|
||||||
|
|
||||||
if version {
|
if version {
|
||||||
showVersion()
|
showVersion()
|
||||||
|
|
|
@ -7,9 +7,10 @@
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
By just using *log* you dump all queries (and parts for the reply) on standard output. Options exist
|
By just using *log* you dump all queries (and parts for the reply) on standard output. Options exist
|
||||||
to tweak the output a little.
|
to tweak the output a little. The date/time prefix on log lines is RFC3339 formatted with
|
||||||
|
milliseconds.
|
||||||
|
|
||||||
Note that for busy servers this will incur a performance hit.
|
Note that for busy servers logging will incur a performance hit.
|
||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|
||||||
|
@ -59,7 +60,6 @@ The following place holders are supported:
|
||||||
* `{name}`: qname of the request
|
* `{name}`: qname of the request
|
||||||
* `{class}`: qclass of the request
|
* `{class}`: qclass of the request
|
||||||
* `{proto}`: protocol used (tcp or udp)
|
* `{proto}`: protocol used (tcp or udp)
|
||||||
* `{when}`: time of the query
|
|
||||||
* `{remote}`: client's IP address, for IPv6 addresses these are enclosed in brackets: `[::1]`
|
* `{remote}`: client's IP address, for IPv6 addresses these are enclosed in brackets: `[::1]`
|
||||||
* `{size}`: request size in bytes
|
* `{size}`: request size in bytes
|
||||||
* `{port}`: client's port
|
* `{port}`: client's port
|
||||||
|
@ -76,9 +76,15 @@ The following place holders are supported:
|
||||||
The default Common Log Format is:
|
The default Common Log Format is:
|
||||||
|
|
||||||
~~~ txt
|
~~~ txt
|
||||||
`{remote}:{port} - [{when}] {>id} "{type} {class} {name} {proto} {size} {>do} {>bufsize}" {rcode} {>rflags} {rsize} {duration}`
|
`{remote}:{port} - {>id} "{type} {class} {name} {proto} {size} {>do} {>bufsize}" {rcode} {>rflags} {rsize} {duration}`
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
|
Each of these logs will be outputted with `log.Infof`, so a typical example looks like this:
|
||||||
|
|
||||||
|
~~~ txt
|
||||||
|
2018-10-30T19:10:07.547Z [INFO] [::1]:50759 - 29008 "A IN example.org. udp 41 false 4096" NOERROR qr,rd,ra,ad 68 0.037990251s
|
||||||
|
~~~~
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Log all requests to stdout
|
Log all requests to stdout
|
||||||
|
|
|
@ -3,12 +3,12 @@ package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"log"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
"github.com/coredns/coredns/plugin/metrics/vars"
|
"github.com/coredns/coredns/plugin/metrics/vars"
|
||||||
"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/pkg/rcode"
|
"github.com/coredns/coredns/plugin/pkg/rcode"
|
||||||
"github.com/coredns/coredns/plugin/pkg/replacer"
|
"github.com/coredns/coredns/plugin/pkg/replacer"
|
||||||
"github.com/coredns/coredns/plugin/pkg/response"
|
"github.com/coredns/coredns/plugin/pkg/response"
|
||||||
|
@ -58,7 +58,7 @@ func (l Logger) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns.Msg)
|
||||||
// and we shouldn't have an empty rule.Class.
|
// and we shouldn't have an empty rule.Class.
|
||||||
if rule.Class[response.All] || rule.Class[class] {
|
if rule.Class[response.All] || rule.Class[class] {
|
||||||
rep := replacer.New(r, rrw, CommonLogEmptyValue)
|
rep := replacer.New(r, rrw, CommonLogEmptyValue)
|
||||||
rule.Log.Println(rep.Replace(rule.Format))
|
clog.Infof(rep.Replace(rule.Format))
|
||||||
}
|
}
|
||||||
|
|
||||||
return rc, err
|
return rc, err
|
||||||
|
@ -75,12 +75,11 @@ type Rule struct {
|
||||||
NameScope string
|
NameScope string
|
||||||
Class map[response.Class]bool
|
Class map[response.Class]bool
|
||||||
Format string
|
Format string
|
||||||
Log *log.Logger
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// CommonLogFormat is the common log format.
|
// CommonLogFormat is the common log format.
|
||||||
CommonLogFormat = `{remote}:{port} ` + CommonLogEmptyValue + ` [{when}] {>id} "{type} {class} {name} {proto} {size} {>do} {>bufsize}" {rcode} {>rflags} {rsize} {duration}`
|
CommonLogFormat = `{remote}:{port} ` + CommonLogEmptyValue + ` {>id} "{type} {class} {name} {proto} {size} {>do} {>bufsize}" {rcode} {>rflags} {rsize} {duration}`
|
||||||
// CommonLogEmptyValue is the common empty log value.
|
// CommonLogEmptyValue is the common empty log value.
|
||||||
CommonLogEmptyValue = "-"
|
CommonLogEmptyValue = "-"
|
||||||
// CombinedLogFormat is the combined log format.
|
// CombinedLogFormat is the combined log format.
|
||||||
|
|
|
@ -18,14 +18,15 @@ import (
|
||||||
func init() { clog.Discard() }
|
func init() { clog.Discard() }
|
||||||
|
|
||||||
func TestLoggedStatus(t *testing.T) {
|
func TestLoggedStatus(t *testing.T) {
|
||||||
var f bytes.Buffer
|
|
||||||
rule := Rule{
|
rule := Rule{
|
||||||
NameScope: ".",
|
NameScope: ".",
|
||||||
Format: DefaultLogFormat,
|
Format: DefaultLogFormat,
|
||||||
Log: log.New(&f, "", 0),
|
|
||||||
Class: map[response.Class]bool{response.All: true},
|
Class: map[response.Class]bool{response.All: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var f bytes.Buffer
|
||||||
|
log.SetOutput(&f)
|
||||||
|
|
||||||
logger := Logger{
|
logger := Logger{
|
||||||
Rules: []Rule{rule},
|
Rules: []Rule{rule},
|
||||||
Next: test.ErrorHandler(),
|
Next: test.ErrorHandler(),
|
||||||
|
@ -49,14 +50,15 @@ func TestLoggedStatus(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoggedClassDenial(t *testing.T) {
|
func TestLoggedClassDenial(t *testing.T) {
|
||||||
var f bytes.Buffer
|
|
||||||
rule := Rule{
|
rule := Rule{
|
||||||
NameScope: ".",
|
NameScope: ".",
|
||||||
Format: DefaultLogFormat,
|
Format: DefaultLogFormat,
|
||||||
Log: log.New(&f, "", 0),
|
|
||||||
Class: map[response.Class]bool{response.Denial: true},
|
Class: map[response.Class]bool{response.Denial: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var f bytes.Buffer
|
||||||
|
log.SetOutput(&f)
|
||||||
|
|
||||||
logger := Logger{
|
logger := Logger{
|
||||||
Rules: []Rule{rule},
|
Rules: []Rule{rule},
|
||||||
Next: test.ErrorHandler(),
|
Next: test.ErrorHandler(),
|
||||||
|
@ -77,14 +79,15 @@ func TestLoggedClassDenial(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLoggedClassError(t *testing.T) {
|
func TestLoggedClassError(t *testing.T) {
|
||||||
var f bytes.Buffer
|
|
||||||
rule := Rule{
|
rule := Rule{
|
||||||
NameScope: ".",
|
NameScope: ".",
|
||||||
Format: DefaultLogFormat,
|
Format: DefaultLogFormat,
|
||||||
Log: log.New(&f, "", 0),
|
|
||||||
Class: map[response.Class]bool{response.Error: true},
|
Class: map[response.Class]bool{response.Error: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var f bytes.Buffer
|
||||||
|
log.SetOutput(&f)
|
||||||
|
|
||||||
logger := Logger{
|
logger := Logger{
|
||||||
Rules: []Rule{rule},
|
Rules: []Rule{rule},
|
||||||
Next: test.ErrorHandler(),
|
Next: test.ErrorHandler(),
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/coredns/coredns/core/dnsserver"
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
"github.com/coredns/coredns/plugin"
|
"github.com/coredns/coredns/plugin"
|
||||||
"github.com/coredns/coredns/plugin/pkg/response"
|
"github.com/coredns/coredns/plugin/pkg/response"
|
||||||
|
@ -25,15 +22,6 @@ func setup(c *caddy.Controller) error {
|
||||||
return plugin.Error("log", err)
|
return plugin.Error("log", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open the log files for writing when the server starts
|
|
||||||
c.OnStartup(func() error {
|
|
||||||
for i := 0; i < len(rules); i++ {
|
|
||||||
rules[i].Log = log.New(os.Stdout, "", 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
dnsserver.GetConfig(c).AddPlugin(func(next plugin.Handler) plugin.Handler {
|
||||||
return Logger{Next: next, Rules: rules, ErrorFunc: dnsserver.DefaultErrorFunc}
|
return Logger{Next: next, Rules: rules, ErrorFunc: dnsserver.DefaultErrorFunc}
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
// Package log implements a small wrapper around the std lib log package.
|
// Package log implements a small wrapper around the std lib log package.
|
||||||
// It implements log levels by prefixing the logs with [INFO], [DEBUG],
|
// It implements log levels by prefixing the logs with the current time
|
||||||
// [WARNING] or [ERROR].
|
// with in RFC3339Milli and [INFO], [DEBUG], [WARNING] or [ERROR].
|
||||||
// Debug logging is available and enabled if the *debug* plugin is used.
|
// Debug logging is available and enabled if the *debug* plugin is used.
|
||||||
//
|
//
|
||||||
// log.Info("this is some logging"), will log on the Info level.
|
// log.Info("this is some logging"), will log on the Info level.
|
||||||
//
|
//
|
||||||
// log.Debug("this is debug output"), will log in the Debug level.
|
// log.Debug("this is debug output"), will log in the Debug level, etc.
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
@ -13,19 +13,24 @@ import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
golog "log"
|
golog "log"
|
||||||
"os"
|
"os"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// D controls whether we should output debug logs. If true, we do.
|
// D controls whether we should output debug logs. If true, we do.
|
||||||
var D bool
|
var D bool
|
||||||
|
|
||||||
|
// RFC3339Milli doesn't exist, invent it here.
|
||||||
|
func clock() string { return time.Now().Format("2006-01-02T15:04:05.999Z07:00") }
|
||||||
|
|
||||||
// logf calls log.Printf prefixed with level.
|
// logf calls log.Printf prefixed with level.
|
||||||
func logf(level, format string, v ...interface{}) {
|
func logf(level, format string, v ...interface{}) {
|
||||||
s := level + fmt.Sprintf(format, v...)
|
golog.Print(clock(), level, fmt.Sprintf(format, v...))
|
||||||
golog.Print(s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// log calls log.Print prefixed with level.
|
// log calls log.Print prefixed with level.
|
||||||
func log(level string, v ...interface{}) { s := level + fmt.Sprint(v...); golog.Print(s) }
|
func log(level string, v ...interface{}) {
|
||||||
|
golog.Print(clock(), level, fmt.Sprint(v...))
|
||||||
|
}
|
||||||
|
|
||||||
// Debug is equivalent to log.Print(), but prefixed with "[DEBUG] ". It only outputs something
|
// Debug is equivalent to log.Print(), but prefixed with "[DEBUG] ". It only outputs something
|
||||||
// if D is true.
|
// if D is true.
|
||||||
|
@ -75,9 +80,9 @@ func Fatalf(format string, v ...interface{}) { logf(fatal, format, v...); os.Exi
|
||||||
func Discard() { golog.SetOutput(ioutil.Discard) }
|
func Discard() { golog.SetOutput(ioutil.Discard) }
|
||||||
|
|
||||||
const (
|
const (
|
||||||
debug = "[DEBUG] "
|
debug = " [DEBUG] "
|
||||||
err = "[ERROR] "
|
err = " [ERROR] "
|
||||||
fatal = "[FATAL] "
|
fatal = " [FATAL] "
|
||||||
info = "[INFO] "
|
info = " [INFO] "
|
||||||
warning = "[WARNING] "
|
warning = " [WARNING] "
|
||||||
)
|
)
|
||||||
|
|
|
@ -35,13 +35,11 @@ func New(r *dns.Msg, rr *dnstest.Recorder, emptyValue string) Replacer {
|
||||||
req := request.Request{W: rr, Req: r}
|
req := request.Request{W: rr, Req: r}
|
||||||
rep := replacer{
|
rep := replacer{
|
||||||
replacements: map[string]string{
|
replacements: map[string]string{
|
||||||
"{type}": req.Type(),
|
"{type}": req.Type(),
|
||||||
"{name}": req.Name(),
|
"{name}": req.Name(),
|
||||||
"{class}": req.Class(),
|
"{class}": req.Class(),
|
||||||
"{proto}": req.Proto(),
|
"{proto}": req.Proto(),
|
||||||
"{when}": func() string {
|
"{when}": "", // made a noop
|
||||||
return time.Now().Format(timeFormat)
|
|
||||||
}(),
|
|
||||||
"{size}": strconv.Itoa(req.Len()),
|
"{size}": strconv.Itoa(req.Len()),
|
||||||
"{remote}": addrToRFC3986(req.IP()),
|
"{remote}": addrToRFC3986(req.IP()),
|
||||||
"{port}": req.Port(),
|
"{port}": req.Port(),
|
||||||
|
@ -163,7 +161,4 @@ func addrToRFC3986(addr string) string {
|
||||||
return addr
|
return addr
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const headerReplacer = "{>"
|
||||||
timeFormat = "02/Jan/2006:15:04:05 -0700"
|
|
||||||
headerReplacer = "{>"
|
|
||||||
)
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue