blob: 338c044d179eccc116226630ad626aee599ac947 [file] [log] [blame]
// Copyright 2015 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package gologger
import (
"context"
"io"
"os"
"sync"
gol "github.com/op/go-logging"
"golang.org/x/term"
"go.chromium.org/luci/common/logging"
)
// StdFormat is a preferred logging format to use.
//
// It is compatible with logging format used by luci-py. The zero after %{pid}
// is "thread ID" which is unavailable in go.
const StdFormat = `[%{level:.1s}%{time:2006-01-02T15:04:05.000000Z07:00} ` +
`%{pid} 0 %{shortfile}] %{message}`
// StdFormatWithColor is same as StdFormat, except with fancy colors.
//
// Use it when logging to terminal. Note that StdConfig will pick it
// automatically if it detects that given io.Writer is an os.File and it
// is a terminal. See PickStdFormat().
const StdFormatWithColor = `%{color}[%{level:.1s}%{time:2006-01-02T15:04:05.000000Z07:00} ` +
`%{pid} 0 %{shortfile}]%{color:reset} %{message}`
// PickStdFormat returns StdFormat for non terminal-backed files or
// StdFormatWithColor for io.Writers that are io.Files backed by a terminal.
//
// Used by default StdConfig.
func PickStdFormat(w io.Writer) string {
if file, _ := w.(*os.File); file != nil {
if term.IsTerminal(int(file.Fd())) {
return StdFormatWithColor
}
}
return StdFormat
}
// StdConfig defines default logger configuration.
//
// It logs to Stderr using default logging format compatible with luci-py, see
// StdFormat.
//
// Call StdConfig.Use(ctx) to install it as a default context logger.
var StdConfig = LoggerConfig{Out: os.Stderr}
// LoggerConfig owns a go-logging logger, configured in some way.
//
// Despite its name it is not a configuration. It is a stateful object that
// lazy-initializes go-logging logger on a first use.
//
// If you are using os.File as Out, you are responsible for closing it when you
// are done with logging.
type LoggerConfig struct {
Out io.Writer // where to write the log to, required
Format string // how to format the log, default is PickStdFormat(Out)
Logger *gol.Logger // if set, will be used as is, overrides everything else
initOnce sync.Once
w *goLoggerWrapper
}
// NewLogger returns new go-logging based logger bound to the given logging
// context.
//
// It will use the logging level and fields specified in LogContext.
//
// lc.NewLogger is in fact logging.Factory and can be used in SetFactory. That's
// the reason it also takes context.Context, even though it isn't currently
// using it.
//
// All loggers produced by LoggerConfig share single underlying go-logging
// Logger instance.
func (lc *LoggerConfig) NewLogger(_ context.Context, lctx *logging.LogContext) logging.Logger {
lc.initOnce.Do(func() {
logger := lc.Logger
if logger == nil {
fmt := lc.Format
if fmt == "" {
fmt = PickStdFormat(lc.Out)
}
// Leveled formatted file backend.
backend := gol.AddModuleLevel(
gol.NewBackendFormatter(
gol.NewLogBackend(lc.Out, "", 0),
gol.MustStringFormatter(fmt)))
backend.SetLevel(gol.DEBUG, "")
logger = gol.MustGetLogger("")
logger.SetBackend(backend)
}
lc.w = &goLoggerWrapper{l: logger}
})
ret := &loggerImpl{goLoggerWrapper: lc.w, lctx: lctx}
if len(lctx.Fields) > 0 {
ret.fields = lctx.Fields.String()
}
return ret
}
// Use registers go-logging based logger as default logger of the context.
func (lc *LoggerConfig) Use(ctx context.Context) context.Context {
return logging.SetFactory(ctx, lc.NewLogger)
}