Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

config: add more option-ful logging configuration #1903

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
114 changes: 114 additions & 0 deletions config/logging.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
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"`
Prose bool `yaml:"prose,omitempty" json:"prose,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.
//
// Note [FatalLog] and [PanicLog] are not used in Clair or Claircore, and will
// result in almost no logging.
const (
TraceLog LogLevel = iota - 3 // trace
// Deprecated: Use the "Prose" toggle to request a non-JSON output format.
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
// We never re-serialize the "debug-color" level, because it's an annoying
// wart.
if *l == DebugColorLog {
i++
}
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) {
ws, err := l.lint()
if err != nil {
return ws, err
}
if l.Level == DebugColorLog {
l.Level++
l.Prose = true
}
return ws, nil
}

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; use "debug" and set the "prose" option`,
})
}
return ws, nil
}
Loading