summaryrefslogtreecommitdiff
path: root/modules/logging
diff options
context:
space:
mode:
authorMatt Holt <mholt@users.noreply.github.com>2020-02-25 22:00:33 -0700
committerGitHub <noreply@github.com>2020-02-25 22:00:33 -0700
commit5d97522d18da39cd3f9dd309774a5ad2c51f4c51 (patch)
tree8d90c0200edbf2f252a7d57c6f65255a0e38b9e0 /modules/logging
parentf6b9cb7122c670ae063a9049455ec99edf0dc8be (diff)
v2: 'log' directive for Caddyfile, and debug mode (#3052)
* httpcaddyfile: Begin implementing log directive, and debug mode For now, debug mode just sets the log level for all logs to DEBUG (unless a level is specified explicitly). * httpcaddyfile: Finish 'log' directive Also rename StringEncoder -> SingleFieldEncoder * Fix minor bug in replacer (when vals are empty)
Diffstat (limited to 'modules/logging')
-rw-r--r--modules/logging/encoders.go155
-rw-r--r--modules/logging/filewriter.go76
-rw-r--r--modules/logging/netwriter.go23
3 files changed, 239 insertions, 15 deletions
diff --git a/modules/logging/encoders.go b/modules/logging/encoders.go
index 49ad11a..bd120d5 100644
--- a/modules/logging/encoders.go
+++ b/modules/logging/encoders.go
@@ -21,6 +21,7 @@ import (
"time"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
zaplogfmt "github.com/jsternberg/zap-logfmt"
"go.uber.org/zap"
"go.uber.org/zap/buffer"
@@ -31,7 +32,7 @@ func init() {
caddy.RegisterModule(ConsoleEncoder{})
caddy.RegisterModule(JSONEncoder{})
caddy.RegisterModule(LogfmtEncoder{})
- caddy.RegisterModule(StringEncoder{})
+ caddy.RegisterModule(SingleFieldEncoder{})
}
// ConsoleEncoder encodes log entries that are mostly human-readable.
@@ -54,6 +55,27 @@ func (ce *ConsoleEncoder) Provision(_ caddy.Context) error {
return nil
}
+// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
+//
+// console {
+// <common encoder config subdirectives...>
+// }
+//
+// See the godoc on the LogEncoderConfig type for the syntax of
+// subdirectives that are common to most/all encoders.
+func (ce *ConsoleEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ if d.NextArg() {
+ return d.ArgErr()
+ }
+ err := ce.LogEncoderConfig.UnmarshalCaddyfile(d)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
// JSONEncoder encodes entries as JSON.
type JSONEncoder struct {
zapcore.Encoder `json:"-"`
@@ -74,6 +96,27 @@ func (je *JSONEncoder) Provision(_ caddy.Context) error {
return nil
}
+// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
+//
+// json {
+// <common encoder config subdirectives...>
+// }
+//
+// See the godoc on the LogEncoderConfig type for the syntax of
+// subdirectives that are common to most/all encoders.
+func (je *JSONEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ if d.NextArg() {
+ return d.ArgErr()
+ }
+ err := je.LogEncoderConfig.UnmarshalCaddyfile(d)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
// LogfmtEncoder encodes log entries as logfmt:
// https://www.brandur.org/logfmt
type LogfmtEncoder struct {
@@ -95,26 +138,47 @@ func (lfe *LogfmtEncoder) Provision(_ caddy.Context) error {
return nil
}
-// StringEncoder writes a log entry that consists entirely
+// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
+//
+// logfmt {
+// <common encoder config subdirectives...>
+// }
+//
+// See the godoc on the LogEncoderConfig type for the syntax of
+// subdirectives that are common to most/all encoders.
+func (lfe *LogfmtEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ if d.NextArg() {
+ return d.ArgErr()
+ }
+ err := lfe.LogEncoderConfig.UnmarshalCaddyfile(d)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// SingleFieldEncoder writes a log entry that consists entirely
// of a single string field in the log entry. This is useful
// for custom, self-encoded log entries that consist of a
// single field in the structured log entry.
-type StringEncoder struct {
+type SingleFieldEncoder struct {
zapcore.Encoder `json:"-"`
FieldName string `json:"field,omitempty"`
FallbackRaw json.RawMessage `json:"fallback,omitempty" caddy:"namespace=caddy.logging.encoders inline_key=format"`
}
// CaddyModule returns the Caddy module information.
-func (StringEncoder) CaddyModule() caddy.ModuleInfo {
+func (SingleFieldEncoder) CaddyModule() caddy.ModuleInfo {
return caddy.ModuleInfo{
- ID: "caddy.logging.encoders.string",
- New: func() caddy.Module { return new(StringEncoder) },
+ ID: "caddy.logging.encoders.single_field",
+ New: func() caddy.Module { return new(SingleFieldEncoder) },
}
}
// Provision sets up the encoder.
-func (se *StringEncoder) Provision(ctx caddy.Context) error {
+func (se *SingleFieldEncoder) Provision(ctx caddy.Context) error {
if se.FallbackRaw != nil {
val, err := ctx.LoadModule(se, "FallbackRaw")
if err != nil {
@@ -132,16 +196,16 @@ func (se *StringEncoder) Provision(ctx caddy.Context) error {
// necessary because we implement our own EncodeEntry,
// and if we simply let the embedded encoder's Clone
// be promoted, it would return a clone of that, and
-// we'd lose our StringEncoder's EncodeEntry.
-func (se StringEncoder) Clone() zapcore.Encoder {
- return StringEncoder{
+// we'd lose our SingleFieldEncoder's EncodeEntry.
+func (se SingleFieldEncoder) Clone() zapcore.Encoder {
+ return SingleFieldEncoder{
Encoder: se.Encoder.Clone(),
FieldName: se.FieldName,
}
}
// EncodeEntry partially implements the zapcore.Encoder interface.
-func (se StringEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
+func (se SingleFieldEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (*buffer.Buffer, error) {
for _, f := range fields {
if f.Key == se.FieldName {
buf := bufferpool.Get()
@@ -158,6 +222,21 @@ func (se StringEncoder) EncodeEntry(ent zapcore.Entry, fields []zapcore.Field) (
return se.Encoder.EncodeEntry(ent, fields)
}
+// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
+//
+// single_field <field_name>
+//
+func (se *SingleFieldEncoder) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ var fieldName string
+ if !d.AllArgs(&fieldName) {
+ return d.ArgErr()
+ }
+ se.FieldName = d.Val()
+ }
+ return nil
+}
+
// LogEncoderConfig holds configuration common to most encoders.
type LogEncoderConfig struct {
MessageKey *string `json:"message_key,omitempty"`
@@ -172,6 +251,53 @@ type LogEncoderConfig struct {
LevelFormat string `json:"level_format,omitempty"`
}
+// UnmarshalCaddyfile populates the struct from Caddyfile tokens. Syntax:
+//
+// {
+// message_key <key>
+// level_key <key>
+// time_key <key>
+// name_key <key>
+// caller_key <key>
+// stacktrace_key <key>
+// line_ending <char>
+// time_format <format>
+// level_format <format>
+// }
+//
+func (lec *LogEncoderConfig) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for nesting := d.Nesting(); d.NextBlock(nesting); {
+ subdir := d.Val()
+ var arg string
+ if !d.AllArgs(&arg) {
+ return d.ArgErr()
+ }
+ switch subdir {
+ case "message_key":
+ lec.MessageKey = &arg
+ case "level_key":
+ lec.LevelKey = &arg
+ case "time_key":
+ lec.TimeKey = &arg
+ case "name_key":
+ lec.NameKey = &arg
+ case "caller_key":
+ lec.CallerKey = &arg
+ case "stacktrace_key":
+ lec.StacktraceKey = &arg
+ case "line_ending":
+ lec.LineEnding = &arg
+ case "time_format":
+ lec.TimeFormat = arg
+ case "level_format":
+ lec.LevelFormat = arg
+ default:
+ return d.Errf("unrecognized subdirective %s", subdir)
+ }
+ }
+ return nil
+}
+
// ZapcoreEncoderConfig returns the equivalent zapcore.EncoderConfig.
// If lec is nil, zap.NewProductionEncoderConfig() is returned.
func (lec *LogEncoderConfig) ZapcoreEncoderConfig() zapcore.EncoderConfig {
@@ -263,5 +389,10 @@ var (
_ zapcore.Encoder = (*ConsoleEncoder)(nil)
_ zapcore.Encoder = (*JSONEncoder)(nil)
_ zapcore.Encoder = (*LogfmtEncoder)(nil)
- _ zapcore.Encoder = (*StringEncoder)(nil)
+ _ zapcore.Encoder = (*SingleFieldEncoder)(nil)
+
+ _ caddyfile.Unmarshaler = (*ConsoleEncoder)(nil)
+ _ caddyfile.Unmarshaler = (*JSONEncoder)(nil)
+ _ caddyfile.Unmarshaler = (*LogfmtEncoder)(nil)
+ _ caddyfile.Unmarshaler = (*SingleFieldEncoder)(nil)
)
diff --git a/modules/logging/filewriter.go b/modules/logging/filewriter.go
index f17f975..e9c2dd8 100644
--- a/modules/logging/filewriter.go
+++ b/modules/logging/filewriter.go
@@ -19,8 +19,12 @@ import (
"io"
"os"
"path/filepath"
+ "strconv"
+ "time"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
+ "github.com/dustin/go-humanize"
"gopkg.in/natefinch/lumberjack.v2"
)
@@ -125,7 +129,77 @@ func (fw FileWriter) OpenWriter() (io.WriteCloser, error) {
return os.OpenFile(fw.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
}
+// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
+//
+// file <filename> {
+// roll_disabled
+// roll_size <size>
+// roll_keep <num>
+// roll_keep_for <days>
+// }
+//
+// The roll_size value will be rounded down to number of megabytes (MiB).
+// The roll_keep_for duration will be rounded down to number of days.
+func (fw *FileWriter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ fw.Filename = d.Val()
+ if d.NextArg() {
+ return d.ArgErr()
+ }
+
+ for d.NextBlock(0) {
+ switch d.Val() {
+ case "roll_disabled":
+ var f bool
+ fw.Roll = &f
+ if d.NextArg() {
+ return d.ArgErr()
+ }
+
+ case "roll_size":
+ var sizeStr string
+ if !d.AllArgs(&sizeStr) {
+ return d.ArgErr()
+ }
+ size, err := humanize.ParseBytes(sizeStr)
+ if err != nil {
+ return d.Errf("parsing size: %v", err)
+ }
+ fw.RollSizeMB = int(size) / 1024 / 1024
+
+ case "roll_keep":
+ var keepStr string
+ if !d.AllArgs(&keepStr) {
+ return d.ArgErr()
+ }
+ keep, err := strconv.Atoi(keepStr)
+ if err != nil {
+ return d.Errf("parsing roll_keep number: %v", err)
+ }
+ fw.RollKeep = keep
+
+ case "roll_keep_for":
+ var keepForStr string
+ if !d.AllArgs(&keepForStr) {
+ return d.ArgErr()
+ }
+ keepFor, err := time.ParseDuration(keepForStr)
+ if err != nil {
+ return d.Errf("parsing roll_keep_for duration: %v", err)
+ }
+ fw.RollKeepDays = int(keepFor.Hours()) / 24
+ }
+ }
+ }
+ return nil
+}
+
// Interface guards
var (
- _ caddy.Provisioner = (*FileWriter)(nil)
+ _ caddy.Provisioner = (*FileWriter)(nil)
+ _ caddy.WriterOpener = (*FileWriter)(nil)
+ _ caddyfile.Unmarshaler = (*FileWriter)(nil)
)
diff --git a/modules/logging/netwriter.go b/modules/logging/netwriter.go
index 1df80b6..7d2dafa 100644
--- a/modules/logging/netwriter.go
+++ b/modules/logging/netwriter.go
@@ -20,6 +20,7 @@ import (
"net"
"github.com/caddyserver/caddy/v2"
+ "github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
)
func init() {
@@ -75,8 +76,26 @@ func (nw NetWriter) OpenWriter() (io.WriteCloser, error) {
return net.Dial(nw.addr.Network, nw.addr.JoinHostPort(0))
}
+// UnmarshalCaddyfile sets up the handler from Caddyfile tokens. Syntax:
+//
+// net <address>
+//
+func (nw *NetWriter) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
+ for d.Next() {
+ if !d.NextArg() {
+ return d.ArgErr()
+ }
+ nw.Address = d.Val()
+ if d.NextArg() {
+ return d.ArgErr()
+ }
+ }
+ return nil
+}
+
// Interface guards
var (
- _ caddy.Provisioner = (*NetWriter)(nil)
- _ caddy.WriterOpener = (*NetWriter)(nil)
+ _ caddy.Provisioner = (*NetWriter)(nil)
+ _ caddy.WriterOpener = (*NetWriter)(nil)
+ _ caddyfile.Unmarshaler = (*NetWriter)(nil)
)