package event import ( "bytes" "context" "errors" "fmt" "io" "log" "net/url" "os" "reflect" "runtime" "strings" "sync" "time" "github.com/hashicorp/eventlogger" "github.com/hashicorp/eventlogger/filters/encrypt" "github.com/hashicorp/eventlogger/filters/gated" "github.com/hashicorp/eventlogger/formatter_filters/cloudevents" "github.com/hashicorp/eventlogger/sinks/writer" "github.com/hashicorp/go-hclog" wrapping "github.com/hashicorp/go-kms-wrapping/v2" "github.com/hashicorp/go-multierror" "go.uber.org/atomic" "google.golang.org/protobuf/types/known/fieldmaskpb" ) const ( OpField = "op" // OpField in an event. RequestInfoField = "request_info" // RequestInfoField in an event. VersionField = "version" // VersionField in an event DetailsField = "details" // Details field in an event. HeaderField = "header" // HeaderField in an event. IdField = "id" // IdField in an event. CreatedAtField = "created_at" // CreatedAtField in an event. TypeField = "type" // TypeField in an event. auditPipeline = "audit-pipeline" // auditPipeline is a pipeline for audit events observationPipeline = "observation-pipeline" // observationPipeline is a pipeline for observation events errPipeline = "err-pipeline" // errPipeline is a pipeline for error events sysPipeline = "sys-pipeline" // sysPipeline is a pipeline for system events ) // flushable defines an interface that all eventlogger Nodes must implement if // they are "flushable" type flushable interface { FlushAll(ctx context.Context) error } // broker defines an interface for an eventlogger Broker... which will allow us // to substitute our testing broker when needed to write tests for things // like event send retrying. type broker interface { Send(ctx context.Context, t eventlogger.EventType, payload interface{}) (eventlogger.Status, error) Reopen(ctx context.Context) error StopTimeAt(now time.Time) RegisterNode(id eventlogger.NodeID, node eventlogger.Node) error SetSuccessThreshold(t eventlogger.EventType, successThreshold int) error RegisterPipeline(def eventlogger.Pipeline) error } // queuedEvent stores an event and the context that was associated with it when // gating type queuedEvent struct { ctx context.Context event interface{} } // Eventer provides a method to send events to pipelines of sinks type Eventer struct { broker broker flushableNodes []flushable conf EventerConfig logger hclog.Logger auditPipelines []pipeline observationPipelines []pipeline errPipelines []pipeline auditWrapperNodes []interface{} // Gating is used to delay output of events until after we have a chance to // render startup info, similar to what was done for hclog before eventing // supplanted it. It affects only error and system events. gated atomic.Bool gatedQueue []*queuedEvent gatedQueueLock *sync.Mutex } type pipeline struct { eventType Type fmtId eventlogger.NodeID sinkId eventlogger.NodeID gateId eventlogger.NodeID encryptFilterId eventlogger.NodeID sinkConfig *SinkConfig } var ( sysEventer *Eventer // sysEventer is the system-wide Eventer sysEventerLock sync.RWMutex // sysEventerLock allows the sysEventer to safely be written concurrently. sysFallbackLogger struct { mu sync.RWMutex logger hclog.Logger } ) func defaultLogger() hclog.Logger { opts := hclog.DefaultOptions opts.JSONFormat = true return hclog.New(opts) } // fallbackLogger returns the current fallback logger for eventing. If there's // no current fallback logger, then a default logger is returned. func fallbackLogger() hclog.Logger { sysFallbackLogger.mu.RLock() defer sysFallbackLogger.mu.RUnlock() if sysFallbackLogger.logger == nil { return defaultLogger() } return sysFallbackLogger.logger } // InitFallbackLogger will initialize the fallback logger for eventing func InitFallbackLogger(l hclog.Logger) error { const op = "event.InitFallbackLogger" if isNil(l) { return fmt.Errorf("%s: missing logger: %w", op, ErrInvalidParameter) } sysFallbackLogger.mu.Lock() defer sysFallbackLogger.mu.Unlock() sysFallbackLogger.logger = l return nil } // InitSysEventer provides a mechanism to initialize a "system wide" eventer // singleton for Boundary. Support the options of: WithEventer(...) and // WithEventerConfig(...) // // IMPORTANT: Eventers cannot share file sinks, which likely means that each // process should only have one Eventer. In practice this means the process // Server (Controller or Worker) and the SysEventer both need a pointer to a // single Eventer. func InitSysEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName string, opt ...Option) error { const op = "event.InitSysEventer" if log == nil { return fmt.Errorf("%s: missing hclog: %w", op, ErrInvalidParameter) } if serializationLock == nil { return fmt.Errorf("%s: missing serialization lock: %w", op, ErrInvalidParameter) } if serverName == "" { return fmt.Errorf("%s: missing server name: %w", op, ErrInvalidParameter) } // the order of operations is important here. we want to determine if // there's an error before setting the singleton sysEventer var e *Eventer opts := getOpts(opt...) switch { case opts.withEventer == nil && opts.withEventerConfig == nil: return fmt.Errorf("%s: missing both eventer and eventer config: %w", op, ErrInvalidParameter) case opts.withEventer != nil && opts.withEventerConfig != nil: return fmt.Errorf("%s: both eventer and eventer config provided: %w", op, ErrInvalidParameter) case opts.withEventerConfig != nil: var err error if e, err = NewEventer(log, serializationLock, serverName, *opts.withEventerConfig); err != nil { return fmt.Errorf("%s: %w", op, err) } case opts.withEventer != nil: e = opts.withEventer } sysEventerLock.Lock() defer sysEventerLock.Unlock() sysEventer = e return nil } // SysEventer returns the "system wide" eventer for Boundary and can/will return // a nil Eventer func SysEventer() *Eventer { sysEventerLock.RLock() defer sysEventerLock.RUnlock() return sysEventer } // NewAuditEncryptFilter returns a new encrypt filter which is initialized for // audit events. func NewAuditEncryptFilter(opt ...Option) (*encrypt.Filter, error) { opts := getOpts(opt...) return &encrypt.Filter{ Wrapper: opts.withAuditWrapper, IgnoreTypes: []reflect.Type{ reflect.TypeOf(&fieldmaskpb.FieldMask{}), }, }, nil } // NewEventer creates a new Eventer using the config. Supports options: // WithNow, WithSerializationLock, WithBroker, WithAuditWrapper, // WithNoDefaultSink func NewEventer(log hclog.Logger, serializationLock *sync.Mutex, serverName string, c EventerConfig, opt ...Option) (*Eventer, error) { const op = "event.NewEventer" if log == nil { return nil, fmt.Errorf("%s: missing logger: %w", op, ErrInvalidParameter) } if serializationLock == nil { return nil, fmt.Errorf("%s: missing serialization lock: %w", op, ErrInvalidParameter) } if serverName == "" { return nil, fmt.Errorf("%s: missing server name: %w", op, ErrInvalidParameter) } opts := getOpts(opt...) // if there are no sinks in config, then we'll default to just one stderr // sink. if len(c.Sinks) == 0 && !opts.withNoDefaultSink { c.Sinks = append(c.Sinks, DefaultSink()) } if err := c.Validate(); err != nil { return nil, fmt.Errorf("%s: %w", op, err) } var auditPipelines, observationPipelines, errPipelines, sysPipelines []pipeline var b broker switch { case opts.withBroker != nil: b = opts.withBroker default: b = eventlogger.NewBroker() } e := &Eventer{ gatedQueueLock: new(sync.Mutex), logger: log, conf: c, broker: b, auditWrapperNodes: []interface{}{}, } if !opts.withNow.IsZero() { e.broker.StopTimeAt(opts.withNow) } if opts.withGating { e.gated.Store(true) } // serializedStderr will be shared among all StderrSinks so their output is not // interwoven serializedStderr := serializedWriter{ w: os.Stderr, l: serializationLock, } // we need to keep track of all the Sink filenames to ensure they aren't // reused. allSinkFilenames := map[string]bool{} for _, s := range c.Sinks { fmtId, fmtNode, err := newFmtFilterNode(serverName, *s, opt...) e.auditWrapperNodes = append(e.auditWrapperNodes, fmtNode) if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } err = e.broker.RegisterNode(eventlogger.NodeID(fmtId), fmtNode) if err != nil { return nil, fmt.Errorf("%s: unable to register fmt/filter node: %w", op, err) } var sinkId eventlogger.NodeID var sinkNode eventlogger.Node switch s.Type { case StderrSink: sinkNode = &writer.Sink{ Format: string(s.Format), Writer: &serializedStderr, } id, err := NewId("stderr") if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } sinkId = eventlogger.NodeID(id) case FileSink: fsc := s.FileConfig if _, found := allSinkFilenames[fsc.Path+fsc.FileName]; found { return nil, fmt.Errorf("%s: duplicate file sink: %s %s: %w", op, fsc.Path, fsc.FileName, ErrInvalidParameter) } allSinkFilenames[fsc.Path+fsc.FileName] = true sinkNode = &eventlogger.FileSink{ Format: string(s.Format), Path: fsc.Path, FileName: fsc.FileName, MaxBytes: fsc.RotateBytes, MaxDuration: fsc.RotateDuration, MaxFiles: fsc.RotateMaxFiles, } id, err := NewId(fmt.Sprintf("file_%s_%s_", fsc.Path, fsc.FileName)) if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } sinkId = eventlogger.NodeID(id) case WriterSink: wsc := s.WriterConfig sinkNode = &writer.Sink{ Format: string(s.Format), Writer: wsc.Writer, } id, err := NewId("writer") if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } sinkId = eventlogger.NodeID(id) default: return nil, fmt.Errorf("%s: unknown sink type %s", op, s.Type) } err = e.broker.RegisterNode(sinkId, sinkNode) if err != nil { return nil, fmt.Errorf("%s: failed to register sink node %s: %w", op, sinkId, err) } var addToAudit, addToObservation, addToErr, addToSys bool for _, t := range s.EventTypes { switch t { case EveryType: addToAudit = true addToObservation = true addToErr = true addToSys = true case ErrorType: addToErr = true case AuditType: addToAudit = true case ObservationType: addToObservation = true case SystemType: addToSys = true } } if addToAudit { var fop AuditFilterOperations if s.AuditConfig != nil { fop = s.AuditConfig.FilterOverrides } s.AuditConfig, err = NewAuditConfig(WithAuditWrapper(opts.withAuditWrapper), WithFilterOperations(fop)) if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } encryptFilter, err := NewAuditEncryptFilter(opt...) if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } e.auditWrapperNodes = append(e.auditWrapperNodes, encryptFilter) if len(s.AuditConfig.FilterOverrides) > 0 { overrides := encrypt.DefaultFilterOperations() for k, v := range s.AuditConfig.FilterOverrides { overrides[encrypt.DataClassification(k)] = encrypt.FilterOperation(v) } encryptFilter.FilterOperationOverrides = overrides } id, err := NewId("encrypt-audit") if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } encryptFilterId := eventlogger.NodeID(id) if err := b.RegisterNode(encryptFilterId, encryptFilter); err != nil { return nil, fmt.Errorf("%s: %w", op, err) } auditPipelines = append(auditPipelines, pipeline{ eventType: AuditType, fmtId: fmtId, sinkId: sinkId, encryptFilterId: encryptFilterId, sinkConfig: s, }) } if addToObservation { observationPipelines = append(observationPipelines, pipeline{ eventType: ObservationType, fmtId: fmtId, sinkId: sinkId, sinkConfig: s, }) } if addToErr { errPipelines = append(errPipelines, pipeline{ eventType: ErrorType, fmtId: fmtId, sinkId: sinkId, sinkConfig: s, }) } if addToSys { sysPipelines = append(sysPipelines, pipeline{ eventType: SystemType, fmtId: fmtId, sinkId: sinkId, }) } } if c.AuditEnabled && len(auditPipelines) == 0 { return nil, fmt.Errorf("%s: audit events enabled but no sink defined for it: %w", op, ErrInvalidParameter) } if c.ObservationsEnabled && len(observationPipelines) == 0 { return nil, fmt.Errorf("%s: observation events enabled but no sink defined for it: %w", op, ErrInvalidParameter) } if c.SysEventsEnabled && len(sysPipelines) == 0 { return nil, fmt.Errorf("%s: system events enabled but no sink defined for it: %w", op, ErrInvalidParameter) } auditNodeIds := make([]eventlogger.NodeID, 0, len(auditPipelines)) for _, p := range auditPipelines { gatedFilterNode := gated.Filter{ Broker: e.broker, } e.flushableNodes = append(e.flushableNodes, &gatedFilterNode) gateId, err := NewId("gated-audit") if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } p.gateId = eventlogger.NodeID(gateId) if err := e.broker.RegisterNode(p.gateId, &gatedFilterNode); err != nil { return nil, fmt.Errorf("%s: unable to register audit gated filter: %w", op, err) } pipeId, err := NewId(auditPipeline) if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } err = e.broker.RegisterPipeline(eventlogger.Pipeline{ EventType: eventlogger.EventType(p.eventType), PipelineID: eventlogger.PipelineID(pipeId), // order of nodes is important! gate (aggregate), then filter/format, then encrypt, then write to sink NodeIDs: []eventlogger.NodeID{p.gateId, p.encryptFilterId, p.fmtId, p.sinkId}, }) if err != nil { return nil, fmt.Errorf("%s: failed to register audit pipeline: %w", op, err) } auditNodeIds = append(auditNodeIds, p.sinkId) } observationNodeIds := make([]eventlogger.NodeID, 0, len(observationPipelines)) for _, p := range observationPipelines { gatedFilterNode := gated.Filter{ Broker: e.broker, } e.flushableNodes = append(e.flushableNodes, &gatedFilterNode) gateId, err := NewId("gated-observation") if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } p.gateId = eventlogger.NodeID(gateId) if err := e.broker.RegisterNode(p.gateId, &gatedFilterNode); err != nil { return nil, fmt.Errorf("%s: unable to register audit gated filter: %w", op, err) } pipeId, err := NewId(observationPipeline) if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } err = e.broker.RegisterPipeline(eventlogger.Pipeline{ EventType: eventlogger.EventType(p.eventType), PipelineID: eventlogger.PipelineID(pipeId), // order of nodes is important! gate (aggregate), then filter/format, then write to sink NodeIDs: []eventlogger.NodeID{p.gateId, p.fmtId, p.sinkId}, }) if err != nil { return nil, fmt.Errorf("%s: failed to register observation pipeline: %w", op, err) } observationNodeIds = append(observationNodeIds, p.sinkId) } errNodeIds := make([]eventlogger.NodeID, 0, len(errPipelines)) for _, p := range errPipelines { pipeId, err := NewId(errPipeline) if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } err = e.broker.RegisterPipeline(eventlogger.Pipeline{ EventType: eventlogger.EventType(p.eventType), PipelineID: eventlogger.PipelineID(pipeId), // order of nodes is important! filter/format, then write to sink NodeIDs: []eventlogger.NodeID{p.fmtId, p.sinkId}, }) if err != nil { return nil, fmt.Errorf("%s: failed to register err pipeline: %w", op, err) } errNodeIds = append(errNodeIds, p.sinkId) } sysNodeIds := make([]eventlogger.NodeID, 0, len(sysPipelines)) for _, p := range sysPipelines { pipeId, err := NewId(sysPipeline) if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } err = e.broker.RegisterPipeline(eventlogger.Pipeline{ EventType: eventlogger.EventType(p.eventType), PipelineID: eventlogger.PipelineID(pipeId), // order of nodes is important! filter/format, then write to sink NodeIDs: []eventlogger.NodeID{p.fmtId, p.sinkId}, }) if err != nil { return nil, fmt.Errorf("%s: failed to register sys pipeline: %w", op, err) } sysNodeIds = append(sysNodeIds, p.sinkId) } err := e.broker.SetSuccessThreshold(eventlogger.EventType(ObservationType), len(observationNodeIds)) if err != nil { return nil, fmt.Errorf("%s: failed to set success threshold for observation events: %w", op, err) } err = e.broker.SetSuccessThreshold(eventlogger.EventType(AuditType), len(auditNodeIds)) if err != nil { return nil, fmt.Errorf("%s: failed to set success threshold for audit events: %w", op, err) } err = e.broker.SetSuccessThreshold(eventlogger.EventType(ErrorType), len(errNodeIds)) if err != nil { return nil, fmt.Errorf("%s: failed to set success threshold for error events: %w", op, err) } err = e.broker.SetSuccessThreshold(eventlogger.EventType(SystemType), len(sysNodeIds)) if err != nil { return nil, fmt.Errorf("%s: failed to set success threshold for sysevents: %w", op, err) } e.auditPipelines = append(e.auditPipelines, auditPipelines...) e.errPipelines = append(e.errPipelines, errPipelines...) e.observationPipelines = append(e.observationPipelines, observationPipelines...) return e, nil } func newFmtFilterNode(serverName string, c SinkConfig, opt ...Option) (eventlogger.NodeID, eventlogger.Node, error) { const op = "newFmtFilterNode" if serverName == "" { return "", nil, fmt.Errorf("%s: missing server name: %w", op, ErrInvalidParameter) } opts := getOpts(opt...) var fmtId eventlogger.NodeID var fmtNode eventlogger.Node switch c.Format { case TextHclogSinkFormat, JSONHclogSinkFormat: id, err := NewId(string(c.Format)) if err != nil { return "", nil, fmt.Errorf("%s: unable to generate id: %w", op, err) } fmtId = eventlogger.NodeID(id) fmtNode, err = newHclogFormatterFilter(c.Format == JSONHclogSinkFormat, WithAllow(c.AllowFilters...), WithDeny(c.DenyFilters...), WithAuditWrapper(opts.withAuditWrapper)) if err != nil { return "", nil, fmt.Errorf("%s: %w", op, err) } default: id, err := NewId("cloudevents") if err != nil { return "", nil, fmt.Errorf("%s: unable to generate id: %w", op, err) } fmtId = eventlogger.NodeID(id) var sourceUrl *url.URL switch { case c.EventSourceUrl != "": sourceUrl, err = url.Parse(c.EventSourceUrl) if err != nil { return "", nil, fmt.Errorf("%s: invalid event source URL (%s): %w", op, c.EventSourceUrl, err) } default: s := fmt.Sprintf("https://hashicorp.com/boundary/%s", serverName) sourceUrl, err = url.Parse(s) if err != nil { return "", nil, fmt.Errorf("%s: invalid event source URL (%s): %w", op, s, err) } } fmtNode, err = newCloudEventsFormatterFilter(sourceUrl, cloudevents.Format(c.Format), WithAllow(c.AllowFilters...), WithDeny(c.DenyFilters...)) if err != nil { return "", nil, fmt.Errorf("%s: %w", op, err) } } return fmtId, fmtNode, nil } func DefaultEventerConfig() *EventerConfig { return &EventerConfig{ AuditEnabled: false, ObservationsEnabled: true, SysEventsEnabled: true, Sinks: []*SinkConfig{DefaultSink()}, } } func DefaultSink() *SinkConfig { return &SinkConfig{ Name: "default", EventTypes: []Type{EveryType}, Format: JSONSinkFormat, Type: StderrSink, AuditConfig: DefaultAuditConfig(), } } func (e *Eventer) RotateAuditWrapper(ctx context.Context, newWrapper wrapping.Wrapper) error { const op = "event.(Eventer).RotateAuditWrapper" if newWrapper == nil { return fmt.Errorf("%s: missing wrapper: %w", op, ErrInvalidParameter) } for _, n := range e.auditWrapperNodes { switch w := n.(type) { case *hclogFormatterFilter: w.Rotate(newWrapper) case *cloudEventsFormatterFilter: w.Rotate(newWrapper) case *encrypt.Filter: w.Rotate(encrypt.WithWrapper(newWrapper)) default: return fmt.Errorf("%s: unsupported node type (%s): %w", op, reflect.TypeOf(w), ErrInvalidParameter) } } return nil } // writeObservation writes/sends an Observation event. func (e *Eventer) writeObservation(ctx context.Context, event *observation, _ ...Option) error { const op = "event.(Eventer).writeObservation" if event == nil { return fmt.Errorf("%s: missing event: %w", op, ErrInvalidParameter) } if !e.conf.ObservationsEnabled { return nil } err := e.retrySend(ctx, stdRetryCount, expBackoff{}, func() (eventlogger.Status, error) { if event.Header != nil { event.Header[RequestInfoField] = event.RequestInfo event.Header[VersionField] = event.Version } if event.Detail != nil { event.Detail[OpField] = string(event.Op) } return e.broker.Send(ctx, eventlogger.EventType(ObservationType), event) }) if err != nil { e.logger.Error("encountered an error sending an observation event", "error:", err.Error()) return fmt.Errorf("%s: %w", op, err) } return nil } // writeError writes/sends an Err event func (e *Eventer) writeError(ctx context.Context, event *err, opt ...Option) error { const op = "event.(Eventer).writeError" if event == nil { return fmt.Errorf("%s: missing event: %w", op, ErrInvalidParameter) } opts := getOpts(opt...) if e.gated.Load() && !opts.withNoGateLocking { e.gatedQueueLock.Lock() defer e.gatedQueueLock.Unlock() if e.gated.Load() { // validate that it's still gated and we haven't just drained the queue e.gatedQueue = append(e.gatedQueue, &queuedEvent{ ctx: ctx, event: event, }) return nil } } err := e.retrySend(ctx, stdRetryCount, expBackoff{}, func() (eventlogger.Status, error) { return e.broker.Send(ctx, eventlogger.EventType(ErrorType), event) }) if err != nil { e.logger.Error("encountered an error sending an error event", "error:", err.Error()) return fmt.Errorf("%s: %w", op, err) } return nil } // writeSysEvent writes/sends an sysEvent event func (e *Eventer) writeSysEvent(ctx context.Context, event *sysEvent, opt ...Option) error { const op = "event.(Eventer).writeSysEvent" if event == nil { return fmt.Errorf("%s: missing event: %w", op, ErrInvalidParameter) } opts := getOpts(opt...) if e.gated.Load() && !opts.withNoGateLocking { e.gatedQueueLock.Lock() defer e.gatedQueueLock.Unlock() if e.gated.Load() { // validate that it's still gated and we haven't just drained the queue e.gatedQueue = append(e.gatedQueue, &queuedEvent{ ctx: ctx, event: event, }) return nil } } err := e.retrySend(ctx, stdRetryCount, expBackoff{}, func() (eventlogger.Status, error) { return e.broker.Send(ctx, eventlogger.EventType(SystemType), event) }) if err != nil { e.logger.Error("encountered an error sending an sys event", "error:", err.Error()) return fmt.Errorf("%s: %w", op, err) } return nil } // writeAudit writes/send an audit event func (e *Eventer) writeAudit(ctx context.Context, event *audit, _ ...Option) error { const op = "event.(Eventer).writeAudit" if event == nil { return fmt.Errorf("%s: missing event: %w", op, ErrInvalidParameter) } if !e.conf.AuditEnabled { return nil } err := e.retrySend(ctx, stdRetryCount, expBackoff{}, func() (eventlogger.Status, error) { return e.broker.Send(ctx, eventlogger.EventType(AuditType), event) }) if err != nil { e.logger.Error("encountered an error sending an audit event", "error:", err.Error()) return fmt.Errorf("%s: %w", op, err) } return nil } // Reopen can used during a SIGHUP to reopen nodes, most importantly the underlying // file sinks. func (e *Eventer) Reopen() error { if e.broker != nil { return e.broker.Reopen(context.Background()) } return nil } // FlushNodes will flush any of the eventer's flushable nodes. This // needs to be called whenever Boundary is stopping (aka shutting down). func (e *Eventer) FlushNodes(ctx context.Context) error { const op = "event.(Eventer).FlushNodes" for _, n := range e.flushableNodes { if err := n.FlushAll(ctx); err != nil { return fmt.Errorf("%s: %w", op, err) } } return nil } // ReleaseGate releases queued events. If any event isn't successfully written, // it remains in the queue and we could try a flush later. func (e *Eventer) ReleaseGate() error { const op = "event.(Eventer).ReleaseGate" e.gatedQueueLock.Lock() defer e.gatedQueueLock.Unlock() // Don't gate anymore. By the time we hit this we will have the lock and be // guaranteed that nothing else will add onto the queue while we drain it. // Logic in the functions that add to the queue validate this boolean again // after they acquire the lock so running this function should fully drain // the events except in the case of an error. e.gated.Store(false) // Don't do anything if we're not gated and are fully drained if len(e.gatedQueue) == 0 && !e.gated.Load() { return nil } var totalErrs *multierror.Error for _, qe := range e.gatedQueue { var writeErr error if qe == nil { continue // we may have already sent this but gotten errors later } var queuedOp string switch t := qe.event.(type) { case *sysEvent: queuedOp = "system" writeErr = e.writeSysEvent(qe.ctx, t, WithNoGateLocking(true)) case *err: queuedOp = "error" writeErr = e.writeError(qe.ctx, t, WithNoGateLocking(true)) default: // Have no idea what this is and shouldn't have gotten in here to // begin with, so just continue, and log it writeErr = fmt.Errorf("unknown event type %T", t) } if writeErr != nil { totalErrs = multierror.Append(fmt.Errorf("in %s, error sending queued %s event: %w", op, queuedOp, writeErr)) } } e.gatedQueue = nil return totalErrs.ErrorOrNil() } // StandardLogger will create *log.Logger that will emit events through this // Logger. This allows packages that require the stdlib log to emit events // instead. func (e *Eventer) StandardLogger(ctx context.Context, loggerName string, typ Type) (*log.Logger, error) { const op = "event.(Eventer).StandardLogger" if e == nil { return nil, fmt.Errorf("%s: nil eventer: %w", op, ErrInvalidParameter) } if ctx == nil { return nil, fmt.Errorf("%s: missing context: %w", op, ErrInvalidParameter) } if typ == "" { return nil, fmt.Errorf("%s: missing type: %w", op, ErrInvalidParameter) } w, err := e.StandardWriter(ctx, typ) if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } return log.New(w, loggerName, 0), nil } // StandardWriter will create an io.Writer that will emit events through this // io.Writer. func (e *Eventer) StandardWriter(ctx context.Context, typ Type) (io.Writer, error) { const op = "event.(Eventer).StandardErrorWriter" if e == nil { return nil, fmt.Errorf("%s: nil eventer: %w", op, ErrInvalidParameter) } if ctx == nil { return nil, fmt.Errorf("%s: missing context: %w", op, ErrInvalidParameter) } if typ == "" { return nil, fmt.Errorf("%s: missing type: %w", op, ErrInvalidParameter) } if err := typ.Validate(); err != nil { return nil, fmt.Errorf("%s: %w", op, err) } newEventer := *e ctx, err := NewEventerContext(ctx, e) if err != nil { return nil, fmt.Errorf("%s: %w", op, err) } return &logAdapter{ ctxWithEventer: ctx, e: &newEventer, emitEventType: typ, }, nil } type logAdapter struct { ctxWithEventer context.Context e *Eventer emitEventType Type } // Write satisfies the io.Writer interface and will take the data, infer the // type of event and then emit an event. func (s *logAdapter) Write(data []byte) (int, error) { const op = "event.(stdlogAdapter).Write" if s == nil { return 0, fmt.Errorf("%s: nil log adapter: %w", op, ErrInvalidParameter) } var caller Op pc, _, _, ok := runtime.Caller(1) details := runtime.FuncForPC(pc) if ok && details != nil { caller = Op(details.Name()) } else { caller = "unknown operation" } str := string(bytes.TrimRight(data, " \t\n")) switch s.emitEventType { case ErrorType, SystemType: if err := s.send(s.emitEventType, caller, str); err != nil { return 0, fmt.Errorf("%s: %w", op, err) } default: t, str := s.pickType(str) if err := s.send(t, caller, str); err != nil { return 0, fmt.Errorf("%s: %w", op, err) } } return len(data), nil } func (s *logAdapter) send(typ Type, caller Op, str string) error { const op = "events.(stdlogAdapter).send" if typ == "" { return fmt.Errorf("%s: type is missing: %w", op, ErrInvalidParameter) } if caller == "" { return fmt.Errorf("%s: missing caller: %w", op, ErrInvalidParameter) } switch typ { case ErrorType: WriteError(s.ctxWithEventer, caller, errors.New(str)) return nil case SystemType: WriteSysEvent(s.ctxWithEventer, caller, str) return nil default: return fmt.Errorf("%s: unsupported event type %s: %w", op, typ, ErrInvalidParameter) } } func (s *logAdapter) pickType(str string) (Type, string) { switch { case strings.HasPrefix(str, "[DEBUG]"): return SystemType, strings.TrimSpace(str[7:]) case strings.HasPrefix(str, "[TRACE]"): return SystemType, strings.TrimSpace(str[7:]) case strings.HasPrefix(str, "[INFO]"): return SystemType, strings.TrimSpace(str[6:]) case strings.HasPrefix(str, "[WARN]"): return SystemType, strings.TrimSpace(str[6:]) case strings.HasPrefix(str, "[ERROR]"): return ErrorType, strings.TrimSpace(str[7:]) case strings.HasPrefix(str, "[ERR]"): return ErrorType, strings.TrimSpace(str[5:]) default: return SystemType, str } } func isNil(i interface{}) bool { if i == nil { return true } switch reflect.TypeOf(i).Kind() { case reflect.Ptr, reflect.Map, reflect.Array, reflect.Chan, reflect.Slice: return reflect.ValueOf(i).IsNil() } return false }