gitlab.com/gitlab-org/labkit@v1.21.0/log/logger_options.go (about)

     1  package log
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"time"
     8  
     9  	"github.com/sirupsen/logrus"
    10  )
    11  
    12  const (
    13  	iso8601TimestampFormat = "2006-01-02T15:04:05.000Z07:00"
    14  	iso8601TimestampEnvKey = "GITLAB_ISO8601_LOG_TIMESTAMP"
    15  )
    16  
    17  type loggerConfig struct {
    18  	logger     *logrus.Logger
    19  	level      logrus.Level
    20  	formatter  logrus.Formatter
    21  	location   *time.Location
    22  	outputPath string
    23  	writer     io.Writer
    24  
    25  	// A list of warnings that will be emitted once the logger is configured
    26  	warnings []string
    27  }
    28  
    29  type timezoneFormatter struct {
    30  	formatter logrus.Formatter
    31  	location  *time.Location
    32  }
    33  
    34  // LoggerOption will configure a new logrus Logger.
    35  type LoggerOption func(*loggerConfig)
    36  
    37  // We default to time.RFC3339 (to match the Logrus default) for the timestamp format for backward compatibility reasons.
    38  // Optionally, users can opt in for ISO8601 with millisecond precision by setting (to any value) the
    39  // iso8601TimestampEnvKey environment variable.
    40  func timestampFormat() string {
    41  	if _, exists := os.LookupEnv(iso8601TimestampEnvKey); exists {
    42  		return iso8601TimestampFormat
    43  	}
    44  	return time.RFC3339
    45  }
    46  
    47  func applyLoggerOptions(opts []LoggerOption) *loggerConfig {
    48  	conf := loggerConfig{
    49  		logger:    logger,
    50  		level:     logrus.InfoLevel,
    51  		formatter: &logrus.TextFormatter{TimestampFormat: timestampFormat()},
    52  		writer:    os.Stdout,
    53  	}
    54  
    55  	for _, v := range opts {
    56  		v(&conf)
    57  	}
    58  
    59  	return &conf
    60  }
    61  
    62  func (l *loggerConfig) buildFormatter() logrus.Formatter {
    63  	out := l.formatter
    64  
    65  	if l.location != nil {
    66  		out = &timezoneFormatter{formatter: out, location: l.location}
    67  	}
    68  
    69  	return out
    70  }
    71  
    72  func (f *timezoneFormatter) Format(entry *logrus.Entry) ([]byte, error) {
    73  	// Avoid mutating the passed-in entry as callers may retain a reference to
    74  	// it. Since we don't change the `Data` field, a shallow copy is sufficient.
    75  	if entry != nil {
    76  		entryCopy := *entry
    77  		entryCopy.Time = entryCopy.Time.In(f.location)
    78  
    79  		entry = &entryCopy
    80  	}
    81  
    82  	return f.formatter.Format(entry)
    83  }
    84  
    85  // WithFormatter allows setting the format to `text`, `json`, `color` or `combined`. In case
    86  // the input is not recognized it defaults to text with a warning.
    87  // More details of these formats:
    88  // * `text` - human readable.
    89  // * `json` - computer readable, new-line delimited JSON.
    90  // * `color` - human readable, in color. Useful for development.
    91  // * `combined` - httpd access logs. Good for legacy access log parsers.
    92  func WithFormatter(format string) LoggerOption {
    93  	return func(conf *loggerConfig) {
    94  		timestampFormat := timestampFormat()
    95  		switch format {
    96  		case "text":
    97  			conf.formatter = &logrus.TextFormatter{TimestampFormat: timestampFormat}
    98  		case "color":
    99  			conf.formatter = &logrus.TextFormatter{TimestampFormat: timestampFormat, ForceColors: true, EnvironmentOverrideColors: true}
   100  		case "json":
   101  			conf.formatter = &logrus.JSONFormatter{TimestampFormat: timestampFormat}
   102  		case "combined":
   103  			conf.formatter = newCombinedcombinedAccessLogFormatter()
   104  		default:
   105  			conf.warnings = append(conf.warnings, fmt.Sprintf("unknown logging format %s, ignoring option", format))
   106  		}
   107  	}
   108  }
   109  
   110  // WithTimezone allows setting the timezone that will be used for log messages.
   111  // The default behaviour is to use the local timezone, but a specific timezone
   112  // (such as time.UTC) may be preferred.
   113  func WithTimezone(location *time.Location) LoggerOption {
   114  	return func(conf *loggerConfig) {
   115  		conf.location = location
   116  	}
   117  }
   118  
   119  // WithLogLevel is used to set the log level when defaulting to `info` is not
   120  // wanted. Other options are: `debug`, `warn`, `error`, `fatal`, and `panic`.
   121  func WithLogLevel(level string) LoggerOption {
   122  	return func(conf *loggerConfig) {
   123  		logrusLevel, err := logrus.ParseLevel(level)
   124  		if err != nil {
   125  			conf.warnings = append(conf.warnings, fmt.Sprintf("unknown log level, ignoring option: %v", err))
   126  		} else {
   127  			conf.level = logrusLevel
   128  		}
   129  	}
   130  }
   131  
   132  // WithOutputName allows customization of the sink of the logger. Output is either:
   133  // `stdout`, `stderr`, or a path to a file.
   134  func WithOutputName(outputName string) LoggerOption {
   135  	return func(conf *loggerConfig) {
   136  		switch outputName {
   137  		case "stdout":
   138  			conf.writer = os.Stdout
   139  		case "stderr":
   140  			conf.writer = os.Stderr
   141  		default:
   142  			conf.writer = nil
   143  			conf.outputPath = outputName
   144  		}
   145  	}
   146  }
   147  
   148  // WithWriter allows the writer to be customized. The application is responsible for closing the writer manually.
   149  func WithWriter(writer io.Writer) LoggerOption {
   150  	return func(conf *loggerConfig) {
   151  		conf.writer = writer
   152  	}
   153  }
   154  
   155  // WithLogger allows you to configure a proprietary logger using the `Initialize` method.
   156  func WithLogger(logger *logrus.Logger) LoggerOption {
   157  	return func(conf *loggerConfig) {
   158  		conf.logger = logger
   159  	}
   160  }