226 lines
6.7 KiB
Go
226 lines
6.7 KiB
Go
package protocol
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
)
|
|
|
|
// Envelope represents a Sentry envelope containing headers and items.
|
|
type Envelope struct {
|
|
Header *EnvelopeHeader `json:"-"`
|
|
Items []*EnvelopeItem `json:"-"`
|
|
}
|
|
|
|
// EnvelopeHeader represents the header of a Sentry envelope.
|
|
type EnvelopeHeader struct {
|
|
// EventID is the unique identifier for this event
|
|
EventID string `json:"event_id"`
|
|
|
|
// SentAt is the timestamp when the event was sent from the SDK as string in RFC 3339 format.
|
|
// Used for clock drift correction of the event timestamp. The time zone must be UTC.
|
|
SentAt time.Time `json:"sent_at,omitzero"`
|
|
|
|
// Dsn can be used for self-authenticated envelopes.
|
|
// This means that the envelope has all the information necessary to be sent to sentry.
|
|
// In this case the full DSN must be stored in this key.
|
|
Dsn string `json:"dsn,omitempty"`
|
|
|
|
// Sdk carries the same payload as the sdk interface in the event payload but can be carried for all events.
|
|
// This means that SDK information can be carried for minidumps, session data and other submissions.
|
|
Sdk *SdkInfo `json:"sdk,omitempty"`
|
|
|
|
// Trace contains the [Dynamic Sampling Context](https://develop.sentry.dev/sdk/telemetry/traces/dynamic-sampling-context/)
|
|
Trace map[string]string `json:"trace,omitempty"`
|
|
}
|
|
|
|
// EnvelopeItemType represents the type of envelope item.
|
|
type EnvelopeItemType string
|
|
|
|
// Constants for envelope item types as defined in the Sentry documentation.
|
|
const (
|
|
EnvelopeItemTypeEvent EnvelopeItemType = "event"
|
|
EnvelopeItemTypeTransaction EnvelopeItemType = "transaction"
|
|
EnvelopeItemTypeCheckIn EnvelopeItemType = "check_in"
|
|
EnvelopeItemTypeAttachment EnvelopeItemType = "attachment"
|
|
EnvelopeItemTypeLog EnvelopeItemType = "log"
|
|
EnvelopeItemTypeTraceMetric EnvelopeItemType = "trace_metric"
|
|
)
|
|
|
|
// EnvelopeItemHeader represents the header of an envelope item.
|
|
type EnvelopeItemHeader struct {
|
|
// Type specifies the type of this Item and its contents.
|
|
// Based on the Item type, more headers may be required.
|
|
Type EnvelopeItemType `json:"type"`
|
|
|
|
// Length is the length of the payload in bytes.
|
|
// If no length is specified, the payload implicitly goes to the next newline.
|
|
// For payloads containing newline characters, the length must be specified.
|
|
Length *int `json:"length,omitempty"`
|
|
|
|
// Filename is the name of the attachment file (used for attachments)
|
|
Filename string `json:"filename,omitempty"`
|
|
|
|
// ContentType is the MIME type of the item payload (used for attachments and some other item types)
|
|
ContentType string `json:"content_type,omitempty"`
|
|
|
|
// ItemCount is the number of items in a batch (used for logs)
|
|
ItemCount *int `json:"item_count,omitempty"`
|
|
}
|
|
|
|
// EnvelopeItem represents a single item or batch within an envelope.
|
|
type EnvelopeItem struct {
|
|
Header *EnvelopeItemHeader `json:"-"`
|
|
Payload []byte `json:"-"`
|
|
}
|
|
|
|
// NewEnvelope creates a new envelope with the given header.
|
|
func NewEnvelope(header *EnvelopeHeader) *Envelope {
|
|
return &Envelope{
|
|
Header: header,
|
|
Items: make([]*EnvelopeItem, 0),
|
|
}
|
|
}
|
|
|
|
// AddItem adds an item to the envelope.
|
|
func (e *Envelope) AddItem(item *EnvelopeItem) {
|
|
if item == nil {
|
|
return
|
|
}
|
|
e.Items = append(e.Items, item)
|
|
}
|
|
|
|
// Serialize serializes the envelope to the Sentry envelope format.
|
|
//
|
|
// Format: Headers "\n" { Item } [ "\n" ]
|
|
// Item: Headers "\n" Payload "\n".
|
|
func (e *Envelope) Serialize() ([]byte, error) {
|
|
var buf bytes.Buffer
|
|
|
|
headerBytes, err := json.Marshal(e.Header)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to marshal envelope header: %w", err)
|
|
}
|
|
|
|
if _, err := buf.Write(headerBytes); err != nil {
|
|
return nil, fmt.Errorf("failed to write envelope header: %w", err)
|
|
}
|
|
|
|
if _, err := buf.WriteString("\n"); err != nil {
|
|
return nil, fmt.Errorf("failed to write newline after envelope header: %w", err)
|
|
}
|
|
|
|
for _, item := range e.Items {
|
|
if err := e.writeItem(&buf, item); err != nil {
|
|
return nil, fmt.Errorf("failed to write envelope item: %w", err)
|
|
}
|
|
}
|
|
|
|
return buf.Bytes(), nil
|
|
}
|
|
|
|
// WriteTo writes the envelope to the given writer in the Sentry envelope format.
|
|
func (e *Envelope) WriteTo(w io.Writer) (int64, error) {
|
|
data, err := e.Serialize()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
n, err := w.Write(data)
|
|
return int64(n), err
|
|
}
|
|
|
|
// writeItem writes a single envelope item to the buffer.
|
|
func (e *Envelope) writeItem(buf *bytes.Buffer, item *EnvelopeItem) error {
|
|
headerBytes, err := json.Marshal(item.Header)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal item header: %w", err)
|
|
}
|
|
|
|
if _, err := buf.Write(headerBytes); err != nil {
|
|
return fmt.Errorf("failed to write item header: %w", err)
|
|
}
|
|
|
|
if _, err := buf.WriteString("\n"); err != nil {
|
|
return fmt.Errorf("failed to write newline after item header: %w", err)
|
|
}
|
|
|
|
if len(item.Payload) > 0 {
|
|
if _, err := buf.Write(item.Payload); err != nil {
|
|
return fmt.Errorf("failed to write item payload: %w", err)
|
|
}
|
|
}
|
|
|
|
if _, err := buf.WriteString("\n"); err != nil {
|
|
return fmt.Errorf("failed to write newline after item payload: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Size returns the total size of the envelope when serialized.
|
|
func (e *Envelope) Size() (int, error) {
|
|
data, err := e.Serialize()
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return len(data), nil
|
|
}
|
|
|
|
// NewEnvelopeItem creates a new envelope item with the specified type and payload.
|
|
func NewEnvelopeItem(itemType EnvelopeItemType, payload []byte) *EnvelopeItem {
|
|
length := len(payload)
|
|
return &EnvelopeItem{
|
|
Header: &EnvelopeItemHeader{
|
|
Type: itemType,
|
|
Length: &length,
|
|
},
|
|
Payload: payload,
|
|
}
|
|
}
|
|
|
|
// NewAttachmentItem creates a new envelope item for an attachment.
|
|
// Parameters: filename, contentType, payload.
|
|
func NewAttachmentItem(filename, contentType string, payload []byte) *EnvelopeItem {
|
|
length := len(payload)
|
|
return &EnvelopeItem{
|
|
Header: &EnvelopeItemHeader{
|
|
Type: EnvelopeItemTypeAttachment,
|
|
Length: &length,
|
|
ContentType: contentType,
|
|
Filename: filename,
|
|
},
|
|
Payload: payload,
|
|
}
|
|
}
|
|
|
|
// NewLogItem creates a new envelope item for logs.
|
|
func NewLogItem(itemCount int, payload []byte) *EnvelopeItem {
|
|
length := len(payload)
|
|
return &EnvelopeItem{
|
|
Header: &EnvelopeItemHeader{
|
|
Type: EnvelopeItemTypeLog,
|
|
Length: &length,
|
|
ItemCount: &itemCount,
|
|
ContentType: "application/vnd.sentry.items.log+json",
|
|
},
|
|
Payload: payload,
|
|
}
|
|
}
|
|
|
|
// NewTraceMetricItem creates a new envelope item for trace metrics.
|
|
func NewTraceMetricItem(itemCount int, payload []byte) *EnvelopeItem {
|
|
length := len(payload)
|
|
return &EnvelopeItem{
|
|
Header: &EnvelopeItemHeader{
|
|
Type: EnvelopeItemTypeTraceMetric,
|
|
Length: &length,
|
|
ItemCount: &itemCount,
|
|
ContentType: "application/vnd.sentry.items.trace-metric+json",
|
|
},
|
|
Payload: payload,
|
|
}
|
|
}
|