Skip to content

Commit 0faba36

Browse files
authored
chore: add omitzero and remove custom serialization (#1197)
1 parent 648d2d2 commit 0faba36

File tree

14 files changed

+44
-280
lines changed

14 files changed

+44
-280
lines changed

interfaces.go

Lines changed: 14 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -66,39 +66,12 @@ type Breadcrumb struct {
6666
Message string `json:"message,omitempty"`
6767
Data map[string]interface{} `json:"data,omitempty"`
6868
Level Level `json:"level,omitempty"`
69-
Timestamp time.Time `json:"timestamp"`
69+
Timestamp time.Time `json:"timestamp,omitzero"`
7070
}
7171

7272
// TODO: provide constants for known breadcrumb types.
7373
// See https://develop.sentry.dev/sdk/event-payloads/breadcrumbs/#breadcrumb-types.
7474

75-
// MarshalJSON converts the Breadcrumb struct to JSON.
76-
func (b *Breadcrumb) MarshalJSON() ([]byte, error) {
77-
// We want to omit time.Time zero values, otherwise the server will try to
78-
// interpret dates too far in the past. However, encoding/json doesn't
79-
// support the "omitempty" option for struct types. See
80-
// https://golang.org/issues/11939.
81-
//
82-
// We overcome the limitation and achieve what we want by shadowing fields
83-
// and a few type tricks.
84-
85-
// breadcrumb aliases Breadcrumb to allow calling json.Marshal without an
86-
// infinite loop. It preserves all fields while none of the attached
87-
// methods.
88-
type breadcrumb Breadcrumb
89-
90-
if b.Timestamp.IsZero() {
91-
return json.Marshal(struct {
92-
// Embed all the fields of Breadcrumb.
93-
*breadcrumb
94-
// Timestamp shadows the original Timestamp field and is meant to
95-
// remain nil, triggering the omitempty behavior.
96-
Timestamp json.RawMessage `json:"timestamp,omitempty"`
97-
}{breadcrumb: (*breadcrumb)(b)})
98-
}
99-
return json.Marshal((*breadcrumb)(b))
100-
}
101-
10275
// Logger provides a chaining API for structured logging to Sentry.
10376
type Logger interface {
10477
// Write implements the io.Writer interface. Currently, the [sentry.Hub] is
@@ -436,7 +409,7 @@ type Event struct {
436409
ServerName string `json:"server_name,omitempty"`
437410
Threads []Thread `json:"threads,omitempty"`
438411
Tags map[string]string `json:"tags,omitempty"`
439-
Timestamp time.Time `json:"timestamp"`
412+
Timestamp time.Time `json:"timestamp,omitzero"`
440413
Transaction string `json:"transaction,omitempty"`
441414
User User `json:"user,omitempty"`
442415
Logger string `json:"logger,omitempty"`
@@ -449,7 +422,7 @@ type Event struct {
449422
// The fields below are only relevant for transactions.
450423

451424
Type string `json:"type,omitempty"`
452-
StartTime time.Time `json:"start_timestamp"`
425+
StartTime time.Time `json:"start_timestamp,omitzero"`
453426
Spans []*Span `json:"spans,omitempty"`
454427
TransactionInfo *TransactionInfo `json:"transaction_info,omitempty"`
455428

@@ -561,17 +534,6 @@ func (e *Event) GetDynamicSamplingContext() map[string]string {
561534

562535
// MarshalJSON converts the Event struct to JSON.
563536
func (e *Event) MarshalJSON() ([]byte, error) {
564-
// We want to omit time.Time zero values, otherwise the server will try to
565-
// interpret dates too far in the past. However, encoding/json doesn't
566-
// support the "omitempty" option for struct types. See
567-
// https://golang.org/issues/11939.
568-
//
569-
// We overcome the limitation and achieve what we want by shadowing fields
570-
// and a few type tricks.
571-
if e.Type == transactionType {
572-
return e.transactionMarshalJSON()
573-
}
574-
575537
if e.Type == checkInType {
576538
return e.checkInMarshalJSON()
577539
}
@@ -583,29 +545,24 @@ func (e *Event) defaultMarshalJSON() ([]byte, error) {
583545
// loop. It preserves all fields while none of the attached methods.
584546
type event Event
585547

548+
if e.Type == transactionType {
549+
return json.Marshal(struct{ *event }{(*event)(e)})
550+
}
586551
// metrics and logs should be serialized under the same `items` json field.
587552
if e.Type == logEvent.Type {
588553
type logEvent struct {
589554
*event
590-
Items []Log `json:"items,omitempty"`
591-
Type json.RawMessage `json:"type,omitempty"`
592-
Timestamp json.RawMessage `json:"timestamp,omitempty"`
593-
StartTime json.RawMessage `json:"start_timestamp,omitempty"`
594-
Spans json.RawMessage `json:"spans,omitempty"`
595-
TransactionInfo json.RawMessage `json:"transaction_info,omitempty"`
555+
Items []Log `json:"items,omitempty"`
556+
Type json.RawMessage `json:"type,omitempty"`
596557
}
597558
return json.Marshal(logEvent{event: (*event)(e), Items: e.Logs})
598559
}
599560

600561
if e.Type == traceMetricEvent.Type {
601562
type metricEvent struct {
602563
*event
603-
Items []Metric `json:"items,omitempty"`
604-
Type json.RawMessage `json:"type,omitempty"`
605-
Timestamp json.RawMessage `json:"timestamp,omitempty"`
606-
StartTime json.RawMessage `json:"start_timestamp,omitempty"`
607-
Spans json.RawMessage `json:"spans,omitempty"`
608-
TransactionInfo json.RawMessage `json:"transaction_info,omitempty"`
564+
Items []Metric `json:"items,omitempty"`
565+
Type json.RawMessage `json:"type,omitempty"`
609566
}
610567
return json.Marshal(metricEvent{event: (*event)(e), Items: e.Metrics})
611568
}
@@ -615,10 +572,6 @@ func (e *Event) defaultMarshalJSON() ([]byte, error) {
615572
type errorEvent struct {
616573
*event
617574

618-
// Timestamp shadows the original Timestamp field. It allows us to
619-
// include the timestamp when non-zero and omit it otherwise.
620-
Timestamp json.RawMessage `json:"timestamp,omitempty"`
621-
622575
// The fields below are not part of error events and only make sense to
623576
// be sent for transactions. They shadow the respective fields in Event
624577
// and are meant to remain nil, triggering the omitempty behavior.
@@ -630,48 +583,6 @@ func (e *Event) defaultMarshalJSON() ([]byte, error) {
630583
}
631584

632585
x := errorEvent{event: (*event)(e)}
633-
if !e.Timestamp.IsZero() {
634-
b, err := e.Timestamp.MarshalJSON()
635-
if err != nil {
636-
return nil, err
637-
}
638-
x.Timestamp = b
639-
}
640-
return json.Marshal(x)
641-
}
642-
643-
func (e *Event) transactionMarshalJSON() ([]byte, error) {
644-
// event aliases Event to allow calling json.Marshal without an infinite
645-
// loop. It preserves all fields while none of the attached methods.
646-
type event Event
647-
648-
// transactionEvent is like Event with shadowed fields for customizing JSON
649-
// marshaling.
650-
type transactionEvent struct {
651-
*event
652-
653-
// The fields below shadow the respective fields in Event. They allow us
654-
// to include timestamps when non-zero and omit them otherwise.
655-
656-
StartTime json.RawMessage `json:"start_timestamp,omitempty"`
657-
Timestamp json.RawMessage `json:"timestamp,omitempty"`
658-
}
659-
660-
x := transactionEvent{event: (*event)(e)}
661-
if !e.Timestamp.IsZero() {
662-
b, err := e.Timestamp.MarshalJSON()
663-
if err != nil {
664-
return nil, err
665-
}
666-
x.Timestamp = b
667-
}
668-
if !e.StartTime.IsZero() {
669-
b, err := e.StartTime.MarshalJSON()
670-
if err != nil {
671-
return nil, err
672-
}
673-
x.StartTime = b
674-
}
675586
return json.Marshal(x)
676587
}
677588

@@ -748,9 +659,9 @@ type EventHint struct {
748659
}
749660

750661
type Log struct {
751-
Timestamp time.Time `json:"timestamp"`
662+
Timestamp time.Time `json:"timestamp,omitzero"`
752663
TraceID TraceID `json:"trace_id"`
753-
SpanID SpanID `json:"span_id,omitempty"`
664+
SpanID SpanID `json:"span_id,omitzero"`
754665
Level LogLevel `json:"level"`
755666
Severity int `json:"severity_number,omitempty"`
756667
Body string `json:"body"`
@@ -792,35 +703,6 @@ type Attribute struct {
792703
Type AttrType `json:"type"`
793704
}
794705

795-
// MarshalJSON converts a Log to JSON that skips SpanID and timestamp when zero.
796-
func (l *Log) MarshalJSON() ([]byte, error) {
797-
type log Log
798-
799-
var spanID string
800-
if l.SpanID != zeroSpanID {
801-
spanID = l.SpanID.String()
802-
}
803-
804-
var ts json.RawMessage
805-
if !l.Timestamp.IsZero() {
806-
b, err := l.Timestamp.MarshalJSON()
807-
if err != nil {
808-
return nil, err
809-
}
810-
ts = b
811-
}
812-
813-
return json.Marshal(struct {
814-
*log
815-
SpanID string `json:"span_id,omitempty"`
816-
Timestamp json.RawMessage `json:"timestamp,omitempty"`
817-
}{
818-
log: (*log)(l),
819-
SpanID: spanID,
820-
Timestamp: ts,
821-
})
822-
}
823-
824706
type MetricType string
825707

826708
const (
@@ -831,45 +713,16 @@ const (
831713
)
832714

833715
type Metric struct {
834-
Timestamp time.Time `json:"timestamp"`
716+
Timestamp time.Time `json:"timestamp,omitzero"`
835717
TraceID TraceID `json:"trace_id"`
836-
SpanID SpanID `json:"span_id,omitempty"`
718+
SpanID SpanID `json:"span_id,omitzero"`
837719
Type MetricType `json:"type"`
838720
Name string `json:"name"`
839721
Value MetricValue `json:"value"`
840722
Unit string `json:"unit,omitempty"`
841723
Attributes map[string]Attribute `json:"attributes,omitempty"`
842724
}
843725

844-
// MarshalJSON converts a Metric to JSON that skips SpanID and timestamp when zero.
845-
func (m *Metric) MarshalJSON() ([]byte, error) {
846-
type metric Metric
847-
848-
var spanID string
849-
if m.SpanID != zeroSpanID {
850-
spanID = m.SpanID.String()
851-
}
852-
853-
var ts json.RawMessage
854-
if !m.Timestamp.IsZero() {
855-
b, err := m.Timestamp.MarshalJSON()
856-
if err != nil {
857-
return nil, err
858-
}
859-
ts = b
860-
}
861-
862-
return json.Marshal(struct {
863-
*metric
864-
SpanID string `json:"span_id,omitempty"`
865-
Timestamp json.RawMessage `json:"timestamp,omitempty"`
866-
}{
867-
metric: (*metric)(m),
868-
SpanID: spanID,
869-
Timestamp: ts,
870-
})
871-
}
872-
873726
// GetCategory returns the rate limit category for metrics.
874727
func (m *Metric) GetCategory() ratelimit.Category {
875728
return ratelimit.CategoryTraceMetric

interfaces_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ func TestEventMarshalJSON(t *testing.T) {
196196
}
197197

198198
// Non-transaction event should not have fields Spans and StartTime
199-
want := `{"sdk":{},"user":{},"timestamp":"1970-01-01T00:00:14Z"}`
199+
want := `{"sdk":{},"timestamp":"1970-01-01T00:00:14Z","user":{}}`
200200

201201
if diff := cmp.Diff(want, string(got)); diff != "" {
202202
t.Errorf("Event mismatch (-want +got):\n%s", diff)

internal/protocol/envelope.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type EnvelopeHeader struct {
2121

2222
// SentAt is the timestamp when the event was sent from the SDK as string in RFC 3339 format.
2323
// Used for clock drift correction of the event timestamp. The time zone must be UTC.
24-
SentAt time.Time `json:"sent_at,omitempty"`
24+
SentAt time.Time `json:"sent_at,omitzero"`
2525

2626
// Dsn can be used for self-authenticated envelopes.
2727
// This means that the envelope has all the information necessary to be sent to sentry.
@@ -169,12 +169,6 @@ func (e *Envelope) Size() (int, error) {
169169
return len(data), nil
170170
}
171171

172-
// MarshalJSON converts the EnvelopeHeader to JSON.
173-
func (h *EnvelopeHeader) MarshalJSON() ([]byte, error) {
174-
type header EnvelopeHeader
175-
return json.Marshal((*header)(h))
176-
}
177-
178172
// NewEnvelopeItem creates a new envelope item with the specified type and payload.
179173
func NewEnvelopeItem(itemType EnvelopeItemType, payload []byte) *EnvelopeItem {
180174
length := len(payload)

internal/protocol/envelope_test.go

Lines changed: 0 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -184,34 +184,3 @@ func TestEnvelope_Size(t *testing.T) {
184184
t.Errorf("Size() = %d, but Serialize() length = %d", size2, len(data))
185185
}
186186
}
187-
188-
func TestEnvelopeHeader_MarshalJSON(t *testing.T) {
189-
header := &EnvelopeHeader{
190-
EventID: "12345678901234567890123456789012",
191-
SentAt: time.Date(2023, 1, 1, 12, 0, 0, 0, time.UTC),
192-
Dsn: "https://public@example.com/1",
193-
Trace: map[string]string{"trace_id": "abc123"},
194-
}
195-
196-
data, err := header.MarshalJSON()
197-
if err != nil {
198-
t.Errorf("MarshalJSON() error = %v", err)
199-
}
200-
201-
var result map[string]interface{}
202-
if err := json.Unmarshal(data, &result); err != nil {
203-
t.Errorf("Marshaled JSON is invalid: %v", err)
204-
}
205-
206-
if result["event_id"] != header.EventID {
207-
t.Errorf("Expected event_id %s, got %v", header.EventID, result["event_id"])
208-
}
209-
210-
if result["dsn"] != header.Dsn {
211-
t.Errorf("Expected dsn %s, got %v", header.Dsn, result["dsn"])
212-
}
213-
214-
if bytes.Contains(data, []byte("\n")) {
215-
t.Error("Marshaled JSON contains newlines")
216-
}
217-
}

propagation_context.go

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,31 +2,15 @@ package sentry
22

33
import (
44
"crypto/rand"
5-
"encoding/json"
65
)
76

87
type PropagationContext struct {
98
TraceID TraceID `json:"trace_id"`
109
SpanID SpanID `json:"span_id"`
11-
ParentSpanID SpanID `json:"parent_span_id"`
10+
ParentSpanID SpanID `json:"parent_span_id,omitzero"`
1211
DynamicSamplingContext DynamicSamplingContext `json:"-"`
1312
}
1413

15-
func (p PropagationContext) MarshalJSON() ([]byte, error) {
16-
type propagationContext PropagationContext
17-
var parentSpanID string
18-
if p.ParentSpanID != zeroSpanID {
19-
parentSpanID = p.ParentSpanID.String()
20-
}
21-
return json.Marshal(struct {
22-
*propagationContext
23-
ParentSpanID string `json:"parent_span_id,omitempty"`
24-
}{
25-
propagationContext: (*propagationContext)(&p),
26-
ParentSpanID: parentSpanID,
27-
})
28-
}
29-
3014
func (p PropagationContext) Map() map[string]interface{} {
3115
m := map[string]interface{}{
3216
"trace_id": p.TraceID,

testdata/error_event.golden

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@
3838
]
3939
},
4040
"server_name": "myhost",
41+
"timestamp": "1970-01-01T00:00:05Z",
4142
"transaction": "mytransaction",
4243
"user": {
4344
"id": "foo"
44-
},
45-
"timestamp": "1970-01-01T00:00:05Z"
45+
}
4646
}

testdata/json/event/000.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"message": "test",
33
"sdk": {},
4-
"user": {},
5-
"timestamp": "2009-11-10T23:00:00Z"
4+
"timestamp": "2009-11-10T23:00:00Z",
5+
"user": {}
66
}

testdata/json/event/001.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"message": "test",
33
"sdk": {},
4-
"user": {},
5-
"timestamp": "2009-11-10T21:00:00-02:00"
4+
"timestamp": "2009-11-10T21:00:00-02:00",
5+
"user": {}
66
}

0 commit comments

Comments
 (0)