Adds nomad monitor command

Adds nomad monitor command. Like consul monitor, this command allows you
to stream logs from a nomad agent in real time with a a specified log
level

add endpoint tests

Upgrade go-hclog to latest version

The current version of go-hclog pads log prefixes to equal lengths
so info becomes [INFO ] and debug becomes [DEBUG]. This breaks
hashicorp/logutils/level.go Check function. Upgrading to the latest
version removes this padding and fixes log filtering that uses logutils
Check
This commit is contained in:
Drew Bailey
2019-10-07 16:41:52 -04:00
parent dc3286481a
commit 74cfdf55bb
19 changed files with 673 additions and 205 deletions

View File

@@ -128,6 +128,21 @@ stdLogger.Printf("[DEBUG] %+v", stdLogger)
... [DEBUG] my-app: &{mu:{state:0 sema:0} prefix: flag:0 out:0xc42000a0a0 buf:[]}
```
Alternatively, you may configure the system-wide logger:
```go
// log the standard logger from 'import "log"'
log.SetOutput(appLogger.Writer(&hclog.StandardLoggerOptions{InferLevels: true}))
log.SetPrefix("")
log.SetFlags(0)
log.Printf("[DEBUG] %d", 42)
```
```text
... [DEBUG] my-app: 42
```
Notice that if `appLogger` is initialized with the `INFO` log level _and_ you
specify `InferLevels: true`, you will not see any output here. You must change
`appLogger` to `DEBUG` to see output. See the docs for more information.

38
vendor/github.com/hashicorp/go-hclog/context.go generated vendored Normal file
View File

@@ -0,0 +1,38 @@
package hclog
import (
"context"
)
// WithContext inserts a logger into the context and is retrievable
// with FromContext. The optional args can be set with the same syntax as
// Logger.With to set fields on the inserted logger. This will not modify
// the logger argument in-place.
func WithContext(ctx context.Context, logger Logger, args ...interface{}) context.Context {
// While we could call logger.With even with zero args, we have this
// check to avoid unnecessary allocations around creating a copy of a
// logger.
if len(args) > 0 {
logger = logger.With(args...)
}
return context.WithValue(ctx, contextKey, logger)
}
// FromContext returns a logger from the context. This will return L()
// (the default logger) if no logger is found in the context. Therefore,
// this will never return a nil value.
func FromContext(ctx context.Context) Logger {
logger, _ := ctx.Value(contextKey).(Logger)
if logger == nil {
return L()
}
return logger
}
// Unexported new type so that our context key never collides with another.
type contextKeyType struct{}
// contextKey is the key used for the context to store the logger.
var contextKey = contextKeyType{}

View File

@@ -8,27 +8,55 @@ var (
protect sync.Once
def Logger
// The options used to create the Default logger. These are
// read only when the Default logger is created, so set them
// as soon as the process starts.
// DefaultOptions is used to create the Default logger. These are read
// only when the Default logger is created, so set them as soon as the
// process starts.
DefaultOptions = &LoggerOptions{
Level: DefaultLevel,
Output: DefaultOutput,
}
)
// Return a logger that is held globally. This can be a good starting
// Default returns a globally held logger. This can be a good starting
// place, and then you can use .With() and .Name() to create sub-loggers
// to be used in more specific contexts.
// The value of the Default logger can be set via SetDefault() or by
// changing the options in DefaultOptions.
//
// This method is goroutine safe, returning a global from memory, but
// cause should be used if SetDefault() is called it random times
// in the program as that may result in race conditions and an unexpected
// Logger being returned.
func Default() Logger {
protect.Do(func() {
def = New(DefaultOptions)
// If SetDefault was used before Default() was called, we need to
// detect that here.
if def == nil {
def = New(DefaultOptions)
}
})
return def
}
// A short alias for Default()
// L is a short alias for Default().
func L() Logger {
return Default()
}
// SetDefault changes the logger to be returned by Default()and L()
// to the one given. This allows packages to use the default logger
// and have higher level packages change it to match the execution
// environment. It returns any old default if there is one.
//
// NOTE: This is expected to be called early in the program to setup
// a default logger. As such, it does not attempt to make itself
// not racy with regard to the value of the default logger. Ergo
// if it is called in goroutines, you may experience race conditions
// with other goroutines retrieving the default logger. Basically,
// don't do that.
func SetDefault(log Logger) Logger {
old := def
def = log
return old
}

7
vendor/github.com/hashicorp/go-hclog/go.mod generated vendored Normal file
View File

@@ -0,0 +1,7 @@
module github.com/hashicorp/go-hclog
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.2.2
)

6
vendor/github.com/hashicorp/go-hclog/go.sum generated vendored Normal file
View File

@@ -0,0 +1,6 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=

View File

@@ -1,12 +1,13 @@
package hclog
import (
"bufio"
"bytes"
"encoding"
"encoding/json"
"fmt"
"io"
"log"
"os"
"reflect"
"runtime"
"sort"
"strconv"
@@ -16,17 +17,44 @@ import (
"time"
)
// TimeFormat to use for logging. This is a version of RFC3339 that contains
// contains millisecond precision
const TimeFormat = "2006-01-02T15:04:05.000Z0700"
// errJsonUnsupportedTypeMsg is included in log json entries, if an arg cannot be serialized to json
const errJsonUnsupportedTypeMsg = "logging contained values that don't serialize to json"
var (
_levelToBracket = map[Level]string{
Debug: "[DEBUG]",
Trace: "[TRACE]",
Info: "[INFO ]",
Warn: "[WARN ]",
Info: "[INFO] ",
Warn: "[WARN] ",
Error: "[ERROR]",
}
)
// Given the options (nil for defaults), create a new Logger
// Make sure that intLogger is a Logger
var _ Logger = &intLogger{}
// intLogger is an internal logger implementation. Internal in that it is
// defined entirely by this package.
type intLogger struct {
json bool
caller bool
name string
timeFormat string
// This is a pointer so that it's shared by any derived loggers, since
// those derived loggers share the bufio.Writer as well.
mutex *sync.Mutex
writer *writer
level *int32
implied []interface{}
}
// New returns a configured logger.
func New(opts *LoggerOptions) Logger {
if opts == nil {
opts = &LoggerOptions{}
@@ -34,7 +62,7 @@ func New(opts *LoggerOptions) Logger {
output := opts.Output
if output == nil {
output = os.Stderr
output = DefaultOutput
}
level := opts.Level
@@ -42,70 +70,49 @@ func New(opts *LoggerOptions) Logger {
level = DefaultLevel
}
mtx := opts.Mutex
if mtx == nil {
mtx = new(sync.Mutex)
mutex := opts.Mutex
if mutex == nil {
mutex = new(sync.Mutex)
}
ret := &intLogger{
m: mtx,
l := &intLogger{
json: opts.JSONFormat,
caller: opts.IncludeLocation,
name: opts.Name,
timeFormat: TimeFormat,
w: bufio.NewWriter(output),
mutex: mutex,
writer: newWriter(output),
level: new(int32),
}
if opts.TimeFormat != "" {
ret.timeFormat = opts.TimeFormat
l.timeFormat = opts.TimeFormat
}
atomic.StoreInt32(ret.level, int32(level))
return ret
atomic.StoreInt32(l.level, int32(level))
return l
}
// The internal logger implementation. Internal in that it is defined entirely
// by this package.
type intLogger struct {
json bool
caller bool
name string
timeFormat string
// this is a pointer so that it's shared by any derived loggers, since
// those derived loggers share the bufio.Writer as well.
m *sync.Mutex
w *bufio.Writer
level *int32
implied []interface{}
}
// Make sure that intLogger is a Logger
var _ Logger = &intLogger{}
// The time format to use for logging. This is a version of RFC3339 that
// contains millisecond precision
const TimeFormat = "2006-01-02T15:04:05.000Z0700"
// Log a message and a set of key/value pairs if the given level is at
// or more severe that the threshold configured in the Logger.
func (z *intLogger) Log(level Level, msg string, args ...interface{}) {
if level < Level(atomic.LoadInt32(z.level)) {
func (l *intLogger) Log(level Level, msg string, args ...interface{}) {
if level < Level(atomic.LoadInt32(l.level)) {
return
}
t := time.Now()
z.m.Lock()
defer z.m.Unlock()
l.mutex.Lock()
defer l.mutex.Unlock()
if z.json {
z.logJson(t, level, msg, args...)
if l.json {
l.logJSON(t, level, msg, args...)
} else {
z.log(t, level, msg, args...)
l.log(t, level, msg, args...)
}
z.w.Flush()
l.writer.Flush(level)
}
// Cleanup a path by returning the last 2 segments of the path only.
@@ -121,10 +128,8 @@ func trimCallerPath(path string) string {
// and https://github.com/golang/go/issues/18151
//
// for discussion on the issue on Go side.
//
// Find the last separator.
//
idx := strings.LastIndexByte(path, '/')
if idx == -1 {
return path
@@ -140,37 +145,37 @@ func trimCallerPath(path string) string {
}
// Non-JSON logging format function
func (z *intLogger) log(t time.Time, level Level, msg string, args ...interface{}) {
z.w.WriteString(t.Format(z.timeFormat))
z.w.WriteByte(' ')
func (l *intLogger) log(t time.Time, level Level, msg string, args ...interface{}) {
l.writer.WriteString(t.Format(l.timeFormat))
l.writer.WriteByte(' ')
s, ok := _levelToBracket[level]
if ok {
z.w.WriteString(s)
l.writer.WriteString(s)
} else {
z.w.WriteString("[UNKN ]")
l.writer.WriteString("[?????]")
}
if z.caller {
if l.caller {
if _, file, line, ok := runtime.Caller(3); ok {
z.w.WriteByte(' ')
z.w.WriteString(trimCallerPath(file))
z.w.WriteByte(':')
z.w.WriteString(strconv.Itoa(line))
z.w.WriteByte(':')
l.writer.WriteByte(' ')
l.writer.WriteString(trimCallerPath(file))
l.writer.WriteByte(':')
l.writer.WriteString(strconv.Itoa(line))
l.writer.WriteByte(':')
}
}
z.w.WriteByte(' ')
l.writer.WriteByte(' ')
if z.name != "" {
z.w.WriteString(z.name)
z.w.WriteString(": ")
if l.name != "" {
l.writer.WriteString(l.name)
l.writer.WriteString(": ")
}
z.w.WriteString(msg)
l.writer.WriteString(msg)
args = append(z.implied, args...)
args = append(l.implied, args...)
var stacktrace CapturedStacktrace
@@ -185,11 +190,14 @@ func (z *intLogger) log(t time.Time, level Level, msg string, args ...interface{
}
}
z.w.WriteByte(':')
l.writer.WriteByte(':')
FOR:
for i := 0; i < len(args); i = i + 2 {
var val string
var (
val string
raw bool
)
switch st := args[i+1].(type) {
case string:
@@ -220,66 +228,79 @@ func (z *intLogger) log(t time.Time, level Level, msg string, args ...interface{
case Format:
val = fmt.Sprintf(st[0].(string), st[1:]...)
default:
val = fmt.Sprintf("%v", st)
v := reflect.ValueOf(st)
if v.Kind() == reflect.Slice {
val = l.renderSlice(v)
raw = true
} else {
val = fmt.Sprintf("%v", st)
}
}
z.w.WriteByte(' ')
z.w.WriteString(args[i].(string))
z.w.WriteByte('=')
l.writer.WriteByte(' ')
l.writer.WriteString(args[i].(string))
l.writer.WriteByte('=')
if strings.ContainsAny(val, " \t\n\r") {
z.w.WriteByte('"')
z.w.WriteString(val)
z.w.WriteByte('"')
if !raw && strings.ContainsAny(val, " \t\n\r") {
l.writer.WriteByte('"')
l.writer.WriteString(val)
l.writer.WriteByte('"')
} else {
z.w.WriteString(val)
l.writer.WriteString(val)
}
}
}
z.w.WriteString("\n")
l.writer.WriteString("\n")
if stacktrace != "" {
z.w.WriteString(string(stacktrace))
l.writer.WriteString(string(stacktrace))
}
}
// JSON logging function
func (z *intLogger) logJson(t time.Time, level Level, msg string, args ...interface{}) {
vals := map[string]interface{}{
"@message": msg,
"@timestamp": t.Format("2006-01-02T15:04:05.000000Z07:00"),
}
func (l *intLogger) renderSlice(v reflect.Value) string {
var buf bytes.Buffer
var levelStr string
switch level {
case Error:
levelStr = "error"
case Warn:
levelStr = "warn"
case Info:
levelStr = "info"
case Debug:
levelStr = "debug"
case Trace:
levelStr = "trace"
default:
levelStr = "all"
}
buf.WriteRune('[')
vals["@level"] = levelStr
for i := 0; i < v.Len(); i++ {
if i > 0 {
buf.WriteString(", ")
}
if z.name != "" {
vals["@module"] = z.name
}
sv := v.Index(i)
if z.caller {
if _, file, line, ok := runtime.Caller(3); ok {
vals["@caller"] = fmt.Sprintf("%s:%d", file, line)
var val string
switch sv.Kind() {
case reflect.String:
val = sv.String()
case reflect.Int, reflect.Int16, reflect.Int32, reflect.Int64:
val = strconv.FormatInt(sv.Int(), 10)
case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
val = strconv.FormatUint(sv.Uint(), 10)
default:
val = fmt.Sprintf("%v", sv.Interface())
}
if strings.ContainsAny(val, " \t\n\r") {
buf.WriteByte('"')
buf.WriteString(val)
buf.WriteByte('"')
} else {
buf.WriteString(val)
}
}
args = append(z.implied, args...)
buf.WriteRune(']')
return buf.String()
}
// JSON logging function
func (l *intLogger) logJSON(t time.Time, level Level, msg string, args ...interface{}) {
vals := l.jsonMapEntry(t, level, msg)
args = append(l.implied, args...)
if args != nil && len(args) > 0 {
if len(args)%2 != 0 {
@@ -317,80 +338,121 @@ func (z *intLogger) logJson(t time.Time, level Level, msg string, args ...interf
}
}
err := json.NewEncoder(z.w).Encode(vals)
err := json.NewEncoder(l.writer).Encode(vals)
if err != nil {
panic(err)
if _, ok := err.(*json.UnsupportedTypeError); ok {
plainVal := l.jsonMapEntry(t, level, msg)
plainVal["@warn"] = errJsonUnsupportedTypeMsg
json.NewEncoder(l.writer).Encode(plainVal)
}
}
}
func (l intLogger) jsonMapEntry(t time.Time, level Level, msg string) map[string]interface{} {
vals := map[string]interface{}{
"@message": msg,
"@timestamp": t.Format("2006-01-02T15:04:05.000000Z07:00"),
}
var levelStr string
switch level {
case Error:
levelStr = "error"
case Warn:
levelStr = "warn"
case Info:
levelStr = "info"
case Debug:
levelStr = "debug"
case Trace:
levelStr = "trace"
default:
levelStr = "all"
}
vals["@level"] = levelStr
if l.name != "" {
vals["@module"] = l.name
}
if l.caller {
if _, file, line, ok := runtime.Caller(4); ok {
vals["@caller"] = fmt.Sprintf("%s:%d", file, line)
}
}
return vals
}
// Emit the message and args at DEBUG level
func (z *intLogger) Debug(msg string, args ...interface{}) {
z.Log(Debug, msg, args...)
func (l *intLogger) Debug(msg string, args ...interface{}) {
l.Log(Debug, msg, args...)
}
// Emit the message and args at TRACE level
func (z *intLogger) Trace(msg string, args ...interface{}) {
z.Log(Trace, msg, args...)
func (l *intLogger) Trace(msg string, args ...interface{}) {
l.Log(Trace, msg, args...)
}
// Emit the message and args at INFO level
func (z *intLogger) Info(msg string, args ...interface{}) {
z.Log(Info, msg, args...)
func (l *intLogger) Info(msg string, args ...interface{}) {
l.Log(Info, msg, args...)
}
// Emit the message and args at WARN level
func (z *intLogger) Warn(msg string, args ...interface{}) {
z.Log(Warn, msg, args...)
func (l *intLogger) Warn(msg string, args ...interface{}) {
l.Log(Warn, msg, args...)
}
// Emit the message and args at ERROR level
func (z *intLogger) Error(msg string, args ...interface{}) {
z.Log(Error, msg, args...)
func (l *intLogger) Error(msg string, args ...interface{}) {
l.Log(Error, msg, args...)
}
// Indicate that the logger would emit TRACE level logs
func (z *intLogger) IsTrace() bool {
return Level(atomic.LoadInt32(z.level)) == Trace
func (l *intLogger) IsTrace() bool {
return Level(atomic.LoadInt32(l.level)) == Trace
}
// Indicate that the logger would emit DEBUG level logs
func (z *intLogger) IsDebug() bool {
return Level(atomic.LoadInt32(z.level)) <= Debug
func (l *intLogger) IsDebug() bool {
return Level(atomic.LoadInt32(l.level)) <= Debug
}
// Indicate that the logger would emit INFO level logs
func (z *intLogger) IsInfo() bool {
return Level(atomic.LoadInt32(z.level)) <= Info
func (l *intLogger) IsInfo() bool {
return Level(atomic.LoadInt32(l.level)) <= Info
}
// Indicate that the logger would emit WARN level logs
func (z *intLogger) IsWarn() bool {
return Level(atomic.LoadInt32(z.level)) <= Warn
func (l *intLogger) IsWarn() bool {
return Level(atomic.LoadInt32(l.level)) <= Warn
}
// Indicate that the logger would emit ERROR level logs
func (z *intLogger) IsError() bool {
return Level(atomic.LoadInt32(z.level)) <= Error
func (l *intLogger) IsError() bool {
return Level(atomic.LoadInt32(l.level)) <= Error
}
// Return a sub-Logger for which every emitted log message will contain
// the given key/value pairs. This is used to create a context specific
// Logger.
func (z *intLogger) With(args ...interface{}) Logger {
func (l *intLogger) With(args ...interface{}) Logger {
if len(args)%2 != 0 {
panic("With() call requires paired arguments")
}
var nz intLogger = *z
sl := *l
result := make(map[string]interface{}, len(z.implied)+len(args))
keys := make([]string, 0, len(z.implied)+len(args))
result := make(map[string]interface{}, len(l.implied)+len(args))
keys := make([]string, 0, len(l.implied)+len(args))
// Read existing args, store map and key for consistent sorting
for i := 0; i < len(z.implied); i += 2 {
key := z.implied[i].(string)
for i := 0; i < len(l.implied); i += 2 {
key := l.implied[i].(string)
keys = append(keys, key)
result[key] = z.implied[i+1]
result[key] = l.implied[i+1]
}
// Read new args, store map and key for consistent sorting
for i := 0; i < len(args); i += 2 {
@@ -405,53 +467,61 @@ func (z *intLogger) With(args ...interface{}) Logger {
// Sort keys to be consistent
sort.Strings(keys)
nz.implied = make([]interface{}, 0, len(z.implied)+len(args))
sl.implied = make([]interface{}, 0, len(l.implied)+len(args))
for _, k := range keys {
nz.implied = append(nz.implied, k)
nz.implied = append(nz.implied, result[k])
sl.implied = append(sl.implied, k)
sl.implied = append(sl.implied, result[k])
}
return &nz
return &sl
}
// Create a new sub-Logger that a name decending from the current name.
// This is used to create a subsystem specific Logger.
func (z *intLogger) Named(name string) Logger {
var nz intLogger = *z
func (l *intLogger) Named(name string) Logger {
sl := *l
if nz.name != "" {
nz.name = nz.name + "." + name
if sl.name != "" {
sl.name = sl.name + "." + name
} else {
nz.name = name
sl.name = name
}
return &nz
return &sl
}
// Create a new sub-Logger with an explicit name. This ignores the current
// name. This is used to create a standalone logger that doesn't fall
// within the normal hierarchy.
func (z *intLogger) ResetNamed(name string) Logger {
var nz intLogger = *z
func (l *intLogger) ResetNamed(name string) Logger {
sl := *l
nz.name = name
sl.name = name
return &nz
return &sl
}
// Update the logging level on-the-fly. This will affect all subloggers as
// well.
func (z *intLogger) SetLevel(level Level) {
atomic.StoreInt32(z.level, int32(level))
func (l *intLogger) SetLevel(level Level) {
atomic.StoreInt32(l.level, int32(level))
}
// Create a *log.Logger that will send it's data through this Logger. This
// allows packages that expect to be using the standard library log to actually
// use this logger.
func (z *intLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger {
func (l *intLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger {
if opts == nil {
opts = &StandardLoggerOptions{}
}
return log.New(&stdlogAdapter{z, opts.InferLevels}, "", 0)
return log.New(l.StandardWriter(opts), "", 0)
}
func (l *intLogger) StandardWriter(opts *StandardLoggerOptions) io.Writer {
return &stdlogAdapter{
log: l,
inferLevels: opts.InferLevels,
forceLevel: opts.ForceLevel,
}
}

View File

@@ -9,38 +9,42 @@ import (
)
var (
DefaultOutput = os.Stderr
DefaultLevel = Info
//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 (
// This is a special level used to indicate that no level has been
// 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
// The most verbose level. Intended to be used for the tracing of actions
// in code, such as function enters/exits, etc.
// 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
// For programmer lowlevel analysis.
// Debug information for programmer lowlevel analysis.
Debug Level = 2
// For information about steady state operations.
// Info information about steady state operations.
Info Level = 3
// For information about rare but handled events.
// Warn information about rare but handled events.
Warn Level = 4
// For information about unrecoverable events.
// Error information about unrecoverable events.
Error Level = 5
)
// 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}). This is a simple
// convience type for when formatting is required.
// Format is a simple convience 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 convience function for creating a Format
@@ -53,7 +57,7 @@ func Fmt(str string, args ...interface{}) Format {
// 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 "INFO" or "info"
// We don't care about case. Accept both "INFO" and "info".
levelStr = strings.ToLower(strings.TrimSpace(levelStr))
switch levelStr {
case "trace":
@@ -71,7 +75,7 @@ func LevelFromString(levelStr string) Level {
}
}
// The main Logger interface. All code should code against this interface only.
// Logger describes the interface that must be implemeted by all loggers.
type Logger interface {
// Args are alternating key, val pairs
// keys must be strings
@@ -127,16 +131,27 @@ type Logger interface {
// 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
// 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
}
// LoggerOptions can be used to configure a new logger.
type LoggerOptions struct {
// Name of the subsystem to prefix logs with
Name string
@@ -144,7 +159,7 @@ type LoggerOptions struct {
// The threshold for the logger. Anything less severe is supressed
Level Level
// Where to write the logs to. Defaults to os.Stdout if nil
// Where to write the logs to. Defaults to os.Stderr if nil
Output io.Writer
// An optional mutex pointer in case Output is shared

View File

@@ -1,6 +1,7 @@
package hclog
import (
"io"
"io/ioutil"
"log"
)
@@ -43,5 +44,9 @@ func (l *nullLogger) ResetNamed(name string) Logger { return l }
func (l *nullLogger) SetLevel(level Level) {}
func (l *nullLogger) StandardLogger(opts *StandardLoggerOptions) *log.Logger {
return log.New(ioutil.Discard, "", log.LstdFlags)
return log.New(l.StandardWriter(opts), "", log.LstdFlags)
}
func (l *nullLogger) StandardWriter(opts *StandardLoggerOptions) io.Writer {
return ioutil.Discard
}

View File

@@ -40,12 +40,13 @@ var (
}
)
// A stacktrace gathered by a previous call to log.Stacktrace. If passed
// to a logging function, the stacktrace will be appended.
// CapturedStacktrace represents a stacktrace captured by a previous call
// to log.Stacktrace. If passed to a logging function, the stacktrace
// will be appended.
type CapturedStacktrace string
// Gather a stacktrace of the current goroutine and return it to be passed
// to a logging function.
// Stacktrace captures a stacktrace of the current goroutine and returns
// it to be passed to a logging function.
func Stacktrace() CapturedStacktrace {
return CapturedStacktrace(takeStacktrace())
}

View File

@@ -9,39 +9,51 @@ import (
// and back into our Logger. This is basically the only way to
// build upon *log.Logger.
type stdlogAdapter struct {
hl Logger
log Logger
inferLevels bool
forceLevel Level
}
// Take the data, infer the levels if configured, and send it through
// a regular Logger
// a regular Logger.
func (s *stdlogAdapter) Write(data []byte) (int, error) {
str := string(bytes.TrimRight(data, " \t\n"))
if s.inferLevels {
if s.forceLevel != NoLevel {
// Use pickLevel to strip log levels included in the line since we are
// forcing the level
_, str := s.pickLevel(str)
// Log at the forced level
s.dispatch(str, s.forceLevel)
} else if s.inferLevels {
level, str := s.pickLevel(str)
switch level {
case Trace:
s.hl.Trace(str)
case Debug:
s.hl.Debug(str)
case Info:
s.hl.Info(str)
case Warn:
s.hl.Warn(str)
case Error:
s.hl.Error(str)
default:
s.hl.Info(str)
}
s.dispatch(str, level)
} else {
s.hl.Info(str)
s.log.Info(str)
}
return len(data), nil
}
// Detect, based on conventions, what log level this is
func (s *stdlogAdapter) dispatch(str string, level Level) {
switch level {
case Trace:
s.log.Trace(str)
case Debug:
s.log.Debug(str)
case Info:
s.log.Info(str)
case Warn:
s.log.Warn(str)
case Error:
s.log.Error(str)
default:
s.log.Info(str)
}
}
// Detect, based on conventions, what log level this is.
func (s *stdlogAdapter) pickLevel(str string) (Level, string) {
switch {
case strings.HasPrefix(str, "[DEBUG]"):

74
vendor/github.com/hashicorp/go-hclog/writer.go generated vendored Normal file
View File

@@ -0,0 +1,74 @@
package hclog
import (
"bytes"
"io"
)
type writer struct {
b bytes.Buffer
w io.Writer
}
func newWriter(w io.Writer) *writer {
return &writer{w: w}
}
func (w *writer) Flush(level Level) (err error) {
if lw, ok := w.w.(LevelWriter); ok {
_, err = lw.LevelWrite(level, w.b.Bytes())
} else {
_, err = w.w.Write(w.b.Bytes())
}
w.b.Reset()
return err
}
func (w *writer) Write(p []byte) (int, error) {
return w.b.Write(p)
}
func (w *writer) WriteByte(c byte) error {
return w.b.WriteByte(c)
}
func (w *writer) WriteString(s string) (int, error) {
return w.b.WriteString(s)
}
// LevelWriter is the interface that wraps the LevelWrite method.
type LevelWriter interface {
LevelWrite(level Level, p []byte) (n int, err error)
}
// LeveledWriter writes all log messages to the standard writer,
// except for log levels that are defined in the overrides map.
type LeveledWriter struct {
standard io.Writer
overrides map[Level]io.Writer
}
// NewLeveledWriter returns an initialized LeveledWriter.
//
// standard will be used as the default writer for all log levels,
// except for log levels that are defined in the overrides map.
func NewLeveledWriter(standard io.Writer, overrides map[Level]io.Writer) *LeveledWriter {
return &LeveledWriter{
standard: standard,
overrides: overrides,
}
}
// Write implements io.Writer.
func (lw *LeveledWriter) Write(p []byte) (int, error) {
return lw.standard.Write(p)
}
// LevelWrite implements LevelWriter.
func (lw *LeveledWriter) LevelWrite(level Level, p []byte) (int, error) {
w, ok := lw.overrides[level]
if !ok {
w = lw.standard
}
return w.Write(p)
}