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

Added additional severity levels and ability to customize levels. #32

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
46 changes: 35 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,19 +349,38 @@ Level holds a severity level.

``` go
const (
UNSPECIFIED Level = iota
TRACE
DEBUG
INFO
WARNING
ERROR
CRITICAL
UNSPECIFIED Level = 0000
EMERGENCY Level = 0001
ALERT Level = 1000
CRITICAL Level = 2000
ERROR Level = 3000
WARNING Level = 4000
NOTICE Level = 5000
INFO Level = 6000
DEBUG Level = 7000
TRACE Level = 8000
)
```
The severity levels. Higher values are more considered more
important.


The severity levels. **Lower values** are more considered more
important, as with syslog. The levels have been synchronized with
[syslog severity levels](https://en.wikipedia.org/wiki/Syslog#Severity_level) with
addition of the `TRACE` level.

Custom levels may be defined by overriding the `loggo.Levels` map:
``` go
var Levels = map[Level]LevelType{
UNSPECIFIED: {"", UNSPECIFIED, "UNSPECIFIED"},
TRACE: {"TRACE", TRACE, "TRACE"},
DEBUG: {"DEBUG", DEBUG, "DEBUG"},
INFO: {"INFO", INFO, "INFO"},
NOTICE: {"NOTE", NOTICE, "NOTICE"},
WARNING: {"WARN", WARNING, "WARNING"},
ERROR: {"ERROR", ERROR, "ERROR"},
CRITICAL: {"CRIT", CRITICAL, "CRITICAL"},
ALERT: {"ALERT", ALERT, "ALERT"},
EMERGENCY: {"EMERG", EMERGENCY, "EMERGENCY"},
}
```



Expand Down Expand Up @@ -567,6 +586,11 @@ the effective log level of the logger.
Note that the writers may also filter out messages that
are less than their registered minimum severity level.

**Please note:** As levels *NOTICE*, *ALERT* and *EMERGENCY* are seldom used,
there are no corresponding `Noticef`, `Alertf` and `Emergencyf` methods. Use
the method `Logf(level, ...)` instead.

This method is also appropriate if you define your custom log levels.


### func (Logger) Name
Expand Down
2 changes: 1 addition & 1 deletion config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func (*ConfigSuite) TestParseConfigValue(c *gc.C) {
}
}

func (*ConfigSuite) TestPaarseConfigurationString(c *gc.C) {
func (*ConfigSuite) TestParseConfigurationString(c *gc.C) {
for i, test := range []struct {
configuration string
expected Config
Expand Down
5 changes: 4 additions & 1 deletion context.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,12 @@ type Context struct {
// NewLoggers returns a new Context with no writers set.
// If the root level is UNSPECIFIED, WARNING is used.
func NewContext(rootLevel Level) *Context {
if rootLevel < TRACE || rootLevel > CRITICAL {
if rootLevel == UNSPECIFIED {
rootLevel = WARNING
} else if _, ok := Levels[rootLevel]; !ok {
rootLevel = WARNING
}

context := &Context{
modules: make(map[string]*module),
writers: make(map[string]Writer),
Expand Down
3 changes: 2 additions & 1 deletion context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,12 @@ func (*ContextSuite) TestNewContextRootLevel(c *gc.C) {
level: loggo.Level(42),
expected: loggo.WARNING,
}} {
c.Log("%d: %s", i, test.level)
c.Logf("Iteration %d: level=%v, expected=%v", i, test.level, test.expected)
context := loggo.NewContext(test.level)
cfg := context.Config()
c.Check(cfg, gc.HasLen, 1)
value, found := cfg[""]
c.Logf(" value=%v found=%v", value, found)
c.Check(found, gc.Equals, true)
c.Check(value, gc.Equals, test.expected)
}
Expand Down
105 changes: 49 additions & 56 deletions level.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,90 +4,83 @@
package loggo

import (
"fmt"
"strings"
"sync/atomic"
)

// The severity levels. Higher values are more considered more
// important.
const (
UNSPECIFIED Level = iota
TRACE
DEBUG
INFO
WARNING
ERROR
CRITICAL
UNSPECIFIED Level = 0000
EMERGENCY Level = 0001 // Syslog level 0
ALERT Level = 1000 // Syslog level 1
CRITICAL Level = 2000 // Syslog level 2
ERROR Level = 3000 // Syslog level 3
WARNING Level = 4000 // Syslog level 4
NOTICE Level = 5000 // Syslog level 5
INFO Level = 6000 // Syslog level 6
DEBUG Level = 7000 // Syslog level 7
TRACE Level = 8000
)

var Levels = map[Level]LevelType{
UNSPECIFIED: {"", UNSPECIFIED, "UNSPECIFIED"},
TRACE: {"TRACE", TRACE, "TRACE"},
DEBUG: {"DEBUG", DEBUG, "DEBUG"}, // Syslog 7
INFO: {"INFO", INFO, "INFO"}, // Syslog 6
NOTICE: {"NOTE", NOTICE, "NOTICE"}, // Syslog 5
WARNING: {"WARN", WARNING, "WARNING"}, // Syslog 4
ERROR: {"ERROR", ERROR, "ERROR"}, // Syslog 3
CRITICAL: {"CRIT", CRITICAL, "CRITICAL"}, // Syslog 2
ALERT: {"ALERT", ALERT, "ALERT"}, // Syslog 1
EMERGENCY: {"EMERG", EMERGENCY, "EMERGENCY"}, // Syslog 0
}

// Level holds a severity level.
type Level uint32

type LevelType struct {
ShortName string
Value Level
LongName string
}

// ParseLevel converts a string representation of a logging level to a
// Level. It returns the level and whether it was valid or not.
func ParseLevel(level string) (Level, bool) {
level = strings.ToUpper(level)
switch level {
case "UNSPECIFIED":
return UNSPECIFIED, true
case "TRACE":
return TRACE, true
case "DEBUG":
return DEBUG, true
case "INFO":
return INFO, true
case "WARN", "WARNING":
return WARNING, true
case "ERROR":
return ERROR, true
case "CRITICAL":
return CRITICAL, true
default:
if len(level) == 0 {
return UNSPECIFIED, false
}

level = strings.ToUpper(level)
for _, l := range Levels {
if level == l.LongName || level == l.ShortName {
return l.Value, true
}
}
return UNSPECIFIED, false
}

// String implements Stringer.
func (level Level) String() string {
switch level {
case UNSPECIFIED:
return "UNSPECIFIED"
case TRACE:
return "TRACE"
case DEBUG:
return "DEBUG"
case INFO:
return "INFO"
case WARNING:
return "WARNING"
case ERROR:
return "ERROR"
case CRITICAL:
return "CRITICAL"
default:
return "<unknown>"
if l, ok := Levels[level]; ok {
return l.LongName
} else {
return "UNKNOWN"
}
}

// Short returns a five character string to use in
// aligned logging output.
func (level Level) Short() string {
switch level {
case TRACE:
return "TRACE"
case DEBUG:
return "DEBUG"
case INFO:
return "INFO "
case WARNING:
return "WARN "
case ERROR:
return "ERROR"
case CRITICAL:
return "CRITC"
default:
return " "
if l, ok := Levels[level]; ok {
return fmt.Sprintf("%5s", l.ShortName)
} else {
return "UNKNOWN"
}
return "UNKN "

}

// get atomically gets the value of the given level.
Expand Down
39 changes: 32 additions & 7 deletions level_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ var parseLevelTests = []struct {
}, {
str: "INFO",
level: loggo.INFO,
}, {
str: "notE",
level: loggo.NOTICE,
}, {
str: "NoTICE",
level: loggo.NOTICE,
}, {
str: "warn",
level: loggo.WARNING,
Expand All @@ -60,19 +66,35 @@ var parseLevelTests = []struct {
str: "critical",
level: loggo.CRITICAL,
}, {
str: "not_specified",
fail: true,
str: "Alert",
level: loggo.ALERT,
}, {
str: "EMERG",
level: loggo.EMERGENCY,
}, {
str: "EMERGENCY",
level: loggo.EMERGENCY,
}, {
str: "Emerg",
level: loggo.EMERGENCY,
}, {
str: "not_specified",
level: loggo.UNSPECIFIED,
fail: true,
}, {
str: "other",
fail: true,
str: "other",
level: loggo.UNSPECIFIED,
fail: true,
}, {
str: "",
fail: true,
str: "",
level: loggo.UNSPECIFIED,
fail: true,
}}

func (s *LevelSuite) TestParseLevel(c *gc.C) {
for _, test := range parseLevelTests {
level, ok := loggo.ParseLevel(test.str)
c.Logf("str=%s, level=%v, ok=%v", test.str, level, ok)
c.Assert(level, gc.Equals, test.level)
c.Assert(ok, gc.Equals, !test.fail)
}
Expand All @@ -83,10 +105,13 @@ var levelStringValueTests = map[loggo.Level]string{
loggo.DEBUG: "DEBUG",
loggo.TRACE: "TRACE",
loggo.INFO: "INFO",
loggo.NOTICE: "NOTICE",
loggo.WARNING: "WARNING",
loggo.ERROR: "ERROR",
loggo.CRITICAL: "CRITICAL",
loggo.Level(42): "<unknown>", // other values are unknown
loggo.ALERT: "ALERT",
loggo.EMERGENCY: "EMERGENCY",
loggo.Level(42): "UNKNOWN", // other values are unknown
}

func (s *LevelSuite) TestLevelStringValue(c *gc.C) {
Expand Down
47 changes: 40 additions & 7 deletions loggocolor/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,45 @@ import (
"io"
"path/filepath"

"github.com/juju/loggo"
"github.com/juju/ansiterm"
"github.com/juju/loggo"
"strconv"
)

var (
// SeverityColor defines the colors for the levels output by the ColorWriter.
SeverityColor = map[loggo.Level]*ansiterm.Context{
loggo.TRACE: ansiterm.Foreground(ansiterm.Default),
loggo.TRACE: ansiterm.Foreground(ansiterm.Gray),
loggo.DEBUG: ansiterm.Foreground(ansiterm.Green),
loggo.INFO: ansiterm.Foreground(ansiterm.BrightBlue),
loggo.NOTICE: ansiterm.Foreground(ansiterm.BrightGreen),
loggo.WARNING: ansiterm.Foreground(ansiterm.Yellow),
loggo.ERROR: ansiterm.Foreground(ansiterm.BrightRed),
loggo.CRITICAL: &ansiterm.Context{
loggo.CRITICAL: {
Foreground: ansiterm.White,
Background: ansiterm.Red,
},
loggo.ALERT: {
Foreground: ansiterm.White,
Background: ansiterm.Red,
},
loggo.EMERGENCY: {
Foreground: ansiterm.White,
Background: ansiterm.Red,
},
}
// LocationColor defines the colors for the location output by the ColorWriter.
LocationColor = ansiterm.Foreground(ansiterm.BrightBlue)
// TimeStampColor defines the colors of timestamps
TimeStampColor = ansiterm.Foreground(ansiterm.Yellow)
// ModuleColor defines the color of module name
ModuleColor = ansiterm.Foreground(ansiterm.Blue)

// How long (padded) should be the module name
ModuleLength = 35

// How long (padded) should be the location name
LocationLength = 25
)

type colorWriter struct {
Expand All @@ -42,9 +62,22 @@ func (w *colorWriter) Write(entry loggo.Entry) {
// Just get the basename from the filename
filename := filepath.Base(entry.Filename)

fmt.Fprintf(w.writer, "%s ", ts)
SeverityColor[entry.Level].Fprintf(w.writer, entry.Level.Short())
fmt.Fprintf(w.writer, " %s ", entry.Module)
LocationColor.Fprintf(w.writer, "%s:%d ", filename, entry.Line)
TimeStampColor.Fprintf(w.writer, "%s", ts)
fmt.Fprintf(w.writer, " ")

SeverityColor[entry.Level].Fprintf(w.writer, "%5s", entry.Level.Short())
fmt.Fprintf(w.writer, " ")

module := entry.Module
if len(module) > ModuleLength {
module = "..." + module[len(module)-(ModuleLength-3):]
}

ModuleColor.Fprintf(w.writer, "%-"+strconv.Itoa(ModuleLength)+"s", module)
fmt.Fprintf(w.writer, " ")

line := fmt.Sprintf("%s:%d", filename, entry.Line)
LocationColor.Fprintf(w.writer, "%-"+strconv.Itoa(LocationLength)+"s ", line)

fmt.Fprintln(w.writer, entry.Message)
}
Loading