Skip to content

Commit

Permalink
config: add more option-ful logging configuration
Browse files Browse the repository at this point in the history
This moves the logging configuration from the single "level" key to
top-level struct like all the new additions.

This also does some internal shuffling of types and constants, taking
inspiration from the `log/slog` package.

Signed-off-by: Hank Donnay <[email protected]>
  • Loading branch information
hdonnay committed Oct 30, 2023
1 parent 4405fda commit 98ef5e5
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 97 deletions.
33 changes: 23 additions & 10 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type Config struct {
// environment variable is the recommended way to do that. The release
// container has `/var/run/certs` added to the list already.
TLS *TLS `yaml:"tls,omitempty" json:"tls,omitempty"`
// Sets which mode the clair instance will run.
// Sets which mode the Clair instance will run.
Mode Mode `yaml:"-" json:"-"`
// A string in <host>:<port> format where <host> can be an empty string.
//
Expand All @@ -31,15 +31,18 @@ type Config struct {
// exposes Clair's metrics and health endpoints.
IntrospectionAddr string `yaml:"introspection_addr" json:"introspection_addr"`
// Set the logging level.
LogLevel LogLevel `yaml:"log_level" json:"log_level"`
Indexer Indexer `yaml:"indexer,omitempty" json:"indexer,omitempty"`
Matcher Matcher `yaml:"matcher,omitempty" json:"matcher,omitempty"`
Matchers Matchers `yaml:"matchers,omitempty" json:"matchers,omitempty"`
Updaters Updaters `yaml:"updaters,omitempty" json:"updaters,omitempty"`
Notifier Notifier `yaml:"notifier,omitempty" json:"notifier,omitempty"`
Auth Auth `yaml:"auth,omitempty" json:"auth,omitempty"`
Trace Trace `yaml:"trace,omitempty" json:"trace,omitempty"`
Metrics Metrics `yaml:"metrics,omitempty" json:"metrics,omitempty"`
//
// Deprecated: Use the "Logging" member.
LogLevel *LogLevel `yaml:"log_level,omitempty" json:"log_level,omitempty"`
Indexer Indexer `yaml:"indexer,omitempty" json:"indexer,omitempty"`
Matcher Matcher `yaml:"matcher,omitempty" json:"matcher,omitempty"`
Matchers Matchers `yaml:"matchers,omitempty" json:"matchers,omitempty"`
Updaters Updaters `yaml:"updaters,omitempty" json:"updaters,omitempty"`
Notifier Notifier `yaml:"notifier,omitempty" json:"notifier,omitempty"`
Auth Auth `yaml:"auth,omitempty" json:"auth,omitempty"`
Trace Trace `yaml:"trace,omitempty" json:"trace,omitempty"`
Metrics Metrics `yaml:"metrics,omitempty" json:"metrics,omitempty"`
Logging Logging `yaml:"logging,omitempty" json:"logging,omitempty"`
}

func (c *Config) validate(mode Mode) ([]Warning, error) {
Expand All @@ -58,6 +61,10 @@ func (c *Config) validate(mode Mode) ([]Warning, error) {
if _, _, err := net.SplitHostPort(c.HTTPListenAddr); err != nil {
return nil, err
}
if c.LogLevel != nil {
c.Logging.Level = *c.LogLevel
}

return c.lint()
}

Expand All @@ -74,6 +81,12 @@ func (c *Config) lint() (ws []Warning, err error) {
msg: `introspection address not provided, default will be used`,
})
}
if c.LogLevel != nil {
ws = append(ws, Warning{
path: ".log_level",
msg: `"log_level" is deprecated, use "logging"`,
})
}
return ws, nil
}

Expand Down
61 changes: 0 additions & 61 deletions config/enums.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package config

import (
"encoding"
"errors"
"fmt"
"strings"
)
Expand Down Expand Up @@ -35,62 +33,3 @@ func ParseMode(s string) (Mode, error) {
}
return Mode(-1), fmt.Errorf(`unknown mode %q`, s)
}

// A LogLevel is a log level recognized by Clair.
//
// The zero value is "info".
type LogLevel int

// The recognized log levels, with their string representations as the comments.
//
// NB "Fatal" and "Panic" are not used in clair or claircore, and will result in
// almost no logging.
const (
InfoLog LogLevel = iota // info
DebugColorLog // debug-color
DebugLog // debug
WarnLog // warn
ErrorLog // error
FatalLog // fatal
PanicLog // panic
)

// ParseLogLevel returns the log lever for the given string.
//
// The passed string is case-insensitive.
func ParseLogLevel(s string) (LogLevel, error) {
for i, lim := 0, len(_LogLevel_index); i < lim; i++ {
l := LogLevel(i)
if strings.EqualFold(s, l.String()) {
return l, nil
}
}
return LogLevel(-1), fmt.Errorf(`unknown log level %q`, s)
}

// UnmarshalText implements encoding.TextUnmarshaler.
func (l *LogLevel) UnmarshalText(b []byte) (err error) {
*l, err = ParseLogLevel(string(b))
if err != nil {
return err
}
return nil
}

// MarshalText implements encoding.TextMarshaler.
func (l *LogLevel) MarshalText() ([]byte, error) {
if l == nil {
return nil, errors.New("invalid LogLevel pointer: <nil>")
}
i := *l
if i < 0 || i >= LogLevel(len(_LogLevel_index)-1) {
return nil, fmt.Errorf("invalid LogLevel: %q", l.String())
}
return []byte(_LogLevel_name[_LogLevel_index[i]:_LogLevel_index[i+1]]), nil
}

// Assert LogLevel implements TextUnmarshaler and TextMarshaler.
var (
_ encoding.TextUnmarshaler = (*LogLevel)(nil)
_ encoding.TextMarshaler = (*LogLevel)(nil)
)
20 changes: 11 additions & 9 deletions config/enums_string.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

37 changes: 20 additions & 17 deletions config/enums_test.go
Original file line number Diff line number Diff line change
@@ -1,44 +1,47 @@
package config_test

import (
"bytes"
"testing"

"github.com/quay/clair/config"
)

func TestEnumMarshal(t *testing.T) {
t.Run("LogLevel", func(t *testing.T) {
tt := [][]byte{
[]byte("info"),
[]byte("debug-color"),
[]byte("debug"),
[]byte("warn"),
[]byte("error"),
[]byte("fatal"),
[]byte("panic"),
type testcase struct {
Level config.LogLevel
String string
}
tt := []testcase{
{Level: config.TraceLog, String: "trace"},
{Level: config.DebugColorLog, String: "debug-color"},
{Level: config.DebugLog, String: "debug"},
{Level: config.InfoLog, String: "info"},
{Level: config.WarnLog, String: "warn"},
{Level: config.ErrorLog, String: "error"},
{Level: config.FatalLog, String: "fatal"},
{Level: config.PanicLog, String: "panic"},
}
t.Run("Marshal", func(t *testing.T) {
for i, want := range tt {
l := config.LogLevel(i)
got, err := l.MarshalText()
for _, tc := range tt {
m, err := tc.Level.MarshalText()
if err != nil {
t.Error(err)
continue
}
if !bytes.Equal(got, want) {
if got, want := string(m), tc.String; got != want {
t.Errorf("got: %q, want: %q", got, want)
}
}
})
t.Run("Unmarshal", func(t *testing.T) {
for want, in := range tt {
var l config.LogLevel
if err := l.UnmarshalText(in); err != nil {
for _, tc := range tt {
var got config.LogLevel
if err := got.UnmarshalText([]byte(tc.String)); err != nil {
t.Error(err)
continue
}
if got := int(l); got != want {
if want := tc.Level; got != want {
t.Errorf("got: %q, want: %q", got, want)
}
}
Expand Down
99 changes: 99 additions & 0 deletions config/logging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package config

import (
"encoding"
"errors"
"fmt"
"strings"
)

// Logging is all the log configuration.
type Logging struct {
Level LogLevel `yaml:"level,omitempty" json:"level,omitempty"`
OmitTimestamps bool `yaml:"omit_timestamps,omitempty" json:"omit_timestamps,omitempty"`
}

// A LogLevel is a log level recognized by Clair.
//
// The zero value is [InfoLog].
type LogLevel int

// The recognized log levels, with their string representations as the comments.
//
// NB [FatalLog] and [PanicLog] are not used in Clair or Claircore, and will
// result in almost no logging.
const (
TraceLog LogLevel = iota - 3 // trace
DebugColorLog // debug-color
DebugLog // debug
InfoLog // info
WarnLog // warn
ErrorLog // error
FatalLog // fatal
PanicLog // panic
)

// Assert that the zero value is correct:
var _ = [1]struct{}{{}}[InfoLog]

// ParseLogLevel returns the log level for the given string.
//
// The passed string is case-insensitive.
func ParseLogLevel(s string) (LogLevel, error) {
const offset = int(TraceLog)
for i, lim := 0, len(_LogLevel_index); i < lim; i++ {
l := LogLevel(i + offset)
if strings.EqualFold(s, l.String()) {
return l, nil
}
}
return LogLevel(-127), fmt.Errorf(`unknown log level %q`, s)
}

// UnmarshalText implements [encoding.TextUnmarshaler].
func (l *LogLevel) UnmarshalText(b []byte) (err error) {
*l, err = ParseLogLevel(string(b))
if err != nil {
return err
}
return nil
}

// MarshalText implements [encoding.TextMarshaler].
func (l *LogLevel) MarshalText() ([]byte, error) {
const offset = int(TraceLog)
if l == nil {
return nil, errors.New("invalid LogLevel pointer: <nil>")
}
i := int(*l) - offset
if i < 0 || i >= len(_LogLevel_index)-1 {
return nil, fmt.Errorf("invalid LogLevel: %q", l.String())
}
return []byte(_LogLevel_name[_LogLevel_index[i]:_LogLevel_index[i+1]]), nil
}

// Assert LogLevel implements everything that's needed.
var (
_ encoding.TextUnmarshaler = (*LogLevel)(nil)
_ encoding.TextMarshaler = (*LogLevel)(nil)
)

func (l *Logging) validate(mode Mode) ([]Warning, error) {
return l.lint()
}

func (l *Logging) lint() (ws []Warning, _ error) {
if l.Level > ErrorLog {
ws = append(ws, Warning{
path: ".level",
msg: `"fatal" and "panic" levels are not used and will result in almost no logging`,
})
}
if l.Level == DebugColorLog {
ws = append(ws, Warning{
path: ".level",
msg: `"debug-color" is deprecated; it will become an alias for "debug" in a future release`,
})
}
return ws, nil
}

0 comments on commit 98ef5e5

Please sign in to comment.