Pārlūkot izejas kodu

Adopt calmh/logger into lib/logger

Jakob Borg 10 gadi atpakaļ
vecāks
revīzija
2de364414f
3 mainītis faili ar 384 papildinājumiem un 0 dzēšanām
  1. 19 0
      lib/logger/LICENSE
  2. 281 0
      lib/logger/logger.go
  3. 84 0
      lib/logger/logger_test.go

+ 19 - 0
lib/logger/LICENSE

@@ -0,0 +1,19 @@
+Copyright (C) 2013 Jakob Borg
+
+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.

+ 281 - 0
lib/logger/logger.go

@@ -0,0 +1,281 @@
+// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
+// is governed by an MIT-style license that can be found in the LICENSE file.
+
+// Package logger implements a standardized logger with callback functionality
+package logger
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"os"
+	"strings"
+	"sync"
+)
+
+type LogLevel int
+
+const (
+	LevelDebug LogLevel = iota
+	LevelVerbose
+	LevelInfo
+	LevelOK
+	LevelWarn
+	LevelFatal
+	NumLevels
+)
+
+// A MessageHandler is called with the log level and message text.
+type MessageHandler func(l LogLevel, msg string)
+
+type Logger interface {
+	AddHandler(level LogLevel, h MessageHandler)
+	SetFlags(flag int)
+	SetPrefix(prefix string)
+	Debugln(vals ...interface{})
+	Debugf(format string, vals ...interface{})
+	Verboseln(vals ...interface{})
+	Verbosef(format string, vals ...interface{})
+	Infoln(vals ...interface{})
+	Infof(format string, vals ...interface{})
+	Okln(vals ...interface{})
+	Okf(format string, vals ...interface{})
+	Warnln(vals ...interface{})
+	Warnf(format string, vals ...interface{})
+	Fatalln(vals ...interface{})
+	Fatalf(format string, vals ...interface{})
+	ShouldDebug(facility string) bool
+	SetDebug(facility string, enabled bool)
+	Facilities() (enabled, disabled []string)
+	NewFacility(facility string) Logger
+}
+
+type logger struct {
+	logger   *log.Logger
+	handlers [NumLevels][]MessageHandler
+	debug    map[string]bool
+	mut      sync.Mutex
+}
+
+// The default logger logs to standard output with a time prefix.
+var DefaultLogger = New()
+
+func New() Logger {
+	if os.Getenv("LOGGER_DISCARD") != "" {
+		// Hack to completely disable logging, for example when running benchmarks.
+		return &logger{
+			logger: log.New(ioutil.Discard, "", 0),
+		}
+	}
+
+	return &logger{
+		logger: log.New(os.Stdout, "", log.Ltime),
+	}
+}
+
+// AddHandler registers a new MessageHandler to receive messages with the
+// specified log level or above.
+func (l *logger) AddHandler(level LogLevel, h MessageHandler) {
+	l.mut.Lock()
+	defer l.mut.Unlock()
+	l.handlers[level] = append(l.handlers[level], h)
+}
+
+// See log.SetFlags
+func (l *logger) SetFlags(flag int) {
+	l.logger.SetFlags(flag)
+}
+
+// See log.SetPrefix
+func (l *logger) SetPrefix(prefix string) {
+	l.logger.SetPrefix(prefix)
+}
+
+func (l *logger) callHandlers(level LogLevel, s string) {
+	for ll := LevelDebug; ll <= level; ll++ {
+		for _, h := range l.handlers[ll] {
+			h(level, strings.TrimSpace(s))
+		}
+	}
+}
+
+// Debugln logs a line with a DEBUG prefix.
+func (l *logger) Debugln(vals ...interface{}) {
+	l.mut.Lock()
+	defer l.mut.Unlock()
+	s := fmt.Sprintln(vals...)
+	l.logger.Output(2, "DEBUG: "+s)
+	l.callHandlers(LevelDebug, s)
+}
+
+// Debugf logs a formatted line with a DEBUG prefix.
+func (l *logger) Debugf(format string, vals ...interface{}) {
+	l.mut.Lock()
+	defer l.mut.Unlock()
+	s := fmt.Sprintf(format, vals...)
+	l.logger.Output(2, "DEBUG: "+s)
+	l.callHandlers(LevelDebug, s)
+}
+
+// Infoln logs a line with a VERBOSE prefix.
+func (l *logger) Verboseln(vals ...interface{}) {
+	l.mut.Lock()
+	defer l.mut.Unlock()
+	s := fmt.Sprintln(vals...)
+	l.logger.Output(2, "VERBOSE: "+s)
+	l.callHandlers(LevelVerbose, s)
+}
+
+// Infof logs a formatted line with a VERBOSE prefix.
+func (l *logger) Verbosef(format string, vals ...interface{}) {
+	l.mut.Lock()
+	defer l.mut.Unlock()
+	s := fmt.Sprintf(format, vals...)
+	l.logger.Output(2, "VERBOSE: "+s)
+	l.callHandlers(LevelVerbose, s)
+}
+
+// Infoln logs a line with an INFO prefix.
+func (l *logger) Infoln(vals ...interface{}) {
+	l.mut.Lock()
+	defer l.mut.Unlock()
+	s := fmt.Sprintln(vals...)
+	l.logger.Output(2, "INFO: "+s)
+	l.callHandlers(LevelInfo, s)
+}
+
+// Infof logs a formatted line with an INFO prefix.
+func (l *logger) Infof(format string, vals ...interface{}) {
+	l.mut.Lock()
+	defer l.mut.Unlock()
+	s := fmt.Sprintf(format, vals...)
+	l.logger.Output(2, "INFO: "+s)
+	l.callHandlers(LevelInfo, s)
+}
+
+// Okln logs a line with an OK prefix.
+func (l *logger) Okln(vals ...interface{}) {
+	l.mut.Lock()
+	defer l.mut.Unlock()
+	s := fmt.Sprintln(vals...)
+	l.logger.Output(2, "OK: "+s)
+	l.callHandlers(LevelOK, s)
+}
+
+// Okf logs a formatted line with an OK prefix.
+func (l *logger) Okf(format string, vals ...interface{}) {
+	l.mut.Lock()
+	defer l.mut.Unlock()
+	s := fmt.Sprintf(format, vals...)
+	l.logger.Output(2, "OK: "+s)
+	l.callHandlers(LevelOK, s)
+}
+
+// Warnln logs a formatted line with a WARNING prefix.
+func (l *logger) Warnln(vals ...interface{}) {
+	l.mut.Lock()
+	defer l.mut.Unlock()
+	s := fmt.Sprintln(vals...)
+	l.logger.Output(2, "WARNING: "+s)
+	l.callHandlers(LevelWarn, s)
+}
+
+// Warnf logs a formatted line with a WARNING prefix.
+func (l *logger) Warnf(format string, vals ...interface{}) {
+	l.mut.Lock()
+	defer l.mut.Unlock()
+	s := fmt.Sprintf(format, vals...)
+	l.logger.Output(2, "WARNING: "+s)
+	l.callHandlers(LevelWarn, s)
+}
+
+// Fatalln logs a line with a FATAL prefix and exits the process with exit
+// code 1.
+func (l *logger) Fatalln(vals ...interface{}) {
+	l.mut.Lock()
+	defer l.mut.Unlock()
+	s := fmt.Sprintln(vals...)
+	l.logger.Output(2, "FATAL: "+s)
+	l.callHandlers(LevelFatal, s)
+	os.Exit(1)
+}
+
+// Fatalf logs a formatted line with a FATAL prefix and exits the process with
+// exit code 1.
+func (l *logger) Fatalf(format string, vals ...interface{}) {
+	l.mut.Lock()
+	defer l.mut.Unlock()
+	s := fmt.Sprintf(format, vals...)
+	l.logger.Output(2, "FATAL: "+s)
+	l.callHandlers(LevelFatal, s)
+	os.Exit(1)
+}
+
+// ShouldDebug returns true if the given facility has debugging enabled.
+func (l *logger) ShouldDebug(facility string) bool {
+	l.mut.Lock()
+	res := l.debug[facility]
+	l.mut.Unlock()
+	return res
+}
+
+// SetDebug enabled or disables debugging for the given facility name.
+func (l *logger) SetDebug(facility string, enabled bool) {
+	l.mut.Lock()
+	l.debug[facility] = enabled
+	l.mut.Unlock()
+}
+
+// Facilities returns the currently known set of facilities, both those for
+// which debug is enabled and those for which it is disabled.
+func (l *logger) Facilities() (enabled, disabled []string) {
+	l.mut.Lock()
+	for facility, isEnabled := range l.debug {
+		if isEnabled {
+			enabled = append(enabled, facility)
+		} else {
+			disabled = append(disabled, facility)
+		}
+	}
+	l.mut.Unlock()
+	return
+}
+
+// NewFacility returns a new logger bound to the named facility.
+func (l *logger) NewFacility(facility string) Logger {
+	l.mut.Lock()
+	if l.debug == nil {
+		l.debug = make(map[string]bool)
+	}
+	l.debug[facility] = false
+	l.mut.Unlock()
+
+	return &facilityLogger{
+		logger:   l,
+		facility: facility,
+	}
+}
+
+// A facilityLogger is a regular logger but bound to a facility name. The
+// Debugln and Debugf methods are no-ops unless debugging has been enabled for
+// this facility on the parent logger.
+type facilityLogger struct {
+	*logger
+	facility string
+}
+
+// Debugln logs a line with a DEBUG prefix.
+func (l *facilityLogger) Debugln(vals ...interface{}) {
+	if !l.ShouldDebug(l.facility) {
+		return
+	}
+	l.logger.Debugln(vals...)
+}
+
+// Debugf logs a formatted line with a DEBUG prefix.
+func (l *facilityLogger) Debugf(format string, vals ...interface{}) {
+	if !l.ShouldDebug(l.facility) {
+		return
+	}
+	l.logger.Debugf(format, vals...)
+}

+ 84 - 0
lib/logger/logger_test.go

@@ -0,0 +1,84 @@
+// Copyright (C) 2014 Jakob Borg. All rights reserved. Use of this source code
+// is governed by an MIT-style license that can be found in the LICENSE file.
+
+package logger
+
+import (
+	"strings"
+	"testing"
+)
+
+func TestAPI(t *testing.T) {
+	l := New()
+	l.SetFlags(0)
+	l.SetPrefix("testing")
+
+	debug := 0
+	l.AddHandler(LevelDebug, checkFunc(t, LevelDebug, "test 0", &debug))
+	info := 0
+	l.AddHandler(LevelInfo, checkFunc(t, LevelInfo, "test 1", &info))
+	warn := 0
+	l.AddHandler(LevelWarn, checkFunc(t, LevelWarn, "test 2", &warn))
+	ok := 0
+	l.AddHandler(LevelOK, checkFunc(t, LevelOK, "test 3", &ok))
+
+	l.Debugf("test %d", 0)
+	l.Debugln("test", 0)
+	l.Infof("test %d", 1)
+	l.Infoln("test", 1)
+	l.Warnf("test %d", 2)
+	l.Warnln("test", 2)
+	l.Okf("test %d", 3)
+	l.Okln("test", 3)
+
+	if debug != 2 {
+		t.Errorf("Debug handler called %d != 2 times", debug)
+	}
+	if info != 2 {
+		t.Errorf("Info handler called %d != 2 times", info)
+	}
+	if warn != 2 {
+		t.Errorf("Warn handler called %d != 2 times", warn)
+	}
+	if ok != 2 {
+		t.Errorf("Ok handler called %d != 2 times", ok)
+	}
+}
+
+func checkFunc(t *testing.T, expectl LogLevel, expectmsg string, counter *int) func(LogLevel, string) {
+	return func(l LogLevel, msg string) {
+		*counter++
+		if l != expectl {
+			t.Errorf("Incorrect message level %d != %d", l, expectl)
+		}
+		if !strings.HasSuffix(msg, expectmsg) {
+			t.Errorf("%q does not end with %q", msg, expectmsg)
+		}
+	}
+}
+
+func TestFacilityDebugging(t *testing.T) {
+	l := New()
+	l.SetFlags(0)
+
+	msgs := 0
+	l.AddHandler(LevelDebug, func(l LogLevel, msg string) {
+		msgs++
+		if strings.Contains(msg, "f1") {
+			t.Fatal("Should not get message for facility f1")
+		}
+	})
+
+	l.SetDebug("f0", true)
+	l.SetDebug("f1", false)
+
+	f0 := l.NewFacility("f0")
+	f1 := l.NewFacility("f1")
+
+	f0.Debugln("Debug line from f0")
+	f1.Debugln("Debug line from f1")
+
+	if msgs != 1 {
+		t.Fatalf("Incorrent number of messages, %d != 1", msgs)
+	}
+}