mirror of
https://github.com/bitechdev/ResolveSpec.git
synced 2025-12-13 17:10:36 +00:00
Tracking provider
This commit is contained in:
parent
a95c28a0bf
commit
1f7a57f8e3
1
go.mod
1
go.mod
@ -38,6 +38,7 @@ require (
|
|||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/getsentry/sentry-go v0.40.0 // indirect
|
||||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.3 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@ -21,6 +21,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp
|
|||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||||
|
github.com/getsentry/sentry-go v0.40.0 h1:VTJMN9zbTvqDqPwheRVLcp0qcUcM+8eFivvGocAaSbo=
|
||||||
|
github.com/getsentry/sentry-go v0.40.0/go.mod h1:eRXCoh3uvmjQLY6qu63BjUZnaBu5L5WhMV1RwYO8W5s=
|
||||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
||||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
||||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||||
|
|||||||
@ -4,13 +4,14 @@ import "time"
|
|||||||
|
|
||||||
// Config represents the complete application configuration
|
// Config represents the complete application configuration
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Server ServerConfig `mapstructure:"server"`
|
Server ServerConfig `mapstructure:"server"`
|
||||||
Tracing TracingConfig `mapstructure:"tracing"`
|
Tracing TracingConfig `mapstructure:"tracing"`
|
||||||
Cache CacheConfig `mapstructure:"cache"`
|
Cache CacheConfig `mapstructure:"cache"`
|
||||||
Logger LoggerConfig `mapstructure:"logger"`
|
Logger LoggerConfig `mapstructure:"logger"`
|
||||||
Middleware MiddlewareConfig `mapstructure:"middleware"`
|
ErrorTracking ErrorTrackingConfig `mapstructure:"error_tracking"`
|
||||||
CORS CORSConfig `mapstructure:"cors"`
|
Middleware MiddlewareConfig `mapstructure:"middleware"`
|
||||||
Database DatabaseConfig `mapstructure:"database"`
|
CORS CORSConfig `mapstructure:"cors"`
|
||||||
|
Database DatabaseConfig `mapstructure:"database"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerConfig holds server-related configuration
|
// ServerConfig holds server-related configuration
|
||||||
@ -78,3 +79,15 @@ type CORSConfig struct {
|
|||||||
type DatabaseConfig struct {
|
type DatabaseConfig struct {
|
||||||
URL string `mapstructure:"url"`
|
URL string `mapstructure:"url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrorTrackingConfig holds error tracking configuration
|
||||||
|
type ErrorTrackingConfig struct {
|
||||||
|
Enabled bool `mapstructure:"enabled"`
|
||||||
|
Provider string `mapstructure:"provider"` // sentry, noop
|
||||||
|
DSN string `mapstructure:"dsn"` // Sentry DSN
|
||||||
|
Environment string `mapstructure:"environment"` // e.g., production, staging, development
|
||||||
|
Release string `mapstructure:"release"` // Application version/release
|
||||||
|
Debug bool `mapstructure:"debug"` // Enable debug mode
|
||||||
|
SampleRate float64 `mapstructure:"sample_rate"` // Error sample rate (0.0-1.0)
|
||||||
|
TracesSampleRate float64 `mapstructure:"traces_sample_rate"` // Traces sample rate (0.0-1.0)
|
||||||
|
}
|
||||||
|
|||||||
150
pkg/errortracking/README.md
Normal file
150
pkg/errortracking/README.md
Normal file
@ -0,0 +1,150 @@
|
|||||||
|
# Error Tracking
|
||||||
|
|
||||||
|
This package provides error tracking integration for ResolveSpec, with built-in support for Sentry.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Provider Interface**: Flexible design supporting multiple error tracking backends
|
||||||
|
- **Sentry Integration**: Full-featured Sentry support with automatic error, warning, and panic tracking
|
||||||
|
- **Automatic Logger Integration**: All `logger.Error()` and `logger.Warn()` calls are automatically sent to the error tracker
|
||||||
|
- **Panic Tracking**: Automatic panic capture with stack traces
|
||||||
|
- **NoOp Provider**: Zero-overhead when error tracking is disabled
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Add error tracking configuration to your config file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
error_tracking:
|
||||||
|
enabled: true
|
||||||
|
provider: "sentry" # Currently supports: "sentry" or "noop"
|
||||||
|
dsn: "https://your-sentry-dsn@sentry.io/project-id"
|
||||||
|
environment: "production" # e.g., production, staging, development
|
||||||
|
release: "v1.0.0" # Your application version
|
||||||
|
debug: false
|
||||||
|
sample_rate: 1.0 # Error sample rate (0.0-1.0)
|
||||||
|
traces_sample_rate: 0.1 # Traces sample rate (0.0-1.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Initialization
|
||||||
|
|
||||||
|
Initialize error tracking in your application startup:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/bitechdev/ResolveSpec/pkg/config"
|
||||||
|
"github.com/bitechdev/ResolveSpec/pkg/errortracking"
|
||||||
|
"github.com/bitechdev/ResolveSpec/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Load your configuration
|
||||||
|
cfg := config.Config{
|
||||||
|
ErrorTracking: config.ErrorTrackingConfig{
|
||||||
|
Enabled: true,
|
||||||
|
Provider: "sentry",
|
||||||
|
DSN: "https://your-sentry-dsn@sentry.io/project-id",
|
||||||
|
Environment: "production",
|
||||||
|
Release: "v1.0.0",
|
||||||
|
SampleRate: 1.0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize logger
|
||||||
|
logger.Init(false)
|
||||||
|
|
||||||
|
// Initialize error tracking
|
||||||
|
provider, err := errortracking.NewProviderFromConfig(cfg.ErrorTracking)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Failed to initialize error tracking: %v", err)
|
||||||
|
} else {
|
||||||
|
logger.InitErrorTracking(provider)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Your application code...
|
||||||
|
|
||||||
|
// Cleanup on shutdown
|
||||||
|
defer logger.CloseErrorTracking()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Automatic Tracking
|
||||||
|
|
||||||
|
Once initialized, all logger errors and warnings are automatically sent to the error tracker:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// This will be logged AND sent to Sentry
|
||||||
|
logger.Error("Database connection failed: %v", err)
|
||||||
|
|
||||||
|
// This will also be logged AND sent to Sentry
|
||||||
|
logger.Warn("Cache miss for key: %s", key)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Panic Tracking
|
||||||
|
|
||||||
|
Panics are automatically captured when using the logger's panic handlers:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Using CatchPanic
|
||||||
|
defer logger.CatchPanic("MyFunction")
|
||||||
|
|
||||||
|
// Using CatchPanicCallback
|
||||||
|
defer logger.CatchPanicCallback("MyFunction", func(err any) {
|
||||||
|
// Custom cleanup
|
||||||
|
})
|
||||||
|
|
||||||
|
// Using HandlePanic
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = logger.HandlePanic("MyMethod", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
```
|
||||||
|
|
||||||
|
### Manual Tracking
|
||||||
|
|
||||||
|
You can also use the provider directly for custom error tracking:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/bitechdev/ResolveSpec/pkg/errortracking"
|
||||||
|
"github.com/bitechdev/ResolveSpec/pkg/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
func someFunction() {
|
||||||
|
tracker := logger.GetErrorTracker()
|
||||||
|
if tracker != nil {
|
||||||
|
// Capture an error
|
||||||
|
tracker.CaptureError(context.Background(), err, errortracking.SeverityError, map[string]interface{}{
|
||||||
|
"user_id": userID,
|
||||||
|
"request_id": requestID,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Capture a message
|
||||||
|
tracker.CaptureMessage(context.Background(), "Important event occurred", errortracking.SeverityInfo, map[string]interface{}{
|
||||||
|
"event_type": "user_signup",
|
||||||
|
})
|
||||||
|
|
||||||
|
// Capture a panic
|
||||||
|
tracker.CapturePanic(context.Background(), recovered, stackTrace, map[string]interface{}{
|
||||||
|
"context": "background_job",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Severity Levels
|
||||||
|
|
||||||
|
The package supports the following severity levels:
|
||||||
|
|
||||||
|
- `SeverityError`: For errors that should be tracked and investigated
|
||||||
|
- `SeverityWarning`: For warnings that may indicate potential issues
|
||||||
|
- `SeverityInfo`: For informational messages
|
||||||
|
- `SeverityDebug`: For debug-level information
|
||||||
|
|
||||||
|
```
|
||||||
67
pkg/errortracking/errortracking_test.go
Normal file
67
pkg/errortracking/errortracking_test.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package errortracking
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNoOpProvider(t *testing.T) {
|
||||||
|
provider := NewNoOpProvider()
|
||||||
|
|
||||||
|
// Test that all methods can be called without panicking
|
||||||
|
t.Run("CaptureError", func(t *testing.T) {
|
||||||
|
provider.CaptureError(context.Background(), errors.New("test error"), SeverityError, nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CaptureMessage", func(t *testing.T) {
|
||||||
|
provider.CaptureMessage(context.Background(), "test message", SeverityWarning, nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CapturePanic", func(t *testing.T) {
|
||||||
|
provider.CapturePanic(context.Background(), "panic!", []byte("stack trace"), nil)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Flush", func(t *testing.T) {
|
||||||
|
result := provider.Flush(5)
|
||||||
|
if !result {
|
||||||
|
t.Error("Expected Flush to return true")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Close", func(t *testing.T) {
|
||||||
|
err := provider.Close()
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Expected Close to return nil, got %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSeverityLevels(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
severity Severity
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"Error", SeverityError, "error"},
|
||||||
|
{"Warning", SeverityWarning, "warning"},
|
||||||
|
{"Info", SeverityInfo, "info"},
|
||||||
|
{"Debug", SeverityDebug, "debug"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if string(tt.severity) != tt.expected {
|
||||||
|
t.Errorf("Expected %s, got %s", tt.expected, string(tt.severity))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProviderInterface(t *testing.T) {
|
||||||
|
// Test that NoOpProvider implements Provider interface
|
||||||
|
var _ Provider = (*NoOpProvider)(nil)
|
||||||
|
|
||||||
|
// Test that SentryProvider implements Provider interface
|
||||||
|
var _ Provider = (*SentryProvider)(nil)
|
||||||
|
}
|
||||||
33
pkg/errortracking/factory.go
Normal file
33
pkg/errortracking/factory.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package errortracking
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/bitechdev/ResolveSpec/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewProviderFromConfig creates an error tracking provider based on the configuration
|
||||||
|
func NewProviderFromConfig(cfg config.ErrorTrackingConfig) (Provider, error) {
|
||||||
|
if !cfg.Enabled {
|
||||||
|
return NewNoOpProvider(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
switch cfg.Provider {
|
||||||
|
case "sentry":
|
||||||
|
if cfg.DSN == "" {
|
||||||
|
return nil, fmt.Errorf("sentry DSN is required when error tracking is enabled")
|
||||||
|
}
|
||||||
|
return NewSentryProvider(SentryConfig{
|
||||||
|
DSN: cfg.DSN,
|
||||||
|
Environment: cfg.Environment,
|
||||||
|
Release: cfg.Release,
|
||||||
|
Debug: cfg.Debug,
|
||||||
|
SampleRate: cfg.SampleRate,
|
||||||
|
TracesSampleRate: cfg.TracesSampleRate,
|
||||||
|
})
|
||||||
|
case "noop", "":
|
||||||
|
return NewNoOpProvider(), nil
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unknown error tracking provider: %s", cfg.Provider)
|
||||||
|
}
|
||||||
|
}
|
||||||
33
pkg/errortracking/interfaces.go
Normal file
33
pkg/errortracking/interfaces.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package errortracking
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Severity represents the severity level of an error
|
||||||
|
type Severity string
|
||||||
|
|
||||||
|
const (
|
||||||
|
SeverityError Severity = "error"
|
||||||
|
SeverityWarning Severity = "warning"
|
||||||
|
SeverityInfo Severity = "info"
|
||||||
|
SeverityDebug Severity = "debug"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Provider defines the interface for error tracking providers
|
||||||
|
type Provider interface {
|
||||||
|
// CaptureError captures an error with the given severity and additional context
|
||||||
|
CaptureError(ctx context.Context, err error, severity Severity, extra map[string]interface{})
|
||||||
|
|
||||||
|
// CaptureMessage captures a message with the given severity and additional context
|
||||||
|
CaptureMessage(ctx context.Context, message string, severity Severity, extra map[string]interface{})
|
||||||
|
|
||||||
|
// CapturePanic captures a panic with stack trace
|
||||||
|
CapturePanic(ctx context.Context, recovered interface{}, stackTrace []byte, extra map[string]interface{})
|
||||||
|
|
||||||
|
// Flush waits for all events to be sent (useful for graceful shutdown)
|
||||||
|
Flush(timeout int) bool
|
||||||
|
|
||||||
|
// Close closes the provider and releases resources
|
||||||
|
Close() error
|
||||||
|
}
|
||||||
37
pkg/errortracking/noop.go
Normal file
37
pkg/errortracking/noop.go
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package errortracking
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// NoOpProvider is a no-op implementation of the Provider interface
|
||||||
|
// Used when error tracking is disabled
|
||||||
|
type NoOpProvider struct{}
|
||||||
|
|
||||||
|
// NewNoOpProvider creates a new NoOp provider
|
||||||
|
func NewNoOpProvider() *NoOpProvider {
|
||||||
|
return &NoOpProvider{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaptureError does nothing
|
||||||
|
func (n *NoOpProvider) CaptureError(ctx context.Context, err error, severity Severity, extra map[string]interface{}) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaptureMessage does nothing
|
||||||
|
func (n *NoOpProvider) CaptureMessage(ctx context.Context, message string, severity Severity, extra map[string]interface{}) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
// CapturePanic does nothing
|
||||||
|
func (n *NoOpProvider) CapturePanic(ctx context.Context, recovered interface{}, stackTrace []byte, extra map[string]interface{}) {
|
||||||
|
// No-op
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush does nothing and returns true
|
||||||
|
func (n *NoOpProvider) Flush(timeout int) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close does nothing
|
||||||
|
func (n *NoOpProvider) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
154
pkg/errortracking/sentry.go
Normal file
154
pkg/errortracking/sentry.go
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
package errortracking
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getsentry/sentry-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SentryProvider implements the Provider interface using Sentry
|
||||||
|
type SentryProvider struct {
|
||||||
|
hub *sentry.Hub
|
||||||
|
}
|
||||||
|
|
||||||
|
// SentryConfig holds the configuration for Sentry
|
||||||
|
type SentryConfig struct {
|
||||||
|
DSN string
|
||||||
|
Environment string
|
||||||
|
Release string
|
||||||
|
Debug bool
|
||||||
|
SampleRate float64
|
||||||
|
TracesSampleRate float64
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSentryProvider creates a new Sentry provider
|
||||||
|
func NewSentryProvider(config SentryConfig) (*SentryProvider, error) {
|
||||||
|
err := sentry.Init(sentry.ClientOptions{
|
||||||
|
Dsn: config.DSN,
|
||||||
|
Environment: config.Environment,
|
||||||
|
Release: config.Release,
|
||||||
|
Debug: config.Debug,
|
||||||
|
AttachStacktrace: true,
|
||||||
|
SampleRate: config.SampleRate,
|
||||||
|
TracesSampleRate: config.TracesSampleRate,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to initialize Sentry: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &SentryProvider{
|
||||||
|
hub: sentry.CurrentHub(),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaptureError captures an error with the given severity and additional context
|
||||||
|
func (s *SentryProvider) CaptureError(ctx context.Context, err error, severity Severity, extra map[string]interface{}) {
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hub := sentry.GetHubFromContext(ctx)
|
||||||
|
if hub == nil {
|
||||||
|
hub = s.hub
|
||||||
|
}
|
||||||
|
|
||||||
|
event := sentry.NewEvent()
|
||||||
|
event.Level = s.convertSeverity(severity)
|
||||||
|
event.Message = err.Error()
|
||||||
|
event.Exception = []sentry.Exception{
|
||||||
|
{
|
||||||
|
Value: err.Error(),
|
||||||
|
Type: fmt.Sprintf("%T", err),
|
||||||
|
Stacktrace: sentry.ExtractStacktrace(err),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if extra != nil {
|
||||||
|
event.Extra = extra
|
||||||
|
}
|
||||||
|
|
||||||
|
hub.CaptureEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaptureMessage captures a message with the given severity and additional context
|
||||||
|
func (s *SentryProvider) CaptureMessage(ctx context.Context, message string, severity Severity, extra map[string]interface{}) {
|
||||||
|
if message == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hub := sentry.GetHubFromContext(ctx)
|
||||||
|
if hub == nil {
|
||||||
|
hub = s.hub
|
||||||
|
}
|
||||||
|
|
||||||
|
event := sentry.NewEvent()
|
||||||
|
event.Level = s.convertSeverity(severity)
|
||||||
|
event.Message = message
|
||||||
|
|
||||||
|
if extra != nil {
|
||||||
|
event.Extra = extra
|
||||||
|
}
|
||||||
|
|
||||||
|
hub.CaptureEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CapturePanic captures a panic with stack trace
|
||||||
|
func (s *SentryProvider) CapturePanic(ctx context.Context, recovered interface{}, stackTrace []byte, extra map[string]interface{}) {
|
||||||
|
if recovered == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hub := sentry.GetHubFromContext(ctx)
|
||||||
|
if hub == nil {
|
||||||
|
hub = s.hub
|
||||||
|
}
|
||||||
|
|
||||||
|
event := sentry.NewEvent()
|
||||||
|
event.Level = sentry.LevelError
|
||||||
|
event.Message = fmt.Sprintf("Panic: %v", recovered)
|
||||||
|
event.Exception = []sentry.Exception{
|
||||||
|
{
|
||||||
|
Value: fmt.Sprintf("%v", recovered),
|
||||||
|
Type: "panic",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
if extra != nil {
|
||||||
|
event.Extra = extra
|
||||||
|
}
|
||||||
|
|
||||||
|
if stackTrace != nil {
|
||||||
|
event.Extra["stack_trace"] = string(stackTrace)
|
||||||
|
}
|
||||||
|
|
||||||
|
hub.CaptureEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush waits for all events to be sent (useful for graceful shutdown)
|
||||||
|
func (s *SentryProvider) Flush(timeout int) bool {
|
||||||
|
return sentry.Flush(time.Duration(timeout) * time.Second)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close closes the provider and releases resources
|
||||||
|
func (s *SentryProvider) Close() error {
|
||||||
|
sentry.Flush(2 * time.Second)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// convertSeverity converts our Severity to Sentry's Level
|
||||||
|
func (s *SentryProvider) convertSeverity(severity Severity) sentry.Level {
|
||||||
|
switch severity {
|
||||||
|
case SeverityError:
|
||||||
|
return sentry.LevelError
|
||||||
|
case SeverityWarning:
|
||||||
|
return sentry.LevelWarning
|
||||||
|
case SeverityInfo:
|
||||||
|
return sentry.LevelInfo
|
||||||
|
case SeverityDebug:
|
||||||
|
return sentry.LevelDebug
|
||||||
|
default:
|
||||||
|
return sentry.LevelError
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,15 +1,18 @@
|
|||||||
package logger
|
package logger
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
|
||||||
|
"github.com/bitechdev/ResolveSpec/pkg/errortracking"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Logger *zap.SugaredLogger
|
var Logger *zap.SugaredLogger
|
||||||
|
var errorTracker errortracking.Provider
|
||||||
|
|
||||||
func Init(dev bool) {
|
func Init(dev bool) {
|
||||||
|
|
||||||
@ -49,6 +52,28 @@ func UpdateLogger(config *zap.Config) {
|
|||||||
Info("ResolveSpec Logger initialized")
|
Info("ResolveSpec Logger initialized")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// InitErrorTracking initializes the error tracking provider
|
||||||
|
func InitErrorTracking(provider errortracking.Provider) {
|
||||||
|
errorTracker = provider
|
||||||
|
if errorTracker != nil {
|
||||||
|
Info("Error tracking initialized")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetErrorTracker returns the current error tracking provider
|
||||||
|
func GetErrorTracker() errortracking.Provider {
|
||||||
|
return errorTracker
|
||||||
|
}
|
||||||
|
|
||||||
|
// CloseErrorTracking flushes and closes the error tracking provider
|
||||||
|
func CloseErrorTracking() error {
|
||||||
|
if errorTracker != nil {
|
||||||
|
errorTracker.Flush(5)
|
||||||
|
return errorTracker.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func Info(template string, args ...interface{}) {
|
func Info(template string, args ...interface{}) {
|
||||||
if Logger == nil {
|
if Logger == nil {
|
||||||
log.Printf(template, args...)
|
log.Printf(template, args...)
|
||||||
@ -58,19 +83,35 @@ func Info(template string, args ...interface{}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Warn(template string, args ...interface{}) {
|
func Warn(template string, args ...interface{}) {
|
||||||
|
message := fmt.Sprintf(template, args...)
|
||||||
if Logger == nil {
|
if Logger == nil {
|
||||||
log.Printf(template, args...)
|
log.Printf("%s", message)
|
||||||
return
|
} else {
|
||||||
|
Logger.Warnw(message, "process_id", os.Getpid())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send to error tracker
|
||||||
|
if errorTracker != nil {
|
||||||
|
errorTracker.CaptureMessage(context.Background(), message, errortracking.SeverityWarning, map[string]interface{}{
|
||||||
|
"process_id": os.Getpid(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
Logger.Warnw(fmt.Sprintf(template, args...), "process_id", os.Getpid())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Error(template string, args ...interface{}) {
|
func Error(template string, args ...interface{}) {
|
||||||
|
message := fmt.Sprintf(template, args...)
|
||||||
if Logger == nil {
|
if Logger == nil {
|
||||||
log.Printf(template, args...)
|
log.Printf("%s", message)
|
||||||
return
|
} else {
|
||||||
|
Logger.Errorw(message, "process_id", os.Getpid())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send to error tracker
|
||||||
|
if errorTracker != nil {
|
||||||
|
errorTracker.CaptureMessage(context.Background(), message, errortracking.SeverityError, map[string]interface{}{
|
||||||
|
"process_id": os.Getpid(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
Logger.Errorw(fmt.Sprintf(template, args...), "process_id", os.Getpid())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Debug(template string, args ...interface{}) {
|
func Debug(template string, args ...interface{}) {
|
||||||
@ -84,7 +125,7 @@ func Debug(template string, args ...interface{}) {
|
|||||||
// CatchPanic - Handle panic
|
// CatchPanic - Handle panic
|
||||||
func CatchPanicCallback(location string, cb func(err any)) {
|
func CatchPanicCallback(location string, cb func(err any)) {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
// callstack := debug.Stack()
|
callstack := debug.Stack()
|
||||||
|
|
||||||
if Logger != nil {
|
if Logger != nil {
|
||||||
Error("Panic in %s : %v", location, err)
|
Error("Panic in %s : %v", location, err)
|
||||||
@ -93,14 +134,13 @@ func CatchPanicCallback(location string, cb func(err any)) {
|
|||||||
debug.PrintStack()
|
debug.PrintStack()
|
||||||
}
|
}
|
||||||
|
|
||||||
// push to sentry
|
// Send to error tracker
|
||||||
// hub := sentry.CurrentHub()
|
if errorTracker != nil {
|
||||||
// if hub != nil {
|
errorTracker.CapturePanic(context.Background(), err, callstack, map[string]interface{}{
|
||||||
// evtID := hub.Recover(err)
|
"location": location,
|
||||||
// if evtID != nil {
|
"process_id": os.Getpid(),
|
||||||
// sentry.Flush(time.Second * 2)
|
})
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
if cb != nil {
|
if cb != nil {
|
||||||
cb(err)
|
cb(err)
|
||||||
@ -125,5 +165,14 @@ func CatchPanic(location string) {
|
|||||||
func HandlePanic(methodName string, r any) error {
|
func HandlePanic(methodName string, r any) error {
|
||||||
stack := debug.Stack()
|
stack := debug.Stack()
|
||||||
Error("Panic in %s: %v\nStack trace:\n%s", methodName, r, string(stack))
|
Error("Panic in %s: %v\nStack trace:\n%s", methodName, r, string(stack))
|
||||||
|
|
||||||
|
// Send to error tracker
|
||||||
|
if errorTracker != nil {
|
||||||
|
errorTracker.CapturePanic(context.Background(), r, stack, map[string]interface{}{
|
||||||
|
"method": methodName,
|
||||||
|
"process_id": os.Getpid(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return fmt.Errorf("panic in %s: %v", methodName, r)
|
return fmt.Errorf("panic in %s: %v", methodName, r)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user