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 root
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2023-08-15 13:50:40 +00:00
|
|
|
"log/slog"
|
2023-11-08 08:51:01 +00:00
|
|
|
"os"
|
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
|
|
|
|
2023-06-01 09:37:33 +00:00
|
|
|
"github.com/databricks/cli/libs/cmdio"
|
2023-09-11 08:18:43 +00:00
|
|
|
"github.com/databricks/cli/libs/env"
|
2023-05-16 16:35:39 +00:00
|
|
|
"github.com/databricks/cli/libs/flags"
|
|
|
|
"github.com/databricks/cli/libs/log"
|
2023-12-01 12:17:04 +00:00
|
|
|
"github.com/databricks/cli/libs/log/handler"
|
2023-07-26 11:17:09 +00:00
|
|
|
"github.com/spf13/cobra"
|
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
|
|
|
)
|
|
|
|
|
2023-03-21 16:05:04 +00:00
|
|
|
const (
|
2023-05-22 14:40:50 +00:00
|
|
|
envLogFile = "DATABRICKS_LOG_FILE"
|
|
|
|
envLogLevel = "DATABRICKS_LOG_LEVEL"
|
|
|
|
envLogFormat = "DATABRICKS_LOG_FORMAT"
|
2023-03-21 16:05:04 +00:00
|
|
|
)
|
|
|
|
|
2023-07-26 11:17:09 +00:00
|
|
|
type logFlags struct {
|
|
|
|
file flags.LogFileFlag
|
|
|
|
level flags.LogLevelFlag
|
|
|
|
output flags.Output
|
2023-11-09 10:40:47 +00:00
|
|
|
debug bool
|
2023-07-26 11:17:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (f *logFlags) makeLogHandler(opts slog.HandlerOptions) (slog.Handler, error) {
|
|
|
|
switch f.output {
|
2023-06-01 09:37:33 +00:00
|
|
|
case flags.OutputJSON:
|
2023-08-15 13:50:40 +00:00
|
|
|
return slog.NewJSONHandler(f.file.Writer(), &opts), nil
|
2023-06-01 09:37:33 +00:00
|
|
|
case flags.OutputText:
|
2023-07-26 11:17:09 +00:00
|
|
|
w := f.file.Writer()
|
2024-01-03 09:39:33 +00:00
|
|
|
return handler.NewFriendlyHandler(w, &handler.Options{
|
|
|
|
Color: cmdio.IsTTY(w),
|
|
|
|
Level: opts.Level,
|
|
|
|
ReplaceAttr: opts.ReplaceAttr,
|
|
|
|
}), nil
|
2023-06-01 09:37:33 +00:00
|
|
|
default:
|
2023-07-26 11:17:09 +00:00
|
|
|
return nil, fmt.Errorf("invalid log output mode: %s", f.output)
|
2023-06-01 09:37:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-07-26 11:17:09 +00:00
|
|
|
func (f *logFlags) initializeContext(ctx context.Context) (context.Context, error) {
|
2023-11-09 10:40:47 +00:00
|
|
|
if f.debug {
|
|
|
|
f.level.Set("debug")
|
|
|
|
}
|
|
|
|
|
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
|
|
|
opts := slog.HandlerOptions{}
|
2023-07-26 11:17:09 +00:00
|
|
|
opts.Level = f.level.Level()
|
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
|
|
|
opts.AddSource = true
|
2023-03-23 07:56:39 +00:00
|
|
|
opts.ReplaceAttr = log.ReplaceAttrFunctions{
|
|
|
|
log.ReplaceLevelAttr,
|
|
|
|
log.ReplaceSourceAttr,
|
|
|
|
}.ReplaceAttr
|
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
|
|
|
|
|
|
|
// Open the underlying log file if the user configured an actual file to log to.
|
2023-07-26 11:17:09 +00:00
|
|
|
err := f.file.Open()
|
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 nil, err
|
|
|
|
}
|
|
|
|
|
2023-07-26 11:17:09 +00:00
|
|
|
handler, err := f.makeLogHandler(opts)
|
2023-06-01 09:37:33 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
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
|
|
|
}
|
|
|
|
|
2023-11-08 08:51:01 +00:00
|
|
|
slog.SetDefault(slog.New(handler).With(slog.Int("pid", os.Getpid())))
|
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
|
|
|
return log.NewContext(ctx, slog.Default()), nil
|
|
|
|
}
|
|
|
|
|
2023-07-26 11:17:09 +00:00
|
|
|
func initLogFlags(cmd *cobra.Command) *logFlags {
|
|
|
|
f := logFlags{
|
|
|
|
file: flags.NewLogFileFlag(),
|
|
|
|
level: flags.NewLogLevelFlag(),
|
|
|
|
output: flags.OutputText,
|
|
|
|
}
|
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
|
|
|
|
2023-03-21 16:05:04 +00:00
|
|
|
// Configure defaults from environment, if applicable.
|
|
|
|
// If the provided value is invalid it is ignored.
|
2023-09-11 08:18:43 +00:00
|
|
|
if v, ok := env.Lookup(cmd.Context(), envLogFile); ok {
|
2023-07-26 11:17:09 +00:00
|
|
|
f.file.Set(v)
|
2023-03-21 16:05:04 +00:00
|
|
|
}
|
2023-09-11 08:18:43 +00:00
|
|
|
if v, ok := env.Lookup(cmd.Context(), envLogLevel); ok {
|
2023-07-26 11:17:09 +00:00
|
|
|
f.level.Set(v)
|
2023-03-21 16:05:04 +00:00
|
|
|
}
|
2023-09-11 08:18:43 +00:00
|
|
|
if v, ok := env.Lookup(cmd.Context(), envLogFormat); ok {
|
2023-07-26 11:17:09 +00:00
|
|
|
f.output.Set(v)
|
2023-03-21 16:05:04 +00:00
|
|
|
}
|
|
|
|
|
2023-11-09 10:40:47 +00:00
|
|
|
flags := cmd.PersistentFlags()
|
|
|
|
flags.BoolVar(&f.debug, "debug", false, "enable debug logging")
|
|
|
|
flags.Var(&f.file, "log-file", "file to write logs to")
|
|
|
|
flags.Var(&f.level, "log-level", "log level")
|
|
|
|
flags.Var(&f.output, "log-format", "log output format (text or json)")
|
|
|
|
|
|
|
|
// mark fine-grained flags hidden from global --help
|
|
|
|
flags.MarkHidden("log-file")
|
|
|
|
flags.MarkHidden("log-level")
|
|
|
|
flags.MarkHidden("log-format")
|
|
|
|
|
2023-07-26 11:17:09 +00:00
|
|
|
cmd.RegisterFlagCompletionFunc("log-file", f.file.Complete)
|
|
|
|
cmd.RegisterFlagCompletionFunc("log-level", f.level.Complete)
|
|
|
|
cmd.RegisterFlagCompletionFunc("log-format", f.output.Complete)
|
|
|
|
return &f
|
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
|
|
|
}
|