This contains to related things: 1. An adapter allowing an hclog interface to take in information and send them to the eventer 2. A gate on the eventer, which is the analogue to the behavior prior to eventing where we'd ensure logs were not printed prior to the startup information. Without this, plugin logs using the hclog adapter will start showing up too early.
238 lines
6.8 KiB
Go
238 lines
6.8 KiB
Go
package event
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"sync"
|
|
|
|
"github.com/hashicorp/go-hclog"
|
|
)
|
|
|
|
// HclogLoggerAdapter is used to provide an hclog-style interface to code that
|
|
// cannot natively handle eventing. Currently, all log lines are written as
|
|
// system events. Note that this is not meant for high throughput; some
|
|
// potential optimizations (such as using atomic values for name and such) are
|
|
// not current implemented. Additionally, some functions (such as fetching a
|
|
// stdlib logger/writer) are simply not supported right now.
|
|
type HclogLoggerAdapter struct {
|
|
eventCtx context.Context
|
|
l *sync.RWMutex
|
|
level hclog.Level
|
|
name string
|
|
withArgs []interface{}
|
|
}
|
|
|
|
// Ensure that we are implementing Logger
|
|
var _ hclog.Logger = (*HclogLoggerAdapter)(nil)
|
|
|
|
// NewHclogLogger creates a new hclog.Logger-compatible implementation that
|
|
// outputs to events
|
|
func NewHclogLogger(ctx context.Context, e *Eventer, opt ...Option) (hclog.Logger, error) {
|
|
const op = "event.HclogLogger"
|
|
eventCtx, err := NewEventerContext(ctx, e)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("%s: %w", op, err)
|
|
}
|
|
opts := getOpts(opt...)
|
|
return &HclogLoggerAdapter{
|
|
eventCtx: eventCtx,
|
|
l: new(sync.RWMutex),
|
|
level: opts.withHclogLevel,
|
|
}, nil
|
|
}
|
|
|
|
// 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
|
|
func (h *HclogLoggerAdapter) Log(level hclog.Level, msg string, args ...interface{}) {
|
|
switch {
|
|
case h.level == hclog.NoLevel: // If logger is not set to any level, accept it
|
|
case h.level <= level: // Otherwise if logger is same or more verbose, accept
|
|
default:
|
|
return
|
|
}
|
|
h.writeEvent("", msg, args)
|
|
}
|
|
|
|
// Emit a message and key/value pairs at the TRACE level
|
|
func (h *HclogLoggerAdapter) Trace(msg string, args ...interface{}) {
|
|
if h.level > hclog.Trace {
|
|
return
|
|
}
|
|
h.writeEvent("", msg, args)
|
|
}
|
|
|
|
// Emit a message and key/value pairs at the DEBUG level
|
|
func (h *HclogLoggerAdapter) Debug(msg string, args ...interface{}) {
|
|
if h.level > hclog.Debug {
|
|
return
|
|
}
|
|
h.writeEvent("", msg, args)
|
|
}
|
|
|
|
// Emit a message and key/value pairs at the INFO level
|
|
func (h *HclogLoggerAdapter) Info(msg string, args ...interface{}) {
|
|
if h.level > hclog.Info {
|
|
return
|
|
}
|
|
h.writeEvent("", msg, args)
|
|
}
|
|
|
|
// Emit a message and key/value pairs at the WARN level
|
|
func (h *HclogLoggerAdapter) Warn(msg string, args ...interface{}) {
|
|
if h.level > hclog.Warn {
|
|
return
|
|
}
|
|
h.writeEvent("", msg, args)
|
|
}
|
|
|
|
// Emit a message and key/value pairs at the ERROR level
|
|
func (h *HclogLoggerAdapter) Error(msg string, args ...interface{}) {
|
|
if h.level > hclog.Error {
|
|
return
|
|
}
|
|
h.writeEvent("", msg, args)
|
|
}
|
|
|
|
func (h *HclogLoggerAdapter) writeEvent(caller Op, msg string, args []interface{}) {
|
|
h.l.RLock()
|
|
defer h.l.RUnlock()
|
|
var allArgs []interface{}
|
|
if len(h.withArgs)+len(args) > 0 {
|
|
allArgs = append(h.withArgs, args...)
|
|
}
|
|
if h.name != "" {
|
|
allArgs = append(allArgs, "@original-log-name", h.name)
|
|
}
|
|
allArgs = append(allArgs, "@original-log-level", h.level.String())
|
|
WriteSysEvent(h.eventCtx, "", msg, allArgs...)
|
|
}
|
|
|
|
// 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.
|
|
func (h *HclogLoggerAdapter) IsTrace() bool {
|
|
return h.level <= hclog.Trace
|
|
}
|
|
|
|
// Indicate if DEBUG logs would be emitted. This and the other Is* guards
|
|
func (h *HclogLoggerAdapter) IsDebug() bool {
|
|
return h.level <= hclog.Debug
|
|
}
|
|
|
|
// Indicate if INFO logs would be emitted. This and the other Is* guards
|
|
func (h *HclogLoggerAdapter) IsInfo() bool {
|
|
return h.level <= hclog.Info
|
|
}
|
|
|
|
// Indicate if WARN logs would be emitted. This and the other Is* guards
|
|
func (h *HclogLoggerAdapter) IsWarn() bool {
|
|
return h.level <= hclog.Warn
|
|
}
|
|
|
|
// Indicate if ERROR logs would be emitted. This and the other Is* guards
|
|
func (h *HclogLoggerAdapter) IsError() bool {
|
|
return h.level <= hclog.Error
|
|
}
|
|
|
|
// ImpliedArgs returns With key/value pairs
|
|
func (h *HclogLoggerAdapter) ImpliedArgs() []interface{} {
|
|
return h.withArgs
|
|
}
|
|
|
|
// Creates a sublogger that will always have the given key/value pairs
|
|
func (h *HclogLoggerAdapter) With(args ...interface{}) hclog.Logger {
|
|
h.l.Lock()
|
|
defer h.l.Unlock()
|
|
newArgs := args
|
|
if len(h.withArgs) > 0 {
|
|
newArgs = make([]interface{}, len(h.withArgs), len(h.withArgs)+len(args))
|
|
copy(newArgs, h.withArgs)
|
|
newArgs = append(newArgs, args...)
|
|
}
|
|
return &HclogLoggerAdapter{
|
|
eventCtx: h.eventCtx,
|
|
l: new(sync.RWMutex),
|
|
level: h.level,
|
|
name: h.name,
|
|
withArgs: newArgs,
|
|
}
|
|
}
|
|
|
|
// Returns the Name of the logger
|
|
func (h *HclogLoggerAdapter) Name() string {
|
|
h.l.RLock()
|
|
defer h.l.RUnlock()
|
|
return h.name
|
|
}
|
|
|
|
// 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.
|
|
func (h *HclogLoggerAdapter) Named(name string) hclog.Logger {
|
|
h.l.Lock()
|
|
defer h.l.Unlock()
|
|
var newArgs []interface{}
|
|
if len(h.withArgs) > 0 {
|
|
newArgs = make([]interface{}, len(h.withArgs))
|
|
copy(newArgs, h.withArgs)
|
|
}
|
|
|
|
newName := name
|
|
if h.name != "" {
|
|
newName = fmt.Sprintf("%s.%s", h.name, name)
|
|
}
|
|
|
|
return &HclogLoggerAdapter{
|
|
eventCtx: h.eventCtx,
|
|
l: new(sync.RWMutex),
|
|
level: h.level,
|
|
name: newName,
|
|
withArgs: newArgs,
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
func (h *HclogLoggerAdapter) ResetNamed(name string) hclog.Logger {
|
|
h.l.Lock()
|
|
defer h.l.Unlock()
|
|
var newArgs []interface{}
|
|
if len(h.withArgs) > 0 {
|
|
newArgs = make([]interface{}, len(h.withArgs))
|
|
copy(newArgs, h.withArgs)
|
|
}
|
|
return &HclogLoggerAdapter{
|
|
eventCtx: h.eventCtx,
|
|
l: new(sync.RWMutex),
|
|
level: h.level,
|
|
name: name,
|
|
withArgs: newArgs,
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
// This implementation is a no-op currently.
|
|
func (h *HclogLoggerAdapter) SetLevel(_ hclog.Level) {}
|
|
|
|
// Return a value that conforms to the stdlib log.Logger interface
|
|
//
|
|
// This implementation does not currently support this and returns nil.
|
|
func (h *HclogLoggerAdapter) StandardLogger(opts *hclog.StandardLoggerOptions) *log.Logger {
|
|
return nil
|
|
}
|
|
|
|
// Return a value that conforms to io.Writer, which can be passed into log.SetOutput()
|
|
//
|
|
// This implementation does not currently support this and returns nil.
|
|
func (h *HclogLoggerAdapter) StandardWriter(opts *hclog.StandardLoggerOptions) io.Writer {
|
|
return nil
|
|
}
|