2019-10-28 15:39:37 -05:00
|
|
|
// Copyright 2015 Matthew Holt and The Caddy Authors
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
package logging
|
|
|
|
|
|
|
|
import (
|
2019-11-11 13:04:41 -05:00
|
|
|
"fmt"
|
2019-10-28 15:39:37 -05:00
|
|
|
"io"
|
2020-05-07 14:06:00 -05:00
|
|
|
"math"
|
2019-10-28 15:39:37 -05:00
|
|
|
"os"
|
|
|
|
"path/filepath"
|
2020-02-26 00:00:33 -05:00
|
|
|
"strconv"
|
2019-10-28 15:39:37 -05:00
|
|
|
|
|
|
|
"github.com/caddyserver/caddy/v2"
|
2020-02-26 00:00:33 -05:00
|
|
|
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
|
|
|
"github.com/dustin/go-humanize"
|
2019-10-28 15:39:37 -05:00
|
|
|
"gopkg.in/natefinch/lumberjack.v2"
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
caddy.RegisterModule(FileWriter{})
|
|
|
|
}
|
|
|
|
|
2019-12-10 15:36:46 -05:00
|
|
|
// FileWriter can write logs to files. By default, log files
|
|
|
|
// are rotated ("rolled") when they get large, and old log
|
|
|
|
// files get deleted, to ensure that the process does not
|
|
|
|
// exhaust disk space.
|
2019-10-28 15:39:37 -05:00
|
|
|
type FileWriter struct {
|
2019-12-10 15:36:46 -05:00
|
|
|
// Filename is the name of the file to write.
|
|
|
|
Filename string `json:"filename,omitempty"`
|
|
|
|
|
|
|
|
// Roll toggles log rolling or rotation, which is
|
|
|
|
// enabled by default.
|
|
|
|
Roll *bool `json:"roll,omitempty"`
|
|
|
|
|
|
|
|
// When a log file reaches approximately this size,
|
|
|
|
// it will be rotated.
|
|
|
|
RollSizeMB int `json:"roll_size_mb,omitempty"`
|
|
|
|
|
|
|
|
// Whether to compress rolled files. Default: true
|
|
|
|
RollCompress *bool `json:"roll_gzip,omitempty"`
|
|
|
|
|
|
|
|
// Whether to use local timestamps in rolled filenames.
|
|
|
|
// Default: false
|
|
|
|
RollLocalTime bool `json:"roll_local_time,omitempty"`
|
|
|
|
|
|
|
|
// The maximum number of rolled log files to keep.
|
|
|
|
// Default: 10
|
|
|
|
RollKeep int `json:"roll_keep,omitempty"`
|
|
|
|
|
|
|
|
// How many days to keep rolled log files. Default: 90
|
|
|
|
RollKeepDays int `json:"roll_keep_days,omitempty"`
|
2019-10-28 15:39:37 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// CaddyModule returns the Caddy module information.
|
|
|
|
func (FileWriter) CaddyModule() caddy.ModuleInfo {
|
|
|
|
return caddy.ModuleInfo{
|
2019-12-10 15:36:46 -05:00
|
|
|
ID: "caddy.logging.writers.file",
|
|
|
|
New: func() caddy.Module { return new(FileWriter) },
|
2019-10-28 15:39:37 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-11 13:04:41 -05:00
|
|
|
// Provision sets up the module
|
|
|
|
func (fw *FileWriter) Provision(ctx caddy.Context) error {
|
|
|
|
// Replace placeholder in filename
|
|
|
|
repl := caddy.NewReplacer()
|
|
|
|
filename, err := repl.ReplaceOrErr(fw.Filename, true, true)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("invalid filename for log file: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
fw.Filename = filename
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-10-28 15:39:37 -05:00
|
|
|
func (fw FileWriter) String() string {
|
|
|
|
fpath, err := filepath.Abs(fw.Filename)
|
|
|
|
if err == nil {
|
|
|
|
return fpath
|
|
|
|
}
|
|
|
|
return fw.Filename
|
|
|
|
}
|
|
|
|
|
|
|
|
// WriterKey returns a unique key representing this fw.
|
|
|
|
func (fw FileWriter) WriterKey() string {
|
|
|
|
return "file:" + fw.Filename
|
|
|
|
}
|
|
|
|
|
|
|
|
// OpenWriter opens a new file writer.
|
|
|
|
func (fw FileWriter) OpenWriter() (io.WriteCloser, error) {
|
|
|
|
// roll log files by default
|
2020-01-03 13:33:22 -05:00
|
|
|
if fw.Roll == nil || *fw.Roll {
|
2019-10-28 15:39:37 -05:00
|
|
|
if fw.RollSizeMB == 0 {
|
|
|
|
fw.RollSizeMB = 100
|
|
|
|
}
|
|
|
|
if fw.RollCompress == nil {
|
|
|
|
compress := true
|
|
|
|
fw.RollCompress = &compress
|
|
|
|
}
|
|
|
|
if fw.RollKeep == 0 {
|
|
|
|
fw.RollKeep = 10
|
|
|
|
}
|
|
|
|
if fw.RollKeepDays == 0 {
|
|
|
|
fw.RollKeepDays = 90
|
|
|
|
}
|
2019-11-11 13:04:41 -05:00
|
|
|
|
2019-10-28 15:39:37 -05:00
|
|
|
return &lumberjack.Logger{
|
|
|
|
Filename: fw.Filename,
|
|
|
|
MaxSize: fw.RollSizeMB,
|
|
|
|
MaxAge: fw.RollKeepDays,
|
|
|
|
MaxBackups: fw.RollKeep,
|
|
|
|
LocalTime: fw.RollLocalTime,
|
|
|
|
Compress: *fw.RollCompress,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// otherwise just open a regular file
|
|
|
|
return os.OpenFile(fw.Filename, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
|
|
|
|
}
|
2019-11-11 13:04:41 -05:00
|
|
|
|
2020-02-26 00:00:33 -05:00
|
|
|
// UnmarshalCaddyfile sets up the module from Caddyfile tokens. Syntax:
|
|
|
|
//
|
|
|
|
// file <filename> {
|
|
|
|
// roll_disabled
|
|
|
|
// roll_size <size>
|
|
|
|
// roll_keep <num>
|
|
|
|
// roll_keep_for <days>
|
|
|
|
// }
|
|
|
|
//
|
2020-05-07 14:06:00 -05:00
|
|
|
// The roll_size value has megabyte resolution.
|
|
|
|
// Fractional values are rounded up to the next whole megabyte (MiB).
|
|
|
|
//
|
|
|
|
// The roll_keep_for duration has day resolution.
|
|
|
|
// Fractional values are rounded up to the next whole number of days.
|
|
|
|
//
|
|
|
|
// If any of the roll_size, roll_keep, or roll_keep_for subdirectives are
|
|
|
|
// omitted or set to a zero value, then Caddy's default value for that
|
|
|
|
// subdirective is used.
|
2020-02-26 00:00:33 -05:00
|
|
|
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)
|
|
|
|
}
|
2020-05-07 14:06:00 -05:00
|
|
|
fw.RollSizeMB = int(math.Ceil(float64(size) / humanize.MiByte))
|
2020-02-26 00:00:33 -05:00
|
|
|
|
|
|
|
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()
|
|
|
|
}
|
2020-05-11 17:41:11 -05:00
|
|
|
keepFor, err := caddy.ParseDuration(keepForStr)
|
2020-02-26 00:00:33 -05:00
|
|
|
if err != nil {
|
|
|
|
return d.Errf("parsing roll_keep_for duration: %v", err)
|
|
|
|
}
|
2020-05-07 14:06:00 -05:00
|
|
|
if keepFor < 0 {
|
|
|
|
return d.Errf("negative roll_keep_for duration: %v", keepFor)
|
|
|
|
}
|
|
|
|
fw.RollKeepDays = int(math.Ceil(keepFor.Hours() / 24))
|
2020-02-26 00:00:33 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2019-11-11 13:04:41 -05:00
|
|
|
// Interface guards
|
|
|
|
var (
|
2020-02-26 00:00:33 -05:00
|
|
|
_ caddy.Provisioner = (*FileWriter)(nil)
|
|
|
|
_ caddy.WriterOpener = (*FileWriter)(nil)
|
|
|
|
_ caddyfile.Unmarshaler = (*FileWriter)(nil)
|
2019-11-11 13:04:41 -05:00
|
|
|
)
|