Add structured logging infrastructure (#246)
New global flags:
* `--log-file FILE`: can be literal `stdout`, `stderr`, or a file name (default `stderr`)
* `--log-level LEVEL`: can be `error`, `warn`, `info`, `debug`, `trace`, or `disabled` (default `disabled`)
* `--log-format TYPE`: can be `text` or `json` (default `text`)
New functions in the `log` package take a `context.Context` and retrieve
the logger from said context.
Because we carry the logger in a context, adding
[attributes](https://pkg.go.dev/golang.org/x/exp/slog#hdr-Attrs_and_Values)
to the logger can be done as follows:
```go
ctx = log.NewContext(ctx, log.GetLogger(ctx).With("foo", "bar"))
```
2023-03-16 13:46:53 +00:00
|
|
|
package flags
|
|
|
|
|
|
|
|
import (
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Abstract over files that are already open (e.g. stderr) and
|
|
|
|
// files that need to be opened before use.
|
|
|
|
type logFile interface {
|
|
|
|
Writer() io.Writer
|
|
|
|
Open() error
|
|
|
|
Close() error
|
|
|
|
}
|
|
|
|
|
|
|
|
// nopLogFile implements [logFile] for [os.Stderr] and [os.Stdout].
|
|
|
|
// The [logFile.Open] and [logFile.Close] functions do nothing.
|
|
|
|
type nopLogFile struct {
|
|
|
|
f *os.File
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *nopLogFile) Writer() io.Writer {
|
|
|
|
return f.f
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *nopLogFile) Open() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *nopLogFile) Close() error {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// nopLogFile implements [logFile] for actual files.
|
|
|
|
type realLogFile struct {
|
|
|
|
s string
|
|
|
|
f *os.File
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *realLogFile) Writer() io.Writer {
|
|
|
|
if f.f == nil {
|
|
|
|
panic("file hasn't been opened")
|
|
|
|
}
|
|
|
|
return f.f
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *realLogFile) Open() error {
|
2024-12-12 09:28:42 +00:00
|
|
|
file, err := os.OpenFile(f.s, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
|
Add structured logging infrastructure (#246)
New global flags:
* `--log-file FILE`: can be literal `stdout`, `stderr`, or a file name (default `stderr`)
* `--log-level LEVEL`: can be `error`, `warn`, `info`, `debug`, `trace`, or `disabled` (default `disabled`)
* `--log-format TYPE`: can be `text` or `json` (default `text`)
New functions in the `log` package take a `context.Context` and retrieve
the logger from said context.
Because we carry the logger in a context, adding
[attributes](https://pkg.go.dev/golang.org/x/exp/slog#hdr-Attrs_and_Values)
to the logger can be done as follows:
```go
ctx = log.NewContext(ctx, log.GetLogger(ctx).With("foo", "bar"))
```
2023-03-16 13:46:53 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
f.f = file
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *realLogFile) Close() error {
|
|
|
|
if f.f == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return f.f.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
type LogFileFlag struct {
|
|
|
|
name string
|
|
|
|
logFile
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewLogFileFlag() LogFileFlag {
|
|
|
|
return LogFileFlag{
|
|
|
|
name: "stderr",
|
|
|
|
logFile: &nopLogFile{os.Stderr},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *LogFileFlag) String() string {
|
|
|
|
return f.name
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *LogFileFlag) Set(s string) error {
|
|
|
|
lower := strings.ToLower(s)
|
|
|
|
switch lower {
|
|
|
|
case "stderr":
|
|
|
|
f.name = lower
|
|
|
|
f.logFile = &nopLogFile{os.Stderr}
|
|
|
|
case "stdout":
|
|
|
|
f.name = lower
|
|
|
|
f.logFile = &nopLogFile{os.Stdout}
|
|
|
|
default:
|
|
|
|
f.name = s
|
|
|
|
f.logFile = &realLogFile{s: s}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (f *LogFileFlag) Type() string {
|
|
|
|
return "file"
|
|
|
|
}
|
|
|
|
|
|
|
|
// Complete is the Cobra compatible completion function for this flag.
|
|
|
|
func (f *LogFileFlag) Complete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
|
|
return []string{
|
|
|
|
"stdout",
|
|
|
|
"stderr",
|
|
|
|
}, cobra.ShellCompDirectiveDefault
|
|
|
|
}
|