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
							
								
									6c0dd59701
								
							
						
					
					
						commit
						a1419a73a5
					
				
							
								
								
									
										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  |  | ||||||
| 
 |  | ||||||
| [](https://travis-ci.org/getsentry/raven-go) |  | ||||||
| [](https://goreportcard.com/report/github.com/getsentry/raven-go) |  | ||||||
| [](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