github.com/kyma-incubator/compass/components/director@v0.0.0-20230623144113-d764f56ff805/pkg/log/configure.go (about)

     1  /*
     2   * Copyright 2020 The Compass Authors
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package log
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  	"path/filepath"
    25  	"strings"
    26  	"sync"
    27  
    28  	"github.com/onrik/logrus/filename"
    29  
    30  	"github.com/sirupsen/logrus"
    31  )
    32  
    33  type logKey struct{}
    34  
    35  const (
    36  	// FieldRequestID missing godoc
    37  	FieldRequestID     = "x-request-id"
    38  	fieldComponentName = "component"
    39  	jsonFormatterKey   = "json"
    40  	textFormatterKey   = "text"
    41  )
    42  
    43  var (
    44  	defaultEntry = logrus.NewEntry(logrus.StandardLogger())
    45  
    46  	supportedFormatters = map[string]logrus.Formatter{
    47  		jsonFormatterKey: &logrus.JSONFormatter{},
    48  		textFormatterKey: &logrus.TextFormatter{},
    49  	}
    50  
    51  	supportedOutputs = map[string]io.Writer{
    52  		os.Stdout.Name(): os.Stdout,
    53  		os.Stderr.Name(): os.Stderr,
    54  	}
    55  	mutex           = sync.RWMutex{}
    56  	currentSettings = DefaultConfig()
    57  
    58  	// C missing godoc
    59  	C = LoggerFromContext
    60  	// D missing godoc
    61  	D = DefaultLogger
    62  )
    63  
    64  func init() {
    65  	// Configure default logger in init so we can log even before actual logging settings are loaded
    66  	hook := prepareLogSourceHook()
    67  	AddHook(hook)
    68  	AddHook(&ErrorLocationHook{})
    69  	defaultEntry = defaultEntry.WithField(FieldRequestID, currentSettings.BootstrapCorrelationID)
    70  
    71  	_, err := Configure(context.Background(), currentSettings)
    72  	if err != nil {
    73  		panic(err)
    74  	}
    75  }
    76  
    77  // Configure creates a new context with a logger using the provided settings.
    78  func Configure(ctx context.Context, config *Config) (context.Context, error) {
    79  	mutex.Lock()
    80  	defer mutex.Unlock()
    81  
    82  	if err := config.Validate(); err != nil {
    83  		return nil, err
    84  	}
    85  
    86  	level, err := logrus.ParseLevel(config.Level)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  	formatter := supportedFormatters[config.Format]
    91  	output := supportedOutputs[config.Output]
    92  
    93  	currentSettings = config
    94  
    95  	entry := ctx.Value(logKey{})
    96  	if entry == nil {
    97  		entry = defaultEntry
    98  	} else {
    99  		defaultEntry.Logger.SetOutput(output)
   100  		defaultEntry.Logger.SetFormatter(formatter)
   101  		defaultEntry.Logger.SetLevel(level)
   102  	}
   103  	e := copyEntry(entry.(*logrus.Entry))
   104  	e.Logger.SetLevel(level)
   105  	e.Logger.SetFormatter(formatter)
   106  	e.Logger.SetOutput(output)
   107  
   108  	return ContextWithLogger(ctx, e), nil
   109  }
   110  
   111  // Configuration returns the logger settings
   112  func Configuration() Config {
   113  	mutex.RLock()
   114  	defer mutex.RUnlock()
   115  
   116  	return *currentSettings
   117  }
   118  
   119  // ContextWithLogger returns a new context with the provided logger.
   120  func ContextWithLogger(ctx context.Context, entry *logrus.Entry) context.Context {
   121  	return context.WithValue(ctx, logKey{}, entry)
   122  }
   123  
   124  // LoggerFromContext retrieves the current logger from the context.
   125  func LoggerFromContext(ctx context.Context) *logrus.Entry {
   126  	mutex.RLock()
   127  	defer mutex.RUnlock()
   128  	entry := ctx.Value(logKey{})
   129  	if entry == nil {
   130  		entry = defaultEntry
   131  	}
   132  	return copyEntry(entry.(*logrus.Entry))
   133  }
   134  
   135  // DefaultLogger returns the default logger
   136  func DefaultLogger() *logrus.Entry {
   137  	return LoggerFromContext(context.Background())
   138  }
   139  
   140  // RegisterFormatter registers a new logrus Formatter with the given name.
   141  // Returns an error if there is a formatter with the same name.
   142  func RegisterFormatter(name string, formatter logrus.Formatter) error {
   143  	if _, exists := supportedFormatters[name]; exists {
   144  		return fmt.Errorf("formatter with name %s is already registered", name)
   145  	}
   146  	supportedFormatters[name] = formatter
   147  	return nil
   148  }
   149  
   150  // AddHook adds a hook to all loggers
   151  func AddHook(hook logrus.Hook) {
   152  	defaultEntry.Logger.AddHook(hook)
   153  }
   154  
   155  func copyEntry(entry *logrus.Entry) *logrus.Entry {
   156  	entryData := make(logrus.Fields, len(entry.Data))
   157  	for k, v := range entry.Data {
   158  		entryData[k] = v
   159  	}
   160  
   161  	newEntry := logrus.NewEntry(entry.Logger)
   162  	newEntry.Level = entry.Level
   163  	newEntry.Data = entryData
   164  	newEntry.Time = entry.Time
   165  	newEntry.Message = entry.Message
   166  	newEntry.Buffer = entry.Buffer
   167  
   168  	return newEntry
   169  }
   170  
   171  func prepareLogSourceHook() *filename.Hook {
   172  	hook := filename.NewHook()
   173  	hook.Field = fieldComponentName
   174  	hook.SkipPrefixes = append(hook.SkipPrefixes, "log/")
   175  	hook.Formatter = func(file, function string, line int) string {
   176  		split := strings.Split(function, string(filepath.Separator))
   177  		return fmt.Sprintf("%s:%d:%s", file, line, split[len(split)-1])
   178  	}
   179  
   180  	return hook
   181  }