Files
threshold/internal/observability/event/hclog_event_adapter.go
T
Jeff Mitchell e596daf34d Add hclog adapter and gating to eventer (#1915)
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.
2022-03-16 17:25:34 -04:00

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
}