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 }