Skip to content
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
242 lines (211 sloc) 5.94 KB
// log.go - Logging backend.
// Copyright (C) 2017 Yawning Angel.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// GNU Affero General Public License for more details.
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <>.
// Package log provides a logging backend, based around the go-logging package.
package log
import (
goLog "log"
type discardCloser struct {
discard io.Writer
func (d *discardCloser) Close() error {
return nil
func newDiscardCloser() *discardCloser {
d := new(discardCloser)
d.discard = ioutil.Discard
return d
// Backend is a log backend.
type Backend struct {
_backend logging.LeveledBackend
w io.WriteCloser
file string
level string
disable bool
// Log is used to log a message as per
// the logging.Backend interface.
func (b *Backend) Log(level logging.Level, calldepth int, record *logging.Record) error {
defer b.RUnlock()
return b._backend.Log(level, calldepth, record)
// GetLevel returns the logging level for the specified module
// as per the logging.Leveled interface.
func (b *Backend) GetLevel(level string) logging.Level {
defer b.RUnlock()
return b._backend.GetLevel(level)
// SetLevel sets the logging level for the specified module.
// The module corresponds to the string specified in GetLogger.
// We use this function as part of our implementation of the
// logging.Leveled interface.
func (b *Backend) SetLevel(level logging.Level, module string) {
defer b.RUnlock()
b._backend.SetLevel(level, module)
// IsEnabledFor returns true if the logger is enabled for the given level.
// We use this function as part of our implementation of the
// logging.Leveled interface.
func (b *Backend) IsEnabledFor(level logging.Level, module string) bool {
defer b.RUnlock()
return b._backend.IsEnabledFor(level, module)
// GetLogger returns a per-module logger that writes to the backend.
func (b *Backend) GetLogger(module string) *logging.Logger {
l := logging.MustGetLogger(module)
return l
// GetGoLogger returns a per-module Go runtime *log.Logger that writes to
// the backend. Due to limitations of the Go runtime log package, only one
// level is supported per returned Logger.
func (b *Backend) GetGoLogger(module string, level string) *goLog.Logger {
lvl, err := logLevelFromString(level)
if err != nil {
panic("log: GetGoLogger(): Invalid level: " + err.Error())
w := new(logWriter)
w.m = b.GetLogger(module)
w.l = goLog.New(w, "", 0) // Owns w.
w.lvl = lvl
return w.l
// GetLogWriter returns a per-module io.Writer that writes to the backend at
// the provided level.
func (b *Backend) GetLogWriter(module string, level string) io.Writer {
lvl, err := logLevelFromString(level)
if err != nil {
panic("log: GetLogWriter(): Invalid level: " + err.Error())
w := new(logWriter)
w.m = b.GetLogger(module)
w.lvl = lvl
return w
// Rotate simply reopens the log file for writing
// and should be used to implement log rotation
// where this is invoked upon HUP signal for example.
func (b *Backend) Rotate() error {
defer b.Unlock()
err := b.w.Close()
if err != nil {
return err
return nil
func (b *Backend) newBackend() error {
lvl, err := logLevelFromString(b.level)
if err != nil {
return err
// Figure out where the log should go to, creating a log file as needed.
if b.disable {
b.w = newDiscardCloser()
} else if b.file == "" {
b.w = os.Stdout
} else {
const fileMode = 0600
var err error
flags := os.O_CREATE | os.O_APPEND | os.O_WRONLY
b.w, err = os.OpenFile(b.file, flags, fileMode)
if err != nil {
return fmt.Errorf("server: failed to create log file: %v", err)
// Create a new log backend, using the configured output, and initialize
// the server logger.
logFmt := logging.MustStringFormatter("%{time:15:04:05.000} %{level:.4s} %{module}: %{message}")
base := logging.NewLogBackend(b.w, "", 0)
formatted := logging.NewBackendFormatter(base, logFmt)
b._backend = logging.AddModuleLevel(formatted)
b._backend.SetLevel(lvl, "")
return nil
// New initializes a logging backend.
func New(f string, level string, disable bool) (*Backend, error) {
b := new(Backend)
b.file = f
b.level = level
b.disable = disable
err := b.newBackend()
if err != nil {
return nil, err
return b, nil
func logLevelFromString(l string) (logging.Level, error) {
switch strings.ToUpper(l) {
case "ERROR":
return logging.ERROR, nil
case "WARNING":
return logging.WARNING, nil
case "NOTICE":
return logging.NOTICE, nil
case "INFO":
return logging.INFO, nil
case "DEBUG":
return logging.DEBUG, nil
return logging.CRITICAL, fmt.Errorf("log: invalid level: '%v'", l)
type logWriter struct {
m *logging.Logger
l *goLog.Logger
lvl logging.Level
func (w logWriter) Write(p []byte) (n int, err error) {
// The `log` package will always pass a byte array with a newline at
// the end, so it needs to be stripped off.
s := strings.TrimSpace(string(p))
if len(s) == 0 {
switch w.lvl {
case logging.ERROR:
case logging.WARNING:
case logging.NOTICE:
case logging.INFO:
case logging.DEBUG:
case logging.CRITICAL:
panic("BUG: Invalid log level in logWriter.Write()")
return len(p), nil
You can’t perform that action at this time.