TUN-7551: Complete removal of raven-go to sentry-go
Removes the final usage of raven-go and removes the dependency.
This commit is contained in:
parent
aca3575b6d
commit
960c5a7baf
4
go.mod
4
go.mod
|
@ -10,9 +10,9 @@ require (
|
||||||
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
|
||||||
github.com/facebookgo/grace v0.0.0-20180706040059-75cf19382434
|
github.com/facebookgo/grace v0.0.0-20180706040059-75cf19382434
|
||||||
github.com/fsnotify/fsnotify v1.4.9
|
github.com/fsnotify/fsnotify v1.4.9
|
||||||
github.com/getsentry/raven-go v0.2.0
|
|
||||||
github.com/getsentry/sentry-go v0.16.0
|
github.com/getsentry/sentry-go v0.16.0
|
||||||
github.com/go-chi/chi/v5 v5.0.8
|
github.com/go-chi/chi/v5 v5.0.8
|
||||||
|
github.com/go-chi/cors v1.2.1
|
||||||
github.com/go-jose/go-jose/v3 v3.0.0
|
github.com/go-jose/go-jose/v3 v3.0.0
|
||||||
github.com/gobwas/ws v1.0.4
|
github.com/gobwas/ws v1.0.4
|
||||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3
|
||||||
|
@ -55,7 +55,6 @@ require (
|
||||||
github.com/BurntSushi/toml v1.2.0 // indirect
|
github.com/BurntSushi/toml v1.2.0 // indirect
|
||||||
github.com/apparentlymart/go-cidr v1.1.0 // indirect
|
github.com/apparentlymart/go-cidr v1.1.0 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d // indirect
|
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
github.com/cloudflare/circl v1.2.1-0.20220809205628-0a9554f37a47 // indirect
|
github.com/cloudflare/circl v1.2.1-0.20220809205628-0a9554f37a47 // indirect
|
||||||
github.com/coredns/caddy v1.1.1 // indirect
|
github.com/coredns/caddy v1.1.1 // indirect
|
||||||
|
@ -66,7 +65,6 @@ require (
|
||||||
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
github.com/facebookgo/stack v0.0.0-20160209184415-751773369052 // indirect
|
||||||
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
github.com/facebookgo/subset v0.0.0-20150612182917-8dac2c3c4870 // indirect
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 // indirect
|
||||||
github.com/go-chi/cors v1.2.1 // indirect
|
|
||||||
github.com/go-logr/logr v1.2.3 // indirect
|
github.com/go-logr/logr v1.2.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -74,8 +74,6 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
|
||||||
github.com/bwesterb/go-ristretto v1.2.2/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
github.com/bwesterb/go-ristretto v1.2.2/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
|
||||||
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d h1:S2NE3iHSwP0XV47EEXL8mWmRdEfGscSJ+7EgePNgt0s=
|
|
||||||
github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d/go.mod h1:sGbDF6GwGcLpkNXPUTkMRoywsNa/ol15pxFe6ERfguA=
|
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
|
||||||
|
@ -145,8 +143,6 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjr
|
||||||
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
|
||||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/getsentry/raven-go v0.2.0 h1:no+xWJRb5ZI7eE8TWgIq1jLulQiIoLG0IfYxv5JYMGs=
|
|
||||||
github.com/getsentry/raven-go v0.2.0/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
|
|
||||||
github.com/getsentry/sentry-go v0.16.0 h1:owk+S+5XcgJLlGR/3+3s6N4d+uKwqYvh/eS0AIMjPWo=
|
github.com/getsentry/sentry-go v0.16.0 h1:owk+S+5XcgJLlGR/3+3s6N4d+uKwqYvh/eS0AIMjPWo=
|
||||||
github.com/getsentry/sentry-go v0.16.0/go.mod h1:ZXCloQLj0pG7mja5NK6NPf2V4A88YJ4pNlc2mOHwh6Y=
|
github.com/getsentry/sentry-go v0.16.0/go.mod h1:ZXCloQLj0pG7mja5NK6NPf2V4A88YJ4pNlc2mOHwh6Y=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/getsentry/raven-go"
|
"github.com/getsentry/sentry-go"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
|
||||||
|
@ -58,24 +58,15 @@ func unidirectionalStream(dst io.Writer, src io.Reader, dir string, status *bidi
|
||||||
// server/origin listens forever until closure), it may read/write from the underlying ReadWriter (backed by
|
// server/origin listens forever until closure), it may read/write from the underlying ReadWriter (backed by
|
||||||
// the Edge<->cloudflared transport) in an unexpected state.
|
// the Edge<->cloudflared transport) in an unexpected state.
|
||||||
// Because of this, we set this recover() logic.
|
// Because of this, we set this recover() logic.
|
||||||
if r := recover(); r != nil {
|
if err := recover(); err != nil {
|
||||||
if status.isAnyDone() {
|
if status.isAnyDone() {
|
||||||
// We handle such unexpected errors only when we detect that one side of the streaming is done.
|
// We handle such unexpected errors only when we detect that one side of the streaming is done.
|
||||||
log.Debug().Msgf("Gracefully handled error %v in Streaming for %s, error %s", r, dir, debug.Stack())
|
log.Debug().Msgf("recovered from panic in stream.Pipe for %s, error %s, %s", dir, err, debug.Stack())
|
||||||
} else {
|
} else {
|
||||||
// Otherwise, this is unexpected, but we prevent the program from crashing anyway.
|
// Otherwise, this is unexpected, but we prevent the program from crashing anyway.
|
||||||
log.Warn().Msgf("Gracefully handled unexpected error %v in Streaming for %s, error %s", r, dir, debug.Stack())
|
log.Warn().Msgf("recovered from panic in stream.Pipe for %s, error %s, %s", dir, err, debug.Stack())
|
||||||
|
sentry.CurrentHub().Recover(err)
|
||||||
tags := make(map[string]string)
|
sentry.Flush(time.Second * 5)
|
||||||
tags["root"] = "websocket.stream"
|
|
||||||
tags["dir"] = dir
|
|
||||||
switch rval := r.(type) {
|
|
||||||
case error:
|
|
||||||
raven.CaptureError(rval, tags)
|
|
||||||
default:
|
|
||||||
rvalStr := fmt.Sprint(rval)
|
|
||||||
raven.CaptureMessage(rvalStr, tags)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
|
@ -1,373 +0,0 @@
|
||||||
Mozilla Public License Version 2.0
|
|
||||||
==================================
|
|
||||||
|
|
||||||
1. Definitions
|
|
||||||
--------------
|
|
||||||
|
|
||||||
1.1. "Contributor"
|
|
||||||
means each individual or legal entity that creates, contributes to
|
|
||||||
the creation of, or owns Covered Software.
|
|
||||||
|
|
||||||
1.2. "Contributor Version"
|
|
||||||
means the combination of the Contributions of others (if any) used
|
|
||||||
by a Contributor and that particular Contributor's Contribution.
|
|
||||||
|
|
||||||
1.3. "Contribution"
|
|
||||||
means Covered Software of a particular Contributor.
|
|
||||||
|
|
||||||
1.4. "Covered Software"
|
|
||||||
means Source Code Form to which the initial Contributor has attached
|
|
||||||
the notice in Exhibit A, the Executable Form of such Source Code
|
|
||||||
Form, and Modifications of such Source Code Form, in each case
|
|
||||||
including portions thereof.
|
|
||||||
|
|
||||||
1.5. "Incompatible With Secondary Licenses"
|
|
||||||
means
|
|
||||||
|
|
||||||
(a) that the initial Contributor has attached the notice described
|
|
||||||
in Exhibit B to the Covered Software; or
|
|
||||||
|
|
||||||
(b) that the Covered Software was made available under the terms of
|
|
||||||
version 1.1 or earlier of the License, but not also under the
|
|
||||||
terms of a Secondary License.
|
|
||||||
|
|
||||||
1.6. "Executable Form"
|
|
||||||
means any form of the work other than Source Code Form.
|
|
||||||
|
|
||||||
1.7. "Larger Work"
|
|
||||||
means a work that combines Covered Software with other material, in
|
|
||||||
a separate file or files, that is not Covered Software.
|
|
||||||
|
|
||||||
1.8. "License"
|
|
||||||
means this document.
|
|
||||||
|
|
||||||
1.9. "Licensable"
|
|
||||||
means having the right to grant, to the maximum extent possible,
|
|
||||||
whether at the time of the initial grant or subsequently, any and
|
|
||||||
all of the rights conveyed by this License.
|
|
||||||
|
|
||||||
1.10. "Modifications"
|
|
||||||
means any of the following:
|
|
||||||
|
|
||||||
(a) any file in Source Code Form that results from an addition to,
|
|
||||||
deletion from, or modification of the contents of Covered
|
|
||||||
Software; or
|
|
||||||
|
|
||||||
(b) any new file in Source Code Form that contains any Covered
|
|
||||||
Software.
|
|
||||||
|
|
||||||
1.11. "Patent Claims" of a Contributor
|
|
||||||
means any patent claim(s), including without limitation, method,
|
|
||||||
process, and apparatus claims, in any patent Licensable by such
|
|
||||||
Contributor that would be infringed, but for the grant of the
|
|
||||||
License, by the making, using, selling, offering for sale, having
|
|
||||||
made, import, or transfer of either its Contributions or its
|
|
||||||
Contributor Version.
|
|
||||||
|
|
||||||
1.12. "Secondary License"
|
|
||||||
means either the GNU General Public License, Version 2.0, the GNU
|
|
||||||
Lesser General Public License, Version 2.1, the GNU Affero General
|
|
||||||
Public License, Version 3.0, or any later versions of those
|
|
||||||
licenses.
|
|
||||||
|
|
||||||
1.13. "Source Code Form"
|
|
||||||
means the form of the work preferred for making modifications.
|
|
||||||
|
|
||||||
1.14. "You" (or "Your")
|
|
||||||
means an individual or a legal entity exercising rights under this
|
|
||||||
License. For legal entities, "You" includes any entity that
|
|
||||||
controls, is controlled by, or is under common control with You. For
|
|
||||||
purposes of this definition, "control" means (a) the power, direct
|
|
||||||
or indirect, to cause the direction or management of such entity,
|
|
||||||
whether by contract or otherwise, or (b) ownership of more than
|
|
||||||
fifty percent (50%) of the outstanding shares or beneficial
|
|
||||||
ownership of such entity.
|
|
||||||
|
|
||||||
2. License Grants and Conditions
|
|
||||||
--------------------------------
|
|
||||||
|
|
||||||
2.1. Grants
|
|
||||||
|
|
||||||
Each Contributor hereby grants You a world-wide, royalty-free,
|
|
||||||
non-exclusive license:
|
|
||||||
|
|
||||||
(a) under intellectual property rights (other than patent or trademark)
|
|
||||||
Licensable by such Contributor to use, reproduce, make available,
|
|
||||||
modify, display, perform, distribute, and otherwise exploit its
|
|
||||||
Contributions, either on an unmodified basis, with Modifications, or
|
|
||||||
as part of a Larger Work; and
|
|
||||||
|
|
||||||
(b) under Patent Claims of such Contributor to make, use, sell, offer
|
|
||||||
for sale, have made, import, and otherwise transfer either its
|
|
||||||
Contributions or its Contributor Version.
|
|
||||||
|
|
||||||
2.2. Effective Date
|
|
||||||
|
|
||||||
The licenses granted in Section 2.1 with respect to any Contribution
|
|
||||||
become effective for each Contribution on the date the Contributor first
|
|
||||||
distributes such Contribution.
|
|
||||||
|
|
||||||
2.3. Limitations on Grant Scope
|
|
||||||
|
|
||||||
The licenses granted in this Section 2 are the only rights granted under
|
|
||||||
this License. No additional rights or licenses will be implied from the
|
|
||||||
distribution or licensing of Covered Software under this License.
|
|
||||||
Notwithstanding Section 2.1(b) above, no patent license is granted by a
|
|
||||||
Contributor:
|
|
||||||
|
|
||||||
(a) for any code that a Contributor has removed from Covered Software;
|
|
||||||
or
|
|
||||||
|
|
||||||
(b) for infringements caused by: (i) Your and any other third party's
|
|
||||||
modifications of Covered Software, or (ii) the combination of its
|
|
||||||
Contributions with other software (except as part of its Contributor
|
|
||||||
Version); or
|
|
||||||
|
|
||||||
(c) under Patent Claims infringed by Covered Software in the absence of
|
|
||||||
its Contributions.
|
|
||||||
|
|
||||||
This License does not grant any rights in the trademarks, service marks,
|
|
||||||
or logos of any Contributor (except as may be necessary to comply with
|
|
||||||
the notice requirements in Section 3.4).
|
|
||||||
|
|
||||||
2.4. Subsequent Licenses
|
|
||||||
|
|
||||||
No Contributor makes additional grants as a result of Your choice to
|
|
||||||
distribute the Covered Software under a subsequent version of this
|
|
||||||
License (see Section 10.2) or under the terms of a Secondary License (if
|
|
||||||
permitted under the terms of Section 3.3).
|
|
||||||
|
|
||||||
2.5. Representation
|
|
||||||
|
|
||||||
Each Contributor represents that the Contributor believes its
|
|
||||||
Contributions are its original creation(s) or it has sufficient rights
|
|
||||||
to grant the rights to its Contributions conveyed by this License.
|
|
||||||
|
|
||||||
2.6. Fair Use
|
|
||||||
|
|
||||||
This License is not intended to limit any rights You have under
|
|
||||||
applicable copyright doctrines of fair use, fair dealing, or other
|
|
||||||
equivalents.
|
|
||||||
|
|
||||||
2.7. Conditions
|
|
||||||
|
|
||||||
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
|
|
||||||
in Section 2.1.
|
|
||||||
|
|
||||||
3. Responsibilities
|
|
||||||
-------------------
|
|
||||||
|
|
||||||
3.1. Distribution of Source Form
|
|
||||||
|
|
||||||
All distribution of Covered Software in Source Code Form, including any
|
|
||||||
Modifications that You create or to which You contribute, must be under
|
|
||||||
the terms of this License. You must inform recipients that the Source
|
|
||||||
Code Form of the Covered Software is governed by the terms of this
|
|
||||||
License, and how they can obtain a copy of this License. You may not
|
|
||||||
attempt to alter or restrict the recipients' rights in the Source Code
|
|
||||||
Form.
|
|
||||||
|
|
||||||
3.2. Distribution of Executable Form
|
|
||||||
|
|
||||||
If You distribute Covered Software in Executable Form then:
|
|
||||||
|
|
||||||
(a) such Covered Software must also be made available in Source Code
|
|
||||||
Form, as described in Section 3.1, and You must inform recipients of
|
|
||||||
the Executable Form how they can obtain a copy of such Source Code
|
|
||||||
Form by reasonable means in a timely manner, at a charge no more
|
|
||||||
than the cost of distribution to the recipient; and
|
|
||||||
|
|
||||||
(b) You may distribute such Executable Form under the terms of this
|
|
||||||
License, or sublicense it under different terms, provided that the
|
|
||||||
license for the Executable Form does not attempt to limit or alter
|
|
||||||
the recipients' rights in the Source Code Form under this License.
|
|
||||||
|
|
||||||
3.3. Distribution of a Larger Work
|
|
||||||
|
|
||||||
You may create and distribute a Larger Work under terms of Your choice,
|
|
||||||
provided that You also comply with the requirements of this License for
|
|
||||||
the Covered Software. If the Larger Work is a combination of Covered
|
|
||||||
Software with a work governed by one or more Secondary Licenses, and the
|
|
||||||
Covered Software is not Incompatible With Secondary Licenses, this
|
|
||||||
License permits You to additionally distribute such Covered Software
|
|
||||||
under the terms of such Secondary License(s), so that the recipient of
|
|
||||||
the Larger Work may, at their option, further distribute the Covered
|
|
||||||
Software under the terms of either this License or such Secondary
|
|
||||||
License(s).
|
|
||||||
|
|
||||||
3.4. Notices
|
|
||||||
|
|
||||||
You may not remove or alter the substance of any license notices
|
|
||||||
(including copyright notices, patent notices, disclaimers of warranty,
|
|
||||||
or limitations of liability) contained within the Source Code Form of
|
|
||||||
the Covered Software, except that You may alter any license notices to
|
|
||||||
the extent required to remedy known factual inaccuracies.
|
|
||||||
|
|
||||||
3.5. Application of Additional Terms
|
|
||||||
|
|
||||||
You may choose to offer, and to charge a fee for, warranty, support,
|
|
||||||
indemnity or liability obligations to one or more recipients of Covered
|
|
||||||
Software. However, You may do so only on Your own behalf, and not on
|
|
||||||
behalf of any Contributor. You must make it absolutely clear that any
|
|
||||||
such warranty, support, indemnity, or liability obligation is offered by
|
|
||||||
You alone, and You hereby agree to indemnify every Contributor for any
|
|
||||||
liability incurred by such Contributor as a result of warranty, support,
|
|
||||||
indemnity or liability terms You offer. You may include additional
|
|
||||||
disclaimers of warranty and limitations of liability specific to any
|
|
||||||
jurisdiction.
|
|
||||||
|
|
||||||
4. Inability to Comply Due to Statute or Regulation
|
|
||||||
---------------------------------------------------
|
|
||||||
|
|
||||||
If it is impossible for You to comply with any of the terms of this
|
|
||||||
License with respect to some or all of the Covered Software due to
|
|
||||||
statute, judicial order, or regulation then You must: (a) comply with
|
|
||||||
the terms of this License to the maximum extent possible; and (b)
|
|
||||||
describe the limitations and the code they affect. Such description must
|
|
||||||
be placed in a text file included with all distributions of the Covered
|
|
||||||
Software under this License. Except to the extent prohibited by statute
|
|
||||||
or regulation, such description must be sufficiently detailed for a
|
|
||||||
recipient of ordinary skill to be able to understand it.
|
|
||||||
|
|
||||||
5. Termination
|
|
||||||
--------------
|
|
||||||
|
|
||||||
5.1. The rights granted under this License will terminate automatically
|
|
||||||
if You fail to comply with any of its terms. However, if You become
|
|
||||||
compliant, then the rights granted under this License from a particular
|
|
||||||
Contributor are reinstated (a) provisionally, unless and until such
|
|
||||||
Contributor explicitly and finally terminates Your grants, and (b) on an
|
|
||||||
ongoing basis, if such Contributor fails to notify You of the
|
|
||||||
non-compliance by some reasonable means prior to 60 days after You have
|
|
||||||
come back into compliance. Moreover, Your grants from a particular
|
|
||||||
Contributor are reinstated on an ongoing basis if such Contributor
|
|
||||||
notifies You of the non-compliance by some reasonable means, this is the
|
|
||||||
first time You have received notice of non-compliance with this License
|
|
||||||
from such Contributor, and You become compliant prior to 30 days after
|
|
||||||
Your receipt of the notice.
|
|
||||||
|
|
||||||
5.2. If You initiate litigation against any entity by asserting a patent
|
|
||||||
infringement claim (excluding declaratory judgment actions,
|
|
||||||
counter-claims, and cross-claims) alleging that a Contributor Version
|
|
||||||
directly or indirectly infringes any patent, then the rights granted to
|
|
||||||
You by any and all Contributors for the Covered Software under Section
|
|
||||||
2.1 of this License shall terminate.
|
|
||||||
|
|
||||||
5.3. In the event of termination under Sections 5.1 or 5.2 above, all
|
|
||||||
end user license agreements (excluding distributors and resellers) which
|
|
||||||
have been validly granted by You or Your distributors under this License
|
|
||||||
prior to termination shall survive termination.
|
|
||||||
|
|
||||||
************************************************************************
|
|
||||||
* *
|
|
||||||
* 6. Disclaimer of Warranty *
|
|
||||||
* ------------------------- *
|
|
||||||
* *
|
|
||||||
* Covered Software is provided under this License on an "as is" *
|
|
||||||
* basis, without warranty of any kind, either expressed, implied, or *
|
|
||||||
* statutory, including, without limitation, warranties that the *
|
|
||||||
* Covered Software is free of defects, merchantable, fit for a *
|
|
||||||
* particular purpose or non-infringing. The entire risk as to the *
|
|
||||||
* quality and performance of the Covered Software is with You. *
|
|
||||||
* Should any Covered Software prove defective in any respect, You *
|
|
||||||
* (not any Contributor) assume the cost of any necessary servicing, *
|
|
||||||
* repair, or correction. This disclaimer of warranty constitutes an *
|
|
||||||
* essential part of this License. No use of any Covered Software is *
|
|
||||||
* authorized under this License except under this disclaimer. *
|
|
||||||
* *
|
|
||||||
************************************************************************
|
|
||||||
|
|
||||||
************************************************************************
|
|
||||||
* *
|
|
||||||
* 7. Limitation of Liability *
|
|
||||||
* -------------------------- *
|
|
||||||
* *
|
|
||||||
* Under no circumstances and under no legal theory, whether tort *
|
|
||||||
* (including negligence), contract, or otherwise, shall any *
|
|
||||||
* Contributor, or anyone who distributes Covered Software as *
|
|
||||||
* permitted above, be liable to You for any direct, indirect, *
|
|
||||||
* special, incidental, or consequential damages of any character *
|
|
||||||
* including, without limitation, damages for lost profits, loss of *
|
|
||||||
* goodwill, work stoppage, computer failure or malfunction, or any *
|
|
||||||
* and all other commercial damages or losses, even if such party *
|
|
||||||
* shall have been informed of the possibility of such damages. This *
|
|
||||||
* limitation of liability shall not apply to liability for death or *
|
|
||||||
* personal injury resulting from such party's negligence to the *
|
|
||||||
* extent applicable law prohibits such limitation. Some *
|
|
||||||
* jurisdictions do not allow the exclusion or limitation of *
|
|
||||||
* incidental or consequential damages, so this exclusion and *
|
|
||||||
* limitation may not apply to You. *
|
|
||||||
* *
|
|
||||||
************************************************************************
|
|
||||||
|
|
||||||
8. Litigation
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Any litigation relating to this License may be brought only in the
|
|
||||||
courts of a jurisdiction where the defendant maintains its principal
|
|
||||||
place of business and such litigation shall be governed by laws of that
|
|
||||||
jurisdiction, without reference to its conflict-of-law provisions.
|
|
||||||
Nothing in this Section shall prevent a party's ability to bring
|
|
||||||
cross-claims or counter-claims.
|
|
||||||
|
|
||||||
9. Miscellaneous
|
|
||||||
----------------
|
|
||||||
|
|
||||||
This License represents the complete agreement concerning the subject
|
|
||||||
matter hereof. If any provision of this License is held to be
|
|
||||||
unenforceable, such provision shall be reformed only to the extent
|
|
||||||
necessary to make it enforceable. Any law or regulation which provides
|
|
||||||
that the language of a contract shall be construed against the drafter
|
|
||||||
shall not be used to construe this License against a Contributor.
|
|
||||||
|
|
||||||
10. Versions of the License
|
|
||||||
---------------------------
|
|
||||||
|
|
||||||
10.1. New Versions
|
|
||||||
|
|
||||||
Mozilla Foundation is the license steward. Except as provided in Section
|
|
||||||
10.3, no one other than the license steward has the right to modify or
|
|
||||||
publish new versions of this License. Each version will be given a
|
|
||||||
distinguishing version number.
|
|
||||||
|
|
||||||
10.2. Effect of New Versions
|
|
||||||
|
|
||||||
You may distribute the Covered Software under the terms of the version
|
|
||||||
of the License under which You originally received the Covered Software,
|
|
||||||
or under the terms of any subsequent version published by the license
|
|
||||||
steward.
|
|
||||||
|
|
||||||
10.3. Modified Versions
|
|
||||||
|
|
||||||
If you create software not governed by this License, and you want to
|
|
||||||
create a new license for such software, you may create and use a
|
|
||||||
modified version of this License if you rename the license and remove
|
|
||||||
any references to the name of the license steward (except to note that
|
|
||||||
such modified license differs from this License).
|
|
||||||
|
|
||||||
10.4. Distributing Source Code Form that is Incompatible With Secondary
|
|
||||||
Licenses
|
|
||||||
|
|
||||||
If You choose to distribute Source Code Form that is Incompatible With
|
|
||||||
Secondary Licenses under the terms of this version of the License, the
|
|
||||||
notice described in Exhibit B of this License must be attached.
|
|
||||||
|
|
||||||
Exhibit A - Source Code Form License Notice
|
|
||||||
-------------------------------------------
|
|
||||||
|
|
||||||
This Source Code Form is subject to the terms of the Mozilla Public
|
|
||||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
||||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
||||||
|
|
||||||
If it is not possible or desirable to put the notice in a particular
|
|
||||||
file, then You may include the notice in a location (such as a LICENSE
|
|
||||||
file in a relevant directory) where a recipient would be likely to look
|
|
||||||
for such a notice.
|
|
||||||
|
|
||||||
You may add additional accurate notices of copyright ownership.
|
|
||||||
|
|
||||||
Exhibit B - "Incompatible With Secondary Licenses" Notice
|
|
||||||
---------------------------------------------------------
|
|
||||||
|
|
||||||
This Source Code Form is "Incompatible With Secondary Licenses", as
|
|
||||||
defined by the Mozilla Public License, v. 2.0.
|
|
|
@ -1,69 +0,0 @@
|
||||||
# GoCertifi: SSL Certificates for Golang
|
|
||||||
|
|
||||||
This Go package contains a CA bundle that you can reference in your Go code.
|
|
||||||
This is useful for systems that do not have CA bundles that Golang can find
|
|
||||||
itself, or where a uniform set of CAs is valuable.
|
|
||||||
|
|
||||||
This is the same CA bundle that ships with the
|
|
||||||
[Python Requests](https://github.com/kennethreitz/requests) library, and is a
|
|
||||||
Golang specific port of [certifi](https://github.com/kennethreitz/certifi). The
|
|
||||||
CA bundle is derived from Mozilla's canonical set.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
You can use the `gocertifi` package as follows:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/certifi/gocertifi"
|
|
||||||
|
|
||||||
certPool, err := gocertifi.CACerts()
|
|
||||||
```
|
|
||||||
|
|
||||||
You can use the returned `*x509.CertPool` as part of an HTTP transport, for example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"crypto/tls"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Setup an HTTP client with a custom transport
|
|
||||||
transport := &http.Transport{
|
|
||||||
Proxy: ProxyFromEnvironment,
|
|
||||||
DialContext: (&net.Dialer{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
KeepAlive: 30 * time.Second,
|
|
||||||
DualStack: true,
|
|
||||||
}).DialContext,
|
|
||||||
ForceAttemptHTTP2: true,
|
|
||||||
MaxIdleConns: 100,
|
|
||||||
IdleConnTimeout: 90 * time.Second,
|
|
||||||
TLSHandshakeTimeout: 10 * time.Second,
|
|
||||||
ExpectContinueTimeout: 1 * time.Second,
|
|
||||||
}
|
|
||||||
// or, starting with go1.13 simply use:
|
|
||||||
// transport := http.DefaultTransport.(*http.Transport).Clone()
|
|
||||||
|
|
||||||
transport.TLSClientConfig = &tls.Config{RootCAs: certPool}
|
|
||||||
client := &http.Client{Transport: transport}
|
|
||||||
|
|
||||||
// Make an HTTP request using our custom transport
|
|
||||||
resp, err := client.Get("https://example.com")
|
|
||||||
```
|
|
||||||
|
|
||||||
## Detailed Documentation
|
|
||||||
|
|
||||||
Import as follows:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/certifi/gocertifi"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Functions
|
|
||||||
|
|
||||||
```go
|
|
||||||
func CACerts() (*x509.CertPool, error)
|
|
||||||
```
|
|
||||||
CACerts builds an X.509 certificate pool containing the Mozilla CA Certificate
|
|
||||||
bundle. This can't actually error and always returns successfully with `nil`
|
|
||||||
as the error. This will be replaced in `v2` to only return the `CertPool`.
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1 +0,0 @@
|
||||||
.git
|
|
|
@ -1,5 +0,0 @@
|
||||||
*.test
|
|
||||||
*.out
|
|
||||||
example/example
|
|
||||||
/xunit.xml
|
|
||||||
/coverage.xml
|
|
|
@ -1,41 +0,0 @@
|
||||||
sudo: false
|
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.7.x
|
|
||||||
- 1.8.x
|
|
||||||
- 1.9.x
|
|
||||||
- 1.10.x
|
|
||||||
- 1.11.x
|
|
||||||
- tip
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- go install -race std
|
|
||||||
- go get golang.org/x/tools/cmd/cover
|
|
||||||
- go get github.com/tebeka/go2xunit
|
|
||||||
- go get github.com/t-yuki/gocover-cobertura
|
|
||||||
- go get -v ./...
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go test -v -race ./... | tee gotest.out
|
|
||||||
- $GOPATH/bin/go2xunit -fail -input gotest.out -output xunit.xml
|
|
||||||
- go test -v -coverprofile=coverage.txt -covermode count .
|
|
||||||
- $GOPATH/bin/gocover-cobertura < coverage.txt > coverage.xml
|
|
||||||
|
|
||||||
after_script:
|
|
||||||
- npm install -g @zeus-ci/cli
|
|
||||||
- zeus upload -t "application/x-cobertura+xml" coverage.xml
|
|
||||||
- zeus upload -t "application/x-xunit+xml" xunit.xml
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
notifications:
|
|
||||||
webhooks:
|
|
||||||
urls:
|
|
||||||
- https://zeus.ci/hooks/cd949996-d30a-11e8-ba53-0a580a28042d/public/provider/travis/webhook
|
|
||||||
on_success: always
|
|
||||||
on_failure: always
|
|
||||||
on_start: always
|
|
||||||
on_cancel: always
|
|
||||||
on_error: always
|
|
|
@ -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.
|
|
|
@ -1,19 +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 the official Go SDK 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
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: Go 1.7 and newer are supported.
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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" }
|
|
|
@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -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" }
|
|
|
@ -1,4 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
go test -race ./...
|
|
||||||
go test -cover ./...
|
|
||||||
go test -v ./...
|
|
|
@ -1,277 +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"
|
|
||||||
"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 fName string
|
|
||||||
var file string
|
|
||||||
var line int
|
|
||||||
if fn != nil {
|
|
||||||
file, line = fn.FileLine(pc)
|
|
||||||
fName = fn.Name()
|
|
||||||
} else {
|
|
||||||
file = "unknown"
|
|
||||||
fName = "unknown"
|
|
||||||
}
|
|
||||||
frame := NewStacktraceFrame(pc, fName, 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
|
|
||||||
|
|
||||||
callerPcs := make([]uintptr, 100)
|
|
||||||
numCallers := runtime.Callers(skip+2, callerPcs)
|
|
||||||
|
|
||||||
// If there are no callers, the entire stacktrace is nil
|
|
||||||
if numCallers == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
callersFrames := runtime.CallersFrames(callerPcs)
|
|
||||||
|
|
||||||
for {
|
|
||||||
fr, more := callersFrames.Next()
|
|
||||||
if fr.Func != nil {
|
|
||||||
frame := NewStacktraceFrame(fr.PC, fr.Function, fr.File, fr.Line, context, appPackagePrefixes)
|
|
||||||
if frame != nil {
|
|
||||||
frames = append(frames, frame)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !more {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 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, fName, file string, line, context int, appPackagePrefixes []string) *StacktraceFrame {
|
|
||||||
frame := &StacktraceFrame{AbsolutePath: file, Filename: trimPath(file), Lineno: line, InApp: false}
|
|
||||||
frame.Module, frame.Function = functionName(fName)
|
|
||||||
|
|
||||||
// `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(fName string) (pack string, name string) {
|
|
||||||
name = fName
|
|
||||||
// We get this:
|
|
||||||
// runtime/debug.*T·ptrmethod
|
|
||||||
// and want this:
|
|
||||||
// pack = runtime/debug
|
|
||||||
// name = *T.ptrmethod
|
|
||||||
if idx := strings.LastIndex(name, "."); idx != -1 {
|
|
||||||
pack = name[:idx]
|
|
||||||
name = name[idx+1:]
|
|
||||||
}
|
|
||||||
name = strings.Replace(name, "·", ".", -1)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -8,9 +8,6 @@ github.com/apparentlymart/go-cidr/cidr
|
||||||
# github.com/beorn7/perks v1.0.1
|
# github.com/beorn7/perks v1.0.1
|
||||||
## explicit; go 1.11
|
## explicit; go 1.11
|
||||||
github.com/beorn7/perks/quantile
|
github.com/beorn7/perks/quantile
|
||||||
# github.com/certifi/gocertifi v0.0.0-20210507211836-431795d63e8d
|
|
||||||
## explicit; go 1.12
|
|
||||||
github.com/certifi/gocertifi
|
|
||||||
# github.com/cespare/xxhash/v2 v2.1.2
|
# github.com/cespare/xxhash/v2 v2.1.2
|
||||||
## explicit; go 1.11
|
## explicit; go 1.11
|
||||||
github.com/cespare/xxhash/v2
|
github.com/cespare/xxhash/v2
|
||||||
|
@ -109,9 +106,6 @@ github.com/flynn/go-shlex
|
||||||
# github.com/fsnotify/fsnotify v1.4.9
|
# github.com/fsnotify/fsnotify v1.4.9
|
||||||
## explicit; go 1.13
|
## explicit; go 1.13
|
||||||
github.com/fsnotify/fsnotify
|
github.com/fsnotify/fsnotify
|
||||||
# github.com/getsentry/raven-go v0.2.0
|
|
||||||
## explicit
|
|
||||||
github.com/getsentry/raven-go
|
|
||||||
# github.com/getsentry/sentry-go v0.16.0
|
# github.com/getsentry/sentry-go v0.16.0
|
||||||
## explicit; go 1.19
|
## explicit; go 1.19
|
||||||
github.com/getsentry/sentry-go
|
github.com/getsentry/sentry-go
|
||||||
|
|
Loading…
Reference in New Issue