Implement IronTelemetry Go SDK
- Core client with exception/message capture - Journey and step tracking with breadcrumb correlation - Breadcrumb management with ring buffer - HTTP transport with context support - Full stack trace capture for exceptions - Thread-safe operations with mutex protection - Sample rate and beforeSend filtering - Tags, extras, and user context
This commit is contained in:
parent
49c96bdedc
commit
09626831a2
|
|
@ -0,0 +1,29 @@
|
|||
# Binaries
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool
|
||||
*.out
|
||||
|
||||
# Dependency directories
|
||||
vendor/
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Debug
|
||||
debug
|
||||
*.log
|
||||
310
README.md
310
README.md
|
|
@ -1,2 +1,308 @@
|
|||
# irontelemetry-go
|
||||
IronTelemetry SDK for Go - Error monitoring and crash reporting
|
||||
# IronTelemetry SDK for Go
|
||||
|
||||
Error monitoring and crash reporting SDK for Go applications. Capture exceptions, track user journeys, and get insights to fix issues faster.
|
||||
|
||||
[](https://pkg.go.dev/github.com/IronServices/irontelemetry-go)
|
||||
[](https://goreportcard.com/report/github.com/IronServices/irontelemetry-go)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
go get github.com/IronServices/irontelemetry-go
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Basic Exception Capture
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"log"
|
||||
|
||||
irontelemetry "github.com/IronServices/irontelemetry-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Initialize with your DSN
|
||||
client, err := irontelemetry.New(irontelemetry.Options{
|
||||
DSN: "https://pk_live_xxx@irontelemetry.com",
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
// Capture exceptions
|
||||
if err := doSomething(); err != nil {
|
||||
client.CaptureException(err)
|
||||
}
|
||||
}
|
||||
|
||||
func doSomething() error {
|
||||
return errors.New("something went wrong")
|
||||
}
|
||||
```
|
||||
|
||||
### Journey Tracking
|
||||
|
||||
Track user journeys to understand the context of errors:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
irontelemetry "github.com/IronServices/irontelemetry-go"
|
||||
)
|
||||
|
||||
func main() {
|
||||
client, _ := irontelemetry.New(irontelemetry.Options{
|
||||
DSN: "https://pk_live_xxx@irontelemetry.com",
|
||||
})
|
||||
defer client.Close()
|
||||
|
||||
// Start a journey
|
||||
journey := client.StartJourney("Checkout Flow")
|
||||
journey.SetUser("user-123", "user@example.com", "John Doe")
|
||||
|
||||
// Track steps
|
||||
step := journey.StartStep("Validate Cart", irontelemetry.CategoryBusiness)
|
||||
if err := validateCart(); err != nil {
|
||||
step.Fail(err)
|
||||
journey.Fail(err)
|
||||
client.CaptureException(err)
|
||||
return
|
||||
}
|
||||
step.Complete()
|
||||
|
||||
step = journey.StartStep("Process Payment", irontelemetry.CategoryBusiness)
|
||||
if err := processPayment(); err != nil {
|
||||
step.Fail(err)
|
||||
journey.Fail(err)
|
||||
client.CaptureException(err)
|
||||
return
|
||||
}
|
||||
step.Complete()
|
||||
|
||||
journey.Complete()
|
||||
}
|
||||
```
|
||||
|
||||
Or use the helper method:
|
||||
|
||||
```go
|
||||
journey := client.StartJourney("Checkout Flow")
|
||||
|
||||
err := journey.RunStep("Validate Cart", irontelemetry.CategoryBusiness, func() error {
|
||||
return validateCart()
|
||||
})
|
||||
if err != nil {
|
||||
journey.Fail(err)
|
||||
client.CaptureException(err)
|
||||
return
|
||||
}
|
||||
|
||||
err = journey.RunStep("Process Payment", irontelemetry.CategoryBusiness, func() error {
|
||||
return processPayment()
|
||||
})
|
||||
if err != nil {
|
||||
journey.Fail(err)
|
||||
client.CaptureException(err)
|
||||
return
|
||||
}
|
||||
|
||||
journey.Complete()
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
```go
|
||||
client, err := irontelemetry.New(irontelemetry.Options{
|
||||
DSN: "https://pk_live_xxx@irontelemetry.com",
|
||||
Environment: "production",
|
||||
AppVersion: "1.2.3",
|
||||
SampleRate: 1.0, // 100% of events
|
||||
Debug: false,
|
||||
BeforeSend: func(event *irontelemetry.TelemetryEvent) *irontelemetry.TelemetryEvent {
|
||||
// Filter or modify events
|
||||
if strings.Contains(event.Message, "expected") {
|
||||
return nil // Drop the event
|
||||
}
|
||||
return event
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
### Configuration Options
|
||||
|
||||
| Option | Type | Default | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| `DSN` | string | required | Your Data Source Name |
|
||||
| `Environment` | string | "production" | Environment name |
|
||||
| `AppVersion` | string | "0.0.0" | Application version |
|
||||
| `SampleRate` | float64 | 1.0 | Sample rate (0.0 to 1.0) |
|
||||
| `MaxBreadcrumbs` | int | 100 | Max breadcrumbs to keep |
|
||||
| `Debug` | bool | false | Enable debug logging |
|
||||
| `BeforeSend` | func | nil | Hook to filter/modify events |
|
||||
| `EnableOfflineQueue` | bool | true | Enable offline queue |
|
||||
| `MaxOfflineQueueSize` | int | 500 | Max offline queue size |
|
||||
|
||||
## Features
|
||||
|
||||
- **Automatic Stack Traces**: Full stack traces captured with every exception
|
||||
- **Journey Tracking**: Track user flows and correlate errors with context
|
||||
- **Breadcrumbs**: Leave a trail of events leading up to an error
|
||||
- **User Context**: Associate errors with specific users
|
||||
- **Tags & Extras**: Add custom metadata to your events
|
||||
- **Context Support**: Full context.Context support for cancellation
|
||||
- **Thread-Safe**: All operations are safe for concurrent use
|
||||
|
||||
## Breadcrumbs
|
||||
|
||||
```go
|
||||
// Add simple breadcrumbs
|
||||
client.AddBreadcrumb("User clicked checkout button", irontelemetry.CategoryUI)
|
||||
client.AddBreadcrumb("Payment API called", irontelemetry.CategoryHTTP)
|
||||
|
||||
// With level
|
||||
client.AddBreadcrumbWithLevel(
|
||||
"User logged in",
|
||||
irontelemetry.CategoryAuth,
|
||||
irontelemetry.SeverityInfo,
|
||||
)
|
||||
|
||||
// With data
|
||||
client.AddBreadcrumbWithData(
|
||||
"API request completed",
|
||||
irontelemetry.CategoryHTTP,
|
||||
irontelemetry.SeverityInfo,
|
||||
map[string]any{
|
||||
"url": "/api/checkout",
|
||||
"statusCode": 200,
|
||||
"duration": 150,
|
||||
},
|
||||
)
|
||||
```
|
||||
|
||||
### Breadcrumb Categories
|
||||
|
||||
```go
|
||||
irontelemetry.CategoryUI // User interface interactions
|
||||
irontelemetry.CategoryHTTP // HTTP requests
|
||||
irontelemetry.CategoryNavigation // Page/route navigation
|
||||
irontelemetry.CategoryConsole // Console output
|
||||
irontelemetry.CategoryAuth // Authentication events
|
||||
irontelemetry.CategoryBusiness // Business logic events
|
||||
irontelemetry.CategoryNotification // Notification events
|
||||
irontelemetry.CategoryCustom // Custom events
|
||||
```
|
||||
|
||||
## Severity Levels
|
||||
|
||||
```go
|
||||
irontelemetry.SeverityDebug
|
||||
irontelemetry.SeverityInfo
|
||||
irontelemetry.SeverityWarning
|
||||
irontelemetry.SeverityError
|
||||
irontelemetry.SeverityFatal
|
||||
```
|
||||
|
||||
## User Context
|
||||
|
||||
```go
|
||||
// Simple user ID
|
||||
client.SetUserByID("user-123")
|
||||
|
||||
// With email
|
||||
client.SetUserWithEmail("user-123", "user@example.com")
|
||||
|
||||
// Full user object
|
||||
client.SetUser(&irontelemetry.User{
|
||||
ID: "user-123",
|
||||
Email: "user@example.com",
|
||||
Name: "John Doe",
|
||||
Data: map[string]any{
|
||||
"plan": "premium",
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## Tags and Extra Data
|
||||
|
||||
```go
|
||||
// Set individual tags
|
||||
client.SetTag("release", "v1.2.3")
|
||||
client.SetTag("server", "prod-1")
|
||||
|
||||
// Set multiple tags
|
||||
client.SetTags(map[string]string{
|
||||
"release": "v1.2.3",
|
||||
"server": "prod-1",
|
||||
})
|
||||
|
||||
// Set extra data
|
||||
client.SetExtra("request_id", "abc-123")
|
||||
client.SetExtras(map[string]any{
|
||||
"request_id": "abc-123",
|
||||
"user_agent": "Mozilla/5.0...",
|
||||
})
|
||||
```
|
||||
|
||||
## Context Support
|
||||
|
||||
All capture methods support context for cancellation:
|
||||
|
||||
```go
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
result := client.CaptureExceptionWithContext(ctx, err)
|
||||
if !result.Success {
|
||||
log.Printf("Failed to send event: %s", result.Error)
|
||||
}
|
||||
```
|
||||
|
||||
## HTTP Middleware Example
|
||||
|
||||
```go
|
||||
func TelemetryMiddleware(client *irontelemetry.Client) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
client.AddBreadcrumbWithData(
|
||||
"HTTP Request",
|
||||
irontelemetry.CategoryHTTP,
|
||||
irontelemetry.SeverityInfo,
|
||||
map[string]any{
|
||||
"method": r.Method,
|
||||
"url": r.URL.String(),
|
||||
},
|
||||
)
|
||||
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
client.CaptureException(fmt.Errorf("panic: %v", err))
|
||||
panic(err) // Re-panic
|
||||
}
|
||||
}()
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
- Go 1.21+
|
||||
|
||||
## Links
|
||||
|
||||
- [Documentation](https://www.irontelemetry.com/docs)
|
||||
- [Dashboard](https://www.irontelemetry.com)
|
||||
|
||||
## License
|
||||
|
||||
MIT License - see [LICENSE](LICENSE) for details.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
package irontelemetry
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// BreadcrumbManager manages a ring buffer of breadcrumbs
|
||||
type BreadcrumbManager struct {
|
||||
mu sync.RWMutex
|
||||
breadcrumbs []Breadcrumb
|
||||
maxBreadcrumbs int
|
||||
}
|
||||
|
||||
// NewBreadcrumbManager creates a new BreadcrumbManager
|
||||
func NewBreadcrumbManager(maxBreadcrumbs int) *BreadcrumbManager {
|
||||
return &BreadcrumbManager{
|
||||
breadcrumbs: make([]Breadcrumb, 0, maxBreadcrumbs),
|
||||
maxBreadcrumbs: maxBreadcrumbs,
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a breadcrumb to the ring buffer
|
||||
func (bm *BreadcrumbManager) Add(breadcrumb Breadcrumb) {
|
||||
bm.mu.Lock()
|
||||
defer bm.mu.Unlock()
|
||||
|
||||
if breadcrumb.Timestamp.IsZero() {
|
||||
breadcrumb.Timestamp = time.Now()
|
||||
}
|
||||
|
||||
if len(bm.breadcrumbs) >= bm.maxBreadcrumbs {
|
||||
// Shift elements to make room (ring buffer behavior)
|
||||
copy(bm.breadcrumbs, bm.breadcrumbs[1:])
|
||||
bm.breadcrumbs = bm.breadcrumbs[:len(bm.breadcrumbs)-1]
|
||||
}
|
||||
|
||||
bm.breadcrumbs = append(bm.breadcrumbs, breadcrumb)
|
||||
}
|
||||
|
||||
// AddSimple adds a simple breadcrumb with just a message and category
|
||||
func (bm *BreadcrumbManager) AddSimple(message string, category BreadcrumbCategory) {
|
||||
bm.Add(Breadcrumb{
|
||||
Timestamp: time.Now(),
|
||||
Category: category,
|
||||
Message: message,
|
||||
Level: SeverityInfo,
|
||||
})
|
||||
}
|
||||
|
||||
// AddWithLevel adds a breadcrumb with a specific level
|
||||
func (bm *BreadcrumbManager) AddWithLevel(message string, category BreadcrumbCategory, level SeverityLevel) {
|
||||
bm.Add(Breadcrumb{
|
||||
Timestamp: time.Now(),
|
||||
Category: category,
|
||||
Message: message,
|
||||
Level: level,
|
||||
})
|
||||
}
|
||||
|
||||
// AddWithData adds a breadcrumb with additional data
|
||||
func (bm *BreadcrumbManager) AddWithData(message string, category BreadcrumbCategory, level SeverityLevel, data map[string]any) {
|
||||
bm.Add(Breadcrumb{
|
||||
Timestamp: time.Now(),
|
||||
Category: category,
|
||||
Message: message,
|
||||
Level: level,
|
||||
Data: data,
|
||||
})
|
||||
}
|
||||
|
||||
// GetAll returns a copy of all breadcrumbs
|
||||
func (bm *BreadcrumbManager) GetAll() []Breadcrumb {
|
||||
bm.mu.RLock()
|
||||
defer bm.mu.RUnlock()
|
||||
|
||||
result := make([]Breadcrumb, len(bm.breadcrumbs))
|
||||
copy(result, bm.breadcrumbs)
|
||||
return result
|
||||
}
|
||||
|
||||
// Clear removes all breadcrumbs
|
||||
func (bm *BreadcrumbManager) Clear() {
|
||||
bm.mu.Lock()
|
||||
defer bm.mu.Unlock()
|
||||
|
||||
bm.breadcrumbs = make([]Breadcrumb, 0, bm.maxBreadcrumbs)
|
||||
}
|
||||
|
||||
// Count returns the number of breadcrumbs
|
||||
func (bm *BreadcrumbManager) Count() int {
|
||||
bm.mu.RLock()
|
||||
defer bm.mu.RUnlock()
|
||||
|
||||
return len(bm.breadcrumbs)
|
||||
}
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
package irontelemetry
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Client is the main IronTelemetry client
|
||||
type Client struct {
|
||||
mu sync.RWMutex
|
||||
opts Options
|
||||
parsedDSN *ParsedDSN
|
||||
transport *Transport
|
||||
breadcrumbs *BreadcrumbManager
|
||||
journeys *JourneyManager
|
||||
user *User
|
||||
tags map[string]string
|
||||
extra map[string]any
|
||||
initialized bool
|
||||
}
|
||||
|
||||
// New creates a new IronTelemetry client
|
||||
func New(opts Options) (*Client, error) {
|
||||
resolvedOpts, parsedDSN, err := ResolveOptions(opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client := &Client{
|
||||
opts: resolvedOpts,
|
||||
parsedDSN: parsedDSN,
|
||||
transport: NewTransport(parsedDSN, resolvedOpts.APIBaseURL, resolvedOpts.Debug),
|
||||
breadcrumbs: NewBreadcrumbManager(resolvedOpts.MaxBreadcrumbs),
|
||||
tags: make(map[string]string),
|
||||
extra: make(map[string]any),
|
||||
initialized: true,
|
||||
}
|
||||
client.journeys = NewJourneyManager(client)
|
||||
|
||||
if opts.Debug {
|
||||
fmt.Printf("[IronTelemetry] Initialized with DSN: %s\n", parsedDSN.APIBaseURL)
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// CaptureException captures an error and sends it to the server
|
||||
func (c *Client) CaptureException(err error) SendResult {
|
||||
return c.CaptureExceptionWithContext(context.Background(), err)
|
||||
}
|
||||
|
||||
// CaptureExceptionWithContext captures an error with context
|
||||
func (c *Client) CaptureExceptionWithContext(ctx context.Context, err error) SendResult {
|
||||
if err == nil {
|
||||
return SendResult{Success: false, Error: "nil error"}
|
||||
}
|
||||
|
||||
event := c.createEvent(SeverityError, err.Error())
|
||||
event.Exception = &ExceptionInfo{
|
||||
Type: fmt.Sprintf("%T", err),
|
||||
Message: err.Error(),
|
||||
Stacktrace: captureStacktrace(3),
|
||||
}
|
||||
|
||||
return c.sendEvent(ctx, event)
|
||||
}
|
||||
|
||||
// CaptureMessage captures a message and sends it to the server
|
||||
func (c *Client) CaptureMessage(message string, level SeverityLevel) SendResult {
|
||||
return c.CaptureMessageWithContext(context.Background(), message, level)
|
||||
}
|
||||
|
||||
// CaptureMessageWithContext captures a message with context
|
||||
func (c *Client) CaptureMessageWithContext(ctx context.Context, message string, level SeverityLevel) SendResult {
|
||||
event := c.createEvent(level, message)
|
||||
return c.sendEvent(ctx, event)
|
||||
}
|
||||
|
||||
// AddBreadcrumb adds a breadcrumb
|
||||
func (c *Client) AddBreadcrumb(message string, category BreadcrumbCategory) {
|
||||
c.breadcrumbs.AddSimple(message, category)
|
||||
}
|
||||
|
||||
// AddBreadcrumbWithLevel adds a breadcrumb with a level
|
||||
func (c *Client) AddBreadcrumbWithLevel(message string, category BreadcrumbCategory, level SeverityLevel) {
|
||||
c.breadcrumbs.AddWithLevel(message, category, level)
|
||||
}
|
||||
|
||||
// AddBreadcrumbWithData adds a breadcrumb with data
|
||||
func (c *Client) AddBreadcrumbWithData(message string, category BreadcrumbCategory, level SeverityLevel, data map[string]any) {
|
||||
c.breadcrumbs.AddWithData(message, category, level, data)
|
||||
}
|
||||
|
||||
// SetUser sets the user context
|
||||
func (c *Client) SetUser(user *User) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.user = user
|
||||
}
|
||||
|
||||
// SetUserByID sets the user context by ID
|
||||
func (c *Client) SetUserByID(id string) {
|
||||
c.SetUser(&User{ID: id})
|
||||
}
|
||||
|
||||
// SetUserWithEmail sets the user context with ID and email
|
||||
func (c *Client) SetUserWithEmail(id, email string) {
|
||||
c.SetUser(&User{ID: id, Email: email})
|
||||
}
|
||||
|
||||
// SetTag sets a tag
|
||||
func (c *Client) SetTag(key, value string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.tags[key] = value
|
||||
}
|
||||
|
||||
// SetTags sets multiple tags
|
||||
func (c *Client) SetTags(tags map[string]string) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
for k, v := range tags {
|
||||
c.tags[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// SetExtra sets extra data
|
||||
func (c *Client) SetExtra(key string, value any) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.extra[key] = value
|
||||
}
|
||||
|
||||
// SetExtras sets multiple extra data values
|
||||
func (c *Client) SetExtras(extras map[string]any) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
for k, v := range extras {
|
||||
c.extra[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
// StartJourney starts a new journey
|
||||
func (c *Client) StartJourney(name string) *Journey {
|
||||
return c.journeys.StartJourney(name)
|
||||
}
|
||||
|
||||
// GetCurrentJourney returns the current journey context
|
||||
func (c *Client) GetCurrentJourney() *JourneyContext {
|
||||
return c.journeys.GetCurrent()
|
||||
}
|
||||
|
||||
// ClearBreadcrumbs clears all breadcrumbs
|
||||
func (c *Client) ClearBreadcrumbs() {
|
||||
c.breadcrumbs.Clear()
|
||||
}
|
||||
|
||||
// Flush flushes any pending events (placeholder for offline queue)
|
||||
func (c *Client) Flush() {
|
||||
// Future: Implement offline queue flushing
|
||||
}
|
||||
|
||||
// Close closes the client and flushes pending events
|
||||
func (c *Client) Close() {
|
||||
c.Flush()
|
||||
}
|
||||
|
||||
func (c *Client) createEvent(level SeverityLevel, message string) *TelemetryEvent {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
|
||||
// Copy tags
|
||||
tags := make(map[string]string, len(c.tags))
|
||||
for k, v := range c.tags {
|
||||
tags[k] = v
|
||||
}
|
||||
|
||||
// Copy extra
|
||||
extra := make(map[string]any, len(c.extra))
|
||||
for k, v := range c.extra {
|
||||
extra[k] = v
|
||||
}
|
||||
|
||||
event := &TelemetryEvent{
|
||||
EventID: GenerateEventID(),
|
||||
Timestamp: time.Now(),
|
||||
Level: level,
|
||||
Message: message,
|
||||
Tags: tags,
|
||||
Extra: extra,
|
||||
Breadcrumbs: c.breadcrumbs.GetAll(),
|
||||
Environment: c.opts.Environment,
|
||||
AppVersion: c.opts.AppVersion,
|
||||
Platform: PlatformInfo{
|
||||
Name: "go",
|
||||
Version: runtime.Version(),
|
||||
OS: runtime.GOOS + "/" + runtime.GOARCH,
|
||||
},
|
||||
}
|
||||
|
||||
// Copy user if set
|
||||
if c.user != nil {
|
||||
event.User = &User{
|
||||
ID: c.user.ID,
|
||||
Email: c.user.Email,
|
||||
Name: c.user.Name,
|
||||
}
|
||||
if c.user.Data != nil {
|
||||
event.User.Data = make(map[string]any, len(c.user.Data))
|
||||
for k, v := range c.user.Data {
|
||||
event.User.Data[k] = v
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Copy journey if active
|
||||
if journey := c.journeys.GetCurrent(); journey != nil {
|
||||
event.Journey = &JourneyContext{
|
||||
JourneyID: journey.JourneyID,
|
||||
Name: journey.Name,
|
||||
CurrentStep: journey.CurrentStep,
|
||||
StartedAt: journey.StartedAt,
|
||||
Metadata: make(map[string]any, len(journey.Metadata)),
|
||||
}
|
||||
for k, v := range journey.Metadata {
|
||||
event.Journey.Metadata[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
func (c *Client) sendEvent(ctx context.Context, event *TelemetryEvent) SendResult {
|
||||
// Check sample rate
|
||||
if c.opts.SampleRate < 1.0 && rand.Float64() > c.opts.SampleRate {
|
||||
if c.opts.Debug {
|
||||
fmt.Printf("[IronTelemetry] Event sampled out: %s\n", event.EventID)
|
||||
}
|
||||
return SendResult{Success: true, EventID: event.EventID}
|
||||
}
|
||||
|
||||
// Apply beforeSend hook
|
||||
if c.opts.BeforeSend != nil {
|
||||
event = c.opts.BeforeSend(event)
|
||||
if event == nil {
|
||||
if c.opts.Debug {
|
||||
fmt.Println("[IronTelemetry] Event dropped by beforeSend hook")
|
||||
}
|
||||
return SendResult{Success: true}
|
||||
}
|
||||
}
|
||||
|
||||
return c.transport.Send(ctx, event)
|
||||
}
|
||||
|
||||
func captureStacktrace(skip int) []StackFrame {
|
||||
var frames []StackFrame
|
||||
pcs := make([]uintptr, 32)
|
||||
n := runtime.Callers(skip, pcs)
|
||||
if n == 0 {
|
||||
return frames
|
||||
}
|
||||
|
||||
pcs = pcs[:n]
|
||||
runtimeFrames := runtime.CallersFrames(pcs)
|
||||
|
||||
for {
|
||||
frame, more := runtimeFrames.Next()
|
||||
frames = append(frames, StackFrame{
|
||||
Function: frame.Function,
|
||||
Filename: frame.File,
|
||||
Lineno: frame.Line,
|
||||
})
|
||||
if !more {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return frames
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
package irontelemetry
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// ParseDSN parses a DSN string into its components
|
||||
// Format: https://pk_live_xxx@irontelemetry.com
|
||||
func ParseDSN(dsn string) (*ParsedDSN, error) {
|
||||
parsed, err := url.Parse(dsn)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid DSN format: " + dsn)
|
||||
}
|
||||
|
||||
publicKey := parsed.User.Username()
|
||||
if publicKey == "" || !strings.HasPrefix(publicKey, "pk_") {
|
||||
return nil, errors.New("DSN must contain a valid public key starting with pk_")
|
||||
}
|
||||
|
||||
return &ParsedDSN{
|
||||
PublicKey: publicKey,
|
||||
Host: parsed.Host,
|
||||
Protocol: parsed.Scheme,
|
||||
APIBaseURL: parsed.Scheme + "://" + parsed.Host,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GenerateEventID generates a unique event ID
|
||||
func GenerateEventID() string {
|
||||
return uuid.New().String()
|
||||
}
|
||||
|
||||
// ResolveOptions validates and fills in defaults for options
|
||||
func ResolveOptions(opts Options) (Options, *ParsedDSN, error) {
|
||||
parsedDSN, err := ParseDSN(opts.DSN)
|
||||
if err != nil {
|
||||
return opts, nil, err
|
||||
}
|
||||
|
||||
defaults := DefaultOptions()
|
||||
|
||||
if opts.Environment == "" {
|
||||
opts.Environment = defaults.Environment
|
||||
}
|
||||
if opts.AppVersion == "" {
|
||||
opts.AppVersion = defaults.AppVersion
|
||||
}
|
||||
if opts.SampleRate == 0 {
|
||||
opts.SampleRate = defaults.SampleRate
|
||||
}
|
||||
if opts.SampleRate < 0 {
|
||||
opts.SampleRate = 0
|
||||
}
|
||||
if opts.SampleRate > 1 {
|
||||
opts.SampleRate = 1
|
||||
}
|
||||
if opts.MaxBreadcrumbs == 0 {
|
||||
opts.MaxBreadcrumbs = defaults.MaxBreadcrumbs
|
||||
}
|
||||
if opts.MaxOfflineQueueSize == 0 {
|
||||
opts.MaxOfflineQueueSize = defaults.MaxOfflineQueueSize
|
||||
}
|
||||
if opts.APIBaseURL == "" {
|
||||
opts.APIBaseURL = parsedDSN.APIBaseURL
|
||||
}
|
||||
|
||||
return opts, parsedDSN, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
module github.com/IronServices/irontelemetry-go
|
||||
|
||||
go 1.21
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.5.0
|
||||
)
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
package irontelemetry
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// JourneyManager manages journey context
|
||||
type JourneyManager struct {
|
||||
mu sync.RWMutex
|
||||
current *JourneyContext
|
||||
client *Client
|
||||
}
|
||||
|
||||
// NewJourneyManager creates a new JourneyManager
|
||||
func NewJourneyManager(client *Client) *JourneyManager {
|
||||
return &JourneyManager{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// StartJourney starts a new journey
|
||||
func (jm *JourneyManager) StartJourney(name string) *Journey {
|
||||
jm.mu.Lock()
|
||||
defer jm.mu.Unlock()
|
||||
|
||||
ctx := &JourneyContext{
|
||||
JourneyID: uuid.New().String(),
|
||||
Name: name,
|
||||
StartedAt: time.Now(),
|
||||
Metadata: make(map[string]any),
|
||||
}
|
||||
jm.current = ctx
|
||||
|
||||
// Add breadcrumb for journey start
|
||||
if jm.client != nil {
|
||||
jm.client.AddBreadcrumb("Started journey: "+name, CategoryBusiness)
|
||||
}
|
||||
|
||||
return &Journey{
|
||||
manager: jm,
|
||||
context: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
// GetCurrent returns the current journey context
|
||||
func (jm *JourneyManager) GetCurrent() *JourneyContext {
|
||||
jm.mu.RLock()
|
||||
defer jm.mu.RUnlock()
|
||||
|
||||
return jm.current
|
||||
}
|
||||
|
||||
// Clear clears the current journey
|
||||
func (jm *JourneyManager) Clear() {
|
||||
jm.mu.Lock()
|
||||
defer jm.mu.Unlock()
|
||||
|
||||
jm.current = nil
|
||||
}
|
||||
|
||||
// Journey represents an active journey
|
||||
type Journey struct {
|
||||
manager *JourneyManager
|
||||
context *JourneyContext
|
||||
}
|
||||
|
||||
// SetUser sets the user for the journey
|
||||
func (j *Journey) SetUser(id, email, name string) *Journey {
|
||||
j.manager.mu.Lock()
|
||||
defer j.manager.mu.Unlock()
|
||||
|
||||
j.context.Metadata["userId"] = id
|
||||
if email != "" {
|
||||
j.context.Metadata["userEmail"] = email
|
||||
}
|
||||
if name != "" {
|
||||
j.context.Metadata["userName"] = name
|
||||
}
|
||||
|
||||
return j
|
||||
}
|
||||
|
||||
// SetMetadata sets metadata for the journey
|
||||
func (j *Journey) SetMetadata(key string, value any) *Journey {
|
||||
j.manager.mu.Lock()
|
||||
defer j.manager.mu.Unlock()
|
||||
|
||||
j.context.Metadata[key] = value
|
||||
return j
|
||||
}
|
||||
|
||||
// StartStep starts a new step in the journey
|
||||
func (j *Journey) StartStep(name string, category BreadcrumbCategory) *Step {
|
||||
j.manager.mu.Lock()
|
||||
j.context.CurrentStep = name
|
||||
j.manager.mu.Unlock()
|
||||
|
||||
// Add breadcrumb for step start
|
||||
if j.manager.client != nil {
|
||||
j.manager.client.AddBreadcrumbWithData(
|
||||
"Started step: "+name,
|
||||
category,
|
||||
SeverityInfo,
|
||||
map[string]any{
|
||||
"journeyId": j.context.JourneyID,
|
||||
"journeyName": j.context.Name,
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
return &Step{
|
||||
journey: j,
|
||||
name: name,
|
||||
category: category,
|
||||
startedAt: time.Now(),
|
||||
data: make(map[string]any),
|
||||
}
|
||||
}
|
||||
|
||||
// Complete completes the journey successfully
|
||||
func (j *Journey) Complete() {
|
||||
if j.manager.client != nil {
|
||||
j.manager.client.AddBreadcrumbWithData(
|
||||
"Completed journey: "+j.context.Name,
|
||||
CategoryBusiness,
|
||||
SeverityInfo,
|
||||
map[string]any{
|
||||
"journeyId": j.context.JourneyID,
|
||||
"duration": time.Since(j.context.StartedAt).Milliseconds(),
|
||||
},
|
||||
)
|
||||
}
|
||||
j.manager.Clear()
|
||||
}
|
||||
|
||||
// Fail marks the journey as failed
|
||||
func (j *Journey) Fail(err error) {
|
||||
if j.manager.client != nil {
|
||||
data := map[string]any{
|
||||
"journeyId": j.context.JourneyID,
|
||||
"duration": time.Since(j.context.StartedAt).Milliseconds(),
|
||||
}
|
||||
if err != nil {
|
||||
data["error"] = err.Error()
|
||||
}
|
||||
j.manager.client.AddBreadcrumbWithData(
|
||||
"Failed journey: "+j.context.Name,
|
||||
CategoryBusiness,
|
||||
SeverityError,
|
||||
data,
|
||||
)
|
||||
}
|
||||
j.manager.Clear()
|
||||
}
|
||||
|
||||
// Step represents a step within a journey
|
||||
type Step struct {
|
||||
journey *Journey
|
||||
name string
|
||||
category BreadcrumbCategory
|
||||
startedAt time.Time
|
||||
data map[string]any
|
||||
}
|
||||
|
||||
// SetData sets data for the step
|
||||
func (s *Step) SetData(key string, value any) *Step {
|
||||
s.data[key] = value
|
||||
return s
|
||||
}
|
||||
|
||||
// Complete completes the step successfully
|
||||
func (s *Step) Complete() {
|
||||
if s.journey.manager.client != nil {
|
||||
data := map[string]any{
|
||||
"journeyId": s.journey.context.JourneyID,
|
||||
"journeyName": s.journey.context.Name,
|
||||
"duration": time.Since(s.startedAt).Milliseconds(),
|
||||
}
|
||||
for k, v := range s.data {
|
||||
data[k] = v
|
||||
}
|
||||
s.journey.manager.client.AddBreadcrumbWithData(
|
||||
"Completed step: "+s.name,
|
||||
s.category,
|
||||
SeverityInfo,
|
||||
data,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Fail marks the step as failed
|
||||
func (s *Step) Fail(err error) {
|
||||
if s.journey.manager.client != nil {
|
||||
data := map[string]any{
|
||||
"journeyId": s.journey.context.JourneyID,
|
||||
"journeyName": s.journey.context.Name,
|
||||
"duration": time.Since(s.startedAt).Milliseconds(),
|
||||
}
|
||||
for k, v := range s.data {
|
||||
data[k] = v
|
||||
}
|
||||
if err != nil {
|
||||
data["error"] = err.Error()
|
||||
}
|
||||
s.journey.manager.client.AddBreadcrumbWithData(
|
||||
"Failed step: "+s.name,
|
||||
s.category,
|
||||
SeverityError,
|
||||
data,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// RunStep is a helper that runs a function as a step
|
||||
func (j *Journey) RunStep(name string, category BreadcrumbCategory, fn func() error) error {
|
||||
step := j.StartStep(name, category)
|
||||
err := fn()
|
||||
if err != nil {
|
||||
step.Fail(err)
|
||||
return err
|
||||
}
|
||||
step.Complete()
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
package irontelemetry
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Transport handles HTTP communication with the server
|
||||
type Transport struct {
|
||||
apiBaseURL string
|
||||
publicKey string
|
||||
debug bool
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewTransport creates a new Transport
|
||||
func NewTransport(parsedDSN *ParsedDSN, apiBaseURL string, debug bool) *Transport {
|
||||
return &Transport{
|
||||
apiBaseURL: apiBaseURL,
|
||||
publicKey: parsedDSN.PublicKey,
|
||||
debug: debug,
|
||||
client: &http.Client{
|
||||
Timeout: 30 * time.Second,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Send sends an event to the server
|
||||
func (t *Transport) Send(ctx context.Context, event *TelemetryEvent) SendResult {
|
||||
url := t.apiBaseURL + "/api/v1/events"
|
||||
|
||||
body, err := json.Marshal(t.serializeEvent(event))
|
||||
if err != nil {
|
||||
return SendResult{Success: false, Error: err.Error()}
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return SendResult{Success: false, Error: err.Error()}
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Public-Key", t.publicKey)
|
||||
|
||||
resp, err := t.client.Do(req)
|
||||
if err != nil {
|
||||
if t.debug {
|
||||
fmt.Printf("[IronTelemetry] Failed to send event: %v\n", err)
|
||||
}
|
||||
return SendResult{Success: false, Error: err.Error()}
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
bodyBytes, _ := io.ReadAll(resp.Body)
|
||||
errMsg := fmt.Sprintf("HTTP %d: %s", resp.StatusCode, string(bodyBytes))
|
||||
if t.debug {
|
||||
fmt.Printf("[IronTelemetry] Failed to send event: %s\n", errMsg)
|
||||
}
|
||||
return SendResult{Success: false, Error: errMsg}
|
||||
}
|
||||
|
||||
var result struct {
|
||||
EventID string `json:"eventId"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
|
||||
// Even if we can't decode, the request succeeded
|
||||
result.EventID = event.EventID
|
||||
}
|
||||
|
||||
if t.debug {
|
||||
fmt.Printf("[IronTelemetry] Event sent successfully: %s\n", event.EventID)
|
||||
}
|
||||
|
||||
return SendResult{
|
||||
Success: true,
|
||||
EventID: result.EventID,
|
||||
}
|
||||
}
|
||||
|
||||
// IsOnline checks if the server is reachable
|
||||
func (t *Transport) IsOnline(ctx context.Context) bool {
|
||||
url := t.apiBaseURL + "/api/v1/health"
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
req.Header.Set("X-Public-Key", t.publicKey)
|
||||
|
||||
resp, err := t.client.Do(req)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
return resp.StatusCode == 200
|
||||
}
|
||||
|
||||
func (t *Transport) serializeEvent(event *TelemetryEvent) map[string]any {
|
||||
breadcrumbs := make([]map[string]any, len(event.Breadcrumbs))
|
||||
for i, b := range event.Breadcrumbs {
|
||||
breadcrumbs[i] = map[string]any{
|
||||
"timestamp": b.Timestamp.Format(time.RFC3339),
|
||||
"category": b.Category,
|
||||
"message": b.Message,
|
||||
"level": b.Level,
|
||||
"data": b.Data,
|
||||
}
|
||||
}
|
||||
|
||||
result := map[string]any{
|
||||
"eventId": event.EventID,
|
||||
"timestamp": event.Timestamp.Format(time.RFC3339),
|
||||
"level": event.Level,
|
||||
"message": event.Message,
|
||||
"tags": event.Tags,
|
||||
"extra": event.Extra,
|
||||
"breadcrumbs": breadcrumbs,
|
||||
"environment": event.Environment,
|
||||
"appVersion": event.AppVersion,
|
||||
"platform": map[string]any{
|
||||
"name": event.Platform.Name,
|
||||
"version": event.Platform.Version,
|
||||
"os": event.Platform.OS,
|
||||
},
|
||||
}
|
||||
|
||||
if event.Exception != nil {
|
||||
stacktrace := make([]map[string]any, len(event.Exception.Stacktrace))
|
||||
for i, f := range event.Exception.Stacktrace {
|
||||
stacktrace[i] = map[string]any{
|
||||
"function": f.Function,
|
||||
"filename": f.Filename,
|
||||
"lineno": f.Lineno,
|
||||
}
|
||||
}
|
||||
result["exception"] = map[string]any{
|
||||
"type": event.Exception.Type,
|
||||
"message": event.Exception.Message,
|
||||
"stacktrace": stacktrace,
|
||||
}
|
||||
}
|
||||
|
||||
if event.User != nil {
|
||||
result["user"] = map[string]any{
|
||||
"id": event.User.ID,
|
||||
"email": event.User.Email,
|
||||
"name": event.User.Name,
|
||||
"data": event.User.Data,
|
||||
}
|
||||
}
|
||||
|
||||
if event.Journey != nil {
|
||||
result["journey"] = map[string]any{
|
||||
"journeyId": event.Journey.JourneyID,
|
||||
"name": event.Journey.Name,
|
||||
"currentStep": event.Journey.CurrentStep,
|
||||
"startedAt": event.Journey.StartedAt.Format(time.RFC3339),
|
||||
"metadata": event.Journey.Metadata,
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
package irontelemetry
|
||||
|
||||
import "time"
|
||||
|
||||
// SeverityLevel represents the severity of an event
|
||||
type SeverityLevel string
|
||||
|
||||
const (
|
||||
SeverityDebug SeverityLevel = "debug"
|
||||
SeverityInfo SeverityLevel = "info"
|
||||
SeverityWarning SeverityLevel = "warning"
|
||||
SeverityError SeverityLevel = "error"
|
||||
SeverityFatal SeverityLevel = "fatal"
|
||||
)
|
||||
|
||||
// BreadcrumbCategory represents the category of a breadcrumb
|
||||
type BreadcrumbCategory string
|
||||
|
||||
const (
|
||||
CategoryUI BreadcrumbCategory = "ui"
|
||||
CategoryHTTP BreadcrumbCategory = "http"
|
||||
CategoryNavigation BreadcrumbCategory = "navigation"
|
||||
CategoryConsole BreadcrumbCategory = "console"
|
||||
CategoryAuth BreadcrumbCategory = "auth"
|
||||
CategoryBusiness BreadcrumbCategory = "business"
|
||||
CategoryNotification BreadcrumbCategory = "notification"
|
||||
CategoryCustom BreadcrumbCategory = "custom"
|
||||
)
|
||||
|
||||
// Breadcrumb represents an event leading up to an error
|
||||
type Breadcrumb struct {
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Category BreadcrumbCategory `json:"category"`
|
||||
Message string `json:"message"`
|
||||
Level SeverityLevel `json:"level,omitempty"`
|
||||
Data map[string]any `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// User represents user information for context
|
||||
type User struct {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Data map[string]any `json:"data,omitempty"`
|
||||
}
|
||||
|
||||
// StackFrame represents a single frame in a stack trace
|
||||
type StackFrame struct {
|
||||
Function string `json:"function,omitempty"`
|
||||
Filename string `json:"filename,omitempty"`
|
||||
Lineno int `json:"lineno,omitempty"`
|
||||
Colno int `json:"colno,omitempty"`
|
||||
Context []string `json:"context,omitempty"`
|
||||
}
|
||||
|
||||
// ExceptionInfo represents exception/error information
|
||||
type ExceptionInfo struct {
|
||||
Type string `json:"type"`
|
||||
Message string `json:"message"`
|
||||
Stacktrace []StackFrame `json:"stacktrace,omitempty"`
|
||||
}
|
||||
|
||||
// PlatformInfo represents platform/runtime information
|
||||
type PlatformInfo struct {
|
||||
Name string `json:"name"`
|
||||
Version string `json:"version,omitempty"`
|
||||
OS string `json:"os,omitempty"`
|
||||
}
|
||||
|
||||
// JourneyContext represents journey context for tracking user flows
|
||||
type JourneyContext struct {
|
||||
JourneyID string `json:"journeyId"`
|
||||
Name string `json:"name"`
|
||||
CurrentStep string `json:"currentStep,omitempty"`
|
||||
StartedAt time.Time `json:"startedAt"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
}
|
||||
|
||||
// TelemetryEvent represents an event payload sent to the server
|
||||
type TelemetryEvent struct {
|
||||
EventID string `json:"eventId"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
Level SeverityLevel `json:"level"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Exception *ExceptionInfo `json:"exception,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
Tags map[string]string `json:"tags"`
|
||||
Extra map[string]any `json:"extra"`
|
||||
Breadcrumbs []Breadcrumb `json:"breadcrumbs"`
|
||||
Journey *JourneyContext `json:"journey,omitempty"`
|
||||
Environment string `json:"environment,omitempty"`
|
||||
AppVersion string `json:"appVersion,omitempty"`
|
||||
Platform PlatformInfo `json:"platform"`
|
||||
}
|
||||
|
||||
// ParsedDSN represents parsed DSN components
|
||||
type ParsedDSN struct {
|
||||
PublicKey string
|
||||
Host string
|
||||
Protocol string
|
||||
APIBaseURL string
|
||||
}
|
||||
|
||||
// SendResult represents the result of sending an event
|
||||
type SendResult struct {
|
||||
Success bool
|
||||
EventID string
|
||||
Error string
|
||||
Queued bool
|
||||
}
|
||||
|
||||
// Options represents options for initializing the SDK
|
||||
type Options struct {
|
||||
// DSN containing the public key
|
||||
// Format: https://pk_live_xxx@irontelemetry.com
|
||||
DSN string
|
||||
|
||||
// Environment name (e.g., 'production', 'staging')
|
||||
Environment string
|
||||
|
||||
// Application version
|
||||
AppVersion string
|
||||
|
||||
// Sample rate for events (0.0 to 1.0)
|
||||
SampleRate float64
|
||||
|
||||
// Maximum number of breadcrumbs to keep
|
||||
MaxBreadcrumbs int
|
||||
|
||||
// Enable debug logging
|
||||
Debug bool
|
||||
|
||||
// Hook called before sending an event
|
||||
// Return nil to drop the event
|
||||
BeforeSend func(*TelemetryEvent) *TelemetryEvent
|
||||
|
||||
// Enable offline queue for failed events
|
||||
EnableOfflineQueue bool
|
||||
|
||||
// Maximum size of the offline queue
|
||||
MaxOfflineQueueSize int
|
||||
|
||||
// API base URL (defaults to parsed from DSN)
|
||||
APIBaseURL string
|
||||
}
|
||||
|
||||
// DefaultOptions returns Options with default values
|
||||
func DefaultOptions() Options {
|
||||
return Options{
|
||||
Environment: "production",
|
||||
AppVersion: "0.0.0",
|
||||
SampleRate: 1.0,
|
||||
MaxBreadcrumbs: 100,
|
||||
Debug: false,
|
||||
EnableOfflineQueue: true,
|
||||
MaxOfflineQueueSize: 500,
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue