log/logger_options.go (114 lines of code) (raw):
package log
import (
"fmt"
"io"
"os"
"time"
"github.com/sirupsen/logrus"
)
const (
iso8601TimestampFormat = "2006-01-02T15:04:05.000Z07:00"
iso8601TimestampEnvKey = "GITLAB_ISO8601_LOG_TIMESTAMP"
)
type loggerConfig struct {
logger *logrus.Logger
level logrus.Level
formatter logrus.Formatter
location *time.Location
outputPath string
writer io.Writer
// A list of warnings that will be emitted once the logger is configured
warnings []string
}
type timezoneFormatter struct {
formatter logrus.Formatter
location *time.Location
}
// LoggerOption will configure a new logrus Logger.
type LoggerOption func(*loggerConfig)
// We default to time.RFC3339 (to match the Logrus default) for the timestamp format for backward compatibility reasons.
// Optionally, users can opt in for ISO8601 with millisecond precision by setting (to any value) the
// iso8601TimestampEnvKey environment variable.
func timestampFormat() string {
if _, exists := os.LookupEnv(iso8601TimestampEnvKey); exists {
return iso8601TimestampFormat
}
return time.RFC3339
}
func applyLoggerOptions(opts []LoggerOption) *loggerConfig {
conf := loggerConfig{
logger: logger,
level: logrus.InfoLevel,
formatter: &logrus.TextFormatter{TimestampFormat: timestampFormat()},
writer: os.Stdout,
}
for _, v := range opts {
v(&conf)
}
return &conf
}
func (l *loggerConfig) buildFormatter() logrus.Formatter {
out := l.formatter
if l.location != nil {
out = &timezoneFormatter{formatter: out, location: l.location}
}
return out
}
func (f *timezoneFormatter) Format(entry *logrus.Entry) ([]byte, error) {
// Avoid mutating the passed-in entry as callers may retain a reference to
// it. Since we don't change the `Data` field, a shallow copy is sufficient.
if entry != nil {
entryCopy := *entry
entryCopy.Time = entryCopy.Time.In(f.location)
entry = &entryCopy
}
return f.formatter.Format(entry)
}
// WithFormatter allows setting the format to `text`, `json`, `color` or `combined`. In case
// the input is not recognized it defaults to text with a warning.
// More details of these formats:
// * `text` - human readable.
// * `json` - computer readable, new-line delimited JSON.
// * `color` - human readable, in color. Useful for development.
// * `combined` - httpd access logs. Good for legacy access log parsers.
func WithFormatter(format string) LoggerOption {
return func(conf *loggerConfig) {
timestampFormat := timestampFormat()
switch format {
case "text":
conf.formatter = &logrus.TextFormatter{TimestampFormat: timestampFormat}
case "color":
conf.formatter = &logrus.TextFormatter{TimestampFormat: timestampFormat, ForceColors: true, EnvironmentOverrideColors: true}
case "json":
conf.formatter = &logrus.JSONFormatter{TimestampFormat: timestampFormat}
case "combined":
conf.formatter = newCombinedcombinedAccessLogFormatter()
default:
conf.warnings = append(conf.warnings, fmt.Sprintf("unknown logging format %s, ignoring option", format))
}
}
}
// WithTimezone allows setting the timezone that will be used for log messages.
// The default behaviour is to use the local timezone, but a specific timezone
// (such as time.UTC) may be preferred.
func WithTimezone(location *time.Location) LoggerOption {
return func(conf *loggerConfig) {
conf.location = location
}
}
// WithLogLevel is used to set the log level when defaulting to `info` is not
// wanted. Other options are: `debug`, `warn`, `error`, `fatal`, and `panic`.
func WithLogLevel(level string) LoggerOption {
return func(conf *loggerConfig) {
logrusLevel, err := logrus.ParseLevel(level)
if err != nil {
conf.warnings = append(conf.warnings, fmt.Sprintf("unknown log level, ignoring option: %v", err))
} else {
conf.level = logrusLevel
}
}
}
// WithOutputName allows customization of the sink of the logger. Output is either:
// `stdout`, `stderr`, or a path to a file.
func WithOutputName(outputName string) LoggerOption {
return func(conf *loggerConfig) {
switch outputName {
case "stdout":
conf.writer = os.Stdout
case "stderr":
conf.writer = os.Stderr
default:
conf.writer = nil
conf.outputPath = outputName
}
}
}
// WithWriter allows the writer to be customized. The application is responsible for closing the writer manually.
func WithWriter(writer io.Writer) LoggerOption {
return func(conf *loggerConfig) {
conf.writer = writer
}
}
// WithLogger allows you to configure a proprietary logger using the `Initialize` method.
func WithLogger(logger *logrus.Logger) LoggerOption {
return func(conf *loggerConfig) {
conf.logger = logger
}
}