Repository: caddyserver/caddy
File: modules/logging/filewriter.go (Go)

Code before:
	"os"
	"path/filepath"
	"strconv"

	"github.com/dustin/go-humanize"
	"gopkg.in/natefinch/lumberjack.v2"

	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
)

func init() {
	caddy.RegisterModule(FileWriter{})
}

// 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.
type FileWriter struct {
	// Filename is the name of the file to write.
	Filename string `json:"filename,omitempty"`

	// The file permissions mode.
	// 0600 by default.
	Mode os.FileMode `json:"mode,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"`
}

// CaddyModule returns the Caddy module information.

Review comment (suggestion):
The [`os.FileMode`](https://pkg.go.dev/os#FileMode) type is an alias to [`fs.FileMode`](https://pkg.go.dev/io/fs#FileMode), which is defined as `uint32`. The mode 777, for example, is ought interpreted as octal by those parsing the string (pun-intended) of characters in the context of file mode. However, the initial (un)marshaling of the value in Go treats it as decimal. Going back to the example of 777, the user is expected to [pass the integer `511` (the equivalent of o777)](https://unix.stackexchange.com/questions/103413/is-there-any-difference-between-mode-value-0777-and-777) to get the effect of o777, which is confusing.

I'm torn between simply using a string versus defining a new type, e.g. `type FileMode string` with custom `UnmarshalJSON` method (like [`caddy.Duration`](https://pkg.go.dev/github.com/caddyserver/caddy/v2#Duration)) that can accept integers (assuming the digits are octal) as well as strings to be interpreted in the form `rwxrwxrwx`, but the latter may be an overkill.

I know you're not a Go dev. Sorry for making this more complicated/work for you 🙂 

Code after:
	"math"
	"os"
	"path/filepath"
	"strconv"

	"github.com/dustin/go-humanize"
	"gopkg.in/natefinch/lumberjack.v2"

	"github.com/caddyserver/caddy/v2"
	"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
)

func init() {
	caddy.RegisterModule(FileWriter{})
}

// fileMode is a string made of 1 to 4 octal digits representing
// a numeric mode as specified with the `chmod` unix command.
// `"0777"` and `"777"` are thus equivalent values.
type fileMode os.FileMode

// UnmarshalJSON satisfies json.Unmarshaler.
func (m *fileMode) UnmarshalJSON(b []byte) error {
	if len(b) == 0 {
		return io.EOF
	}

	var s string
	if err := json.Unmarshal(b, &s); err != nil {
		return err
	}

	mode, err := parseFileMode(s)
	if err != nil {
		return err
	}

	*m = fileMode(mode)
	return err
}

// parseFileMode parses a file mode string,
// adding support for `chmod` unix command like
// 1 to 4 digital octal values.
func parseFileMode(s string) (os.FileMode, error) {
	modeStr := fmt.Sprintf("%04s", s)
	mode, err := strconv.ParseUint(modeStr, 8, 32)
	if err != nil {
		return 0, err
	}
	return os.FileMode(mode), nil