2021-04-13 01:02:55 +03:00
package config
import (
"github.com/emvi/logbuch"
"github.com/getsentry/sentry-go"
"github.com/muety/wakapi/models"
2021-04-16 16:59:39 +03:00
"io"
2021-04-13 01:02:55 +03:00
"net/http"
"os"
"strings"
)
2021-04-16 16:59:39 +03:00
// How to: Logging
// Use logbuch.[Debug|Info|Warn|Error|Fatal]() by default
// Use config.Log().[Debug|Info|Warn|Error|Fatal]() when wanting the log to appear in Sentry as well
2021-04-13 01:02:55 +03:00
2021-04-16 16:59:39 +03:00
type capturingWriter struct {
Writer io . Writer
Message string
2021-04-13 01:02:55 +03:00
}
2021-04-16 16:59:39 +03:00
func ( c * capturingWriter ) Clear ( ) {
c . Message = ""
}
func ( c * capturingWriter ) Write ( p [ ] byte ) ( n int , err error ) {
c . Message = string ( p )
return c . Writer . Write ( p )
}
// SentryWrapperLogger is a wrapper around a logbuch.Logger that forwards events to Sentry in addition and optionally allows to attach a request context
type SentryWrapperLogger struct {
* logbuch . Logger
req * http . Request
outWriter * capturingWriter
errWriter * capturingWriter
}
func Log ( ) * SentryWrapperLogger {
ow , ew := & capturingWriter { Writer : os . Stdout } , & capturingWriter { Writer : os . Stderr }
return & SentryWrapperLogger {
Logger : logbuch . NewLogger ( ow , ew ) ,
outWriter : ow ,
errWriter : ew ,
}
}
func ( l * SentryWrapperLogger ) Request ( req * http . Request ) * SentryWrapperLogger {
l . req = req
return l
}
func ( l * SentryWrapperLogger ) Debug ( msg string , params ... interface { } ) {
l . outWriter . Clear ( )
l . Logger . Debug ( msg , params ... )
l . log ( l . errWriter . Message , sentry . LevelDebug )
}
func ( l * SentryWrapperLogger ) Info ( msg string , params ... interface { } ) {
l . outWriter . Clear ( )
l . Logger . Info ( msg , params ... )
l . log ( l . errWriter . Message , sentry . LevelInfo )
}
func ( l * SentryWrapperLogger ) Warn ( msg string , params ... interface { } ) {
l . outWriter . Clear ( )
l . Logger . Warn ( msg , params ... )
l . log ( l . errWriter . Message , sentry . LevelWarning )
}
func ( l * SentryWrapperLogger ) Error ( msg string , params ... interface { } ) {
l . errWriter . Clear ( )
l . Logger . Error ( msg , params ... )
l . log ( l . errWriter . Message , sentry . LevelError )
}
func ( l * SentryWrapperLogger ) Fatal ( msg string , params ... interface { } ) {
l . errWriter . Clear ( )
l . Logger . Fatal ( msg , params ... )
l . log ( l . errWriter . Message , sentry . LevelFatal )
}
func ( l * SentryWrapperLogger ) log ( msg string , level sentry . Level ) {
event := sentry . NewEvent ( )
event . Level = level
event . Message = msg
if l . req != nil {
if h := l . req . Context ( ) . Value ( sentry . HubContextKey ) ; h != nil {
hub := h . ( * sentry . Hub )
hub . Scope ( ) . SetRequest ( l . req )
if u := getPrincipal ( l . req ) ; u != nil {
hub . Scope ( ) . SetUser ( sentry . User { ID : u . ID } )
}
hub . CaptureEvent ( event )
return
}
}
sentry . CaptureEvent ( event )
2021-04-13 01:02:55 +03:00
}
2021-04-29 22:19:43 +03:00
var excludedRoutes = [ ] string {
"GET /assets" ,
"GET /api/health" ,
"GET /swagger-ui" ,
"GET /docs" ,
}
2021-04-13 01:02:55 +03:00
func initSentry ( config sentryConfig , debug bool ) {
if err := sentry . Init ( sentry . ClientOptions {
Dsn : config . Dsn ,
Debug : debug ,
TracesSampler : sentry . TracesSamplerFunc ( func ( ctx sentry . SamplingContext ) sentry . Sampled {
if ! config . EnableTracing {
return sentry . SampledFalse
}
hub := sentry . GetHubFromContext ( ctx . Span . Context ( ) )
txName := hub . Scope ( ) . Transaction ( )
2021-04-29 22:19:43 +03:00
for _ , ex := range excludedRoutes {
if strings . HasPrefix ( txName , ex ) {
return sentry . SampledFalse
}
2021-04-13 01:02:55 +03:00
}
if txName == "POST /api/heartbeat" {
return sentry . UniformTracesSampler ( config . SampleRateHeartbeats ) . Sample ( ctx )
}
return sentry . UniformTracesSampler ( config . SampleRate ) . Sample ( ctx )
} ) ,
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 {
2021-04-16 16:59:39 +03:00
if u := getPrincipal ( req ) ; u != nil {
event . User . ID = u . ID
2021-04-13 01:02:55 +03:00
}
}
}
return event
} ,
} ) ; err != nil {
2022-02-17 14:20:22 +03:00
logbuch . Fatal ( "failed to initialized sentry - %v" , err )
2021-04-13 01:02:55 +03:00
}
}
2021-04-16 16:59:39 +03:00
func getPrincipal ( r * http . Request ) * models . User {
type principalGetter interface {
GetPrincipal ( ) * models . User
}
if p := r . Context ( ) . Value ( "principal" ) ; p != nil {
return p . ( principalGetter ) . GetPrincipal ( )
}
return nil
}