middleware/{log,errors}: output everything to stdout (#684)
Limit the options in both errors and log middleware, just output to stdout and let someone else (journald,docker) care about where to route the logs. This removes syslog and logging to a file. Fixes #573 #602
This commit is contained in:
parent
6c774782e0
commit
e261ac1a6e
9 changed files with 49 additions and 186 deletions
|
@ -6,35 +6,21 @@ TODO: what are errors.
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
errors [LOGFILE]
|
errors [FILE]
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
* **LOGFILE** is the path to the error log file to create (or append to), relative to the current
|
* **FILE** is the log file to create (or append to). The *only* valid name for **FILE** is *stdout*
|
||||||
working directory. It can also be `stdout` or `stderr` to write to the console, syslog to write to the
|
|
||||||
system log (except on Windows), or visible to write the error (including full stack trace, if
|
|
||||||
applicable) to the response. Writing errors to the response is NOT advised except in local debug
|
|
||||||
situations. The default is stderr. The above syntax will simply enable error reporting on the
|
|
||||||
server. To specify custom error pages, open a block:
|
|
||||||
|
|
||||||
~~~
|
|
||||||
errors {
|
|
||||||
what where
|
|
||||||
}
|
|
||||||
~~~
|
|
||||||
|
|
||||||
* `what` can only be `log`.
|
|
||||||
* `where` is the path to the log file (as described above) and you can enable rotation to manage the log files.
|
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Log errors into a file in the parent directory:
|
Log errors to *stdout*.
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
errors ../error.log
|
errors
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
Make errors visible to the client (for debugging only):
|
Log errors to *stdout*.
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
errors visible
|
errors stdout
|
||||||
~~~
|
~~~
|
||||||
|
|
|
@ -20,7 +20,6 @@ type errorHandler struct {
|
||||||
Next middleware.Handler
|
Next middleware.Handler
|
||||||
LogFile string
|
LogFile string
|
||||||
Log *log.Logger
|
Log *log.Logger
|
||||||
Debug bool // if true, errors are written out to client rather than to a log
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeDNS implements the middleware.Handler interface.
|
// ServeDNS implements the middleware.Handler interface.
|
||||||
|
@ -33,15 +32,6 @@ func (h errorHandler) ServeDNS(ctx context.Context, w dns.ResponseWriter, r *dns
|
||||||
state := request.Request{W: w, Req: r}
|
state := request.Request{W: w, Req: r}
|
||||||
errMsg := fmt.Sprintf("%s [ERROR %d %s %s] %v", time.Now().Format(timeFormat), rcode, state.Name(), state.Type(), err)
|
errMsg := fmt.Sprintf("%s [ERROR %d %s %s] %v", time.Now().Format(timeFormat), rcode, state.Name(), state.Type(), err)
|
||||||
|
|
||||||
if h.Debug {
|
|
||||||
// Write error to response as a txt message instead of to log
|
|
||||||
answer := debugMsg(rcode, r)
|
|
||||||
txt, _ := dns.NewRR(". IN 0 TXT " + errMsg)
|
|
||||||
answer.Answer = append(answer.Answer, txt)
|
|
||||||
state.SizeAndDo(answer)
|
|
||||||
w.WriteMsg(answer)
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
h.Log.Println(errMsg)
|
h.Log.Println(errMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,7 +46,6 @@ func (h errorHandler) recovery(ctx context.Context, w dns.ResponseWriter, r *dns
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
state := request.Request{W: w, Req: r}
|
|
||||||
// Obtain source of panic
|
// Obtain source of panic
|
||||||
// From: https://gist.github.com/swdunlop/9629168
|
// From: https://gist.github.com/swdunlop/9629168
|
||||||
var name, file string // function name, file name
|
var name, file string // function name, file name
|
||||||
|
@ -83,20 +72,8 @@ func (h errorHandler) recovery(ctx context.Context, w dns.ResponseWriter, r *dns
|
||||||
}
|
}
|
||||||
|
|
||||||
panicMsg := fmt.Sprintf("%s [PANIC %s %s] %s:%d - %v", time.Now().Format(timeFormat), r.Question[0].Name, dns.Type(r.Question[0].Qtype), file, line, rec)
|
panicMsg := fmt.Sprintf("%s [PANIC %s %s] %s:%d - %v", time.Now().Format(timeFormat), r.Question[0].Name, dns.Type(r.Question[0].Qtype), file, line, rec)
|
||||||
if h.Debug {
|
|
||||||
// Write error and stack trace to the response rather than to a log
|
|
||||||
var stackBuf [4096]byte
|
|
||||||
stack := stackBuf[:runtime.Stack(stackBuf[:], false)]
|
|
||||||
answer := debugMsg(dns.RcodeServerFailure, r)
|
|
||||||
// add stack buf in TXT, limited to 255 chars for now.
|
|
||||||
txt, _ := dns.NewRR(". IN 0 TXT " + string(stack[:255]))
|
|
||||||
answer.Answer = append(answer.Answer, txt)
|
|
||||||
state.SizeAndDo(answer)
|
|
||||||
w.WriteMsg(answer)
|
|
||||||
} else {
|
|
||||||
// Currently we don't use the function name, since file:line is more conventional
|
// Currently we don't use the function name, since file:line is more conventional
|
||||||
h.Log.Printf(panicMsg)
|
h.Log.Printf(panicMsg)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// debugMsg creates a debug message that gets send back to the client.
|
// debugMsg creates a debug message that gets send back to the client.
|
||||||
|
|
|
@ -66,30 +66,6 @@ func TestErrors(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestVisibleErrorWithPanic(t *testing.T) {
|
|
||||||
const panicMsg = "I'm a panic"
|
|
||||||
eh := errorHandler{
|
|
||||||
Debug: true,
|
|
||||||
Next: middleware.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
|
||||||
panic(panicMsg)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx := context.TODO()
|
|
||||||
req := new(dns.Msg)
|
|
||||||
req.SetQuestion("example.org.", dns.TypeA)
|
|
||||||
|
|
||||||
rec := dnsrecorder.New(&test.ResponseWriter{})
|
|
||||||
|
|
||||||
code, err := eh.ServeDNS(ctx, rec, req)
|
|
||||||
if code != 0 {
|
|
||||||
t.Errorf("Expected error handler to return 0 (it should write to response), got status %d", code)
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Expected error handler to return nil error (it should panic!), but got '%v'", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func genErrorHandler(rcode int, err error) middleware.Handler {
|
func genErrorHandler(rcode int, err error) middleware.Handler {
|
||||||
return middleware.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
return middleware.HandlerFunc(func(ctx context.Context, w dns.ResponseWriter, r *dns.Msg) (int, error) {
|
||||||
return rcode, err
|
return rcode, err
|
||||||
|
|
|
@ -1,14 +1,13 @@
|
||||||
package errors
|
package errors
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/coredns/coredns/core/dnsserver"
|
"github.com/coredns/coredns/core/dnsserver"
|
||||||
"github.com/coredns/coredns/middleware"
|
"github.com/coredns/coredns/middleware"
|
||||||
|
|
||||||
"github.com/hashicorp/go-syslog"
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -25,34 +24,7 @@ func setup(c *caddy.Controller) error {
|
||||||
return middleware.Error("errors", err)
|
return middleware.Error("errors", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var writer io.Writer
|
handler.Log = log.New(os.Stdout, "", 0)
|
||||||
|
|
||||||
switch handler.LogFile {
|
|
||||||
case "visible":
|
|
||||||
handler.Debug = true
|
|
||||||
case "stdout":
|
|
||||||
writer = os.Stdout
|
|
||||||
case "stderr":
|
|
||||||
writer = os.Stderr
|
|
||||||
case "syslog":
|
|
||||||
writer, err = gsyslog.NewLogger(gsyslog.LOG_ERR, "LOCAL0", "coredns")
|
|
||||||
if err != nil {
|
|
||||||
return middleware.Error("errors", err)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if handler.LogFile == "" {
|
|
||||||
writer = os.Stderr // default
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
var file *os.File
|
|
||||||
file, err = os.OpenFile(handler.LogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return middleware.Error("errors", err)
|
|
||||||
}
|
|
||||||
writer = file
|
|
||||||
}
|
|
||||||
handler.Log = log.New(writer, "", 0)
|
|
||||||
|
|
||||||
dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler {
|
dnsserver.GetConfig(c).AddMiddleware(func(next middleware.Handler) middleware.Handler {
|
||||||
handler.Next = next
|
handler.Next = next
|
||||||
|
@ -65,47 +37,19 @@ func setup(c *caddy.Controller) error {
|
||||||
func errorsParse(c *caddy.Controller) (errorHandler, error) {
|
func errorsParse(c *caddy.Controller) (errorHandler, error) {
|
||||||
handler := errorHandler{}
|
handler := errorHandler{}
|
||||||
|
|
||||||
optionalBlock := func() (bool, error) {
|
|
||||||
var hadBlock bool
|
|
||||||
|
|
||||||
for c.NextBlock() {
|
|
||||||
hadBlock = true
|
|
||||||
|
|
||||||
what := c.Val()
|
|
||||||
if !c.NextArg() {
|
|
||||||
return hadBlock, c.ArgErr()
|
|
||||||
}
|
|
||||||
where := c.Val()
|
|
||||||
|
|
||||||
if what == "log" {
|
|
||||||
if where == "visible" {
|
|
||||||
handler.Debug = true
|
|
||||||
} else {
|
|
||||||
handler.LogFile = where
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return hadBlock, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for c.Next() {
|
for c.Next() {
|
||||||
// Configuration may be in a block
|
args := c.RemainingArgs()
|
||||||
hadBlock, err := optionalBlock()
|
switch len(args) {
|
||||||
if err != nil {
|
case 0:
|
||||||
return handler, err
|
handler.LogFile = "stdout"
|
||||||
|
case 1:
|
||||||
|
if args[0] != "stdout" {
|
||||||
|
return handler, fmt.Errorf("invalid log file: %s", args[0])
|
||||||
}
|
}
|
||||||
|
handler.LogFile = args[0]
|
||||||
// Otherwise, the only argument would be an error log file name or 'visible'
|
default:
|
||||||
if !hadBlock {
|
return handler, c.ArgErr()
|
||||||
if c.NextArg() {
|
|
||||||
if c.Val() == "visible" {
|
|
||||||
handler.Debug = true
|
|
||||||
} else {
|
|
||||||
handler.LogFile = c.Val()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return handler, nil
|
return handler, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,18 +13,19 @@ func TestErrorsParse(t *testing.T) {
|
||||||
expectedErrorHandler errorHandler
|
expectedErrorHandler errorHandler
|
||||||
}{
|
}{
|
||||||
{`errors`, false, errorHandler{
|
{`errors`, false, errorHandler{
|
||||||
|
LogFile: "stdout",
|
||||||
|
}},
|
||||||
|
{`errors stdout`, false, errorHandler{
|
||||||
|
LogFile: "stdout",
|
||||||
|
}},
|
||||||
|
{`errors errors.txt`, true, errorHandler{
|
||||||
LogFile: "",
|
LogFile: "",
|
||||||
}},
|
}},
|
||||||
{`errors errors.txt`, false, errorHandler{
|
{`errors visible`, true, errorHandler{
|
||||||
LogFile: "errors.txt",
|
|
||||||
}},
|
|
||||||
{`errors visible`, false, errorHandler{
|
|
||||||
LogFile: "",
|
LogFile: "",
|
||||||
Debug: true,
|
|
||||||
}},
|
}},
|
||||||
{`errors { log visible }`, false, errorHandler{
|
{`errors { log visible }`, true, errorHandler{
|
||||||
LogFile: "",
|
LogFile: "stdout",
|
||||||
Debug: true,
|
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
|
@ -40,9 +41,5 @@ func TestErrorsParse(t *testing.T) {
|
||||||
t.Errorf("Test %d expected LogFile to be %s, but got %s",
|
t.Errorf("Test %d expected LogFile to be %s, but got %s",
|
||||||
i, test.expectedErrorHandler.LogFile, actualErrorsRule.LogFile)
|
i, test.expectedErrorHandler.LogFile, actualErrorsRule.LogFile)
|
||||||
}
|
}
|
||||||
if actualErrorsRule.Debug != test.expectedErrorHandler.Debug {
|
|
||||||
t.Errorf("Test %d expected Debug to be %v, but got %v",
|
|
||||||
i, test.expectedErrorHandler.Debug, actualErrorsRule.Debug)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# log
|
# log
|
||||||
|
|
||||||
*log* enables query logging.
|
*log* enables query logging to standard output.
|
||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|
||||||
|
@ -8,20 +8,20 @@
|
||||||
log
|
log
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
* With no arguments, a query log entry is written to query.log in the common log format for all requests
|
* With no arguments, a query log entry is written to *stdout* in the common log format for all requests
|
||||||
|
|
||||||
~~~ txt
|
~~~ txt
|
||||||
log FILE
|
log FILE
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
* **FILE** is the log file to create (or append to).
|
* **FILE** is the log file to create (or append to). The *only* valid name for **FILE** is *stdout*.
|
||||||
|
|
||||||
~~~ txt
|
~~~ txt
|
||||||
log [NAME] FILE [FORMAT]
|
log [NAME] FILE [FORMAT]
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
* `NAME` is the name to match in order to be logged
|
* `NAME` is the name to match in order to be logged
|
||||||
* `FILE` is the log file to create (or append to)
|
* `FILE` is the log file (again only *stdout* is allowed here).
|
||||||
* `FORMAT` is the log format to use (default is Common Log Format)
|
* `FORMAT` is the log format to use (default is Common Log Format)
|
||||||
|
|
||||||
You can further specify the class of responses that get logged:
|
You can further specify the class of responses that get logged:
|
||||||
|
@ -45,9 +45,8 @@ If no class is specified, it defaults to *all*.
|
||||||
|
|
||||||
## Log File
|
## Log File
|
||||||
|
|
||||||
The log file can be any filename. It could also be *stdout* or *stderr* to write the log to the console,
|
The "log file" can only be *stdout*. CoreDNS expects another service to pick up this output and deal
|
||||||
or *syslog* to write to the system log (except on Windows). If the log file does not exist beforehand,
|
with it, i.e. journald when using systemd or Docker's logging capabilities.
|
||||||
CoreDNS will create it before appending to it.
|
|
||||||
|
|
||||||
## Log Format
|
## Log Format
|
||||||
|
|
||||||
|
@ -80,22 +79,22 @@ The default Common Log Format is:
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
Log all requests to a file:
|
Log all requests to stdout
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
log /var/log/query.log
|
log stdout
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
Custom log format:
|
Custom log format, for all zones (`.`)
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
log . ../query.log "{proto} Request: {name} {type} {>id}"
|
log . stdout "{proto} Request: {name} {type} {>id}"
|
||||||
~~~
|
~~~
|
||||||
|
|
||||||
Only log denials for example.org (and below to a file)
|
Only log denials for example.org (and below to a file)
|
||||||
|
|
||||||
~~~
|
~~~
|
||||||
log example.org example-query-log {
|
log example.org stdout {
|
||||||
class denial
|
class denial
|
||||||
}
|
}
|
||||||
~~~
|
~~~
|
||||||
|
|
|
@ -78,8 +78,8 @@ type Rule struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DefaultLogFilename is the default log filename.
|
// DefaultLogFilename is the default output name.
|
||||||
DefaultLogFilename = "query.log"
|
DefaultLogFilename = "stdout"
|
||||||
// CommonLogFormat is the common log format.
|
// CommonLogFormat is the common log format.
|
||||||
CommonLogFormat = `{remote} ` + CommonLogEmptyValue + ` [{when}] "{type} {class} {name} {proto} {size} {>do} {>bufsize}" {rcode} {rsize} {duration}`
|
CommonLogFormat = `{remote} ` + CommonLogEmptyValue + ` [{when}] "{type} {class} {name} {proto} {size} {>do} {>bufsize}" {rcode} {rsize} {duration}`
|
||||||
// CommonLogEmptyValue is the common empty log value.
|
// CommonLogEmptyValue is the common empty log value.
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ import (
|
||||||
"github.com/coredns/coredns/middleware"
|
"github.com/coredns/coredns/middleware"
|
||||||
"github.com/coredns/coredns/middleware/pkg/response"
|
"github.com/coredns/coredns/middleware/pkg/response"
|
||||||
|
|
||||||
"github.com/hashicorp/go-syslog"
|
|
||||||
"github.com/mholt/caddy"
|
"github.com/mholt/caddy"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
@ -30,25 +29,10 @@ func setup(c *caddy.Controller) error {
|
||||||
// Open the log files for writing when the server starts
|
// Open the log files for writing when the server starts
|
||||||
c.OnStartup(func() error {
|
c.OnStartup(func() error {
|
||||||
for i := 0; i < len(rules); i++ {
|
for i := 0; i < len(rules); i++ {
|
||||||
var err error
|
// We only support stdout
|
||||||
var writer io.Writer
|
writer := os.Stdout
|
||||||
|
if rules[i].OutputFile != "stdout" {
|
||||||
if rules[i].OutputFile == "stdout" {
|
return middleware.Error("log", fmt.Errorf("invalid log file: %s", rules[i].OutputFile))
|
||||||
writer = os.Stdout
|
|
||||||
} else if rules[i].OutputFile == "stderr" {
|
|
||||||
writer = os.Stderr
|
|
||||||
} else if rules[i].OutputFile == "syslog" {
|
|
||||||
writer, err = gsyslog.NewLogger(gsyslog.LOG_INFO, "LOCAL0", "coredns")
|
|
||||||
if err != nil {
|
|
||||||
return middleware.Error("log", err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var file *os.File
|
|
||||||
file, err = os.OpenFile(rules[i].OutputFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644)
|
|
||||||
if err != nil {
|
|
||||||
return middleware.Error("log", err)
|
|
||||||
}
|
|
||||||
writer = file
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rules[i].Log = log.New(writer, "", 0)
|
rules[i].Log = log.New(writer, "", 0)
|
||||||
|
@ -78,7 +62,7 @@ func logParse(c *caddy.Controller) ([]Rule, error) {
|
||||||
Format: DefaultLogFormat,
|
Format: DefaultLogFormat,
|
||||||
})
|
})
|
||||||
} else if len(args) == 1 {
|
} else if len(args) == 1 {
|
||||||
// Only an output file specified
|
// Only an output file specified.
|
||||||
rules = append(rules, Rule{
|
rules = append(rules, Rule{
|
||||||
NameScope: ".",
|
NameScope: ".",
|
||||||
OutputFile: args[0],
|
OutputFile: args[0],
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue