github.com/newrelic/go-agent@v3.26.0+incompatible/internal/custom_event.go (about)

     1  // Copyright 2020 New Relic Corporation. All rights reserved.
     2  // SPDX-License-Identifier: Apache-2.0
     3  
     4  package internal
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"regexp"
    10  	"time"
    11  )
    12  
    13  // https://newrelic.atlassian.net/wiki/display/eng/Custom+Events+in+New+Relic+Agents
    14  
    15  var (
    16  	eventTypeRegexRaw = `^[a-zA-Z0-9:_ ]+$`
    17  	eventTypeRegex    = regexp.MustCompile(eventTypeRegexRaw)
    18  
    19  	errEventTypeLength = fmt.Errorf("event type exceeds length limit of %d",
    20  		attributeKeyLengthLimit)
    21  	// ErrEventTypeRegex will be returned to caller of app.RecordCustomEvent
    22  	// if the event type is not valid.
    23  	ErrEventTypeRegex = fmt.Errorf("event type must match %s", eventTypeRegexRaw)
    24  	errNumAttributes  = fmt.Errorf("maximum of %d attributes exceeded",
    25  		customEventAttributeLimit)
    26  )
    27  
    28  // CustomEvent is a custom event.
    29  type CustomEvent struct {
    30  	eventType       string
    31  	timestamp       time.Time
    32  	truncatedParams map[string]interface{}
    33  }
    34  
    35  // WriteJSON prepares JSON in the format expected by the collector.
    36  func (e *CustomEvent) WriteJSON(buf *bytes.Buffer) {
    37  	w := jsonFieldsWriter{buf: buf}
    38  	buf.WriteByte('[')
    39  	buf.WriteByte('{')
    40  	w.stringField("type", e.eventType)
    41  	w.floatField("timestamp", timeToFloatSeconds(e.timestamp))
    42  	buf.WriteByte('}')
    43  
    44  	buf.WriteByte(',')
    45  	buf.WriteByte('{')
    46  	w = jsonFieldsWriter{buf: buf}
    47  	for key, val := range e.truncatedParams {
    48  		writeAttributeValueJSON(&w, key, val)
    49  	}
    50  	buf.WriteByte('}')
    51  
    52  	buf.WriteByte(',')
    53  	buf.WriteByte('{')
    54  	buf.WriteByte('}')
    55  	buf.WriteByte(']')
    56  }
    57  
    58  // MarshalJSON is used for testing.
    59  func (e *CustomEvent) MarshalJSON() ([]byte, error) {
    60  	buf := bytes.NewBuffer(make([]byte, 0, 256))
    61  
    62  	e.WriteJSON(buf)
    63  
    64  	return buf.Bytes(), nil
    65  }
    66  
    67  func eventTypeValidate(eventType string) error {
    68  	if len(eventType) > attributeKeyLengthLimit {
    69  		return errEventTypeLength
    70  	}
    71  	if !eventTypeRegex.MatchString(eventType) {
    72  		return ErrEventTypeRegex
    73  	}
    74  	return nil
    75  }
    76  
    77  // CreateCustomEvent creates a custom event.
    78  func CreateCustomEvent(eventType string, params map[string]interface{}, now time.Time) (*CustomEvent, error) {
    79  	if err := eventTypeValidate(eventType); nil != err {
    80  		return nil, err
    81  	}
    82  
    83  	if len(params) > customEventAttributeLimit {
    84  		return nil, errNumAttributes
    85  	}
    86  
    87  	truncatedParams := make(map[string]interface{})
    88  	for key, val := range params {
    89  		val, err := ValidateUserAttribute(key, val)
    90  		if nil != err {
    91  			return nil, err
    92  		}
    93  		truncatedParams[key] = val
    94  	}
    95  
    96  	return &CustomEvent{
    97  		eventType:       eventType,
    98  		timestamp:       now,
    99  		truncatedParams: truncatedParams,
   100  	}, nil
   101  }
   102  
   103  // MergeIntoHarvest implements Harvestable.
   104  func (e *CustomEvent) MergeIntoHarvest(h *Harvest) {
   105  	h.CustomEvents.Add(e)
   106  }