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

Support non-global loggers. #13

Merged
merged 11 commits into from
May 11, 2016
191 changes: 128 additions & 63 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,132 @@ import (
"strings"
)

// loggerInfo returns information about the given loggers and their
// logging levels. The information is returned in the format expected
// by ConfigureLoggers. Loggers with UNSPECIFIED level will not
// be included.
func loggerInfo(modules map[string]*module) string {
output := []string{}
// LoggerConfig holds the configuration for a single logger.
type LoggerConfig struct {
// Level is the log level that should be used by the logger.
Level Level
}

// ParseLoggerConfig parses a logger configuration string into the
// configuration for a single logger. Whitespace around the spec is
// ignored.
func ParseLoggerConfig(spec string) (LoggerConfig, error) {
var cfg LoggerConfig

spec = strings.TrimSpace(spec)
if spec == "" {
return cfg, fmt.Errorf("logger config is blank")
}

levelStr := spec // For now level is the only thing in the spec.
level, ok := ParseLevel(levelStr)
if !ok {
return cfg, fmt.Errorf("unknown log level %q", levelStr)
}
cfg.Level = level

return cfg, nil
}

// String returns a logger configuration string that may be parsed
// using ParseLoggerConfig or ParseLoggersConfig().
func (cfg LoggerConfig) String() string {
return fmt.Sprintf("%s", cfg.Level)
}

// LoggersConfig is a mapping of logger module names to logger configs.
type LoggersConfig map[string]LoggerConfig

// String returns a logger configuration string that may be parsed
// using ParseLoggersConfig.
func (configs LoggersConfig) String() string {
// output in alphabetical order.
keys := []string{}
for key := range modules {
keys = append(keys, key)
names := []string{}
for name := range configs {
if name == rootName {
// This could potentially result in a duplicate entry...
name = rootString
}
names = append(names, name)
}
sort.Strings(names)

var entries []string
for _, name := range names {
cfg := configs[name]
entry := fmt.Sprintf("%s=%s", name, cfg)
entries = append(entries, entry)
}
return strings.Join(entries, ";")
}

// ParseLoggersConfig parses a logger configuration string into a set
// of named logger configs. This method is provided to allow other
// programs to pre-validate a configuration string rather than just
// calling ConfigureLoggers.
//
// Loggers are colon- or semicolon-separated; each module is formatted
// as:
//
// <modulename>=<config>, where <config> consists of <level>
//
// White space outside of module names and config is ignored. The root
// module is specified with the name "<root>".
//
// As a special case, a config may be specified on its own, without
// a module name. This is equivalent to specifying the configuration
// of the root module, so "DEBUG" is equivalent to `<root>=DEBUG`
//
// An example specification:
// `<root>=ERROR; foo.bar=WARNING`
func ParseLoggersConfig(spec string) (LoggersConfig, error) {
spec = strings.TrimSpace(spec)
if spec == "" {
return nil, nil
}

entries := strings.FieldsFunc(spec, func(r rune) bool { return r == ';' || r == ':' })
if len(entries) == 1 && !strings.Contains(spec, "=") {
cfg, err := ParseLoggerConfig(spec)
if err != nil {
return nil, err
}
return LoggersConfig{rootName: cfg}, nil
}
sort.Strings(keys)
for _, name := range keys {
mod := modules[name]
severity := mod.level.get()
if severity == UNSPECIFIED {
continue

configs := make(LoggersConfig)
for _, entry := range entries {
name, cfg, err := parseConfigEntry(entry)
if err != nil {
return nil, err
}
output = append(output, fmt.Sprintf("%s=%s", mod.Name(), severity))
// last entry wins, for a given name
configs[name] = cfg
}
return strings.Join(output, ";")
return configs, nil
}

func parseConfigEntry(entry string) (string, LoggerConfig, error) {
var cfg LoggerConfig
pair := strings.SplitN(entry, "=", 2)
if len(pair) < 2 {
return "", cfg, fmt.Errorf("logger entry expected '=', found %q", entry)
}
name, spec := rootName, entry
if len(pair) == 2 {
name, spec = strings.TrimSpace(pair[0]), strings.TrimSpace(pair[1])
if name == "" {
return "", cfg, fmt.Errorf("logger entry %q has blank name", entry)
}
if name == rootString {
name = rootName
}
}
if spec == "" {
return "", cfg, fmt.Errorf("logger entry %q has blank config", entry)
}
cfg, err := ParseLoggerConfig(spec)
return name, cfg, err
}

// ParseConfigurationString parses a logger configuration string into a map of
Expand All @@ -48,53 +153,13 @@ func loggerInfo(modules map[string]*module) string {
// An example specification:
// `<root>=ERROR; foo.bar=WARNING`
func ParseConfigurationString(specification string) (map[string]Level, error) {
levels := make(map[string]Level)
if level, ok := ParseLevel(specification); ok {
levels[""] = level
return levels, nil
}
values := strings.FieldsFunc(specification, func(r rune) bool { return r == ';' || r == ':' })
for _, value := range values {
s := strings.SplitN(value, "=", 2)
if len(s) < 2 {
return nil, fmt.Errorf("logger specification expected '=', found %q", value)
}
name := strings.TrimSpace(s[0])
levelStr := strings.TrimSpace(s[1])
if name == "" || levelStr == "" {
return nil, fmt.Errorf("logger specification %q has blank name or level", value)
}
if name == "<root>" {
name = ""
}
level, ok := ParseLevel(levelStr)
if !ok {
return nil, fmt.Errorf("unknown severity level %q", levelStr)
}
levels[name] = level
}
return levels, nil
}

// ConfigureLoggers configures loggers according to the given string
// specification, which specifies a set of modules and their associated
// logging levels. Loggers are colon- or semicolon-separated; each
// module is specified as <modulename>=<level>. White space outside of
// module names and levels is ignored. The root module is specified
// with the name "<root>".
//
// An example specification:
// `<root>=ERROR; foo.bar=WARNING`
func ConfigureLoggers(specification string) error {
if specification == "" {
return nil
}
levels, err := ParseConfigurationString(specification)
configs, err := ParseLoggersConfig(specification)
if err != nil {
return err
return nil, err
}
for name, level := range levels {
GetLogger(name).SetLogLevel(level)
levels := make(map[string]Level)
for name, cfg := range configs {
levels[name] = cfg.Level
}
return nil
return levels, nil
}
92 changes: 0 additions & 92 deletions config_test.go

This file was deleted.

17 changes: 0 additions & 17 deletions export_test.go

This file was deleted.

Loading