Create new type and remove un-used code
Create Release & Upload Assets / Upload Assets To Gitea w/ goreleaser (push) Failing after 11s
Create Release & Upload Assets / Upload Assets To Gitea w/ goreleaser (push) Failing after 11s
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Functional Software, Inc. dba Sentry
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
<p align="center">
|
||||
<a href="https://sentry.io" target="_blank" align="center">
|
||||
<img src="https://sentry-brand.storage.googleapis.com/sentry-logo-black.png" width="280">
|
||||
</a>
|
||||
<br />
|
||||
</p>
|
||||
|
||||
# Official Sentry Echo Handler for Sentry-go SDK
|
||||
|
||||
**go.dev:** https://pkg.go.dev/github.com/getsentry/sentry-go/echo
|
||||
|
||||
**Example:** https://github.com/getsentry/sentry-go/tree/master/_examples/echo
|
||||
|
||||
## Installation
|
||||
|
||||
```sh
|
||||
go get github.com/getsentry/sentry-go/echo
|
||||
```
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
sentryecho "github.com/getsentry/sentry-go/echo"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
)
|
||||
|
||||
// To initialize Sentry's handler, you need to initialize Sentry itself beforehand
|
||||
if err := sentry.Init(sentry.ClientOptions{
|
||||
Dsn: "your-public-dsn",
|
||||
}); err != nil {
|
||||
fmt.Printf("Sentry initialization failed: %v\n", err)
|
||||
}
|
||||
|
||||
// Then create your app
|
||||
app := echo.New()
|
||||
|
||||
app.Use(middleware.Logger())
|
||||
app.Use(middleware.Recover())
|
||||
|
||||
// Once it's done, you can attach the handler as one of your middleware
|
||||
app.Use(sentryecho.New(sentryecho.Options{}))
|
||||
|
||||
// Set up routes
|
||||
app.GET("/", func(ctx echo.Context) error {
|
||||
return ctx.String(http.StatusOK, "Hello, World!")
|
||||
})
|
||||
|
||||
// And run it
|
||||
app.Logger.Fatal(app.Start(":3000"))
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
`sentryecho` accepts a struct of `Options` that allows you to configure how the handler will behave.
|
||||
|
||||
Currently it respects 3 options:
|
||||
|
||||
```go
|
||||
// Repanic configures whether Sentry should repanic after recovery, in most cases it should be set to true,
|
||||
// as echo includes its own Recover middleware that handles http responses.
|
||||
Repanic bool
|
||||
// WaitForDelivery configures whether you want to block the request before moving forward with the response.
|
||||
// Because Echo's `Recover` handler doesn't restart the application,
|
||||
// it's safe to either skip this option or set it to `false`.
|
||||
WaitForDelivery bool
|
||||
// Timeout for the event delivery requests.
|
||||
Timeout time.Duration
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
`sentryecho` attaches an instance of `*sentry.Hub` (https://pkg.go.dev/github.com/getsentry/sentry-go#Hub) to the `echo.Context`, which makes it available throughout the rest of the request's lifetime.
|
||||
You can access it by using the `sentryecho.GetHubFromContext()` method on the context itself in any of your proceeding middleware and routes.
|
||||
And it should be used instead of the global `sentry.CaptureMessage`, `sentry.CaptureException`, or any other calls, as it keeps the separation of data between the requests.
|
||||
|
||||
**Keep in mind that `*sentry.Hub` won't be available in middleware attached before to `sentryecho`!**
|
||||
|
||||
```go
|
||||
app := echo.New()
|
||||
|
||||
app.Use(middleware.Logger())
|
||||
app.Use(middleware.Recover())
|
||||
|
||||
app.Use(sentryecho.New(sentryecho.Options{
|
||||
Repanic: true,
|
||||
}))
|
||||
|
||||
app.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(ctx echo.Context) error {
|
||||
if hub := sentryecho.GetHubFromContext(ctx); hub != nil {
|
||||
hub.Scope().SetTag("someRandomTag", "maybeYouNeedIt")
|
||||
}
|
||||
return next(ctx)
|
||||
}
|
||||
})
|
||||
|
||||
app.GET("/", func(ctx echo.Context) error {
|
||||
if hub := sentryecho.GetHubFromContext(ctx); hub != nil {
|
||||
hub.WithScope(func(scope *sentry.Scope) {
|
||||
scope.SetExtra("unwantedQuery", "someQueryDataMaybe")
|
||||
hub.CaptureMessage("User provided unwanted query string, but we recovered just fine")
|
||||
})
|
||||
}
|
||||
return ctx.String(http.StatusOK, "Hello, World!")
|
||||
})
|
||||
|
||||
app.GET("/foo", func(ctx echo.Context) error {
|
||||
// sentryecho handler will catch it just fine. Also, because we attached "someRandomTag"
|
||||
// in the middleware before, it will be sent through as well
|
||||
panic("y tho")
|
||||
})
|
||||
|
||||
app.Logger.Fatal(app.Start(":3000"))
|
||||
```
|
||||
|
||||
### Accessing Request in `BeforeSend` callback
|
||||
|
||||
```go
|
||||
sentry.Init(sentry.ClientOptions{
|
||||
Dsn: "your-public-dsn",
|
||||
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
|
||||
if hint.Context != nil {
|
||||
if req, ok := hint.Context.Value(sentry.RequestContextKey).(*http.Request); ok {
|
||||
// You have access to the original Request here
|
||||
}
|
||||
}
|
||||
|
||||
return event
|
||||
},
|
||||
})
|
||||
```
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
package sentryecho
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/getsentry/sentry-go"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
const (
|
||||
// sdkIdentifier is the identifier of the Echo SDK.
|
||||
sdkIdentifier = "sentry.go.echo"
|
||||
|
||||
// valuesKey is used as a key to store the Sentry Hub instance on the echo.Context.
|
||||
valuesKey = "sentry"
|
||||
|
||||
// transactionKey is used as a key to store the Sentry transaction on the echo.Context.
|
||||
transactionKey = "sentry_transaction"
|
||||
|
||||
// errorKey is used as a key to store the error on the echo.Context.
|
||||
errorKey = "error"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
repanic bool
|
||||
waitForDelivery bool
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
type Options struct {
|
||||
// Repanic configures whether Sentry should repanic after recovery, in most cases it should be set to true,
|
||||
// as Echo includes its own Recover middleware that handles HTTP responses.
|
||||
Repanic bool
|
||||
// WaitForDelivery configures whether you want to block the request before moving forward with the response.
|
||||
// Because Echo's Recover handler doesn't restart the application,
|
||||
// it's safe to either skip this option or set it to false.
|
||||
WaitForDelivery bool
|
||||
// Timeout for the event delivery requests.
|
||||
Timeout time.Duration
|
||||
}
|
||||
|
||||
// New returns a function that satisfies echo.HandlerFunc interface
|
||||
// It can be used with Use() methods.
|
||||
func New(options Options) echo.MiddlewareFunc {
|
||||
if options.Timeout == 0 {
|
||||
options.Timeout = 2 * time.Second
|
||||
}
|
||||
|
||||
return (&handler{
|
||||
repanic: options.Repanic,
|
||||
timeout: options.Timeout,
|
||||
waitForDelivery: options.WaitForDelivery,
|
||||
}).handle
|
||||
}
|
||||
|
||||
func (h *handler) handle(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(ctx echo.Context) error {
|
||||
hub := GetHubFromContext(ctx)
|
||||
if hub == nil {
|
||||
hub = sentry.CurrentHub().Clone()
|
||||
}
|
||||
|
||||
if client := hub.Client(); client != nil {
|
||||
client.SetSDKIdentifier(sdkIdentifier)
|
||||
}
|
||||
|
||||
r := ctx.Request()
|
||||
|
||||
transactionName := r.URL.Path
|
||||
transactionSource := sentry.SourceURL
|
||||
|
||||
if path := ctx.Path(); path != "" {
|
||||
transactionName = path
|
||||
transactionSource = sentry.SourceRoute
|
||||
}
|
||||
|
||||
options := []sentry.SpanOption{
|
||||
sentry.ContinueTrace(hub, r.Header.Get(sentry.SentryTraceHeader), r.Header.Get(sentry.SentryBaggageHeader)),
|
||||
sentry.WithOpName("http.server"),
|
||||
sentry.WithTransactionSource(transactionSource),
|
||||
sentry.WithSpanOrigin(sentry.SpanOriginEcho),
|
||||
}
|
||||
|
||||
transaction := sentry.StartTransaction(
|
||||
sentry.SetHubOnContext(r.Context(), hub),
|
||||
fmt.Sprintf("%s %s", r.Method, transactionName),
|
||||
options...,
|
||||
)
|
||||
|
||||
transaction.SetData("http.request.method", r.Method)
|
||||
|
||||
defer func() {
|
||||
status := ctx.Response().Status
|
||||
if err := ctx.Get(errorKey); err != nil {
|
||||
if httpError, ok := err.(*echo.HTTPError); ok {
|
||||
status = httpError.Code
|
||||
}
|
||||
}
|
||||
|
||||
transaction.Status = sentry.HTTPtoSpanStatus(status)
|
||||
transaction.SetData("http.response.status_code", status)
|
||||
transaction.Finish()
|
||||
}()
|
||||
|
||||
hub.Scope().SetRequest(r)
|
||||
ctx.Set(valuesKey, hub)
|
||||
ctx.Set(transactionKey, transaction)
|
||||
defer h.recoverWithSentry(hub, r)
|
||||
|
||||
err := next(ctx)
|
||||
if err != nil {
|
||||
// Store the error so it can be used in the deferred function
|
||||
ctx.Set(errorKey, err)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) recoverWithSentry(hub *sentry.Hub, r *http.Request) {
|
||||
if err := recover(); err != nil {
|
||||
eventID := hub.RecoverWithContext(
|
||||
context.WithValue(r.Context(), sentry.RequestContextKey, r),
|
||||
err,
|
||||
)
|
||||
if eventID != nil && h.waitForDelivery {
|
||||
hub.Flush(h.timeout)
|
||||
}
|
||||
if h.repanic {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetHubFromContext retrieves attached *sentry.Hub instance from echo.Context.
|
||||
func GetHubFromContext(ctx echo.Context) *sentry.Hub {
|
||||
if hub, ok := ctx.Get(valuesKey).(*sentry.Hub); ok {
|
||||
return hub
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetHubOnContext attaches *sentry.Hub instance to echo.Context.
|
||||
func SetHubOnContext(ctx echo.Context, hub *sentry.Hub) {
|
||||
ctx.Set(valuesKey, hub)
|
||||
}
|
||||
|
||||
// GetSpanFromContext retrieves attached *sentry.Span instance from echo.Context.
|
||||
// If there is no transaction on echo.Context, it will return nil.
|
||||
func GetSpanFromContext(ctx echo.Context) *sentry.Span {
|
||||
if span, ok := ctx.Get(transactionKey).(*sentry.Span); ok {
|
||||
return span
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user