New upstream version 13.10.3+ds2

This commit is contained in:
Pirate Praveen 2021-04-17 23:33:27 +05:30
parent 26db65f672
commit 3cf1e9a2bb
11 changed files with 0 additions and 1593 deletions

View file

@ -1,13 +0,0 @@
FROM golang:1.7
RUN mkdir -p /go/src/github.com/getsentry/raven-go
WORKDIR /go/src/github.com/getsentry/raven-go
ENV GOPATH /go
RUN go install -race std && go get golang.org/x/tools/cmd/cover
COPY . /go/src/github.com/getsentry/raven-go
RUN go get -v ./...
CMD ["./runtests.sh"]

View file

@ -1,28 +0,0 @@
Copyright (c) 2013 Apollic Software, LLC. All rights reserved.
Copyright (c) 2015 Functional Software, Inc. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Apollic Software, LLC nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View file

@ -1,17 +0,0 @@
# raven
[![Build Status](https://api.travis-ci.org/getsentry/raven-go.svg?branch=master)](https://travis-ci.org/getsentry/raven-go)
[![Go Report Card](https://goreportcard.com/badge/github.com/getsentry/raven-go)](https://goreportcard.com/report/github.com/getsentry/raven-go)
[![GoDoc](https://godoc.org/github.com/getsentry/raven-go?status.svg)](https://godoc.org/github.com/getsentry/raven-go)
raven is a Go client for the [Sentry](https://github.com/getsentry/sentry)
event/error logging system.
- [**API Documentation**](https://godoc.org/github.com/getsentry/raven-go)
- [**Usage and Examples**](https://docs.sentry.io/clients/go/)
## Installation
```text
go get github.com/getsentry/raven-go
```

View file

@ -1,977 +0,0 @@
// Package raven implements a client for the Sentry error logging service.
package raven
import (
"bytes"
"compress/zlib"
"crypto/rand"
"crypto/tls"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"log"
mrand "math/rand"
"net/http"
"net/url"
"os"
"regexp"
"runtime"
"strings"
"sync"
"time"
"github.com/certifi/gocertifi"
pkgErrors "github.com/pkg/errors"
)
const (
userAgent = "raven-go/1.0"
timestampFormat = `"2006-01-02T15:04:05.00"`
)
var (
ErrPacketDropped = errors.New("raven: packet dropped")
ErrUnableToUnmarshalJSON = errors.New("raven: unable to unmarshal JSON")
ErrMissingUser = errors.New("raven: dsn missing public key and/or password")
ErrMissingProjectID = errors.New("raven: dsn missing project id")
ErrInvalidSampleRate = errors.New("raven: sample rate should be between 0 and 1")
)
type Severity string
// http://docs.python.org/2/howto/logging.html#logging-levels
const (
DEBUG = Severity("debug")
INFO = Severity("info")
WARNING = Severity("warning")
ERROR = Severity("error")
FATAL = Severity("fatal")
)
type Timestamp time.Time
func (t Timestamp) MarshalJSON() ([]byte, error) {
return []byte(time.Time(t).UTC().Format(timestampFormat)), nil
}
func (timestamp *Timestamp) UnmarshalJSON(data []byte) error {
t, err := time.Parse(timestampFormat, string(data))
if err != nil {
return err
}
*timestamp = Timestamp(t)
return nil
}
func (timestamp Timestamp) Format(format string) string {
t := time.Time(timestamp)
return t.Format(format)
}
// An Interface is a Sentry interface that will be serialized as JSON.
// It must implement json.Marshaler or use json struct tags.
type Interface interface {
// The Sentry class name. Example: sentry.interfaces.Stacktrace
Class() string
}
type Culpriter interface {
Culprit() string
}
type Transport interface {
Send(url, authHeader string, packet *Packet) error
}
type Extra map[string]interface{}
type outgoingPacket struct {
packet *Packet
ch chan error
}
type Tag struct {
Key string
Value string
}
type Tags []Tag
func (tag *Tag) MarshalJSON() ([]byte, error) {
return json.Marshal([2]string{tag.Key, tag.Value})
}
func (t *Tag) UnmarshalJSON(data []byte) error {
var tag [2]string
if err := json.Unmarshal(data, &tag); err != nil {
return err
}
*t = Tag{tag[0], tag[1]}
return nil
}
func (t *Tags) UnmarshalJSON(data []byte) error {
var tags []Tag
switch data[0] {
case '[':
// Unmarshal into []Tag
if err := json.Unmarshal(data, &tags); err != nil {
return err
}
case '{':
// Unmarshal into map[string]string
tagMap := make(map[string]string)
if err := json.Unmarshal(data, &tagMap); err != nil {
return err
}
// Convert to []Tag
for k, v := range tagMap {
tags = append(tags, Tag{k, v})
}
default:
return ErrUnableToUnmarshalJSON
}
*t = tags
return nil
}
// https://docs.getsentry.com/hosted/clientdev/#building-the-json-packet
type Packet struct {
// Required
Message string `json:"message"`
// Required, set automatically by Client.Send/Report via Packet.Init if blank
EventID string `json:"event_id"`
Project string `json:"project"`
Timestamp Timestamp `json:"timestamp"`
Level Severity `json:"level"`
Logger string `json:"logger"`
// Optional
Platform string `json:"platform,omitempty"`
Culprit string `json:"culprit,omitempty"`
ServerName string `json:"server_name,omitempty"`
Release string `json:"release,omitempty"`
Environment string `json:"environment,omitempty"`
Tags Tags `json:"tags,omitempty"`
Modules map[string]string `json:"modules,omitempty"`
Fingerprint []string `json:"fingerprint,omitempty"`
Extra Extra `json:"extra,omitempty"`
Interfaces []Interface `json:"-"`
}
// NewPacket constructs a packet with the specified message and interfaces.
func NewPacket(message string, interfaces ...Interface) *Packet {
extra := Extra{}
setExtraDefaults(extra)
return &Packet{
Message: message,
Interfaces: interfaces,
Extra: extra,
}
}
// NewPacketWithExtra constructs a packet with the specified message, extra information, and interfaces.
func NewPacketWithExtra(message string, extra Extra, interfaces ...Interface) *Packet {
if extra == nil {
extra = Extra{}
}
setExtraDefaults(extra)
return &Packet{
Message: message,
Interfaces: interfaces,
Extra: extra,
}
}
func setExtraDefaults(extra Extra) Extra {
extra["runtime.Version"] = runtime.Version()
extra["runtime.NumCPU"] = runtime.NumCPU()
extra["runtime.GOMAXPROCS"] = runtime.GOMAXPROCS(0) // 0 just returns the current value
extra["runtime.NumGoroutine"] = runtime.NumGoroutine()
return extra
}
// Init initializes required fields in a packet. It is typically called by
// Client.Send/Report automatically.
func (packet *Packet) Init(project string) error {
if packet.Project == "" {
packet.Project = project
}
if packet.EventID == "" {
var err error
packet.EventID, err = uuid()
if err != nil {
return err
}
}
if time.Time(packet.Timestamp).IsZero() {
packet.Timestamp = Timestamp(time.Now())
}
if packet.Level == "" {
packet.Level = ERROR
}
if packet.Logger == "" {
packet.Logger = "root"
}
if packet.ServerName == "" {
packet.ServerName = hostname
}
if packet.Platform == "" {
packet.Platform = "go"
}
if packet.Culprit == "" {
for _, inter := range packet.Interfaces {
if c, ok := inter.(Culpriter); ok {
packet.Culprit = c.Culprit()
if packet.Culprit != "" {
break
}
}
}
}
return nil
}
func (packet *Packet) AddTags(tags map[string]string) {
for k, v := range tags {
packet.Tags = append(packet.Tags, Tag{k, v})
}
}
func uuid() (string, error) {
id := make([]byte, 16)
_, err := io.ReadFull(rand.Reader, id)
if err != nil {
return "", err
}
id[6] &= 0x0F // clear version
id[6] |= 0x40 // set version to 4 (random uuid)
id[8] &= 0x3F // clear variant
id[8] |= 0x80 // set to IETF variant
return hex.EncodeToString(id), nil
}
func (packet *Packet) JSON() ([]byte, error) {
packetJSON, err := json.Marshal(packet)
if err != nil {
return nil, err
}
interfaces := make(map[string]Interface, len(packet.Interfaces))
for _, inter := range packet.Interfaces {
if inter != nil {
interfaces[inter.Class()] = inter
}
}
if len(interfaces) > 0 {
interfaceJSON, err := json.Marshal(interfaces)
if err != nil {
return nil, err
}
packetJSON[len(packetJSON)-1] = ','
packetJSON = append(packetJSON, interfaceJSON[1:]...)
}
return packetJSON, nil
}
type context struct {
user *User
http *Http
tags map[string]string
}
func (c *context) setUser(u *User) { c.user = u }
func (c *context) setHttp(h *Http) { c.http = h }
func (c *context) setTags(t map[string]string) {
if c.tags == nil {
c.tags = make(map[string]string)
}
for k, v := range t {
c.tags[k] = v
}
}
func (c *context) clear() {
c.user = nil
c.http = nil
c.tags = nil
}
// Return a list of interfaces to be used in appending with the rest
func (c *context) interfaces() []Interface {
len, i := 0, 0
if c.user != nil {
len++
}
if c.http != nil {
len++
}
interfaces := make([]Interface, len)
if c.user != nil {
interfaces[i] = c.user
i++
}
if c.http != nil {
interfaces[i] = c.http
i++
}
return interfaces
}
// The maximum number of packets that will be buffered waiting to be delivered.
// Packets will be dropped if the buffer is full. Used by NewClient.
var MaxQueueBuffer = 100
func newTransport() Transport {
t := &HTTPTransport{}
rootCAs, err := gocertifi.CACerts()
if err != nil {
log.Println("raven: failed to load root TLS certificates:", err)
} else {
t.Client = &http.Client{
Transport: &http.Transport{
Proxy: http.ProxyFromEnvironment,
TLSClientConfig: &tls.Config{RootCAs: rootCAs},
},
}
}
return t
}
func newClient(tags map[string]string) *Client {
client := &Client{
Transport: newTransport(),
Tags: tags,
context: &context{},
sampleRate: 1.0,
queue: make(chan *outgoingPacket, MaxQueueBuffer),
}
client.SetDSN(os.Getenv("SENTRY_DSN"))
client.SetRelease(os.Getenv("SENTRY_RELEASE"))
client.SetEnvironment(os.Getenv("SENTRY_ENVIRONMENT"))
return client
}
// New constructs a new Sentry client instance
func New(dsn string) (*Client, error) {
client := newClient(nil)
return client, client.SetDSN(dsn)
}
// NewWithTags constructs a new Sentry client instance with default tags.
func NewWithTags(dsn string, tags map[string]string) (*Client, error) {
client := newClient(tags)
return client, client.SetDSN(dsn)
}
// NewClient constructs a Sentry client and spawns a background goroutine to
// handle packets sent by Client.Report.
//
// Deprecated: use New and NewWithTags instead
func NewClient(dsn string, tags map[string]string) (*Client, error) {
client := newClient(tags)
return client, client.SetDSN(dsn)
}
// Client encapsulates a connection to a Sentry server. It must be initialized
// by calling NewClient. Modification of fields concurrently with Send or after
// calling Report for the first time is not thread-safe.
type Client struct {
Tags map[string]string
Transport Transport
// DropHandler is called when a packet is dropped because the buffer is full.
DropHandler func(*Packet)
// Context that will get appending to all packets
context *context
mu sync.RWMutex
url string
projectID string
authHeader string
release string
environment string
sampleRate float32
// default logger name (leave empty for 'root')
defaultLoggerName string
includePaths []string
ignoreErrorsRegexp *regexp.Regexp
queue chan *outgoingPacket
// A WaitGroup to keep track of all currently in-progress captures
// This is intended to be used with Client.Wait() to assure that
// all messages have been transported before exiting the process.
wg sync.WaitGroup
// A Once to track only starting up the background worker once
start sync.Once
}
// Initialize a default *Client instance
var DefaultClient = newClient(nil)
func (c *Client) SetIgnoreErrors(errs []string) error {
joinedRegexp := strings.Join(errs, "|")
r, err := regexp.Compile(joinedRegexp)
if err != nil {
return fmt.Errorf("failed to compile regexp %q for %q: %v", joinedRegexp, errs, err)
}
c.mu.Lock()
c.ignoreErrorsRegexp = r
c.mu.Unlock()
return nil
}
func (c *Client) shouldExcludeErr(errStr string) bool {
c.mu.RLock()
defer c.mu.RUnlock()
return c.ignoreErrorsRegexp != nil && c.ignoreErrorsRegexp.MatchString(errStr)
}
func SetIgnoreErrors(errs ...string) error {
return DefaultClient.SetIgnoreErrors(errs)
}
// SetDSN updates a client with a new DSN. It safe to call after and
// concurrently with calls to Report and Send.
func (client *Client) SetDSN(dsn string) error {
if dsn == "" {
return nil
}
client.mu.Lock()
defer client.mu.Unlock()
uri, err := url.Parse(dsn)
if err != nil {
return err
}
if uri.User == nil {
return ErrMissingUser
}
publicKey := uri.User.Username()
secretKey, hasSecretKey := uri.User.Password()
uri.User = nil
if idx := strings.LastIndex(uri.Path, "/"); idx != -1 {
client.projectID = uri.Path[idx+1:]
uri.Path = uri.Path[:idx+1] + "api/" + client.projectID + "/store/"
}
if client.projectID == "" {
return ErrMissingProjectID
}
client.url = uri.String()
if hasSecretKey {
client.authHeader = fmt.Sprintf("Sentry sentry_version=4, sentry_key=%s, sentry_secret=%s", publicKey, secretKey)
} else {
client.authHeader = fmt.Sprintf("Sentry sentry_version=4, sentry_key=%s", publicKey)
}
return nil
}
// Sets the DSN for the default *Client instance
func SetDSN(dsn string) error { return DefaultClient.SetDSN(dsn) }
// SetRelease sets the "release" tag.
func (client *Client) SetRelease(release string) {
client.mu.Lock()
defer client.mu.Unlock()
client.release = release
}
// SetEnvironment sets the "environment" tag.
func (client *Client) SetEnvironment(environment string) {
client.mu.Lock()
defer client.mu.Unlock()
client.environment = environment
}
// SetDefaultLoggerName sets the default logger name.
func (client *Client) SetDefaultLoggerName(name string) {
client.mu.Lock()
defer client.mu.Unlock()
client.defaultLoggerName = name
}
// SetSampleRate sets how much sampling we want on client side
func (client *Client) SetSampleRate(rate float32) error {
client.mu.Lock()
defer client.mu.Unlock()
if rate < 0 || rate > 1 {
return ErrInvalidSampleRate
}
client.sampleRate = rate
return nil
}
// SetRelease sets the "release" tag on the default *Client
func SetRelease(release string) { DefaultClient.SetRelease(release) }
// SetEnvironment sets the "environment" tag on the default *Client
func SetEnvironment(environment string) { DefaultClient.SetEnvironment(environment) }
// SetDefaultLoggerName sets the "defaultLoggerName" on the default *Client
func SetDefaultLoggerName(name string) {
DefaultClient.SetDefaultLoggerName(name)
}
// SetSampleRate sets the "sample rate" on the degault *Client
func SetSampleRate(rate float32) error { return DefaultClient.SetSampleRate(rate) }
func (client *Client) worker() {
for outgoingPacket := range client.queue {
client.mu.RLock()
url, authHeader := client.url, client.authHeader
client.mu.RUnlock()
outgoingPacket.ch <- client.Transport.Send(url, authHeader, outgoingPacket.packet)
client.wg.Done()
}
}
// Capture asynchronously delivers a packet to the Sentry server. It is a no-op
// when client is nil. A channel is provided if it is important to check for a
// send's success.
func (client *Client) Capture(packet *Packet, captureTags map[string]string) (eventID string, ch chan error) {
ch = make(chan error, 1)
if client == nil {
// return a chan that always returns nil when the caller receives from it
close(ch)
return
}
if client.sampleRate < 1.0 && mrand.Float32() > client.sampleRate {
return
}
if packet == nil {
close(ch)
return
}
if client.shouldExcludeErr(packet.Message) {
return
}
// Keep track of all running Captures so that we can wait for them all to finish
// *Must* call client.wg.Done() on any path that indicates that an event was
// finished being acted upon, whether success or failure
client.wg.Add(1)
// Merge capture tags and client tags
packet.AddTags(captureTags)
packet.AddTags(client.Tags)
// Initialize any required packet fields
client.mu.RLock()
packet.AddTags(client.context.tags)
projectID := client.projectID
release := client.release
environment := client.environment
defaultLoggerName := client.defaultLoggerName
client.mu.RUnlock()
// set the global logger name on the packet if we must
if packet.Logger == "" && defaultLoggerName != "" {
packet.Logger = defaultLoggerName
}
err := packet.Init(projectID)
if err != nil {
ch <- err
client.wg.Done()
return
}
if packet.Release == "" {
packet.Release = release
}
if packet.Environment == "" {
packet.Environment = environment
}
outgoingPacket := &outgoingPacket{packet, ch}
// Lazily start background worker until we
// do our first write into the queue.
client.start.Do(func() {
go client.worker()
})
select {
case client.queue <- outgoingPacket:
default:
// Send would block, drop the packet
if client.DropHandler != nil {
client.DropHandler(packet)
}
ch <- ErrPacketDropped
client.wg.Done()
}
return packet.EventID, ch
}
// Capture asynchronously delivers a packet to the Sentry server with the default *Client.
// It is a no-op when client is nil. A channel is provided if it is important to check for a
// send's success.
func Capture(packet *Packet, captureTags map[string]string) (eventID string, ch chan error) {
return DefaultClient.Capture(packet, captureTags)
}
// CaptureMessage formats and delivers a string message to the Sentry server.
func (client *Client) CaptureMessage(message string, tags map[string]string, interfaces ...Interface) string {
if client == nil {
return ""
}
if client.shouldExcludeErr(message) {
return ""
}
packet := NewPacket(message, append(append(interfaces, client.context.interfaces()...), &Message{message, nil})...)
eventID, _ := client.Capture(packet, tags)
return eventID
}
// CaptureMessage formats and delivers a string message to the Sentry server with the default *Client
func CaptureMessage(message string, tags map[string]string, interfaces ...Interface) string {
return DefaultClient.CaptureMessage(message, tags, interfaces...)
}
// CaptureMessageAndWait is identical to CaptureMessage except it blocks and waits for the message to be sent.
func (client *Client) CaptureMessageAndWait(message string, tags map[string]string, interfaces ...Interface) string {
if client == nil {
return ""
}
if client.shouldExcludeErr(message) {
return ""
}
packet := NewPacket(message, append(append(interfaces, client.context.interfaces()...), &Message{message, nil})...)
eventID, ch := client.Capture(packet, tags)
if eventID != "" {
<-ch
}
return eventID
}
// CaptureMessageAndWait is identical to CaptureMessage except it blocks and waits for the message to be sent.
func CaptureMessageAndWait(message string, tags map[string]string, interfaces ...Interface) string {
return DefaultClient.CaptureMessageAndWait(message, tags, interfaces...)
}
// CaptureErrors formats and delivers an error to the Sentry server.
// Adds a stacktrace to the packet, excluding the call to this method.
func (client *Client) CaptureError(err error, tags map[string]string, interfaces ...Interface) string {
if client == nil {
return ""
}
if err == nil {
return ""
}
if client.shouldExcludeErr(err.Error()) {
return ""
}
extra := extractExtra(err)
cause := pkgErrors.Cause(err)
packet := NewPacketWithExtra(err.Error(), extra, append(append(interfaces, client.context.interfaces()...), NewException(cause, GetOrNewStacktrace(cause, 1, 3, client.includePaths)))...)
eventID, _ := client.Capture(packet, tags)
return eventID
}
// CaptureErrors formats and delivers an error to the Sentry server using the default *Client.
// Adds a stacktrace to the packet, excluding the call to this method.
func CaptureError(err error, tags map[string]string, interfaces ...Interface) string {
return DefaultClient.CaptureError(err, tags, interfaces...)
}
// CaptureErrorAndWait is identical to CaptureError, except it blocks and assures that the event was sent
func (client *Client) CaptureErrorAndWait(err error, tags map[string]string, interfaces ...Interface) string {
if client == nil {
return ""
}
if client.shouldExcludeErr(err.Error()) {
return ""
}
extra := extractExtra(err)
cause := pkgErrors.Cause(err)
packet := NewPacketWithExtra(err.Error(), extra, append(append(interfaces, client.context.interfaces()...), NewException(cause, GetOrNewStacktrace(cause, 1, 3, client.includePaths)))...)
eventID, ch := client.Capture(packet, tags)
if eventID != "" {
<-ch
}
return eventID
}
// CaptureErrorAndWait is identical to CaptureError, except it blocks and assures that the event was sent
func CaptureErrorAndWait(err error, tags map[string]string, interfaces ...Interface) string {
return DefaultClient.CaptureErrorAndWait(err, tags, interfaces...)
}
// CapturePanic calls f and then recovers and reports a panic to the Sentry server if it occurs.
// If an error is captured, both the error and the reported Sentry error ID are returned.
func (client *Client) CapturePanic(f func(), tags map[string]string, interfaces ...Interface) (err interface{}, errorID string) {
// Note: This doesn't need to check for client, because we still want to go through the defer/recover path
// Down the line, Capture will be noop'd, so while this does a _tiny_ bit of overhead constructing the
// *Packet just to be thrown away, this should not be the normal case. Could be refactored to
// be completely noop though if we cared.
defer func() {
var packet *Packet
err = recover()
switch rval := err.(type) {
case nil:
return
case error:
if client.shouldExcludeErr(rval.Error()) {
return
}
packet = NewPacket(rval.Error(), append(append(interfaces, client.context.interfaces()...), NewException(rval, NewStacktrace(2, 3, client.includePaths)))...)
default:
rvalStr := fmt.Sprint(rval)
if client.shouldExcludeErr(rvalStr) {
return
}
packet = NewPacket(rvalStr, append(append(interfaces, client.context.interfaces()...), NewException(errors.New(rvalStr), NewStacktrace(2, 3, client.includePaths)))...)
}
errorID, _ = client.Capture(packet, tags)
}()
f()
return
}
// CapturePanic calls f and then recovers and reports a panic to the Sentry server if it occurs.
// If an error is captured, both the error and the reported Sentry error ID are returned.
func CapturePanic(f func(), tags map[string]string, interfaces ...Interface) (interface{}, string) {
return DefaultClient.CapturePanic(f, tags, interfaces...)
}
// CapturePanicAndWait is identical to CaptureError, except it blocks and assures that the event was sent
func (client *Client) CapturePanicAndWait(f func(), tags map[string]string, interfaces ...Interface) (err interface{}, errorID string) {
// Note: This doesn't need to check for client, because we still want to go through the defer/recover path
// Down the line, Capture will be noop'd, so while this does a _tiny_ bit of overhead constructing the
// *Packet just to be thrown away, this should not be the normal case. Could be refactored to
// be completely noop though if we cared.
defer func() {
var packet *Packet
err = recover()
switch rval := err.(type) {
case nil:
return
case error:
if client.shouldExcludeErr(rval.Error()) {
return
}
packet = NewPacket(rval.Error(), append(append(interfaces, client.context.interfaces()...), NewException(rval, NewStacktrace(2, 3, client.includePaths)))...)
default:
rvalStr := fmt.Sprint(rval)
if client.shouldExcludeErr(rvalStr) {
return
}
packet = NewPacket(rvalStr, append(append(interfaces, client.context.interfaces()...), NewException(errors.New(rvalStr), NewStacktrace(2, 3, client.includePaths)))...)
}
var ch chan error
errorID, ch = client.Capture(packet, tags)
if errorID != "" {
<-ch
}
}()
f()
return
}
// CapturePanicAndWait is identical to CaptureError, except it blocks and assures that the event was sent
func CapturePanicAndWait(f func(), tags map[string]string, interfaces ...Interface) (interface{}, string) {
return DefaultClient.CapturePanicAndWait(f, tags, interfaces...)
}
func (client *Client) Close() {
close(client.queue)
}
func Close() { DefaultClient.Close() }
// Wait blocks and waits for all events to finish being sent to Sentry server
func (client *Client) Wait() {
client.wg.Wait()
}
// Wait blocks and waits for all events to finish being sent to Sentry server
func Wait() { DefaultClient.Wait() }
func (client *Client) URL() string {
client.mu.RLock()
defer client.mu.RUnlock()
return client.url
}
func URL() string { return DefaultClient.URL() }
func (client *Client) ProjectID() string {
client.mu.RLock()
defer client.mu.RUnlock()
return client.projectID
}
func ProjectID() string { return DefaultClient.ProjectID() }
func (client *Client) Release() string {
client.mu.RLock()
defer client.mu.RUnlock()
return client.release
}
func Release() string { return DefaultClient.Release() }
func IncludePaths() []string { return DefaultClient.IncludePaths() }
func (client *Client) IncludePaths() []string {
client.mu.RLock()
defer client.mu.RUnlock()
return client.includePaths
}
func SetIncludePaths(p []string) { DefaultClient.SetIncludePaths(p) }
func (client *Client) SetIncludePaths(p []string) {
client.mu.Lock()
defer client.mu.Unlock()
client.includePaths = p
}
func (c *Client) SetUserContext(u *User) {
c.mu.Lock()
defer c.mu.Unlock()
c.context.setUser(u)
}
func (c *Client) SetHttpContext(h *Http) {
c.mu.Lock()
defer c.mu.Unlock()
c.context.setHttp(h)
}
func (c *Client) SetTagsContext(t map[string]string) {
c.mu.Lock()
defer c.mu.Unlock()
c.context.setTags(t)
}
func (c *Client) ClearContext() {
c.mu.Lock()
defer c.mu.Unlock()
c.context.clear()
}
func SetUserContext(u *User) { DefaultClient.SetUserContext(u) }
func SetHttpContext(h *Http) { DefaultClient.SetHttpContext(h) }
func SetTagsContext(t map[string]string) { DefaultClient.SetTagsContext(t) }
func ClearContext() { DefaultClient.ClearContext() }
// HTTPTransport is the default transport, delivering packets to Sentry via the
// HTTP API.
type HTTPTransport struct {
*http.Client
}
func (t *HTTPTransport) Send(url, authHeader string, packet *Packet) error {
if url == "" {
return nil
}
body, contentType, err := serializedPacket(packet)
if err != nil {
return fmt.Errorf("error serializing packet: %v", err)
}
req, err := http.NewRequest("POST", url, body)
if err != nil {
return fmt.Errorf("can't create new request: %v", err)
}
req.Header.Set("X-Sentry-Auth", authHeader)
req.Header.Set("User-Agent", userAgent)
req.Header.Set("Content-Type", contentType)
res, err := t.Do(req)
if err != nil {
return err
}
io.Copy(ioutil.Discard, res.Body)
res.Body.Close()
if res.StatusCode != 200 {
return fmt.Errorf("raven: got http status %d - x-sentry-error: %s", res.StatusCode, res.Header.Get("X-Sentry-Error"))
}
return nil
}
func serializedPacket(packet *Packet) (io.Reader, string, error) {
packetJSON, err := packet.JSON()
if err != nil {
return nil, "", fmt.Errorf("error marshaling packet %+v to JSON: %v", packet, err)
}
// Only deflate/base64 the packet if it is bigger than 1KB, as there is
// overhead.
if len(packetJSON) > 1000 {
buf := &bytes.Buffer{}
b64 := base64.NewEncoder(base64.StdEncoding, buf)
deflate, _ := zlib.NewWriterLevel(b64, zlib.BestCompression)
deflate.Write(packetJSON)
deflate.Close()
b64.Close()
return buf, "application/octet-stream", nil
}
return bytes.NewReader(packetJSON), "application/json", nil
}
var hostname string
func init() {
hostname, _ = os.Hostname()
}

View file

@ -1,60 +0,0 @@
package raven
type causer interface {
Cause() error
}
type errWrappedWithExtra struct {
err error
extraInfo map[string]interface{}
}
func (ewx *errWrappedWithExtra) Error() string {
return ewx.err.Error()
}
func (ewx *errWrappedWithExtra) Cause() error {
return ewx.err
}
func (ewx *errWrappedWithExtra) ExtraInfo() Extra {
return ewx.extraInfo
}
// Adds extra data to an error before reporting to Sentry
func WrapWithExtra(err error, extraInfo map[string]interface{}) error {
return &errWrappedWithExtra{
err: err,
extraInfo: extraInfo,
}
}
type ErrWithExtra interface {
Error() string
Cause() error
ExtraInfo() Extra
}
// Iteratively fetches all the Extra data added to an error,
// and it's underlying errors. Extra data defined first is
// respected, and is not overridden when extracting.
func extractExtra(err error) Extra {
extra := Extra{}
currentErr := err
for currentErr != nil {
if errWithExtra, ok := currentErr.(ErrWithExtra); ok {
for k, v := range errWithExtra.ExtraInfo() {
extra[k] = v
}
}
if errWithCause, ok := currentErr.(causer); ok {
currentErr = errWithCause.Cause()
} else {
currentErr = nil
}
}
return extra
}

View file

@ -1,50 +0,0 @@
package raven
import (
"reflect"
"regexp"
)
var errorMsgPattern = regexp.MustCompile(`\A(\w+): (.+)\z`)
func NewException(err error, stacktrace *Stacktrace) *Exception {
msg := err.Error()
ex := &Exception{
Stacktrace: stacktrace,
Value: msg,
Type: reflect.TypeOf(err).String(),
}
if m := errorMsgPattern.FindStringSubmatch(msg); m != nil {
ex.Module, ex.Value = m[1], m[2]
}
return ex
}
// https://docs.getsentry.com/hosted/clientdev/interfaces/#failure-interfaces
type Exception struct {
// Required
Value string `json:"value"`
// Optional
Type string `json:"type,omitempty"`
Module string `json:"module,omitempty"`
Stacktrace *Stacktrace `json:"stacktrace,omitempty"`
}
func (e *Exception) Class() string { return "exception" }
func (e *Exception) Culprit() string {
if e.Stacktrace == nil {
return ""
}
return e.Stacktrace.Culprit()
}
// Exceptions allows for chained errors
// https://docs.sentry.io/clientdev/interfaces/exception/
type Exceptions struct {
// Required
Values []*Exception `json:"values"`
}
func (es Exceptions) Class() string { return "exception" }

View file

@ -1,99 +0,0 @@
package raven
import (
"errors"
"fmt"
"net"
"net/http"
"net/url"
"runtime/debug"
"strings"
)
func NewHttp(req *http.Request) *Http {
proto := "http"
if req.TLS != nil || req.Header.Get("X-Forwarded-Proto") == "https" {
proto = "https"
}
h := &Http{
Method: req.Method,
Cookies: req.Header.Get("Cookie"),
Query: sanitizeQuery(req.URL.Query()).Encode(),
URL: proto + "://" + req.Host + req.URL.Path,
Headers: make(map[string]string, len(req.Header)),
}
if addr, port, err := net.SplitHostPort(req.RemoteAddr); err == nil {
h.Env = map[string]string{"REMOTE_ADDR": addr, "REMOTE_PORT": port}
}
for k, v := range req.Header {
h.Headers[k] = strings.Join(v, ",")
}
h.Headers["Host"] = req.Host
return h
}
var querySecretFields = []string{"password", "passphrase", "passwd", "secret"}
func sanitizeQuery(query url.Values) url.Values {
for _, keyword := range querySecretFields {
for field := range query {
if strings.Contains(field, keyword) {
query[field] = []string{"********"}
}
}
}
return query
}
// https://docs.getsentry.com/hosted/clientdev/interfaces/#context-interfaces
type Http struct {
// Required
URL string `json:"url"`
Method string `json:"method"`
Query string `json:"query_string,omitempty"`
// Optional
Cookies string `json:"cookies,omitempty"`
Headers map[string]string `json:"headers,omitempty"`
Env map[string]string `json:"env,omitempty"`
// Must be either a string or map[string]string
Data interface{} `json:"data,omitempty"`
}
func (h *Http) Class() string { return "request" }
// Recovery handler to wrap the stdlib net/http Mux.
// Example:
// http.HandleFunc("/", raven.RecoveryHandler(func(w http.ResponseWriter, r *http.Request) {
// ...
// }))
func RecoveryHandler(handler func(http.ResponseWriter, *http.Request)) func(http.ResponseWriter, *http.Request) {
return Recoverer(http.HandlerFunc(handler)).ServeHTTP
}
// Recovery handler to wrap the stdlib net/http Mux.
// Example:
// mux := http.NewServeMux
// ...
// http.Handle("/", raven.Recoverer(mux))
func Recoverer(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer func() {
if rval := recover(); rval != nil {
debug.PrintStack()
rvalStr := fmt.Sprint(rval)
var packet *Packet
if err, ok := rval.(error); ok {
packet = NewPacket(rvalStr, NewException(errors.New(rvalStr), GetOrNewStacktrace(err, 2, 3, nil)), NewHttp(r))
} else {
packet = NewPacket(rvalStr, NewException(errors.New(rvalStr), NewStacktrace(2, 3, nil)), NewHttp(r))
}
Capture(packet, nil)
w.WriteHeader(http.StatusInternalServerError)
}
}()
handler.ServeHTTP(w, r)
})
}

View file

@ -1,49 +0,0 @@
package raven
// https://docs.getsentry.com/hosted/clientdev/interfaces/#message-interface
type Message struct {
// Required
Message string `json:"message"`
// Optional
Params []interface{} `json:"params,omitempty"`
}
func (m *Message) Class() string { return "logentry" }
// https://docs.getsentry.com/hosted/clientdev/interfaces/#template-interface
type Template struct {
// Required
Filename string `json:"filename"`
Lineno int `json:"lineno"`
ContextLine string `json:"context_line"`
// Optional
PreContext []string `json:"pre_context,omitempty"`
PostContext []string `json:"post_context,omitempty"`
AbsolutePath string `json:"abs_path,omitempty"`
}
func (t *Template) Class() string { return "template" }
// https://docs.getsentry.com/hosted/clientdev/interfaces/#context-interfaces
type User struct {
// All fields are optional
ID string `json:"id,omitempty"`
Username string `json:"username,omitempty"`
Email string `json:"email,omitempty"`
IP string `json:"ip_address,omitempty"`
}
func (h *User) Class() string { return "user" }
// https://docs.getsentry.com/hosted/clientdev/interfaces/#context-interfaces
type Query struct {
// Required
Query string `json:"query"`
// Optional
Engine string `json:"engine,omitempty"`
}
func (q *Query) Class() string { return "query" }

View file

@ -1,4 +0,0 @@
#!/bin/bash
go test -race ./...
go test -cover ./...
go test -v ./...

View file

@ -1,276 +0,0 @@
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Some code from the runtime/debug package of the Go standard library.
package raven
import (
"bytes"
"go/build"
"io/ioutil"
"net/url"
"path/filepath"
"runtime"
"strings"
"sync"
"github.com/pkg/errors"
)
// https://docs.getsentry.com/hosted/clientdev/interfaces/#failure-interfaces
type Stacktrace struct {
// Required
Frames []*StacktraceFrame `json:"frames"`
}
func (s *Stacktrace) Class() string { return "stacktrace" }
func (s *Stacktrace) Culprit() string {
for i := len(s.Frames) - 1; i >= 0; i-- {
frame := s.Frames[i]
if frame.InApp == true && frame.Module != "" && frame.Function != "" {
return frame.Module + "." + frame.Function
}
}
return ""
}
type StacktraceFrame struct {
// At least one required
Filename string `json:"filename,omitempty"`
Function string `json:"function,omitempty"`
Module string `json:"module,omitempty"`
// Optional
Lineno int `json:"lineno,omitempty"`
Colno int `json:"colno,omitempty"`
AbsolutePath string `json:"abs_path,omitempty"`
ContextLine string `json:"context_line,omitempty"`
PreContext []string `json:"pre_context,omitempty"`
PostContext []string `json:"post_context,omitempty"`
InApp bool `json:"in_app"`
}
// Try to get stacktrace from err as an interface of github.com/pkg/errors, or else NewStacktrace()
func GetOrNewStacktrace(err error, skip int, context int, appPackagePrefixes []string) *Stacktrace {
stacktracer, errHasStacktrace := err.(interface {
StackTrace() errors.StackTrace
})
if errHasStacktrace {
var frames []*StacktraceFrame
for _, f := range stacktracer.StackTrace() {
pc := uintptr(f) - 1
fn := runtime.FuncForPC(pc)
var file string
var line int
if fn != nil {
file, line = fn.FileLine(pc)
} else {
file = "unknown"
}
frame := NewStacktraceFrame(pc, file, line, context, appPackagePrefixes)
if frame != nil {
frames = append([]*StacktraceFrame{frame}, frames...)
}
}
return &Stacktrace{Frames: frames}
} else {
return NewStacktrace(skip+1, context, appPackagePrefixes)
}
}
// Intialize and populate a new stacktrace, skipping skip frames.
//
// context is the number of surrounding lines that should be included for context.
// Setting context to 3 would try to get seven lines. Setting context to -1 returns
// one line with no surrounding context, and 0 returns no context.
//
// appPackagePrefixes is a list of prefixes used to check whether a package should
// be considered "in app".
func NewStacktrace(skip int, context int, appPackagePrefixes []string) *Stacktrace {
var frames []*StacktraceFrame
for i := 1 + skip; ; i++ {
pc, file, line, ok := runtime.Caller(i)
if !ok {
break
}
frame := NewStacktraceFrame(pc, file, line, context, appPackagePrefixes)
if frame != nil {
frames = append(frames, frame)
}
}
// If there are no frames, the entire stacktrace is nil
if len(frames) == 0 {
return nil
}
// Optimize the path where there's only 1 frame
if len(frames) == 1 {
return &Stacktrace{frames}
}
// Sentry wants the frames with the oldest first, so reverse them
for i, j := 0, len(frames)-1; i < j; i, j = i+1, j-1 {
frames[i], frames[j] = frames[j], frames[i]
}
return &Stacktrace{frames}
}
// Build a single frame using data returned from runtime.Caller.
//
// context is the number of surrounding lines that should be included for context.
// Setting context to 3 would try to get seven lines. Setting context to -1 returns
// one line with no surrounding context, and 0 returns no context.
//
// appPackagePrefixes is a list of prefixes used to check whether a package should
// be considered "in app".
func NewStacktraceFrame(pc uintptr, file string, line, context int, appPackagePrefixes []string) *StacktraceFrame {
frame := &StacktraceFrame{AbsolutePath: file, Filename: trimPath(file), Lineno: line, InApp: false}
frame.Module, frame.Function = functionName(pc)
// `runtime.goexit` is effectively a placeholder that comes from
// runtime/asm_amd64.s and is meaningless.
if frame.Module == "runtime" && frame.Function == "goexit" {
return nil
}
if frame.Module == "main" {
frame.InApp = true
} else {
for _, prefix := range appPackagePrefixes {
if strings.HasPrefix(frame.Module, prefix) && !strings.Contains(frame.Module, "vendor") && !strings.Contains(frame.Module, "third_party") {
frame.InApp = true
}
}
}
if context > 0 {
contextLines, lineIdx := sourceCodeLoader.Load(file, line, context)
if len(contextLines) > 0 {
for i, line := range contextLines {
switch {
case i < lineIdx:
frame.PreContext = append(frame.PreContext, string(line))
case i == lineIdx:
frame.ContextLine = string(line)
default:
frame.PostContext = append(frame.PostContext, string(line))
}
}
}
} else if context == -1 {
contextLine, _ := sourceCodeLoader.Load(file, line, 0)
if len(contextLine) > 0 {
frame.ContextLine = string(contextLine[0])
}
}
return frame
}
// Retrieve the name of the package and function containing the PC.
func functionName(pc uintptr) (string, string) {
fn := runtime.FuncForPC(pc)
if fn == nil {
return "", ""
}
return splitFunctionName(fn.Name())
}
func splitFunctionName(name string) (string, string) {
var pack string
if pos := strings.LastIndex(name, "/"); pos != -1 {
pack = name[:pos+1]
name = name[pos+1:]
}
if pos := strings.Index(name, "."); pos != -1 {
pack += name[:pos]
name = name[pos+1:]
}
if p, err := url.QueryUnescape(pack); err == nil {
pack = p
}
return pack, name
}
type SourceCodeLoader interface {
Load(filename string, line, context int) ([][]byte, int)
}
var sourceCodeLoader SourceCodeLoader = &fsLoader{cache: make(map[string][][]byte)}
func SetSourceCodeLoader(loader SourceCodeLoader) {
sourceCodeLoader = loader
}
type fsLoader struct {
mu sync.Mutex
cache map[string][][]byte
}
func (fs *fsLoader) Load(filename string, line, context int) ([][]byte, int) {
fs.mu.Lock()
defer fs.mu.Unlock()
lines, ok := fs.cache[filename]
if !ok {
data, err := ioutil.ReadFile(filename)
if err != nil {
// cache errors as nil slice: code below handles it correctly
// otherwise when missing the source or running as a different user, we try
// reading the file on each error which is unnecessary
fs.cache[filename] = nil
return nil, 0
}
lines = bytes.Split(data, []byte{'\n'})
fs.cache[filename] = lines
}
if lines == nil {
// cached error from ReadFile: return no lines
return nil, 0
}
line-- // stack trace lines are 1-indexed
start := line - context
var idx int
if start < 0 {
start = 0
idx = line
} else {
idx = context
}
end := line + context + 1
if line >= len(lines) {
return nil, 0
}
if end > len(lines) {
end = len(lines)
}
return lines[start:end], idx
}
var trimPaths []string
// Try to trim the GOROOT or GOPATH prefix off of a filename
func trimPath(filename string) string {
for _, prefix := range trimPaths {
if trimmed := strings.TrimPrefix(filename, prefix); len(trimmed) < len(filename) {
return trimmed
}
}
return filename
}
func init() {
// Collect all source directories, and make sure they
// end in a trailing "separator"
for _, prefix := range build.Default.SrcDirs() {
if prefix[len(prefix)-1] != filepath.Separator {
prefix += string(filepath.Separator)
}
trimPaths = append(trimPaths, prefix)
}
}

View file

@ -1,20 +0,0 @@
package raven
type Writer struct {
Client *Client
Level Severity
Logger string // Logger name reported to Sentry
}
// Write formats the byte slice p into a string, and sends a message to
// Sentry at the severity level indicated by the Writer w.
func (w *Writer) Write(p []byte) (int, error) {
message := string(p)
packet := NewPacket(message, &Message{message, nil})
packet.Level = w.Level
packet.Logger = w.Logger
w.Client.Capture(packet, nil)
return len(p), nil
}