package event import ( "context" "fmt" "runtime" "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, fmt.Errorf("%s: missing context: %w", op, ErrInvalidParameter) } if eventer == nil { return nil, fmt.Errorf("%s: missing eventer: %w", op, ErrInvalidParameter) } 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, fmt.Errorf("%s: missing context: %w", op, ErrInvalidParameter) } if info == nil { return nil, fmt.Errorf("%s: missing request info: %w", op, ErrInvalidParameter) } if info.Id == "" { return nil, fmt.Errorf("%s: missing request info id: %w", op, ErrInvalidParameter) } 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 fmt.Errorf("%s: missing context: %w", op, ErrInvalidParameter) } if caller == "" { return fmt.Errorf("%s: missing operation: %w", op, ErrInvalidParameter) } eventer, ok := EventerFromContext(ctx) if !ok { eventer = SysEventer() if eventer == nil { return fmt.Errorf("%s: missing both context and system eventer: %w", op, ErrInvalidParameter) } } opts := getOpts(opt...) if opts.withDetails == nil && opts.withHeader == nil && !opts.withFlush { return fmt.Errorf("%s: specify either header or details options for an event payload: %w", op, ErrInvalidParameter) } if opts.withRequestInfo == nil { var err error if opt, err = addCtxOptions(ctx, opt...); err != nil { return fmt.Errorf("%s: %w", op, err) } } e, err := newObservation(caller, opt...) if err != nil { return fmt.Errorf("%s: %w", op, err) } if err := eventer.writeObservation(ctx, e); err != nil { return fmt.Errorf("%s: %w", op, err) } 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: %v", op, e)) return } } opts := getOpts(opt...) if opts.withRequestInfo == nil { var err error if opt, err = addCtxOptions(ctx, opt...); err != nil { eventer.logger.Error(fmt.Sprintf("%s: %v", op, err)) eventer.logger.Error(fmt.Sprintf("%s: unable to process context options to write error: %v", op, e)) return } } ev, err := newError(caller, e, opt...) if err != nil { eventer.logger.Error(fmt.Sprintf("%s: %v", op, err)) eventer.logger.Error(fmt.Sprintf("%s: unable to create new error to write error: %v", op, e)) return } if err := eventer.writeError(ctx, ev); err != nil { eventer.logger.Error(fmt.Sprintf("%s: %v", op, err)) eventer.logger.Error(fmt.Sprintf("%s: unable to write error: %v", 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 fmt.Errorf("%s: missing context: %w", op, ErrInvalidParameter) } if caller == "" { return fmt.Errorf("%s: missing operation: %w", op, ErrInvalidParameter) } eventer, ok := EventerFromContext(ctx) if !ok { eventer = SysEventer() if eventer == nil { return fmt.Errorf("%s: missing both context and system eventer: %w", op, ErrInvalidParameter) } } opts := getOpts(opt...) if opts.withRequestInfo == nil { var err error if opt, err = addCtxOptions(ctx, opt...); err != nil { return fmt.Errorf("%s: %w", op, err) } } e, err := newAudit(caller, opt...) if err != nil { return fmt.Errorf("%s: %w", op, err) } if err := eventer.writeAudit(ctx, e); err != nil { return fmt.Errorf("%s: %w", op, err) } 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, fmt.Errorf("%s: %w", op, err) } 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, fmt.Errorf("%s: %w", op, err) } 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 } // WriteSysEvent will write a sysevent using the eventer from // event.SysEventer() if no eventer can be found an hclog.Logger will be created // and used. This function should never be used when sending events while // handling API requests. func WriteSysEvent(ctx context.Context, caller Op, data map[string]interface{}) { const op = "event.WriteError" if data == nil { return } if caller == "" { pc, _, _, ok := runtime.Caller(1) details := runtime.FuncForPC(pc) if ok && details != nil { caller = Op(details.Name()) } else { caller = "unknown operation" } } eventer := SysEventer() if eventer == nil { logger := hclog.New(nil) logger.Error(fmt.Sprintf("%s: no eventer available to write sysevent: (%s) %+v", op, caller, data)) return } id, err := newId(string(SystemType)) if err != nil { eventer.logger.Error(fmt.Sprintf("%s: %v", op, err)) eventer.logger.Error(fmt.Sprintf("%s: unable to generate id while writing sysevent: (%s) %+v", op, caller, data)) } e := &sysEvent{ Id: Id(id), Version: sysVersion, Op: caller, Data: data, } if err := eventer.writeSysEvent(ctx, e); err != nil { eventer.logger.Error(fmt.Sprintf("%s: %v", op, err)) eventer.logger.Error(fmt.Sprintf("%s: unable to write sysevent: (%s) %+v", op, caller, e)) return } }