232 lines
7.1 KiB
Go
232 lines
7.1 KiB
Go
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
|
|
}
|