You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
boundary/internal/event/hclog_event_adapter.go

246 lines
6.8 KiB

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
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 []any
}
// 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 ...any) {
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 ...any) {
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 ...any) {
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 ...any) {
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 ...any) {
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 ...any) {
if h.level > hclog.Error {
return
}
h.writeEvent("", msg, args)
}
func (h *HclogLoggerAdapter) writeEvent(caller Op, msg string, args []any) {
h.l.RLock()
defer h.l.RUnlock()
var allArgs []any
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() []any {
return h.withArgs
}
// Creates a sublogger that will always have the given key/value pairs
func (h *HclogLoggerAdapter) With(args ...any) hclog.Logger {
h.l.Lock()
defer h.l.Unlock()
newArgs := args
if len(h.withArgs) > 0 {
newArgs = make([]any, 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 []any
if len(h.withArgs) > 0 {
newArgs = make([]any, 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 []any
if len(h.withArgs) > 0 {
newArgs = make([]any, len(h.withArgs))
copy(newArgs, h.withArgs)
}
return &HclogLoggerAdapter{
eventCtx: h.eventCtx,
l: new(sync.RWMutex),
level: h.level,
name: name,
withArgs: newArgs,
}
}
// GetLevel returns the current level
func (h *HclogLoggerAdapter) GetLevel() hclog.Level {
return h.level
}
// 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
}