Skip to content

Commit

Permalink
add support for Eventer along with all of its dependencies (#1276)
Browse files Browse the repository at this point in the history
  • Loading branch information
jimlambrt authored Jun 6, 2021
1 parent bba9960 commit 6ba8327
Show file tree
Hide file tree
Showing 23 changed files with 2,893 additions and 69 deletions.
231 changes: 231 additions & 0 deletions internal/observability/events/context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
package event

import (
"context"
"fmt"

"github.com/hashicorp/boundary/internal/errors"
"github.com/hashicorp/go-hclog"
)

type key int

const (
eventerKey key = iota
requestInfoKey
)

// NewEventerContext will return a context containing a value of the provided Eventer
func NewEventerContext(ctx context.Context, eventer *Eventer) (context.Context, error) {
const op = "event.NewEventerContext"
if ctx == nil {
return nil, errors.New(errors.InvalidParameter, op, "missing context")
}
if eventer == nil {
return nil, errors.New(errors.InvalidParameter, op, "missing eventer")
}
return context.WithValue(ctx, eventerKey, eventer), nil
}

// EventerFromContext attempts to get the eventer value from the context provided
func EventerFromContext(ctx context.Context) (*Eventer, bool) {
if ctx == nil {
return nil, false
}
eventer, ok := ctx.Value(eventerKey).(*Eventer)
return eventer, ok
}

// NewRequestInfoContext will return a context containing a value for the
// provided RequestInfo
func NewRequestInfoContext(ctx context.Context, info *RequestInfo) (context.Context, error) {
const op = "event.NewRequestInfoContext"
if ctx == nil {
return nil, errors.New(errors.InvalidParameter, op, "missing context")
}
if info == nil {
return nil, errors.New(errors.InvalidParameter, op, "missing request info")
}
if info.Id == "" {
return nil, errors.New(errors.InvalidParameter, op, "missing request info id")
}
return context.WithValue(ctx, requestInfoKey, info), nil
}

// RequestInfoFromContext attempts to get the RequestInfo value from the context
// provided
func RequestInfoFromContext(ctx context.Context) (*RequestInfo, bool) {
if ctx == nil {
return nil, false
}
reqInfo, ok := ctx.Value(requestInfoKey).(*RequestInfo)
return reqInfo, ok
}

// WriteObservation will write an observation event. It will first check the
// ctx for an eventer, then try event.SysEventer() and if no eventer can be
// found an error is returned.
//
// At least one and any combination of the supported options may be used:
// WithHeader, WithDetails, WithId, WithFlush and WithRequestInfo. All other
// options are ignored.
func WriteObservation(ctx context.Context, caller Op, opt ...Option) error {
const op = "event.WriteObservation"
if ctx == nil {
return errors.New(errors.InvalidParameter, op, "missing context")
}
if caller == "" {
return errors.New(errors.InvalidParameter, op, "missing operation")
}
eventer, ok := EventerFromContext(ctx)
if !ok {
eventer = SysEventer()
if eventer == nil {
return errors.New(errors.InvalidParameter, op, "missing both context and system eventer")
}
}
opts := getOpts(opt...)
if opts.withDetails == nil && opts.withHeader == nil && !opts.withFlush {
return errors.New(errors.InvalidParameter, op, "you must specify either header or details options for an event payload")
}
if opts.withRequestInfo == nil {
var err error
if opt, err = addCtxOptions(ctx, opt...); err != nil {
return errors.Wrap(err, op)
}
}
e, err := newObservation(caller, opt...)
if err != nil {
return errors.Wrap(err, op)
}
if err := eventer.writeObservation(ctx, e); err != nil {
return errors.Wrap(err, op)
}
return nil
}

// WriteError will write an error event. It will first check the
// ctx for an eventer, then try event.SysEventer() and if no eventer can be
// found an hclog.Logger will be created and used.
//
// The options WithId and WithRequestInfo are supported and all other options
// are ignored.
func WriteError(ctx context.Context, caller Op, e error, opt ...Option) {
const op = "event.WriteError"
// EventerFromContext will handle a nil ctx appropriately. If e or caller is
// missing, newError(...) will handle them appropriately.
eventer, ok := EventerFromContext(ctx)
if !ok {
eventer = SysEventer()
if eventer == nil {
logger := hclog.New(nil)
logger.Error(fmt.Sprintf("%s: no eventer available to write error: %s", op, e))
return
}
}
opts := getOpts(opt...)
if opts.withRequestInfo == nil {
var err error
if opt, err = addCtxOptions(ctx, opt...); err != nil {
eventer.logger.Error(errors.Wrap(err, op).Error())
eventer.logger.Error(fmt.Sprintf("%s: unable to process context options to write error: %s", op, e))
return
}
}
ev, err := newError(caller, e, opt...)
if err != nil {
eventer.logger.Error(errors.Wrap(err, op).Error())
eventer.logger.Error(fmt.Sprintf("%s: unable to create new error to write error: %s", op, e))
return
}
if err := eventer.writeError(ctx, ev); err != nil {
eventer.logger.Error(errors.Wrap(err, op).Error())
eventer.logger.Error(fmt.Sprintf("%s: unable to write error: %s", op, e))
return
}
}

// WriteAudit will write an audit event. It will first check the ctx for an
// eventer, then try event.SysEventer() and if no eventer can be found an error
// is returned.
//
// At least one and any combination of the supported options may be used:
// WithRequest, WithResponse, WithAuth, WithId, WithFlush and WithRequestInfo.
// All other options are ignored.
func WriteAudit(ctx context.Context, caller Op, opt ...Option) error {
const op = "event.WriteAudit"
if ctx == nil {
return errors.New(errors.InvalidParameter, op, "missing context")
}
if caller == "" {
return errors.New(errors.InvalidParameter, op, "missing operation")
}
eventer, ok := EventerFromContext(ctx)
if !ok {
eventer = SysEventer()
if eventer == nil {
return errors.New(errors.InvalidParameter, op, "missing both context and system eventer")
}
}
opts := getOpts(opt...)
if opts.withRequestInfo == nil {
var err error
if opt, err = addCtxOptions(ctx, opt...); err != nil {
return errors.Wrap(err, op)
}
}
e, err := newAudit(caller, opt...)
if err != nil {
return errors.Wrap(err, op)
}
if err := eventer.writeAudit(ctx, e); err != nil {
return errors.Wrap(err, op)
}
return nil
}

func addCtxOptions(ctx context.Context, opt ...Option) ([]Option, error) {
const op = "event.addCtxOptions"
opts := getOpts(opt...)
retOpts := make([]Option, 0, len(opt))
retOpts = append(retOpts, opt...)
if opts.withRequestInfo == nil {
reqInfo, ok := RequestInfoFromContext(ctx)
if !ok {
// there's no RequestInfo, so there's no id associated with the
// observation and we'll generate one and flush the observation
// since there will never be another with the same id
id, err := newId(string(ObservationType))
if err != nil {
return nil, errors.Wrap(err, op)
}
retOpts = append(retOpts, WithId(id))
if !opts.withFlush {
retOpts = append(retOpts, WithFlush())
}
return retOpts, nil
}
retOpts = append(retOpts, WithRequestInfo(reqInfo))
if reqInfo.Id != "" {
retOpts = append(retOpts, WithId(reqInfo.Id))
}
switch reqInfo.Id {
case "":
// there's no RequestInfo.Id associated with the observation, so we'll
// generate one and flush the observation since there will never be
// another with the same id
id, err := newId(string(ObservationType))
if err != nil {
return nil, errors.Wrap(err, op)
}
retOpts = append(retOpts, WithId(id))
if !opts.withFlush {
retOpts = append(retOpts, WithFlush())
}
return retOpts, nil
default:
retOpts = append(retOpts, WithId(reqInfo.Id))
}
}
return retOpts, nil
}
Loading

0 comments on commit 6ba8327

Please sign in to comment.