Skip to content

Improve Sentry Error Logging with Enhanced Context and Stack Traces #438

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ require (
github.com/JGLTechnologies/gin-rate-limit v1.5.4
github.com/anaskhan96/base58check v0.0.0-20181220122047-b05365d494c4
github.com/btcsuite/btcd/btcec/v2 v2.3.2
github.com/chadsr/logrus-sentry v0.4.1
github.com/getsentry/sentry-go v0.13.0
github.com/getsentry/sentry-go v0.31.1
github.com/gin-gonic/gin v1.9.1
github.com/go-co-op/gocron v1.35.0
github.com/golang-jwt/jwt/v5 v5.0.0
github.com/jarcoal/httpmock v1.3.1
github.com/joho/godotenv v1.5.1
github.com/mailgun/mailgun-go/v3 v3.6.4
github.com/mattn/go-sqlite3 v1.14.16
github.com/opus-domini/fast-shot v0.10.0
Expand Down
15 changes: 8 additions & 7 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,6 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chadsr/logrus-sentry v0.4.1 h1:WC9UQPdWBpIB7bAwtVRsKAhj2Mn8t3XjmZxvZHrHWbo=
github.com/chadsr/logrus-sentry v0.4.1/go.mod h1:9LQsk92Gb2H2+PyCSoJoKBdMhqVIRO/5Eo3Muor4bq4=
github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY=
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk=
github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d h1:77cEq6EriyTZ0g/qfRdp61a3Uu/AWrgIq2s0ClJV1g0=
Expand Down Expand Up @@ -212,8 +210,8 @@ github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c=
github.com/getsentry/sentry-go v0.13.0 h1:20dgTiUSfxRB/EhMPtxcL9ZEbM1ZdR+W/7f7NWD+xWo=
github.com/getsentry/sentry-go v0.13.0/go.mod h1:EOsfu5ZdvKPfeHYV6pTVQnsjfp30+XA7//UooKNumH0=
github.com/getsentry/sentry-go v0.31.1 h1:ELVc0h7gwyhnXHDouXkhqTFSO5oslsRDk0++eyE0KJ4=
github.com/getsentry/sentry-go v0.31.1/go.mod h1:CYNcMMz73YigoHljQRG+qPF+eMq8gG72XcGN/p71BAY=
github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
Expand All @@ -225,8 +223,9 @@ github.com/go-chi/chi v4.0.0+incompatible h1:SiLLEDyAkqNnw+T/uDTf3aFB9T4FTrwMpuY
github.com/go-chi/chi v4.0.0+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-co-op/gocron v1.35.0 h1:niC91OHiSEimXgPPay02AI1gLGL4JGBgDzmWtgZ8n5A=
github.com/go-co-op/gocron v1.35.0/go.mod h1:NLi+bkm4rRSy1F8U7iacZOz0xPseMoIOnvabGoSe/no=
github.com/go-errors/errors v1.0.1 h1:LUHzmkK3GUKUrL/1gfBUxAHzcev3apQlezX/+O7ma6w=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
Expand Down Expand Up @@ -386,6 +385,8 @@ github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+
github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww=
github.com/jarcoal/httpmock v1.3.1/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
Expand Down Expand Up @@ -566,8 +567,6 @@ github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU
github.com/spf13/cast v1.5.1 h1:R+kOtfhWQE6TVQzY+4D7wJLBgkdVasCEFxSUBYBYIlA=
github.com/spf13/cast v1.5.1/go.mod h1:b9PdjNptOpzXr7Rq1q9gJML/2cdGQAo69NKzQ10KN48=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I=
github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
Expand Down Expand Up @@ -659,6 +658,8 @@ go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.4.0 h1:A8WCeEWhLwPBKNbFi5Wv5UTCBx5zzubnXDlMOFAzFMc=
golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
Expand Down
208 changes: 151 additions & 57 deletions utils/logger/logger.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,98 @@ package logger

import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"

sentryhook "github.com/chadsr/logrus-sentry"
"github.com/getsentry/sentry-go"
"github.com/paycrest/aggregator/config"
"github.com/sirupsen/logrus"
)

// Logger instance
var logger = logrus.New()

func init() {
// InitLogger initializes the logger with the given configuration, output, and executable path.
func InitLogger(cfg *config.ServerConfiguration, output io.Writer, executablePath string) {
if cfg == nil {
cfg = config.ServerConfig()
}

logger.Level = logrus.InfoLevel
logger.Formatter = &formatter{}

config := config.ServerConfig()

if config.Environment == "production" || config.Environment == "staging" {
// init sentry
err := sentry.Init(sentry.ClientOptions{
Dsn: config.SentryDSN,
})
if err != nil {
// Environment-specific configuration
if cfg.Environment == "production" || cfg.Environment == "staging" {
if err := sentry.Init(sentry.ClientOptions{
Dsn: cfg.SentryDSN,
Environment: cfg.Environment,
AttachStacktrace: true,
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
return event
},
}); err != nil {
logger.Fatalf("Sentry initialization failed: %v", err)
}

// Sentry hook
hook := sentryhook.New([]logrus.Level{
logrus.PanicLevel,
logrus.FatalLevel,
logrus.ErrorLevel,
sentry.ConfigureScope(func(scope *sentry.Scope) {
scope.SetTag("environment", cfg.Environment)
scope.SetExtra("app_version", "1.0.0")
})
logger.Hooks.Add(hook)
} else {
// File hook for local environment

// Get the directory of the executable
ex, err := os.Executable()
if err != nil {
logger.Errorf("Failed to get the executable path: %v", err)
return
// Use provided output or default to stdout
if output == nil {
logger.Out = os.Stdout
} else {
logger.Out = output
}
} else if executablePath != "" {
// In development, prioritize file output
exDir := filepath.Dir(executablePath)
if err := os.MkdirAll(exDir, 0755); err != nil {
logger.Errorf("Failed to create directory %s: %v", exDir, err)
}

// Get the directory of the executable
exDir := filepath.Dir(ex)

// Construct the file path in the same directory as the executable
filePath := filepath.Join(exDir, "logs.txt")

file, err := os.OpenFile(filePath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
if err == nil {
logger.Out = file
} else {
if err != nil {
logger.Errorf("Failed to open logs.txt: %v", err)
// Fallback to provided output or stdout
if output == nil {
logger.Out = os.Stdout
} else {
logger.Out = output
}
} else {
logger.Out = file // Default to file output in development
}
} else {
// Fallback for no executablePath
if output == nil {
logger.Out = os.Stdout
} else {
logger.Out = output
}
}

logger.SetReportCaller(true)
}

func init() {
cfg := config.ServerConfig()
ex, err := os.Executable()
if err != nil {
logger.Errorf("Failed to get the executable path: %v", err)
ex = ""
}
InitLogger(cfg, nil, ex)
}

// InitForTest is a wrapper for testing
func InitForTest(cfg *config.ServerConfiguration, output io.Writer, executablePath string) {
InitLogger(cfg, output, executablePath)
}

// SetLogLevel sets the log level for the logger.
func SetLogLevel(level logrus.Level) {
logger.Level = level
Expand All @@ -71,61 +102,124 @@ func SetLogLevel(level logrus.Level) {
// Fields type, used to pass to `WithFields`.
type Fields logrus.Fields

// Debugf logs a message at level Debug on the standard logger.
func Debugf(format string, args ...interface{}) {
// ErrorWithFields logs an error with additional context
func ErrorWithFields(err error, fields Fields) {
if logger.Level >= logrus.ErrorLevel {
wrappedErr := fmt.Errorf("error occurred: %w", err)
sentry.WithScope(func(scope *sentry.Scope) {
scope.SetLevel(sentry.LevelError)
for key, value := range fields {
switch v := value.(type) {
case string:
scope.SetTag(key, v)
default:
scope.SetExtra(key, value)
}
}
sentry.CaptureException(wrappedErr)
})
logger.WithFields(logrus.Fields(fields)).Error(wrappedErr)
}
}

// Debugf logs a message at level Debug with optional fields
func Debugf(format string, fields Fields, args ...interface{}) {
if logger.Level >= logrus.DebugLevel {
entry := logger.WithFields(logrus.Fields{})
entry.Debugf(format, args...)
logger.WithFields(logrus.Fields(fields)).Debugf(format, args...)
}
}

// Infof logs a message at level Info on the standard logger.
func Infof(format string, args ...interface{}) {
// Infof logs a message at level Info with optional fields
func Infof(format string, fields Fields, args ...interface{}) {
if logger.Level >= logrus.InfoLevel {
entry := logger.WithFields(logrus.Fields{})
entry.Infof(format, args...)
logger.WithFields(logrus.Fields(fields)).Infof(format, args...)
}
}

// Warnf logs a message at level Warn on the standard logger.
func Warnf(format string, args ...interface{}) {
// Warnf logs a message at level Warn with optional fields
func Warnf(format string, fields Fields, args ...interface{}) {
if logger.Level >= logrus.WarnLevel {
entry := logger.WithFields(logrus.Fields{})
entry.Warnf(format, args...)
wrappedErr := fmt.Errorf(format, args...) // Create error for stack trace
sentry.WithScope(func(scope *sentry.Scope) {
scope.SetLevel(sentry.LevelWarning)
for key, value := range fields {
switch v := value.(type) {
case string:
scope.SetTag(key, v)
default:
scope.SetExtra(key, value)
}
}
sentry.CaptureException(wrappedErr)
})
logger.WithFields(logrus.Fields(fields)).Warnf(format, args...)
}
}

// Errorf logs a message at level Error on the standard logger.
func Errorf(format string, args ...interface{}) {
// Errorf logs an error message with fields and stack trace
func Errorf(format string, fields Fields, args ...interface{}) {
if logger.Level >= logrus.ErrorLevel {
entry := logger.WithFields(logrus.Fields{})
entry.Errorf(format, args...)
// Create the error directly with fmt.Errorf, avoiding intermediate fmt.Sprintf
wrappedErr := fmt.Errorf(format, args...)
sentry.WithScope(func(scope *sentry.Scope) {
scope.SetLevel(sentry.LevelError)
for key, value := range fields {
switch v := value.(type) {
case string:
scope.SetTag(key, v)
default:
scope.SetExtra(key, value)
}
}
sentry.CaptureException(wrappedErr) // Capture the error with stack trace
})
// Log the formatted message directly
logger.WithFields(logrus.Fields(fields)).Errorf(format, args...)
}
}

// Fatalf logs a message at level Fatal on the standard logger.
func Fatalf(format string, args ...interface{}) {
// Fatalf logs a fatal message with fields
func Fatalf(format string, fields Fields, args ...interface{}) {
if logger.Level >= logrus.FatalLevel {
entry := logger.WithFields(logrus.Fields{})
entry.Fatalf(format, args...)
wrappedErr := fmt.Errorf(format, args...)
sentry.WithScope(func(scope *sentry.Scope) {
scope.SetLevel(sentry.LevelFatal)
for key, value := range fields {
switch v := value.(type) {
case string:
scope.SetTag(key, v)
default:
scope.SetExtra(key, value)
}
}
sentry.CaptureException(wrappedErr)
})
logger.WithFields(logrus.Fields(fields)).Fatal(wrappedErr)
}
}

// Formatter implements logrus.Formatter interface.
// Formatter implements logrus.Formatter interface
type formatter struct {
prefix string
}

// Format building log message.
// Format building log message
func (f *formatter) Format(entry *logrus.Entry) ([]byte, error) {
var sb bytes.Buffer

sb.WriteString(strings.ToUpper(entry.Level.String()))
sb.WriteString(" ")
sb.WriteString(entry.Time.Format(time.RFC3339))
sb.WriteString(" ")
sb.WriteString(f.prefix)
sb.WriteString(entry.Message)

if len(entry.Data) > 0 {
sb.WriteString(" [")
for key, value := range entry.Data {
sb.WriteString(fmt.Sprintf("%s=%v ", key, value))
}
sb.WriteString("]")
}
sb.WriteString("\n")
return sb.Bytes(), nil
}
Loading
Loading