commit
248ff19dbd
11 changed files with 527 additions and 0 deletions
@ -0,0 +1,21 @@ |
||||
The MIT License (MIT) |
||||
|
||||
Copyright (c) 2017 Noah Petherbridge |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,71 @@ |
||||
# Go Log |
||||
|
||||
This is Yet Another Logger for Go programs. |
||||
|
||||
 |
||||
|
||||
This is a logging package designed for local interactive shells running text |
||||
based Go programs. To that end, this prints colorful log lines with customizable |
||||
themes. |
||||
|
||||
The color options for the log lines are `NoColor` (default), `ANSIColor` |
||||
which limits the color codes to the standard 16 ANSI colors, and |
||||
`ExtendedColor` which supports the 256-color palette of `xterm` and other |
||||
modern terminal emulators. The theming engine supports defining colors using |
||||
hex codes, supported by [tomnomnom/xtermcolor](https://github.com/tomnomnom/xtermcolor). |
||||
|
||||
This module is still a work in progress and will be extended and improved as I |
||||
use it for other personal Go projects. |
||||
|
||||
# Usage |
||||
|
||||
```go |
||||
package main |
||||
|
||||
import "github.com/kirsle/golog" |
||||
|
||||
var log golog.Logger |
||||
|
||||
func init() { |
||||
// Get a named logger and configure it. Note: you can call GetLogger any |
||||
// number of times from any place in your codebase. It implements the |
||||
// singleton pattern. |
||||
log = golog.GetLogger("main") |
||||
log.Configure(&golog.Config{ |
||||
Colors: golog.ExtendedColor, |
||||
Theme: golog.DarkTheme, |
||||
}) |
||||
} |
||||
|
||||
func main() { |
||||
// The log functions work like `fmt.Printf` |
||||
log.Debug("Running on %s", runtime.GOOS) |
||||
log.Info("Hello, world!") |
||||
} |
||||
``` |
||||
|
||||
# License |
||||
|
||||
``` |
||||
The MIT License (MIT) |
||||
|
||||
Copyright (c) 2017 Noah Petherbridge |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
||||
``` |
@ -0,0 +1,23 @@ |
||||
package ansi |
||||
|
||||
// Names and escape codes for the standard ANSI colors.
|
||||
const ( |
||||
Black = `30` |
||||
BrightBlack = `30;1` |
||||
Red = `31` |
||||
BrightRed = `31;1` |
||||
Green = `32` |
||||
BrightGreen = `32;1` |
||||
Yellow = `33` |
||||
BrightYellow = `33;1` |
||||
Blue = `34` |
||||
BrightBlue = `34;1` |
||||
Magenta = `35` |
||||
BrightMagenta = `35;1` |
||||
Cyan = `36` |
||||
BrightCyan = `36;1` |
||||
White = `37` |
||||
BrightWhite = `37;1` |
||||
|
||||
Reset = `0` |
||||
) |
@ -0,0 +1,34 @@ |
||||
package golog |
||||
|
||||
import ( |
||||
"fmt" |
||||
|
||||
"github.com/tomnomnom/xtermcolor" |
||||
) |
||||
|
||||
type colorLevel int |
||||
|
||||
// Options for color support in your logger.
|
||||
const ( |
||||
// NoColor doesn't use any color codes at all (plain text).
|
||||
NoColor colorLevel = iota |
||||
|
||||
// ANSIColor uses the standard 16 colors supported by most terminals. This
|
||||
// option is the most portable across platforms.
|
||||
ANSIColor |
||||
|
||||
// ExtendedColor allows the use of 256 colors supported by most modern
|
||||
// terminals (24-bit color codes).
|
||||
ExtendedColor |
||||
) |
||||
|
||||
// HexColor is a convenient wrapper around `xtermcolor.FromHexStr` to define colors
|
||||
// for themes for xterm-256color codes.
|
||||
func HexColor(hex string) string { |
||||
code, err := xtermcolor.FromHexStr(hex) |
||||
if err != nil { |
||||
code = 201 // bright magenta seems like a good default
|
||||
} |
||||
|
||||
return fmt.Sprintf("38;5;%d", code) |
||||
} |
@ -0,0 +1,72 @@ |
||||
package golog |
||||
|
||||
import "io" |
||||
|
||||
// Config stores settings that control the logger's behavior.
|
||||
type Config struct { |
||||
// Level is one of DebugLevel, InfoLevel, WarnLevel, ErrorLevel or FatalLevel.
|
||||
// Messages emitted by the logger must be 'at least' this level to be logged.
|
||||
Level logLevel |
||||
|
||||
// What colors are supported? Default is NoColor. Use ANSIColor to support
|
||||
// legacy terminal emulators, or ExtendedColor for modern 256-color support.
|
||||
Colors colorLevel |
||||
|
||||
// Which color theme are you using? The default is DarkTheme.
|
||||
Theme Theme |
||||
|
||||
// Where to write the log messages to? If not defined with a custom io.Writer,
|
||||
// the default goes to standard output for Debug and Info messages and
|
||||
// standard error for warnings, errors, and fatal messages.
|
||||
Writer *io.Writer |
||||
|
||||
// How do you want to format your log lines? This should be a Go text format
|
||||
// string, with the following variable placeholders:
|
||||
//
|
||||
// {{.Time}} inserts the date/time stamp for the log message.
|
||||
// {{.Level}} inserts a label for the log level, e.g. "INFO" or "WARN"
|
||||
// {{.Message}} inserts the text of the log message itself.
|
||||
// {{.Primary}} inserts the color sequence for the primary color based
|
||||
// on the log level for the message.
|
||||
// {{.Secondary}} inserts the color sequence for the secondary color.
|
||||
// {{.Reset}} inserts the 'reset' color sequence to stop coloring
|
||||
// the rest of the text that follows.
|
||||
//
|
||||
// The default log format is as follows:
|
||||
//
|
||||
// {{.Secondary}}{{.Time}}{{.Reset}} {{.Primary}}[{{.Level}}]{{.Reset}} {{.Message}}
|
||||
Format string |
||||
|
||||
// How do you want to format your time stamps? (The `{{.Time}}`). This uses
|
||||
// the Go `time` module, so the TimeFormat should use their reference date/time.
|
||||
// The default TimeFormat is: `2006-01-02 15:04:05`
|
||||
TimeFormat string |
||||
} |
||||
|
||||
// DefaultConfig returns a Config with the default values filled in.
|
||||
func DefaultConfig() *Config { |
||||
return &Config{ |
||||
Theme: DarkTheme, |
||||
Format: DefaultFormat, |
||||
TimeFormat: DefaultTime, |
||||
} |
||||
} |
||||
|
||||
// Configure applies the configuration to the logger. If any of the following
|
||||
// keys are not defined (or have zero-values), the default value for the key will
|
||||
// be used instead:
|
||||
//
|
||||
// Format
|
||||
// TimeFormat
|
||||
func (l *Logger) Configure(cfg *Config) { |
||||
// Important keys and their defaults.
|
||||
if cfg.Format == "" { |
||||
cfg.Format = DefaultFormat |
||||
} |
||||
if cfg.TimeFormat == "" { |
||||
cfg.TimeFormat = DefaultTime |
||||
} |
||||
|
||||
l.Config = cfg |
||||
l.template = nil |
||||
} |
@ -0,0 +1,102 @@ |
||||
package golog |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"text/template" |
||||
"time" |
||||
|
||||
"github.com/kirsle/golog/ansi" |
||||
) |
||||
|
||||
// Convenient log formats to use in your logger.
|
||||
const ( |
||||
// DefaultFormat: shows the date in the secondary (dark) color, the label
|
||||
// in the bright color, and the message text in the normal color.
|
||||
DefaultFormat = `{{.Secondary}}{{.Time}}{{.Reset}} {{.Primary}}[{{.Level}}]{{.Reset}} {{.Message}}` |
||||
|
||||
// ColorfulFormat: like the DefaultFormat, but the message itself is also
|
||||
// colored using the secondary color.
|
||||
ColorfulFormat = `{{.Secondary}}{{.Time}}{{.Reset}} {{.Primary}}[{{.Level}}]{{.Reset}} {{.Secondary}}{{.Message}}{{.Reset}}` |
||||
) |
||||
|
||||
// Convenient time formats to use in your logger.
|
||||
const ( |
||||
// DefaultTime is the default, in `yyyy-mm-dd hh:mm:ss` format.
|
||||
DefaultTime = `2006-01-02 15:04:05` |
||||
|
||||
// FriendlyTime is a human readable `Jan 2 15:04:05 2006` format.
|
||||
FriendlyTime = `Jan 2 15:04:05 2006` |
||||
) |
||||
|
||||
// formatter provides the variables that can be used in the log format.
|
||||
type formatter struct { |
||||
Time string |
||||
Level string |
||||
Message string |
||||
Primary string |
||||
Secondary string |
||||
Reset string |
||||
} |
||||
|
||||
// Format and return a log message.
|
||||
func (l *Logger) Format(level logLevel, tmpl string, args ...interface{}) string { |
||||
// Prepare the variables to apply to the log message format.
|
||||
format := formatter{ |
||||
Time: time.Now().Format(l.Config.TimeFormat), |
||||
Level: levelNames[level], |
||||
Message: fmt.Sprintf(tmpl, args...), |
||||
} |
||||
|
||||
// Find the theme color to use.
|
||||
if l.Config.Colors != NoColor { |
||||
var ( |
||||
primary ThemeColor |
||||
secondary ThemeColor |
||||
) |
||||
|
||||
switch level { |
||||
case DebugLevel: |
||||
primary = l.Config.Theme.Debug |
||||
secondary = l.Config.Theme.DebugSecondary |
||||
case InfoLevel: |
||||
primary = l.Config.Theme.Info |
||||
secondary = l.Config.Theme.InfoSecondary |
||||
case WarnLevel: |
||||
primary = l.Config.Theme.Warn |
||||
secondary = l.Config.Theme.WarnSecondary |
||||
case ErrorLevel: |
||||
primary = l.Config.Theme.Error |
||||
secondary = l.Config.Theme.ErrorSecondary |
||||
} |
||||
|
||||
// What color level are we supporting?
|
||||
if l.Config.Colors == ANSIColor { |
||||
format.Primary = fmt.Sprintf("\x1B[%sm", primary.ANSI) |
||||
format.Secondary = fmt.Sprintf("\x1B[%sm", secondary.ANSI) |
||||
format.Reset = fmt.Sprintf("\x1B[%sm", ansi.Reset) |
||||
} else if l.Config.Colors == ExtendedColor { |
||||
format.Primary = fmt.Sprintf("\x1B[%sm", primary.Extended) |
||||
format.Secondary = fmt.Sprintf("\x1B[%sm", secondary.Extended) |
||||
format.Reset = fmt.Sprintf("\x1B[%sm", ansi.Reset) |
||||
} |
||||
} |
||||
|
||||
// Do we have the template cached?
|
||||
if l.template == nil { |
||||
template, err := template.New("golog").Parse(l.Config.Format) |
||||
if err != nil { |
||||
return fmt.Sprintf("[GoLog format error: %s]", err) |
||||
} |
||||
l.template = template |
||||
} |
||||
|
||||
// Evaluate the template.
|
||||
var buf bytes.Buffer |
||||
err := l.template.Execute(&buf, format) |
||||
if err != nil { |
||||
return fmt.Sprintf("[GoLog template error: %s]", err) |
||||
} |
||||
|
||||
return buf.String() |
||||
} |
@ -0,0 +1,43 @@ |
||||
package golog |
||||
|
||||
import ( |
||||
"sync" |
||||
"text/template" |
||||
) |
||||
|
||||
// An internal map of named loggers. This allows for GetLogger() to be called
|
||||
// many times from anywhere in your code base, but for only one logger instance
|
||||
// to be created for it.
|
||||
var ( |
||||
loggers map[string]*Logger |
||||
loggerMutex sync.Mutex |
||||
) |
||||
|
||||
func init() { |
||||
loggers = map[string]*Logger{} |
||||
} |
||||
|
||||
// Logger stores the configuration for a named logger instance.
|
||||
type Logger struct { |
||||
Name string |
||||
Config *Config |
||||
|
||||
// Private cached text/template, the first time the formatter is used.
|
||||
template *template.Template |
||||
} |
||||
|
||||
// GetLogger initializes and returns a new Logger.
|
||||
func GetLogger(name string) *Logger { |
||||
loggerMutex.Lock() |
||||
defer loggerMutex.Unlock() |
||||
|
||||
// Initialize the logger the first time we ask for it.
|
||||
if _, ok := loggers[name]; !ok { |
||||
loggers[name] = &Logger{ |
||||
Name: name, |
||||
Config: DefaultConfig(), |
||||
} |
||||
} |
||||
|
||||
return loggers[name] |
||||
} |
@ -0,0 +1,54 @@ |
||||
package golog |
||||
|
||||
import "testing" |
||||
|
||||
func TestColors(t *testing.T) { |
||||
log := GetLogger("test") |
||||
|
||||
// Helper function to emit all the log types.
|
||||
emitLogs := func(message string) { |
||||
log.Debug(message) |
||||
log.Info(message) |
||||
log.Warn(message) |
||||
log.Error(message) |
||||
} |
||||
|
||||
log.Configure(&Config{ |
||||
Theme: DarkTheme, |
||||
Colors: ANSIColor, |
||||
}) |
||||
emitLogs("With standard 16-color ANSI codes.") |
||||
|
||||
log.Configure(&Config{ |
||||
Theme: DarkTheme, |
||||
Colors: ExtendedColor, |
||||
}) |
||||
emitLogs("With xterm-256color codes.") |
||||
|
||||
log.Configure(&Config{ |
||||
Theme: DarkTheme, |
||||
Colors: ExtendedColor, |
||||
Format: ColorfulFormat, |
||||
}) |
||||
emitLogs("Colorful format.") |
||||
} |
||||
|
||||
func TestLogLevels(t *testing.T) { |
||||
log := GetLogger("levels") |
||||
|
||||
// Helper function to emit all the log types.
|
||||
emitLogs := func(message string) { |
||||
log.Debug(message) |
||||
log.Info(message) |
||||
log.Warn(message) |
||||
log.Error(message) |
||||
} |
||||
|
||||
emitLogs("Default log level=debug") |
||||
log.Config.Level = InfoLevel |
||||
emitLogs("With Level=Info") |
||||
log.Config.Level = WarnLevel |
||||
emitLogs("With Level=Warn") |
||||
log.Config.Level = ErrorLevel |
||||
emitLogs("With Level=Error") |
||||
} |
@ -0,0 +1,70 @@ |
||||
package golog |
||||
|
||||
import ( |
||||
"fmt" |
||||
"os" |
||||
) |
||||
|
||||
type logLevel int |
||||
|
||||
// Log levels for controlling whether or not logs of certain types will be
|
||||
// emitted by your logger.
|
||||
const ( |
||||
DebugLevel logLevel = iota |
||||
InfoLevel |
||||
WarnLevel |
||||
ErrorLevel |
||||
) |
||||
|
||||
// Map log levels to human readable labels.
|
||||
var levelNames = map[logLevel]string{ |
||||
DebugLevel: "DEBUG", |
||||
InfoLevel: "INFO", |
||||
WarnLevel: "WARN", |
||||
ErrorLevel: "ERROR", |
||||
} |
||||
|
||||
// emit is the general purpose log line emitter.
|
||||
func (l *Logger) emit(level logLevel, tmpl string, args ...interface{}) { |
||||
message := l.Format(level, tmpl, args...) |
||||
|
||||
// If we have a log writer, send it there.
|
||||
if l.Config.Writer != nil { |
||||
// l.Config.Writer.Write(message)
|
||||
} else { |
||||
// No writer given so we default to standard out/error.
|
||||
if level <= InfoLevel { |
||||
fmt.Fprintln(os.Stdout, message) |
||||
} else { |
||||
fmt.Fprintln(os.Stderr, message) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Debug emits a debug-level message from the logger.
|
||||
func (l *Logger) Debug(tmpl string, args ...interface{}) { |
||||
if l.Config.Level <= DebugLevel { |
||||
l.emit(DebugLevel, tmpl, args...) |
||||
} |
||||
} |
||||
|
||||
// Info emits an informational message.
|
||||
func (l *Logger) Info(tmpl string, args ...interface{}) { |
||||
if l.Config.Level <= InfoLevel { |
||||
l.emit(InfoLevel, tmpl, args...) |
||||
} |
||||
} |
||||
|
||||
// Warn emits a warning message.
|
||||
func (l *Logger) Warn(tmpl string, args ...interface{}) { |
||||
if l.Config.Level <= WarnLevel { |
||||
l.emit(WarnLevel, tmpl, args...) |
||||
} |
||||
} |
||||
|
||||
// Error emits an error message.
|
||||
func (l *Logger) Error(tmpl string, args ...interface{}) { |
||||
if l.Config.Level <= ErrorLevel { |
||||
l.emit(ErrorLevel, tmpl, args...) |
||||
} |
||||
} |
After Width: | Height: | Size: 65 KiB |
@ -0,0 +1,37 @@ |
||||
package golog |
||||
|
||||
import "github.com/kirsle/golog/ansi" |
||||
|
||||
// Theme defines the color scheme for a logger. Each log level has two colors:
|
||||
// a primary (for the label itself) and a secondary color. For example, if your
|
||||
// log lines include a date/time this could be colored using the secondary
|
||||
// color.
|
||||
type Theme struct { |
||||
Debug ThemeColor |
||||
DebugSecondary ThemeColor |
||||
Info ThemeColor |
||||
InfoSecondary ThemeColor |
||||
Warn ThemeColor |
||||
WarnSecondary ThemeColor |
||||
Error ThemeColor |
||||
ErrorSecondary ThemeColor |
||||
} |
||||
|
||||
// ThemeColor defines a color tuple for ANSI (legacy) support and modern
|
||||
// 256-color support.
|
||||
type ThemeColor struct { |
||||
ANSI string |
||||
Extended string |
||||
} |
||||
|
||||
// DarkTheme is a suitable default theme for dark terminal backgrounds.
|
||||
var DarkTheme = Theme{ |
||||
Debug: ThemeColor{ansi.BrightCyan, HexColor("#FF99FF")}, |
||||
DebugSecondary: ThemeColor{ansi.Cyan, HexColor("#996699")}, |
||||
Info: ThemeColor{ansi.BrightGreen, HexColor("#0099FF")}, |
||||
InfoSecondary: ThemeColor{ansi.Green, HexColor("#006699")}, |
||||
Warn: ThemeColor{ansi.BrightYellow, HexColor("#FF9900")}, |
||||
WarnSecondary: ThemeColor{ansi.Yellow, HexColor("#996600")}, |
||||
Error: ThemeColor{ansi.BrightRed, HexColor("#FF0000")}, |
||||
ErrorSecondary: ThemeColor{ansi.Red, HexColor("#CC0000")}, |
||||
} |
Loading…
Reference in new issue