• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

pace / bricks / 13717956986

06 Mar 2025 02:30PM UTC coverage: 51.823% (-4.8%) from 56.612%
13717956986

push

github

web-flow
Merge pull request #406 from pace/gomod-update

This updates all dependencies to the latest version, excluding

github.com/bsm/redislock
github.com/dave/jennifer

as newer versions lead to unwanted behavior.

54 of 82 new or added lines in 9 files covered. (65.85%)

453 existing lines in 15 files now uncovered.

4889 of 9434 relevant lines covered (51.82%)

20.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/maintenance/errors/raven/client.go
1
// Package raven implements a client for the Sentry error logging service.
2
package raven
3

4
import (
5
        "bytes"
6
        "compress/zlib"
7
        "crypto/rand"
8
        "crypto/tls"
9
        "encoding/base64"
10
        "encoding/hex"
11
        "encoding/json"
12
        "errors"
13
        "fmt"
14
        "io"
15
        mrand "math/rand"
16
        "net/http"
17
        "net/url"
18
        "os"
19
        "regexp"
20
        "runtime"
21
        "strings"
22
        "sync"
23
        "time"
24

25
        "github.com/certifi/gocertifi"
26
        "github.com/pace/bricks/maintenance/log"
27
        "github.com/pace/bricks/pkg/redact"
28
        pkgErrors "github.com/pkg/errors"
29
)
30

31
const (
32
        userAgent       = "raven-go/1.0"
33
        timestampFormat = `"2006-01-02T15:04:05.00"`
34
)
35

36
var (
37
        ErrPacketDropped         = errors.New("raven: packet dropped")
38
        ErrUnableToUnmarshalJSON = errors.New("raven: unable to unmarshal JSON")
39
        ErrMissingUser           = errors.New("raven: dsn missing public key and/or password")
40
        ErrMissingProjectID      = errors.New("raven: dsn missing project id")
41
        ErrInvalidSampleRate     = errors.New("raven: sample rate should be between 0 and 1")
42
)
43

44
type Severity string
45

46
// http://docs.python.org/2/howto/logging.html#logging-levels
47
const (
48
        DEBUG   = Severity("debug")
49
        INFO    = Severity("info")
50
        WARNING = Severity("warning")
51
        ERROR   = Severity("error")
52
        FATAL   = Severity("fatal")
53
)
54

55
type Timestamp time.Time
56

UNCOV
57
func (t Timestamp) MarshalJSON() ([]byte, error) {
×
UNCOV
58
        return []byte(time.Time(t).UTC().Format(timestampFormat)), nil
×
UNCOV
59
}
×
60

61
func (timestamp *Timestamp) UnmarshalJSON(data []byte) error {
×
62
        t, err := time.Parse(timestampFormat, string(data))
×
63
        if err != nil {
×
64
                return err
×
65
        }
×
66

67
        *timestamp = Timestamp(t)
×
68
        return nil
×
69
}
70

71
func (timestamp Timestamp) Format(format string) string {
×
72
        t := time.Time(timestamp)
×
73
        return t.Format(format)
×
74
}
×
75

76
// An Interface is a Sentry interface that will be serialized as JSON.
77
// It must implement json.Marshaler or use json struct tags.
78
type Interface interface {
79
        // The Sentry class name. Example: sentry.interfaces.Stacktrace
80
        Class() string
81
}
82

83
type Culpriter interface {
84
        Culprit() string
85
}
86

87
type Transport interface {
88
        Send(url, authHeader string, packet *Packet) error
89
}
90

91
type Extra map[string]interface{}
92

93
type outgoingPacket struct {
94
        packet *Packet
95
        ch     chan error
96
}
97

98
type Tag struct {
99
        Key   string
100
        Value string
101
}
102

103
type Tags []Tag
104

105
func (tag *Tag) MarshalJSON() ([]byte, error) {
×
106
        return json.Marshal([2]string{tag.Key, tag.Value})
×
107
}
×
108

109
func (t *Tag) UnmarshalJSON(data []byte) error {
×
110
        var tag [2]string
×
111
        if err := json.Unmarshal(data, &tag); err != nil {
×
112
                return err
×
113
        }
×
114
        *t = Tag{tag[0], tag[1]}
×
115
        return nil
×
116
}
117

118
func (t *Tags) UnmarshalJSON(data []byte) error {
×
119
        var tags []Tag
×
120

×
121
        switch data[0] {
×
122
        case '[':
×
123
                // Unmarshal into []Tag
×
124
                if err := json.Unmarshal(data, &tags); err != nil {
×
125
                        return err
×
126
                }
×
127
        case '{':
×
128
                // Unmarshal into map[string]string
×
129
                tagMap := make(map[string]string)
×
130
                if err := json.Unmarshal(data, &tagMap); err != nil {
×
131
                        return err
×
132
                }
×
133

134
                // Convert to []Tag
135
                for k, v := range tagMap {
×
136
                        tags = append(tags, Tag{k, v})
×
137
                }
×
138
        default:
×
139
                return ErrUnableToUnmarshalJSON
×
140
        }
141

142
        *t = tags
×
143
        return nil
×
144
}
145

146
// https://docs.getsentry.com/hosted/clientdev/#building-the-json-packet
147
type Packet struct {
148
        // Required
149
        Message string `json:"message"`
150

151
        // Required, set automatically by Client.Send/Report via Packet.Init if blank
152
        EventID   string    `json:"event_id"`
153
        Project   string    `json:"project"`
154
        Timestamp Timestamp `json:"timestamp"`
155
        Level     Severity  `json:"level"`
156
        Logger    string    `json:"logger"`
157

158
        // Optional
159
        Platform    string            `json:"platform,omitempty"`
160
        Culprit     string            `json:"culprit,omitempty"`
161
        ServerName  string            `json:"server_name,omitempty"`
162
        Release     string            `json:"release,omitempty"`
163
        Environment string            `json:"environment,omitempty"`
164
        Tags        Tags              `json:"tags,omitempty"`
165
        Modules     map[string]string `json:"modules,omitempty"`
166
        Fingerprint []string          `json:"fingerprint,omitempty"`
167
        Extra       Extra             `json:"extra,omitempty"`
168

169
        Breadcrumbs []*Breadcrumb `json:"breadcrumbs,omitempty"`
170

171
        Interfaces []Interface `json:"-"`
172
}
173

174
type Breadcrumb struct {
175
        Category  string                 `json:"category,omitempty"`
176
        Data      map[string]interface{} `json:"data,omitempty"`
177
        Level     string                 `json:"level,omitempty"`
178
        Message   string                 `json:"message,omitempty"`
179
        Timestamp int64                  `json:"timestamp,omitempty"`
180
        Type      string                 `json:"type,omitempty"`
181
}
182

183
// NewPacket constructs a packet with the specified message and interfaces.
UNCOV
184
func NewPacket(message string, interfaces ...Interface) *Packet {
×
UNCOV
185
        extra := Extra{}
×
UNCOV
186
        setExtraDefaults(extra)
×
UNCOV
187
        return &Packet{
×
UNCOV
188
                Message:    message,
×
UNCOV
189
                Interfaces: interfaces,
×
UNCOV
190
                Extra:      extra,
×
UNCOV
191
        }
×
UNCOV
192
}
×
193

194
// NewPacketWithExtra constructs a packet with the specified message, extra information, and interfaces.
195
func NewPacketWithExtra(message string, extra Extra, interfaces ...Interface) *Packet {
×
196
        if extra == nil {
×
197
                extra = Extra{}
×
198
        }
×
199
        setExtraDefaults(extra)
×
200

×
201
        return &Packet{
×
202
                Message:    message,
×
203
                Interfaces: interfaces,
×
204
                Extra:      extra,
×
205
        }
×
206
}
207

UNCOV
208
func setExtraDefaults(extra Extra) Extra {
×
UNCOV
209
        extra["runtime.Version"] = runtime.Version()
×
UNCOV
210
        extra["runtime.NumCPU"] = runtime.NumCPU()
×
UNCOV
211
        extra["runtime.GOMAXPROCS"] = runtime.GOMAXPROCS(0) // 0 just returns the current value
×
UNCOV
212
        extra["runtime.NumGoroutine"] = runtime.NumGoroutine()
×
UNCOV
213
        return extra
×
UNCOV
214
}
×
215

216
// Init initializes required fields in a packet. It is typically called by
217
// Client.Send/Report automatically.
UNCOV
218
func (packet *Packet) Init(project string) error {
×
UNCOV
219
        if packet.Project == "" {
×
UNCOV
220
                packet.Project = project
×
UNCOV
221
        }
×
UNCOV
222
        if packet.EventID == "" {
×
UNCOV
223
                var err error
×
UNCOV
224
                packet.EventID, err = uuid()
×
UNCOV
225
                if err != nil {
×
226
                        return err
×
227
                }
×
228
        }
UNCOV
229
        if time.Time(packet.Timestamp).IsZero() {
×
UNCOV
230
                packet.Timestamp = Timestamp(time.Now())
×
UNCOV
231
        }
×
UNCOV
232
        if packet.Level == "" {
×
UNCOV
233
                packet.Level = ERROR
×
UNCOV
234
        }
×
UNCOV
235
        if packet.Logger == "" {
×
UNCOV
236
                packet.Logger = "root"
×
UNCOV
237
        }
×
UNCOV
238
        if packet.ServerName == "" {
×
UNCOV
239
                packet.ServerName = hostname
×
UNCOV
240
        }
×
UNCOV
241
        if packet.Platform == "" {
×
UNCOV
242
                packet.Platform = "go"
×
UNCOV
243
        }
×
244

UNCOV
245
        if packet.Culprit == "" {
×
UNCOV
246
                for _, inter := range packet.Interfaces {
×
UNCOV
247
                        if c, ok := inter.(Culpriter); ok {
×
UNCOV
248
                                packet.Culprit = c.Culprit()
×
UNCOV
249
                                if packet.Culprit != "" {
×
250
                                        break
×
251
                                }
252
                        }
253
                }
254
        }
255

UNCOV
256
        return nil
×
257
}
258

UNCOV
259
func (packet *Packet) AddTags(tags map[string]string) {
×
UNCOV
260
        for k, v := range tags {
×
261
                packet.Tags = append(packet.Tags, Tag{k, v})
×
262
        }
×
263
}
264

UNCOV
265
func uuid() (string, error) {
×
UNCOV
266
        id := make([]byte, 16)
×
UNCOV
267
        _, err := io.ReadFull(rand.Reader, id)
×
UNCOV
268
        if err != nil {
×
269
                return "", err
×
270
        }
×
UNCOV
271
        id[6] &= 0x0F // clear version
×
UNCOV
272
        id[6] |= 0x40 // set version to 4 (random uuid)
×
UNCOV
273
        id[8] &= 0x3F // clear variant
×
UNCOV
274
        id[8] |= 0x80 // set to IETF variant
×
UNCOV
275
        return hex.EncodeToString(id), nil
×
276
}
277

UNCOV
278
func (packet *Packet) JSON() ([]byte, error) {
×
UNCOV
279
        packetJSON, err := json.Marshal(packet)
×
UNCOV
280
        if err != nil {
×
281
                return nil, err
×
282
        }
×
283

UNCOV
284
        interfaces := make(map[string]Interface, len(packet.Interfaces))
×
UNCOV
285
        for _, inter := range packet.Interfaces {
×
UNCOV
286
                if inter != nil {
×
UNCOV
287
                        interfaces[inter.Class()] = inter
×
UNCOV
288
                }
×
289
        }
290

UNCOV
291
        if len(interfaces) > 0 {
×
UNCOV
292
                interfaceJSON, err := json.Marshal(interfaces)
×
UNCOV
293
                if err != nil {
×
294
                        return nil, err
×
295
                }
×
UNCOV
296
                packetJSON[len(packetJSON)-1] = ','
×
UNCOV
297
                packetJSON = append(packetJSON, interfaceJSON[1:]...)
×
298
        }
299

UNCOV
300
        return packetJSON, nil
×
301
}
302

303
type context struct {
304
        user *User
305
        http *Http
306
        tags map[string]string
307
}
308

309
func (c *context) setUser(u *User) { c.user = u }
×
310
func (c *context) setHttp(h *Http) { c.http = h }
×
311
func (c *context) setTags(t map[string]string) {
×
312
        if c.tags == nil {
×
313
                c.tags = make(map[string]string)
×
314
        }
×
315
        for k, v := range t {
×
316
                c.tags[k] = v
×
317
        }
×
318
}
319

320
func (c *context) clear() {
×
321
        c.user = nil
×
322
        c.http = nil
×
323
        c.tags = nil
×
324
}
×
325

326
// Return a list of interfaces to be used in appending with the rest
327
func (c *context) interfaces() []Interface {
×
328
        len, i := 0, 0
×
329
        if c.user != nil {
×
330
                len++
×
331
        }
×
332
        if c.http != nil {
×
333
                len++
×
334
        }
×
335
        interfaces := make([]Interface, len)
×
336
        if c.user != nil {
×
337
                interfaces[i] = c.user
×
338
                i++
×
339
        }
×
340
        if c.http != nil {
×
341
                interfaces[i] = c.http
×
342
        }
×
343
        return interfaces
×
344
}
345

346
// The maximum number of packets that will be buffered waiting to be delivered.
347
// Packets will be dropped if the buffer is full. Used by NewClient.
348
var MaxQueueBuffer = 100
349

UNCOV
350
func newTransport() Transport {
×
UNCOV
351
        t := &HTTPTransport{}
×
UNCOV
352
        rootCAs, err := gocertifi.CACerts()
×
UNCOV
353
        if err != nil {
×
354
                log.Println("raven: failed to load root TLS certificates:", err)
×
UNCOV
355
        } else {
×
UNCOV
356
                t.Client = &http.Client{
×
UNCOV
357
                        Transport: &http.Transport{
×
UNCOV
358
                                Proxy:           http.ProxyFromEnvironment,
×
UNCOV
359
                                TLSClientConfig: &tls.Config{RootCAs: rootCAs},
×
UNCOV
360
                        },
×
UNCOV
361
                }
×
UNCOV
362
        }
×
UNCOV
363
        return t
×
364
}
365

UNCOV
366
func newClient(tags map[string]string) *Client {
×
UNCOV
367
        client := &Client{
×
UNCOV
368
                Transport:  newTransport(),
×
UNCOV
369
                Tags:       tags,
×
UNCOV
370
                context:    &context{},
×
UNCOV
371
                sampleRate: 1.0,
×
UNCOV
372
                queue:      make(chan *outgoingPacket, MaxQueueBuffer),
×
UNCOV
373
        }
×
UNCOV
374
        dsn := os.Getenv("SENTRY_DSN")
×
UNCOV
375
        err := client.SetDSN(dsn)
×
UNCOV
376
        if err != nil && dsn != "" {
×
377
                log.Warnf("DSN environment was set to %q but failed: %v", dsn, err)
×
378
        }
×
UNCOV
379
        client.SetRelease(os.Getenv("SENTRY_RELEASE"))
×
UNCOV
380
        client.SetEnvironment(os.Getenv("ENVIRONMENT"))
×
UNCOV
381
        return client
×
382
}
383

384
// New constructs a new Sentry client instance
385
func New(dsn string) (*Client, error) {
×
386
        client := newClient(nil)
×
387
        return client, client.SetDSN(dsn)
×
388
}
×
389

390
// NewWithTags constructs a new Sentry client instance with default tags.
391
func NewWithTags(dsn string, tags map[string]string) (*Client, error) {
×
392
        client := newClient(tags)
×
393
        return client, client.SetDSN(dsn)
×
394
}
×
395

396
// NewClient constructs a Sentry client and spawns a background goroutine to
397
// handle packets sent by Client.Report.
398
//
399
// Deprecated: use New and NewWithTags instead
400
func NewClient(dsn string, tags map[string]string) (*Client, error) {
×
401
        client := newClient(tags)
×
402
        return client, client.SetDSN(dsn)
×
403
}
×
404

405
// Client encapsulates a connection to a Sentry server. It must be initialized
406
// by calling NewClient. Modification of fields concurrently with Send or after
407
// calling Report for the first time is not thread-safe.
408
type Client struct {
409
        Tags map[string]string
410

411
        Transport Transport
412

413
        // DropHandler is called when a packet is dropped because the buffer is full.
414
        DropHandler func(*Packet)
415

416
        // Context that will get appending to all packets
417
        context *context
418

419
        mu          sync.RWMutex
420
        url         string
421
        projectID   string
422
        authHeader  string
423
        release     string
424
        environment string
425
        sampleRate  float32
426

427
        // default logger name (leave empty for 'root')
428
        defaultLoggerName string
429

430
        includePaths       []string
431
        ignoreErrorsRegexp *regexp.Regexp
432
        queue              chan *outgoingPacket
433

434
        // A WaitGroup to keep track of all currently in-progress captures
435
        // This is intended to be used with Client.Wait() to assure that
436
        // all messages have been transported before exiting the process.
437
        wg sync.WaitGroup
438

439
        // A Once to track only starting up the background worker once
440
        start sync.Once
441
}
442

443
// Initialize a default *Client instance
444
var DefaultClient = newClient(nil)
445

446
func (c *Client) SetIgnoreErrors(errs []string) error {
×
447
        joinedRegexp := strings.Join(errs, "|")
×
448
        r, err := regexp.Compile(joinedRegexp)
×
449
        if err != nil {
×
450
                return fmt.Errorf("failed to compile regexp %q for %q: %v", joinedRegexp, errs, err)
×
451
        }
×
452

453
        c.mu.Lock()
×
454
        c.ignoreErrorsRegexp = r
×
455
        c.mu.Unlock()
×
456
        return nil
×
457
}
458

UNCOV
459
func (c *Client) shouldExcludeErr(errStr string) bool {
×
UNCOV
460
        c.mu.RLock()
×
UNCOV
461
        defer c.mu.RUnlock()
×
UNCOV
462
        return c.ignoreErrorsRegexp != nil && c.ignoreErrorsRegexp.MatchString(errStr)
×
UNCOV
463
}
×
464

465
func SetIgnoreErrors(errs ...string) error {
×
466
        return DefaultClient.SetIgnoreErrors(errs)
×
467
}
×
468

469
// SetDSN updates a client with a new DSN. It safe to call after and
470
// concurrently with calls to Report and Send.
UNCOV
471
func (client *Client) SetDSN(dsn string) error {
×
UNCOV
472
        if dsn == "" {
×
UNCOV
473
                return nil
×
UNCOV
474
        }
×
475

476
        client.mu.Lock()
×
477
        defer client.mu.Unlock()
×
478

×
479
        uri, err := url.Parse(dsn)
×
480
        if err != nil {
×
481
                return err
×
482
        }
×
483

484
        if uri.User == nil {
×
485
                return ErrMissingUser
×
486
        }
×
487
        publicKey := uri.User.Username()
×
488
        secretKey, hasSecretKey := uri.User.Password()
×
489
        uri.User = nil
×
490

×
491
        if idx := strings.LastIndex(uri.Path, "/"); idx != -1 {
×
492
                client.projectID = uri.Path[idx+1:]
×
493
                uri.Path = uri.Path[:idx+1] + "api/" + client.projectID + "/store/"
×
494
        }
×
495
        if client.projectID == "" {
×
496
                return ErrMissingProjectID
×
497
        }
×
498

499
        client.url = uri.String()
×
500

×
501
        if hasSecretKey {
×
502
                client.authHeader = fmt.Sprintf("Sentry sentry_version=4, sentry_key=%s, sentry_secret=%s", publicKey, secretKey)
×
503
        } else {
×
504
                client.authHeader = fmt.Sprintf("Sentry sentry_version=4, sentry_key=%s", publicKey)
×
505
        }
×
506

507
        return nil
×
508
}
509

510
// Sets the DSN for the default *Client instance
511
func SetDSN(dsn string) error { return DefaultClient.SetDSN(dsn) }
×
512

513
// SetRelease sets the "release" tag.
UNCOV
514
func (client *Client) SetRelease(release string) {
×
UNCOV
515
        client.mu.Lock()
×
UNCOV
516
        defer client.mu.Unlock()
×
UNCOV
517
        client.release = release
×
UNCOV
518
}
×
519

520
// SetEnvironment sets the "environment" tag.
UNCOV
521
func (client *Client) SetEnvironment(environment string) {
×
UNCOV
522
        client.mu.Lock()
×
UNCOV
523
        defer client.mu.Unlock()
×
UNCOV
524
        client.environment = environment
×
UNCOV
525
}
×
526

527
// SetDefaultLoggerName sets the default logger name.
528
func (client *Client) SetDefaultLoggerName(name string) {
×
529
        client.mu.Lock()
×
530
        defer client.mu.Unlock()
×
531
        client.defaultLoggerName = name
×
532
}
×
533

534
// SetSampleRate sets how much sampling we want on client side
535
func (client *Client) SetSampleRate(rate float32) error {
×
536
        client.mu.Lock()
×
537
        defer client.mu.Unlock()
×
538

×
539
        if rate < 0 || rate > 1 {
×
540
                return ErrInvalidSampleRate
×
541
        }
×
542
        client.sampleRate = rate
×
543
        return nil
×
544
}
545

546
// SetRelease sets the "release" tag on the default *Client
547
func SetRelease(release string) { DefaultClient.SetRelease(release) }
×
548

549
// SetEnvironment sets the "environment" tag on the default *Client
550
func SetEnvironment(environment string) { DefaultClient.SetEnvironment(environment) }
×
551

552
// SetDefaultLoggerName sets the "defaultLoggerName" on the default *Client
553
func SetDefaultLoggerName(name string) {
×
554
        DefaultClient.SetDefaultLoggerName(name)
×
555
}
×
556

557
// SetSampleRate sets the "sample rate" on the degault *Client
558
func SetSampleRate(rate float32) error { return DefaultClient.SetSampleRate(rate) }
×
559

UNCOV
560
func (client *Client) worker() {
×
UNCOV
561
        for outgoingPacket := range client.queue {
×
UNCOV
562

×
UNCOV
563
                client.mu.RLock()
×
UNCOV
564
                url, authHeader := client.url, client.authHeader
×
UNCOV
565
                client.mu.RUnlock()
×
UNCOV
566

×
UNCOV
567
                outgoingPacket.ch <- client.Transport.Send(url, authHeader, outgoingPacket.packet)
×
UNCOV
568
                client.wg.Done()
×
UNCOV
569
        }
×
570
}
571

572
// Capture asynchronously delivers a packet to the Sentry server. It is a no-op
573
// when client is nil. A channel is provided if it is important to check for a
574
// send's success.
UNCOV
575
func (client *Client) Capture(packet *Packet, captureTags map[string]string) (eventID string, ch chan error) {
×
UNCOV
576
        ch = make(chan error, 1)
×
UNCOV
577

×
UNCOV
578
        if client == nil {
×
579
                // return a chan that always returns nil when the caller receives from it
×
580
                close(ch)
×
581
                return
×
582
        }
×
583

UNCOV
584
        if client.sampleRate < 1.0 && mrand.Float32() > client.sampleRate {
×
585
                return
×
586
        }
×
587

UNCOV
588
        if packet == nil {
×
589
                close(ch)
×
590
                return
×
591
        }
×
592

UNCOV
593
        if client.shouldExcludeErr(packet.Message) {
×
594
                return
×
595
        }
×
596

597
        // Keep track of all running Captures so that we can wait for them all to finish
598
        // *Must* call client.wg.Done() on any path that indicates that an event was
599
        // finished being acted upon, whether success or failure
UNCOV
600
        client.wg.Add(1)
×
UNCOV
601

×
UNCOV
602
        // Merge capture tags and client tags
×
UNCOV
603
        packet.AddTags(captureTags)
×
UNCOV
604
        packet.AddTags(client.Tags)
×
UNCOV
605

×
UNCOV
606
        // Initialize any required packet fields
×
UNCOV
607
        client.mu.RLock()
×
UNCOV
608
        packet.AddTags(client.context.tags)
×
UNCOV
609
        projectID := client.projectID
×
UNCOV
610
        release := client.release
×
UNCOV
611
        environment := client.environment
×
UNCOV
612
        defaultLoggerName := client.defaultLoggerName
×
UNCOV
613
        client.mu.RUnlock()
×
UNCOV
614

×
UNCOV
615
        // set the global logger name on the packet if we must
×
UNCOV
616
        if packet.Logger == "" && defaultLoggerName != "" {
×
617
                packet.Logger = defaultLoggerName
×
618
        }
×
619

UNCOV
620
        err := packet.Init(projectID)
×
UNCOV
621
        if err != nil {
×
622
                ch <- err
×
623
                client.wg.Done()
×
624
                return
×
625
        }
×
626

UNCOV
627
        if packet.Release == "" {
×
UNCOV
628
                packet.Release = release
×
UNCOV
629
        }
×
630

UNCOV
631
        if packet.Environment == "" {
×
UNCOV
632
                packet.Environment = environment
×
UNCOV
633
        }
×
634

UNCOV
635
        outgoingPacket := &outgoingPacket{packet, ch}
×
UNCOV
636

×
UNCOV
637
        // Lazily start background worker until we
×
UNCOV
638
        // do our first write into the queue.
×
UNCOV
639
        client.start.Do(func() {
×
UNCOV
640
                go client.worker()
×
UNCOV
641
        })
×
642

UNCOV
643
        select {
×
UNCOV
644
        case client.queue <- outgoingPacket:
×
645
        default:
×
646
                // Send would block, drop the packet
×
647
                if client.DropHandler != nil {
×
648
                        client.DropHandler(packet)
×
649
                }
×
650
                ch <- ErrPacketDropped
×
651
                client.wg.Done()
×
652
        }
653

UNCOV
654
        return packet.EventID, ch
×
655
}
656

657
// Capture asynchronously delivers a packet to the Sentry server with the default *Client.
658
// It is a no-op when client is nil. A channel is provided if it is important to check for a
659
// send's success.
UNCOV
660
func Capture(packet *Packet, captureTags map[string]string) (eventID string, ch chan error) {
×
UNCOV
661
        return DefaultClient.Capture(packet, captureTags)
×
UNCOV
662
}
×
663

664
// CaptureMessage formats and delivers a string message to the Sentry server.
665
func (client *Client) CaptureMessage(message string, tags map[string]string, interfaces ...Interface) string {
×
666
        if client == nil {
×
667
                return ""
×
668
        }
×
669

670
        if client.shouldExcludeErr(message) {
×
671
                return ""
×
672
        }
×
673

674
        packet := NewPacket(message, append(append(interfaces, client.context.interfaces()...), &Message{message, nil})...)
×
675
        eventID, _ := client.Capture(packet, tags)
×
676

×
677
        return eventID
×
678
}
679

680
// CaptureMessage formats and delivers a string message to the Sentry server with the default *Client
681
func CaptureMessage(message string, tags map[string]string, interfaces ...Interface) string {
×
682
        return DefaultClient.CaptureMessage(message, tags, interfaces...)
×
683
}
×
684

685
// CaptureMessageAndWait is identical to CaptureMessage except it blocks and waits for the message to be sent.
686
func (client *Client) CaptureMessageAndWait(message string, tags map[string]string, interfaces ...Interface) string {
×
687
        if client == nil {
×
688
                return ""
×
689
        }
×
690

691
        if client.shouldExcludeErr(message) {
×
692
                return ""
×
693
        }
×
694

695
        packet := NewPacket(message, append(append(interfaces, client.context.interfaces()...), &Message{message, nil})...)
×
696
        eventID, ch := client.Capture(packet, tags)
×
697
        if eventID != "" {
×
698
                <-ch
×
699
        }
×
700

701
        return eventID
×
702
}
703

704
// CaptureMessageAndWait is identical to CaptureMessage except it blocks and waits for the message to be sent.
705
func CaptureMessageAndWait(message string, tags map[string]string, interfaces ...Interface) string {
×
706
        return DefaultClient.CaptureMessageAndWait(message, tags, interfaces...)
×
707
}
×
708

709
// CaptureErrors formats and delivers an error to the Sentry server.
710
// Adds a stacktrace to the packet, excluding the call to this method.
711
func (client *Client) CaptureError(err error, tags map[string]string, interfaces ...Interface) string {
×
712
        if client == nil {
×
713
                return ""
×
714
        }
×
715

716
        if err == nil {
×
717
                return ""
×
718
        }
×
719

720
        if client.shouldExcludeErr(err.Error()) {
×
721
                return ""
×
722
        }
×
723

724
        extra := extractExtra(err)
×
725
        cause := pkgErrors.Cause(err)
×
726

×
727
        packet := NewPacketWithExtra(err.Error(), extra, append(append(interfaces, client.context.interfaces()...), NewException(cause, GetOrNewStacktrace(cause, 1, 3, client.includePaths)))...)
×
728
        eventID, _ := client.Capture(packet, tags)
×
729

×
730
        return eventID
×
731
}
732

733
// CaptureErrors formats and delivers an error to the Sentry server using the default *Client.
734
// Adds a stacktrace to the packet, excluding the call to this method.
735
func CaptureError(err error, tags map[string]string, interfaces ...Interface) string {
×
736
        return DefaultClient.CaptureError(err, tags, interfaces...)
×
737
}
×
738

739
// CaptureErrorAndWait is identical to CaptureError, except it blocks and assures that the event was sent
740
func (client *Client) CaptureErrorAndWait(err error, tags map[string]string, interfaces ...Interface) string {
×
741
        if client == nil {
×
742
                return ""
×
743
        }
×
744

745
        if client.shouldExcludeErr(err.Error()) {
×
746
                return ""
×
747
        }
×
748

749
        extra := extractExtra(err)
×
750
        cause := pkgErrors.Cause(err)
×
751

×
752
        packet := NewPacketWithExtra(err.Error(), extra, append(append(interfaces, client.context.interfaces()...), NewException(cause, GetOrNewStacktrace(cause, 1, 3, client.includePaths)))...)
×
753
        eventID, ch := client.Capture(packet, tags)
×
754
        if eventID != "" {
×
755
                <-ch
×
756
        }
×
757

758
        return eventID
×
759
}
760

761
// CaptureErrorAndWait is identical to CaptureError, except it blocks and assures that the event was sent
762
func CaptureErrorAndWait(err error, tags map[string]string, interfaces ...Interface) string {
×
763
        return DefaultClient.CaptureErrorAndWait(err, tags, interfaces...)
×
764
}
×
765

766
// CapturePanic calls f and then recovers and reports a panic to the Sentry server if it occurs.
767
// If an error is captured, both the error and the reported Sentry error ID are returned.
768
func (client *Client) CapturePanic(f func(), tags map[string]string, interfaces ...Interface) (err interface{}, errorID string) {
×
769
        // Note: This doesn't need to check for client, because we still want to go through the defer/recover path
×
770
        // Down the line, Capture will be noop'd, so while this does a _tiny_ bit of overhead constructing the
×
771
        // *Packet just to be thrown away, this should not be the normal case. Could be refactored to
×
772
        // be completely noop though if we cared.
×
773
        defer func() {
×
774
                var packet *Packet
×
775
                err = recover()
×
776
                switch rval := err.(type) {
×
777
                case nil:
×
778
                        return
×
779
                case error:
×
780
                        if client.shouldExcludeErr(rval.Error()) {
×
781
                                return
×
782
                        }
×
783
                        packet = NewPacket(rval.Error(), append(append(interfaces, client.context.interfaces()...), NewException(rval, NewStacktrace(2, 3, client.includePaths)))...)
×
784
                default:
×
785
                        rvalStr := fmt.Sprint(rval)
×
786
                        if client.shouldExcludeErr(rvalStr) {
×
787
                                return
×
788
                        }
×
789
                        packet = NewPacket(rvalStr, append(append(interfaces, client.context.interfaces()...), NewException(errors.New(rvalStr), NewStacktrace(2, 3, client.includePaths)))...)
×
790
                }
791

792
                errorID, _ = client.Capture(packet, tags)
×
793
        }()
794

795
        f()
×
796
        return
×
797
}
798

799
// CapturePanic calls f and then recovers and reports a panic to the Sentry server if it occurs.
800
// If an error is captured, both the error and the reported Sentry error ID are returned.
801
func CapturePanic(f func(), tags map[string]string, interfaces ...Interface) (interface{}, string) {
×
802
        return DefaultClient.CapturePanic(f, tags, interfaces...)
×
803
}
×
804

805
// CapturePanicAndWait is identical to CaptureError, except it blocks and assures that the event was sent
806
func (client *Client) CapturePanicAndWait(f func(), tags map[string]string, interfaces ...Interface) (err interface{}, errorID string) {
×
807
        // Note: This doesn't need to check for client, because we still want to go through the defer/recover path
×
808
        // Down the line, Capture will be noop'd, so while this does a _tiny_ bit of overhead constructing the
×
809
        // *Packet just to be thrown away, this should not be the normal case. Could be refactored to
×
810
        // be completely noop though if we cared.
×
811
        defer func() {
×
812
                var packet *Packet
×
813
                err = recover()
×
814
                switch rval := err.(type) {
×
815
                case nil:
×
816
                        return
×
817
                case error:
×
818
                        if client.shouldExcludeErr(rval.Error()) {
×
819
                                return
×
820
                        }
×
821
                        packet = NewPacket(rval.Error(), append(append(interfaces, client.context.interfaces()...), NewException(rval, NewStacktrace(2, 3, client.includePaths)))...)
×
822
                default:
×
823
                        rvalStr := fmt.Sprint(rval)
×
824
                        if client.shouldExcludeErr(rvalStr) {
×
825
                                return
×
826
                        }
×
827
                        packet = NewPacket(rvalStr, append(append(interfaces, client.context.interfaces()...), NewException(errors.New(rvalStr), NewStacktrace(2, 3, client.includePaths)))...)
×
828
                }
829

830
                var ch chan error
×
831
                errorID, ch = client.Capture(packet, tags)
×
832
                if errorID != "" {
×
833
                        <-ch
×
834
                }
×
835
        }()
836

837
        f()
×
838
        return
×
839
}
840

841
// CapturePanicAndWait is identical to CaptureError, except it blocks and assures that the event was sent
842
func CapturePanicAndWait(f func(), tags map[string]string, interfaces ...Interface) (interface{}, string) {
×
843
        return DefaultClient.CapturePanicAndWait(f, tags, interfaces...)
×
844
}
×
845

846
func (client *Client) Close() {
×
847
        close(client.queue)
×
848
}
×
849

850
func Close() { DefaultClient.Close() }
×
851

852
// Wait blocks and waits for all events to finish being sent to Sentry server
853
func (client *Client) Wait() {
×
854
        client.wg.Wait()
×
855
}
×
856

857
// Wait blocks and waits for all events to finish being sent to Sentry server
858
func Wait() { DefaultClient.Wait() }
×
859

860
func (client *Client) URL() string {
×
861
        client.mu.RLock()
×
862
        defer client.mu.RUnlock()
×
863

×
864
        return client.url
×
865
}
×
866

867
func URL() string { return DefaultClient.URL() }
×
868

869
func (client *Client) ProjectID() string {
×
870
        client.mu.RLock()
×
871
        defer client.mu.RUnlock()
×
872

×
873
        return client.projectID
×
874
}
×
875

876
func ProjectID() string { return DefaultClient.ProjectID() }
×
877

878
func (client *Client) Release() string {
×
879
        client.mu.RLock()
×
880
        defer client.mu.RUnlock()
×
881

×
882
        return client.release
×
883
}
×
884

885
func Release() string { return DefaultClient.Release() }
×
886

887
func IncludePaths() []string { return DefaultClient.IncludePaths() }
×
888

889
func (client *Client) IncludePaths() []string {
×
890
        client.mu.RLock()
×
891
        defer client.mu.RUnlock()
×
892

×
893
        return client.includePaths
×
894
}
×
895

896
func SetIncludePaths(p []string) { DefaultClient.SetIncludePaths(p) }
×
897

898
func (client *Client) SetIncludePaths(p []string) {
×
899
        client.mu.Lock()
×
900
        defer client.mu.Unlock()
×
901

×
902
        client.includePaths = p
×
903
}
×
904

905
func (c *Client) SetUserContext(u *User) {
×
906
        c.mu.Lock()
×
907
        defer c.mu.Unlock()
×
908
        c.context.setUser(u)
×
909
}
×
910

911
func (c *Client) SetHttpContext(h *Http) {
×
912
        c.mu.Lock()
×
913
        defer c.mu.Unlock()
×
914
        c.context.setHttp(h)
×
915
}
×
916

917
func (c *Client) SetTagsContext(t map[string]string) {
×
918
        c.mu.Lock()
×
919
        defer c.mu.Unlock()
×
920
        c.context.setTags(t)
×
921
}
×
922

923
func (c *Client) ClearContext() {
×
924
        c.mu.Lock()
×
925
        defer c.mu.Unlock()
×
926
        c.context.clear()
×
927
}
×
928

929
func SetUserContext(u *User)             { DefaultClient.SetUserContext(u) }
×
930
func SetHttpContext(h *Http)             { DefaultClient.SetHttpContext(h) }
×
931
func SetTagsContext(t map[string]string) { DefaultClient.SetTagsContext(t) }
×
932
func ClearContext()                      { DefaultClient.ClearContext() }
×
933

934
// HTTPTransport is the default transport, delivering packets to Sentry via the
935
// HTTP API.
936
type HTTPTransport struct {
937
        *http.Client
938
}
939

UNCOV
940
func (t *HTTPTransport) Send(url, authHeader string, packet *Packet) error {
×
UNCOV
941
        if url == "" {
×
UNCOV
942
                return nil
×
UNCOV
943
        }
×
944

945
        body, contentType, err := serializedPacket(packet)
×
946
        if err != nil {
×
947
                return fmt.Errorf("error serializing packet: %v", err)
×
948
        }
×
949
        req, err := http.NewRequest("POST", url, body)
×
950
        if err != nil {
×
951
                return fmt.Errorf("can't create new request: %v", err)
×
952
        }
×
953
        req.Header.Set("X-Sentry-Auth", authHeader)
×
954
        req.Header.Set("User-Agent", userAgent)
×
955
        req.Header.Set("Content-Type", contentType)
×
956
        res, err := t.Do(req)
×
957
        if err != nil {
×
958
                return err
×
959
        }
×
960
        io.Copy(io.Discard, res.Body) // nolint: errcheck
×
961
        res.Body.Close()
×
962
        if res.StatusCode != 200 {
×
963
                return fmt.Errorf("raven: got http status %d", res.StatusCode)
×
964
        }
×
965
        return nil
×
966
}
967

968
func serializedPacket(packet *Packet) (io.Reader, string, error) {
×
969
        packetJSON, err := packet.JSON()
×
970
        if err != nil {
×
971
                return nil, "", fmt.Errorf("error marshaling packet %+v to JSON: %v", packet, err)
×
972
        }
×
973

974
        // redact all data going out to sentry using the default redactor
975
        packetJSON = []byte(redact.Default.Mask(string(packetJSON)))
×
976

×
977
        // Only deflate/base64 the packet if it is bigger than 1KB, as there is
×
978
        // overhead.
×
979
        if len(packetJSON) > 1000 {
×
980
                buf := &bytes.Buffer{}
×
981
                b64 := base64.NewEncoder(base64.StdEncoding, buf)
×
982
                deflate, _ := zlib.NewWriterLevel(b64, zlib.BestCompression)
×
983
                deflate.Write(packetJSON) // nolint: errcheck
×
984
                deflate.Close()
×
985
                b64.Close()
×
986
                return buf, "application/octet-stream", nil
×
987
        }
×
988
        return bytes.NewReader(packetJSON), "application/json", nil
×
989
}
990

991
var hostname string
992

UNCOV
993
func init() {
×
UNCOV
994
        hostname, _ = os.Hostname()
×
UNCOV
995
}
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc