// Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MIT package hclog import ( "io" "log" "os" "strings" "time" ) var ( // DefaultOutput is used as the default log output. DefaultOutput io.Writer = os.Stderr // DefaultLevel is used as the default log level. DefaultLevel = Info ) // Level represents a log level. type Level int32 const ( // NoLevel is a special level used to indicate that no level has been // set and allow for a default to be used. NoLevel Level = 0 // Trace is the most verbose level. Intended to be used for the tracing // of actions in code, such as function enters/exits, etc. Trace Level = 1 // Debug information for programmer low-level analysis. Debug Level = 2 // Info information about steady state operations. Info Level = 3 // Warn information about rare but handled events. Warn Level = 4 // Error information about unrecoverable events. Error Level = 5 // Off disables all logging output. Off Level = 6 ) // Format is a simple convenience type for when formatting is required. When // processing a value of this type, the logger automatically treats the first // argument as a Printf formatting string and passes the rest as the values // to be formatted. For example: L.Info(Fmt{"%d beans/day", beans}). type Format []interface{} // Fmt returns a Format type. This is a convenience function for creating a Format // type. func Fmt(str string, args ...interface{}) Format { return append(Format{str}, args...) } // A simple shortcut to format numbers in hex when displayed with the normal // text output. For example: L.Info("header value", Hex(17)) type Hex int // A simple shortcut to format numbers in octal when displayed with the normal // text output. For example: L.Info("perms", Octal(17)) type Octal int // A simple shortcut to format numbers in binary when displayed with the normal // text output. For example: L.Info("bits", Binary(17)) type Binary int // A simple shortcut to format strings with Go quoting. Control and // non-printable characters will be escaped with their backslash equivalents in // output. Intended for untrusted or multiline strings which should be logged // as concisely as possible. type Quote string // ColorOption expresses how the output should be colored, if at all. type ColorOption uint8 const ( // ColorOff is the default coloration, and does not // inject color codes into the io.Writer. ColorOff ColorOption = iota // AutoColor checks if the io.Writer is a tty, // and if so enables coloring. AutoColor // ForceColor will enable coloring, regardless of whether // the io.Writer is a tty or not. ForceColor ) // SupportsColor is an optional interface that can be implemented by the output // value. If implemented and SupportsColor() returns true, then AutoColor will // enable colorization. type SupportsColor interface { SupportsColor() bool } // LevelFromString returns a Level type for the named log level, or "NoLevel" if // the level string is invalid. This facilitates setting the log level via // config or environment variable by name in a predictable way. func LevelFromString(levelStr string) Level { // We don't care about case. Accept both "INFO" and "info". levelStr = strings.ToLower(strings.TrimSpace(levelStr)) switch levelStr { case "trace": return Trace case "debug": return Debug case "info": return Info case "warn": return Warn case "error": return Error case "off": return Off default: return NoLevel } } func (l Level) String() string { switch l { case Trace: return "trace" case Debug: return "debug" case Info: return "info" case Warn: return "warn" case Error: return "error" case NoLevel: return "none" case Off: return "off" default: return "unknown" } } // Logger describes the interface that must be implemented by all loggers. type Logger interface { // Args are alternating key, val pairs // keys must be strings // vals can be any type, but display is implementation specific // Emit a message and key/value pairs at a provided log level Log(level Level, msg string, args ...interface{}) // Emit a message and key/value pairs at the TRACE level Trace(msg string, args ...interface{}) // Emit a message and key/value pairs at the DEBUG level Debug(msg string, args ...interface{}) // Emit a message and key/value pairs at the INFO level Info(msg string, args ...interface{}) // Emit a message and key/value pairs at the WARN level Warn(msg string, args ...interface{}) // Emit a message and key/value pairs at the ERROR level Error(msg string, args ...interface{}) // Indicate if TRACE logs would be emitted. This and the other Is* guards // are used to elide expensive logging code based on the current level. IsTrace() bool // Indicate if DEBUG logs would be emitted. This and the other Is* guards IsDebug() bool // Indicate if INFO logs would be emitted. This and the other Is* guards IsInfo() bool // Indicate if WARN logs would be emitted. This and the other Is* guards IsWarn() bool // Indicate if ERROR logs would be emitted. This and the other Is* guards IsError() bool // ImpliedArgs returns With key/value pairs ImpliedArgs() []interface{} // Creates a sublogger that will always have the given key/value pairs With(args ...interface{}) Logger // Returns the Name of the logger Name() string // Create a logger that will prepend the name string on the front of all messages. // If the logger already has a name, the new value will be appended to the current // name. That way, a major subsystem can use this to decorate all it's own logs // without losing context. Named(name string) Logger // Create a logger that will prepend the name string on the front of all messages. // This sets the name of the logger to the value directly, unlike Named which honor // the current name as well. ResetNamed(name string) Logger // Updates the level. This should affect all related loggers as well, // unless they were created with IndependentLevels. If an // implementation cannot update the level on the fly, it should no-op. SetLevel(level Level) // Returns the current level GetLevel() Level // Return a value that conforms to the stdlib log.Logger interface StandardLogger(opts *StandardLoggerOptions) *log.Logger // Return a value that conforms to io.Writer, which can be passed into log.SetOutput() StandardWriter(opts *StandardLoggerOptions) io.Writer } // StandardLoggerOptions can be used to configure a new standard logger. type StandardLoggerOptions struct { // Indicate that some minimal parsing should be done on strings to try // and detect their level and re-emit them. // This supports the strings like [ERROR], [ERR] [TRACE], [WARN], [INFO], // [DEBUG] and strip it off before reapplying it. InferLevels bool // Indicate that some minimal parsing should be done on strings to try // and detect their level and re-emit them while ignoring possible // timestamp values in the beginning of the string. // This supports the strings like [ERROR], [ERR] [TRACE], [WARN], [INFO], // [DEBUG] and strip it off before reapplying it. // The timestamp detection may result in false positives and incomplete // string outputs. // InferLevelsWithTimestamp is only relevant if InferLevels is true. InferLevelsWithTimestamp bool // ForceLevel is used to force all output from the standard logger to be at // the specified level. Similar to InferLevels, this will strip any level // prefix contained in the logged string before applying the forced level. // If set, this override InferLevels. ForceLevel Level } type TimeFunction = func() time.Time // LoggerOptions can be used to configure a new logger. type LoggerOptions struct { // Name of the subsystem to prefix logs with Name string // The threshold for the logger. Anything less severe is suppressed Level Level // Where to write the logs to. Defaults to os.Stderr if nil Output io.Writer // An optional Locker in case Output is shared. This can be a sync.Mutex or // a NoopLocker if the caller wants control over output, e.g. for batching // log lines. Mutex Locker // Control if the output should be in JSON. JSONFormat bool // Include file and line information in each log line IncludeLocation bool // AdditionalLocationOffset is the number of additional stack levels to skip // when finding the file and line information for the log line AdditionalLocationOffset int // The time format to use instead of the default TimeFormat string // A function which is called to get the time object that is formatted using `TimeFormat` TimeFn TimeFunction // Control whether or not to display the time at all. This is required // because setting TimeFormat to empty assumes the default format. DisableTime bool // Color the output. On Windows, colored logs are only available for io.Writers that // are concretely instances of *os.File. Color ColorOption // Only color the header, not the body. This can help with readability of long messages. ColorHeaderOnly bool // Color the header and message body fields. This can help with readability // of long messages with multiple fields. ColorHeaderAndFields bool // A function which is called with the log information and if it returns true the value // should not be logged. // This is useful when interacting with a system that you wish to suppress the log // message for (because it's too noisy, etc) Exclude func(level Level, msg string, args ...interface{}) bool // IndependentLevels causes subloggers to be created with an independent // copy of this logger's level. This means that using SetLevel on this // logger will not affect any subloggers, and SetLevel on any subloggers // will not affect the parent or sibling loggers. IndependentLevels bool // When set, changing the level of a logger effects only it's direct sub-loggers // rather than all sub-loggers. For example: // a := logger.Named("a") // a.SetLevel(Error) // b := a.Named("b") // c := a.Named("c") // b.GetLevel() => Error // c.GetLevel() => Error // b.SetLevel(Info) // a.GetLevel() => Error // b.GetLevel() => Info // c.GetLevel() => Error // a.SetLevel(Warn) // a.GetLevel() => Warn // b.GetLevel() => Warn // c.GetLevel() => Warn SyncParentLevel bool // SubloggerHook registers a function that is called when a sublogger via // Named, With, or ResetNamed is created. If defined, the function is passed // the newly created Logger and the returned Logger is returned from the // original function. This option allows customization via interception and // wrapping of Logger instances. SubloggerHook func(sub Logger) Logger } // InterceptLogger describes the interface for using a logger // that can register different output sinks. // This is useful for sending lower level log messages // to a different output while keeping the root logger // at a higher one. type InterceptLogger interface { // Logger is the root logger for an InterceptLogger Logger // RegisterSink adds a SinkAdapter to the InterceptLogger RegisterSink(sink SinkAdapter) // DeregisterSink removes a SinkAdapter from the InterceptLogger DeregisterSink(sink SinkAdapter) // Create a interceptlogger that will prepend the name string on the front of all messages. // If the logger already has a name, the new value will be appended to the current // name. That way, a major subsystem can use this to decorate all it's own logs // without losing context. NamedIntercept(name string) InterceptLogger // Create a interceptlogger that will prepend the name string on the front of all messages. // This sets the name of the logger to the value directly, unlike Named which honor // the current name as well. ResetNamedIntercept(name string) InterceptLogger // Deprecated: use StandardLogger StandardLoggerIntercept(opts *StandardLoggerOptions) *log.Logger // Deprecated: use StandardWriter StandardWriterIntercept(opts *StandardLoggerOptions) io.Writer } // SinkAdapter describes the interface that must be implemented // in order to Register a new sink to an InterceptLogger type SinkAdapter interface { Accept(name string, level Level, msg string, args ...interface{}) } // Flushable represents a method for flushing an output buffer. It can be used // if Resetting the log to use a new output, in order to flush the writes to // the existing output beforehand. type Flushable interface { Flush() error } // OutputResettable provides ways to swap the output in use at runtime type OutputResettable interface { // ResetOutput swaps the current output writer with the one given in the // opts. Color options given in opts will be used for the new output. ResetOutput(opts *LoggerOptions) error // ResetOutputWithFlush swaps the current output writer with the one given // in the opts, first calling Flush on the given Flushable. Color options // given in opts will be used for the new output. ResetOutputWithFlush(opts *LoggerOptions, flushable Flushable) error } // Locker is used for locking output. If not set when creating a logger, a // sync.Mutex will be used internally. type Locker interface { // Lock is called when the output is going to be changed or written to Lock() // Unlock is called when the operation that called Lock() completes Unlock() } // NoopLocker implements locker but does nothing. This is useful if the client // wants tight control over locking, in order to provide grouping of log // entries or other functionality. type NoopLocker struct{} // Lock does nothing func (n NoopLocker) Lock() {} // Unlock does nothing func (n NoopLocker) Unlock() {} var _ Locker = (*NoopLocker)(nil)