Skip to content

Zanderz/colors 2015.12.04 #3

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

Merged
merged 5 commits into from
Dec 7, 2015
Merged
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
3 changes: 2 additions & 1 deletion examples/example.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ func main() {
// For demo purposes, create two backend for os.Stderr.
backend1 := logging.NewLogBackend(os.Stderr, "", 0)
backend2 := logging.NewLogBackend(os.Stderr, "", 0)

backend1.Color = true
backend2.Color = true
// For messages written to backend2 we want to add some additional
// information to the output, including the used log level and the name of
// the function.
Expand Down
3 changes: 3 additions & 0 deletions format.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,9 @@ type stringFormatter struct {
// "%{color:bold}%{time:15:04:05} %{level:-8s}%{color:reset} %{message}" will
// just colorize the time and level, leaving the message uncolored.
//
// Colors on Windows is unfortunately not supported right now and is currently
// a no-op.
//
// There's also a couple of experimental 'verbs'. These are exposed to get
// feedback and needs a bit of tinkering. Hence, they might change in the
// future.
Expand Down
59 changes: 59 additions & 0 deletions log.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright 2013, Örjan Persson. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package logging

import (
"fmt"
"io"
)

type color int

const (
colorBlack = iota + 30
colorRed
colorGreen
colorYellow
colorBlue
colorMagenta
colorCyan
colorWhite
)

var (
colors = []string{
CRITICAL: colorSeq(colorMagenta),
ERROR: colorSeq(colorRed),
WARNING: colorSeq(colorYellow),
NOTICE: colorSeq(colorGreen),
DEBUG: colorSeq(colorCyan),
}
boldcolors = []string{
CRITICAL: colorSeqBold(colorMagenta),
ERROR: colorSeqBold(colorRed),
WARNING: colorSeqBold(colorYellow),
NOTICE: colorSeqBold(colorGreen),
DEBUG: colorSeqBold(colorCyan),
}
)

func colorSeq(color color) string {
return fmt.Sprintf("\033[%dm", int(color))
}

func colorSeqBold(color color) string {
return fmt.Sprintf("\033[%d;1m", int(color))
}

func doFmtVerbLevelColor(layout string, level Level, output io.Writer) {
if layout == "bold" {
output.Write([]byte(boldcolors[level]))
} else if layout == "reset" {
output.Write([]byte("\033[0m"))
} else {
output.Write([]byte(colors[level]))
}
}

52 changes: 0 additions & 52 deletions log_nix.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,10 @@ package logging

import (
"bytes"
"fmt"
"io"
"log"
)

// TODO initialize here
var colors []string
var boldcolors []string

type color int

const (
colorBlack = iota + 30
colorRed
colorGreen
colorYellow
colorBlue
colorMagenta
colorCyan
colorWhite
)

// LogBackend utilizes the standard log module.
type LogBackend struct {
Logger *log.Logger
Expand All @@ -56,37 +38,3 @@ func (b *LogBackend) Log(level Level, calldepth int, rec *Record) error {
return b.Logger.Output(calldepth+2, rec.Formatted(calldepth+1))
}

func colorSeq(color color) string {
return fmt.Sprintf("\033[%dm", int(color))
}

func colorSeqBold(color color) string {
return fmt.Sprintf("\033[%d;1m", int(color))
}

func init() {
colors = []string{
CRITICAL: colorSeq(colorMagenta),
ERROR: colorSeq(colorRed),
WARNING: colorSeq(colorYellow),
NOTICE: colorSeq(colorGreen),
DEBUG: colorSeq(colorCyan),
}
boldcolors = []string{
CRITICAL: colorSeqBold(colorMagenta),
ERROR: colorSeqBold(colorRed),
WARNING: colorSeqBold(colorYellow),
NOTICE: colorSeqBold(colorGreen),
DEBUG: colorSeqBold(colorCyan),
}
}

func doFmtVerbLevelColor(layout string, level Level, output io.Writer) {
if layout == "bold" {
output.Write([]byte(boldcolors[level]))
} else if layout == "reset" {
output.Write([]byte("\033[0m"))
} else {
output.Write([]byte(colors[level]))
}
}
126 changes: 56 additions & 70 deletions log_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"bytes"
"io"
"log"
"os"
"syscall"
)

Expand All @@ -18,103 +17,90 @@ var (
setConsoleTextAttributeProc = kernel32DLL.NewProc("SetConsoleTextAttribute")
)

// TODO initialize here
var colors []WORD
var boldcolors []WORD

type color int
type WORD uint16

// Character attributes
// Note:
// -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan).
// Clearing all foreground or background colors results in black; setting all creates white.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes.
const (
// Character attributes
// Note:
// -- The attributes are combined to produce various colors (e.g., Blue + Green will create Cyan).
// Clearing all foreground or background colors results in black; setting all creates white.
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms682088(v=vs.85).aspx#_win32_character_attributes.
fgBlack WORD = 0x0000
fgBlue WORD = 0x0001
fgGreen WORD = 0x0002
fgCyan WORD = 0x0003
fgRed WORD = 0x0004
fgMagenta WORD = 0x0005
fgYellow WORD = 0x0006
fgWhite WORD = 0x0007
fgIntensity WORD = 0x0008
fgMask WORD = 0x000F
fgBlack = 0x0000
fgBlue = 0x0001
fgGreen = 0x0002
fgCyan = 0x0003
fgRed = 0x0004
fgMagenta = 0x0005
fgYellow = 0x0006
fgWhite = 0x0007
fgIntensity = 0x0008
fgMask = 0x000F
)

// LogBackend utilizes the standard log module.
type LogBackend struct {
Logger *log.Logger
Color bool
Handle uintptr
}

// NewLogBackend creates a new LogBackend.
func NewLogBackend(out *os.File, prefix string, flag int) *LogBackend {
return &LogBackend{Logger: log.New(out, prefix, flag), Handle: out.Fd()}
}

func (b *LogBackend) Log(level Level, calldepth int, rec *Record) error {
if b.Color {
buf := &bytes.Buffer{}
setConsoleTextAttribute(b.Handle, colors[level])
buf.Write([]byte(rec.Formatted(calldepth + 1)))
// For some reason, the Go logger arbitrarily decided "2" was the correct
// call depth...
err := b.Logger.Output(calldepth+2, buf.String())
setConsoleTextAttribute(b.Handle, fgWhite)
return err
}
return b.Logger.Output(calldepth+2, rec.Formatted(calldepth+1))
}

func init() {
colors = []WORD{
var (
win_colors = []uint16{
INFO: fgWhite,
CRITICAL: fgMagenta,
ERROR: fgRed,
WARNING: fgYellow,
NOTICE: fgGreen,
DEBUG: fgCyan,
}
boldcolors = []WORD{
win_boldcolors = []uint16{
INFO: fgWhite | fgIntensity,
CRITICAL: fgMagenta | fgIntensity,
ERROR: fgRed | fgIntensity,
WARNING: fgYellow | fgIntensity,
NOTICE: fgGreen | fgIntensity,
DEBUG: fgCyan | fgIntensity,
}
)

type file interface {
Fd() uintptr
}

// setConsoleTextAttribute sets the attributes of characters written to the
// console screen buffer by the WriteFile or WriteConsole function.
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx.
func setConsoleTextAttribute(handle uintptr, attribute WORD) error {
r1, r2, err := setConsoleTextAttributeProc.Call(handle, uintptr(attribute), 0)
use(attribute)
return checkError(r1, r2, err)
// LogBackend utilizes the standard log module.
type LogBackend struct {
Logger *log.Logger
Color bool

// f is set to a non-nil value if the underlying writer which logs writes to
// implements the file interface. This makes us able to colorise the output.
f file
}

// checkError evaluates the results of a Windows API call and returns the error if it failed.
func checkError(r1, r2 uintptr, err error) error {
// Windows APIs return non-zero to indicate success
if r1 != 0 {
return nil
// NewLogBackend creates a new LogBackend.
func NewLogBackend(out io.Writer, prefix string, flag int) *LogBackend {
b := &LogBackend{Logger: log.New(out, prefix, flag)}

// Unfortunately, the API used only takes an io.Writer where the Windows API
// need the actual fd to change colors.
if f, ok := out.(file); ok {
b.f = f
}

// Return the error if provided, otherwise default to EINVAL
if err != nil {
return b
}

func (b *LogBackend) Log(level Level, calldepth int, rec *Record) error {
if b.Color && b.f != nil {
buf := &bytes.Buffer{}
setConsoleTextAttribute(b.f, win_colors[level])
buf.Write([]byte(rec.Formatted(calldepth + 1)))
err := b.Logger.Output(calldepth+2, buf.String())
setConsoleTextAttribute(b.f, fgWhite)
return err
}
return syscall.EINVAL
return b.Logger.Output(calldepth+2, rec.Formatted(calldepth+1))
}

// use is a no-op, but the compiler cannot see that it is.
// Calling use(p) ensures that p is kept live until that point.
func use(p interface{}) {}

func doFmtVerbLevelColor(layout string, level Level, output io.Writer) {
// no-op for now. We need the file descriptor to do colors.
// setConsoleTextAttribute sets the attributes of characters written to the
// console screen buffer by the WriteFile or WriteConsole function.
// See http://msdn.microsoft.com/en-us/library/windows/desktop/ms686047(v=vs.85).aspx.
func setConsoleTextAttribute(f file, attribute uint16) bool {
ok, _, _ := setConsoleTextAttributeProc.Call(f.Fd(), uintptr(attribute), 0)
return ok != 0
}