The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/infra/log/log.go

481 lines
13 KiB

// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
package log
import (
"context"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"
gokitlog "github.com/go-kit/log"
"github.com/go-kit/log/level"
"github.com/go-stack/stack"
"github.com/mattn/go-isatty"
"gopkg.in/ini.v1"
"github.com/grafana/grafana/pkg/infra/log/term"
"github.com/grafana/grafana/pkg/infra/log/text"
"github.com/grafana/grafana/pkg/util"
)
var (
loggersToClose []DisposableHandler
loggersToReload []ReloadableHandler
root *logManager
now = time.Now
logTimeFormat = time.RFC3339Nano
ctxLogProviders = []ContextualLogProviderFunc{}
)
const (
// top 7 calls in the stack are within logger
DefaultCallerDepth = 7
CallerContextKey = "caller"
)
func init() {
loggersToClose = make([]DisposableHandler, 0)
loggersToReload = make([]ReloadableHandler, 0)
// Use discard by default
format := func(w io.Writer) gokitlog.Logger {
return gokitlog.NewLogfmtLogger(gokitlog.NewSyncWriter(io.Discard))
}
logger := level.NewFilter(format(os.Stderr), level.AllowInfo())
root = newManager(logger)
}
// logManager manage loggers
type logManager struct {
*ConcreteLogger
loggersByName map[string]*ConcreteLogger
logFilters []logWithFilters
mutex sync.RWMutex
}
func newManager(logger gokitlog.Logger) *logManager {
return &logManager{
ConcreteLogger: newConcreteLogger(logger),
loggersByName: map[string]*ConcreteLogger{},
}
}
func (lm *logManager) initialize(loggers []logWithFilters) {
lm.mutex.Lock()
defer lm.mutex.Unlock()
defaultLoggers := make([]gokitlog.Logger, len(loggers))
for index, logger := range loggers {
defaultLoggers[index] = level.NewFilter(logger.val, logger.maxLevel)
}
lm.ConcreteLogger.Swap(&compositeLogger{loggers: defaultLoggers})
lm.logFilters = loggers
loggersByName := []string{}
for k := range lm.loggersByName {
loggersByName = append(loggersByName, k)
}
sort.Strings(loggersByName)
for _, name := range loggersByName {
ctxLoggers := make([]gokitlog.Logger, len(loggers))
for index, logger := range loggers {
ctxLogger := gokitlog.With(logger.val, lm.loggersByName[name].ctx...)
if filterLevel, exists := logger.filters[name]; !exists {
ctxLoggers[index] = level.NewFilter(ctxLogger, logger.maxLevel)
} else {
ctxLoggers[index] = level.NewFilter(ctxLogger, filterLevel)
}
}
lm.loggersByName[name].Swap(&compositeLogger{loggers: ctxLoggers})
}
}
func (lm *logManager) New(ctx ...interface{}) *ConcreteLogger {
if len(ctx) == 0 {
return lm.ConcreteLogger
}
lm.mutex.Lock()
defer lm.mutex.Unlock()
loggerName, ok := ctx[1].(string)
if !ok {
return lm.ConcreteLogger
}
if logger, exists := lm.loggersByName[loggerName]; exists {
return logger
}
if len(lm.logFilters) == 0 {
ctxLogger := newConcreteLogger(&lm.SwapLogger, ctx...)
lm.loggersByName[loggerName] = ctxLogger
return ctxLogger
}
compositeLogger := newCompositeLogger()
for _, logWithFilter := range lm.logFilters {
filterLevel, ok := logWithFilter.filters[loggerName]
if ok {
logWithFilter.val = level.NewFilter(logWithFilter.val, filterLevel)
} else {
logWithFilter.val = level.NewFilter(logWithFilter.val, logWithFilter.maxLevel)
}
compositeLogger.loggers = append(compositeLogger.loggers, logWithFilter.val)
}
ctxLogger := newConcreteLogger(compositeLogger, ctx...)
lm.loggersByName[loggerName] = ctxLogger
return ctxLogger
}
type ConcreteLogger struct {
ctx []interface{}
gokitlog.SwapLogger
}
func newConcreteLogger(logger gokitlog.Logger, ctx ...interface{}) *ConcreteLogger {
var swapLogger gokitlog.SwapLogger
if len(ctx) == 0 {
ctx = []interface{}{}
swapLogger.Swap(logger)
} else {
swapLogger.Swap(gokitlog.With(logger, ctx...))
}
return &ConcreteLogger{
ctx: ctx,
SwapLogger: swapLogger,
}
}
func (cl ConcreteLogger) GetLogger() gokitlog.Logger {
return &cl.SwapLogger
}
func (cl *ConcreteLogger) Warn(msg string, args ...interface{}) {
_ = cl.log(msg, level.WarnValue(), args...)
}
func (cl *ConcreteLogger) Debug(msg string, args ...interface{}) {
_ = cl.log(msg, level.DebugValue(), args...)
}
func (cl *ConcreteLogger) Log(ctx ...interface{}) error {
logger := gokitlog.With(&cl.SwapLogger, "t", gokitlog.TimestampFormat(now, logTimeFormat))
return logger.Log(ctx...)
}
func (cl *ConcreteLogger) Error(msg string, args ...interface{}) {
_ = cl.log(msg, level.ErrorValue(), args...)
}
func (cl *ConcreteLogger) Info(msg string, args ...interface{}) {
_ = cl.log(msg, level.InfoValue(), args...)
}
func (cl *ConcreteLogger) log(msg string, logLevel level.Value, args ...interface{}) error {
return cl.Log(append([]interface{}{level.Key(), logLevel, "msg", msg}, args...)...)
}
func (cl *ConcreteLogger) FromContext(ctx context.Context) Logger {
args := []interface{}{}
for _, p := range ctxLogProviders {
if pArgs, exists := p(ctx); exists {
args = append(args, pArgs...)
}
}
if len(args) > 0 {
return cl.New(args...)
}
return cl
}
func (cl *ConcreteLogger) New(ctx ...interface{}) *ConcreteLogger {
if len(ctx) == 0 {
root.New()
}
return newConcreteLogger(gokitlog.With(&cl.SwapLogger), ctx...)
}
// New creates a new logger.
// First ctx argument is expected to be the name of the logger.
// Note: For a contextual logger, i.e. a logger with a shared
// name plus additional contextual information, you must use the
// Logger interface New method for it to work as expected.
// Example creating a shared logger:
//
// requestLogger := log.New("request-logger")
//
// Example creating a contextual logger:
//
// contextualLogger := requestLogger.New("username", "user123")
func New(ctx ...interface{}) *ConcreteLogger {
if len(ctx) == 0 {
return root.New()
}
ctx = append([]interface{}{"logger"}, ctx...)
return root.New(ctx...)
}
// NewNopLogger returns a logger that doesn't do anything.
func NewNopLogger() *ConcreteLogger {
return newConcreteLogger(gokitlog.NewNopLogger())
}
func with(ctxLogger *ConcreteLogger, withFunc func(gokitlog.Logger, ...interface{}) gokitlog.Logger, ctx []interface{}) *ConcreteLogger {
if len(ctx) == 0 {
return ctxLogger
}
ctxLogger.Swap(withFunc(ctxLogger.GetLogger(), ctx...))
return ctxLogger
}
// WithPrefix adds context that will be added to the log message
func WithPrefix(ctxLogger *ConcreteLogger, ctx ...interface{}) *ConcreteLogger {
return with(ctxLogger, gokitlog.WithPrefix, ctx)
}
// WithSuffix adds context that will be appended at the end of the log message
func WithSuffix(ctxLogger *ConcreteLogger, ctx ...interface{}) *ConcreteLogger {
return with(ctxLogger, gokitlog.WithSuffix, ctx)
}
// ContextualLogProviderFunc contextual log provider function definition.
type ContextualLogProviderFunc func(ctx context.Context) ([]interface{}, bool)
// RegisterContextualLogProvider registers a ContextualLogProviderFunc
// that will be used to provide context when Logger.FromContext is called.
func RegisterContextualLogProvider(mw ContextualLogProviderFunc) {
ctxLogProviders = append(ctxLogProviders, mw)
}
var logLevels = map[string]level.Option{
"trace": level.AllowDebug(),
"debug": level.AllowDebug(),
"info": level.AllowInfo(),
"warn": level.AllowWarn(),
"error": level.AllowError(),
"critical": level.AllowError(),
}
func getLogLevelFromConfig(key string, defaultName string, cfg *ini.File) (string, level.Option) {
levelName := cfg.Section(key).Key("level").MustString(defaultName)
levelName = strings.ToLower(levelName)
level := getLogLevelFromString(levelName)
return levelName, level
}
func getLogLevelFromString(levelName string) level.Option {
loglevel, ok := logLevels[levelName]
if !ok {
_ = level.Error(root).Log("Unknown log level", "level", levelName)
return level.AllowError()
}
return loglevel
}
// the filter is composed with logger name and level
func getFilters(filterStrArray []string) map[string]level.Option {
filterMap := make(map[string]level.Option)
for i := 0; i < len(filterStrArray); i++ {
filterStr := strings.TrimSpace(filterStrArray[i])
if strings.HasPrefix(filterStr, ";") || strings.HasPrefix(filterStr, "#") {
if len(filterStr) == 1 {
i++
}
continue
}
parts := strings.Split(filterStr, ":")
if len(parts) > 1 {
filterMap[parts[0]] = getLogLevelFromString(parts[1])
}
}
return filterMap
}
func Stack(skip int) string {
call := stack.Caller(skip)
s := stack.Trace().TrimBelow(call).TrimRuntime()
return s.String()
}
// StackCaller returns a go-kit Valuer function that returns the stack trace from the place it is called. Argument `skip` allows skipping top n lines from the stack.
func StackCaller(skip int) gokitlog.Valuer {
return func() interface{} {
return Stack(skip + 1)
}
}
// Caller proxies go-kit/log Caller and returns a Valuer function that returns a file and line from a specified depth
// in the callstack
func Caller(depth int) gokitlog.Valuer {
return gokitlog.Caller(depth)
}
type Formatedlogger func(w io.Writer) gokitlog.Logger
func getLogFormat(format string) Formatedlogger {
switch format {
case "console":
if isatty.IsTerminal(os.Stdout.Fd()) {
return func(w io.Writer) gokitlog.Logger {
return term.NewTerminalLogger(w)
}
}
return func(w io.Writer) gokitlog.Logger {
return text.NewTextLogger(w)
}
case "text":
return func(w io.Writer) gokitlog.Logger {
return text.NewTextLogger(w)
}
case "json":
return func(w io.Writer) gokitlog.Logger {
return gokitlog.NewJSONLogger(gokitlog.NewSyncWriter(w))
}
default:
return func(w io.Writer) gokitlog.Logger {
return text.NewTextLogger(w)
}
}
}
// this is for file logger only
func Close() error {
var err error
for _, logger := range loggersToClose {
if e := logger.Close(); e != nil && err == nil {
err = e
}
}
loggersToClose = make([]DisposableHandler, 0)
return err
}
// Reload reloads all loggers.
func Reload() error {
for _, logger := range loggersToReload {
if err := logger.Reload(); err != nil {
return err
}
}
return nil
}
type logWithFilters struct {
val gokitlog.Logger
filters map[string]level.Option
maxLevel level.Option
}
func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) error {
if err := Close(); err != nil {
return err
}
logEnabled := cfg.Section("log").Key("enabled").MustBool(true)
if !logEnabled {
return nil
}
defaultLevelName, _ := getLogLevelFromConfig("log", "info", cfg)
defaultFilters := getFilters(util.SplitString(cfg.Section("log").Key("filters").String()))
var configLoggers []logWithFilters
for _, mode := range modes {
mode = strings.TrimSpace(mode)
sec, err := cfg.GetSection("log." + mode)
if err != nil {
_ = level.Error(root).Log("Unknown log mode", "mode", mode)
return fmt.Errorf("failed to get config section log. %s: %w", mode, err)
}
// Log level.
_, leveloption := getLogLevelFromConfig("log."+mode, defaultLevelName, cfg)
modeFilters := getFilters(util.SplitString(sec.Key("filters").String()))
format := getLogFormat(sec.Key("format").MustString(""))
var handler logWithFilters
switch mode {
case "console":
handler.val = format(os.Stdout)
case "file":
fileName := sec.Key("file_name").MustString(filepath.Join(logsPath, "grafana.log"))
dpath := filepath.Dir(fileName)
if err := os.MkdirAll(dpath, os.ModePerm); err != nil {
_ = level.Error(root).Log("Failed to create directory", "dpath", dpath, "err", err)
continue
}
fileHandler := NewFileWriter()
fileHandler.Filename = fileName
fileHandler.Format = format
fileHandler.Rotate = sec.Key("log_rotate").MustBool(true)
fileHandler.Maxlines = sec.Key("max_lines").MustInt(1000000)
fileHandler.Maxsize = 1 << uint(sec.Key("max_size_shift").MustInt(28))
fileHandler.Daily = sec.Key("daily_rotate").MustBool(true)
fileHandler.Maxdays = sec.Key("max_days").MustInt64(7)
if err := fileHandler.Init(); err != nil {
_ = level.Error(root).Log("Failed to initialize file handler", "dpath", dpath, "err", err)
continue
}
loggersToClose = append(loggersToClose, fileHandler)
loggersToReload = append(loggersToReload, fileHandler)
handler.val = fileHandler
case "syslog":
sysLogHandler := NewSyslog(sec, format)
loggersToClose = append(loggersToClose, sysLogHandler)
handler.val = sysLogHandler.logger
}
if handler.val == nil {
panic(fmt.Sprintf("Handler is uninitialized for mode %q", mode))
}
// join default filters and mode filters together
for key, value := range defaultFilters {
if _, exist := modeFilters[key]; !exist {
modeFilters[key] = value
}
}
handler.filters = modeFilters
handler.maxLevel = leveloption
configLoggers = append(configLoggers, handler)
}
if len(configLoggers) > 0 {
root.initialize(configLoggers)
}
return nil
}