custom_event.go 2.5 KB

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