Initial commit

This commit is contained in:
Noah 2017-06-10 17:01:22 -07:00
commit 248ff19dbd
11 changed files with 527 additions and 0 deletions

21
LICENSE Normal file
View File

@ -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.

71
README.md Normal file
View File

@ -0,0 +1,71 @@
# Go Log
This is Yet Another Logger for Go programs.
![Screenshot](https://raw.githubusercontent.com/kirsle/golog/master/screenshot.png)
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.
```

23
ansi/colors.go Normal file
View File

@ -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`
)

34
colors.go Normal file
View File

@ -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)
}

72
config.go Normal file
View File

@ -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
}

102
formatter.go Normal file
View File

@ -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()
}

43
golog.go Normal file
View File

@ -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]
}

54
golog_test.go Normal file
View File

@ -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")
}

70
levels.go Normal file
View File

@ -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...)
}
}

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

37
themes.go Normal file
View File

@ -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")},
}