logsentry

package module
v0.0.0-...-78ee7a8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 12, 2025 License: MIT Imports: 9 Imported by: 0

README

logsentry

A Sentry integration package for golog, providing seamless structured logging to Sentry error monitoring platform.

Overview

The logsentry package implements the golog.Writer and golog.WriterConfig interfaces to bridge golog's structured logging capabilities with Sentry's error tracking and monitoring system. It automatically maps golog log levels to Sentry event levels and captures structured log data as Sentry events.

Features

  • Automatic Level Mapping: Maps golog log levels to appropriate Sentry event levels
  • Structured Data Capture: Converts golog key-value pairs to Sentry event extra data
  • Stack Trace Filtering: Automatically filters out golog internal frames from stack traces
  • Memory Pooling: Uses object pooling for efficient memory management
  • Context-Aware Logging: Supports disabling Sentry logging via context
  • Configurable Formatting: Supports both message-only and key-value message formats

Installation

go get github.com/domonda/golog/logsentry

Dependencies

  • github.com/domonda/golog - Core logging library
  • github.com/getsentry/sentry-go - Sentry Go SDK

Quick Start

Basic Setup
package main

import (
    "context"
    "os"
    "time"

    "github.com/getsentry/sentry-go"
    "github.com/domonda/golog"
    "github.com/domonda/golog/logsentry"
)

func main() {
    // Initialize Sentry
    err := sentry.Init(sentry.ClientOptions{
        Dsn: os.Getenv("SENTRY_DSN"),
        // Enable stack traces for better debugging
        AttachStacktrace: true,
        // Set sample rate to control event volume
        SampleRate: 1.0,
    })
    if err != nil {
        panic("sentry.Init: " + err.Error())
    }
    defer sentry.Flush(2 * time.Second)

    // Create logsentry writer config
    sentryWriterConfig := logsentry.NewWriterConfig(
        sentry.CurrentHub(),           // Use current Sentry hub
        golog.NewDefaultFormat(),      // Use default golog format
        golog.AllLevelsActive,          // Log all levels
        false,                         // Don't include values in message text
        map[string]any{                // Extra data for all events
            "service": "my-app",
            "version": "1.0.0",
        },
    )

    // Create golog config with Sentry writer
    config := golog.NewConfig(
        &golog.DefaultLevels,
        golog.AllLevelsActive,
        sentryWriterConfig,
    )

    // Create logger
    logger := golog.NewLogger(config)

    // Log some events
    logger.Info("Application started").
        Str("port", "8080").
        Str("environment", "production").
        Log()

    logger.Error("Database connection failed").
        Err(errors.New("connection timeout")).
        Str("host", "db.example.com").
        Int("port", 5432).
        Log()
}
Multiple Writers (Console + Sentry)
// Create both console and Sentry writers
consoleWriter := golog.NewTextWriterConfig(os.Stdout, nil, nil)
sentryWriter := logsentry.NewWriterConfig(
    sentry.CurrentHub(),
    golog.NewDefaultFormat(),
    golog.ErrorLevel().FilterOutBelow(), // Only send ERROR and FATAL to Sentry
    false,
    map[string]any{"service": "my-app"},
)

// Combine writers in golog config
config := golog.NewConfig(
    &golog.DefaultLevels,
    golog.AllLevelsActive,
    consoleWriter,
    sentryWriter,
)

logger := golog.NewLogger(config)

Log Level Mapping

The package automatically maps golog log levels to Sentry event levels:

golog Level Sentry Level Description
TRACE (-20) DEBUG Most verbose tracing information
DEBUG (-10) DEBUG Debug information for development
INFO (0) INFO General information messages
WARN (10) WARNING Warning messages for potentially harmful situations
ERROR (20) ERROR Error conditions that don't require immediate attention
FATAL (30) FATAL Critical errors that may cause application termination
Unknown/Other ERROR Fallback for unmapped levels (uses UnknownLevel variable)

Structured Data Handling

Key-Value Pairs

All golog key-value pairs are automatically captured as Sentry event extra data:

logger.Error("User authentication failed").
    Str("username", "john_doe").
    Str("ip_address", "192.168.1.100").
    Int("attempt_count", 3).
    Bool("account_locked", false).
    Float("response_time", 0.145).
    Log()

This creates a Sentry event with:

  • Message: "User authentication failed"
  • Level: ERROR
  • Extra Data:
    • username: "john_doe"
    • ip_address: "192.168.1.100"
    • attempt_count: 3
    • account_locked: false
    • response_time: 0.145
Slice Data

Slice data is captured as arrays in Sentry extra data:

logger.Info("Processing batch").
    Strs("tags", []string{"batch", "processing", "urgent"}).
    Ints("user_ids", []int{123, 456, 789}).
    Log()
JSON Data

JSON data is preserved as raw JSON in Sentry:

metadata := map[string]any{
    "request_id": "req-123",
    "user_agent": "Mozilla/5.0...",
}
jsonData, _ := json.Marshal(metadata)

logger.Info("Request processed").
    JSON("metadata", jsonData).
    Log()

Context Control

Disabling Sentry Logging

You can disable Sentry logging for specific contexts:

// Disable Sentry logging for this context
ctx := logsentry.ContextWithoutLogging(context.Background())

// This will not be sent to Sentry
logger.WithContext(ctx).Error("This won't go to Sentry").Log()

// Check if context has Sentry disabled
if logsentry.IsContextWithoutLogging(ctx) {
    // Handle accordingly
}
Context-Aware Filtering

The writer respects golog's level filtering system:

// Only send ERROR and FATAL to Sentry
sentryWriter := logsentry.NewWriterConfig(
    sentry.CurrentHub(),
    golog.NewDefaultFormat(),
    golog.ErrorLevel().FilterOutBelow(), // Only ERROR and FATAL
    false,
    nil,
)

Configuration Options

WriterConfig Parameters
func NewWriterConfig(
    hub *sentry.Hub,           // Sentry hub instance
    format *golog.Format,      // golog message format
    filter golog.LevelFilter,   // Level filtering
    valsAsMsg bool,            // Include values in message text
    extra map[string]any,      // Extra data for all events
) *WriterConfig
Parameters Explained
  • hub: The Sentry hub instance to send events to. Use sentry.CurrentHub() for the default hub.
  • format: golog format configuration for message formatting. Use golog.NewDefaultFormat() for standard formatting.
  • filter: Level filter to control which log levels are sent to Sentry. Use golog.AllLevelsActive to send all levels.
  • valsAsMsg: If true, includes key-value pairs in the message text. If false, only sends them as extra data.
  • extra: Additional data to include with every Sentry event (e.g., service name, version).
Global Configuration
// Customize unknown level mapping
logsentry.UnknownLevel = sentry.LevelWarning

// Customize flush timeout
logsentry.FlushTimeout = 5 * time.Second

Runtime Behavior

Memory Management
  • Object Pooling: Writers are pooled and reused to minimize allocations
  • Value Map Pooling: Key-value maps are pooled for efficient memory usage
  • Automatic Cleanup: Writers are automatically reset and returned to pools after each message
Performance Characteristics
  • Non-blocking: Logging operations don't block the calling goroutine
  • Asynchronous: Sentry SDK handles event transmission asynchronously
  • Batched: Sentry SDK batches events for efficient network usage
Error Handling
  • Silent Failures: If Sentry is unavailable, logging continues without errors
  • Graceful Degradation: Application continues running even if Sentry integration fails
  • No Panics: The package is designed to never panic during normal operation

Limitations

Sentry-Specific Limitations
  1. Rate Limiting: Sentry imposes rate limits on event ingestion. High-frequency logging may result in dropped events.

  2. Event Size Limits: Sentry has limits on event size. Very large log messages or excessive extra data may be truncated.

  3. Network Dependency: Requires network connectivity to Sentry servers. Events may be lost if network is unavailable.

  4. Sample Rate: Sentry's sample rate setting affects which events are actually sent, not just which are logged.

golog Integration Limitations
  1. Level Mapping: Only standard golog levels are mapped. Custom levels default to ERROR.

  2. Format Dependencies: Message formatting depends on the provided golog.Format. Changes to format affect Sentry message content.

  3. Context Propagation: Context-based logging control only works when explicitly using WithContext().

  4. Stack Trace Filtering: Only filters frames from github.com/domonda/golog module. Other logging-related frames may still appear.

General Limitations
  1. No Retry Logic: Failed Sentry events are not retried by this package.

  2. No Local Buffering: Events are sent immediately to Sentry (subject to Sentry SDK's internal buffering).

  3. No Compression: Large log messages are not compressed before sending.

  4. Single Hub: Each writer config is tied to a single Sentry hub instance.

Troubleshooting

Common Issues
  1. Events Not Appearing in Sentry

    • Check Sentry DSN configuration
    • Verify network connectivity
    • Check Sentry project settings
    • Ensure sample rate is not too low
  2. Missing Stack Traces

    • Enable AttachStacktrace: true in Sentry options
    • Check if frames are being filtered out
Debugging
// Enable Sentry debug mode
err := sentry.Init(sentry.ClientOptions{
    Dsn:   os.Getenv("SENTRY_DSN"),
    Debug: true, // Enable debug logging
})

// Check if context has Sentry disabled
if logsentry.IsContextWithoutLogging(ctx) {
    log.Println("Sentry logging is disabled for this context")
}

Examples

Web Application Integration
package main

import (
    "net/http"
    "os"
    "time"

    "github.com/getsentry/sentry-go"
    "github.com/domonda/golog"
    "github.com/domonda/golog/logsentry"
)

func main() {
    // Initialize Sentry
    sentry.Init(sentry.ClientOptions{
        Dsn: os.Getenv("SENTRY_DSN"),
        AttachStacktrace: true,
    })
    defer sentry.Flush(2 * time.Second)

    // Create logger with Sentry integration
    sentryWriter := logsentry.NewWriterConfig(
        sentry.CurrentHub(),
        golog.NewDefaultFormat(),
        golog.ErrorLevel().FilterOutBelow(), // Only errors and above
        false,
        map[string]any{
            "service": "web-api",
            "version": "1.0.0",
        },
    )

    config := golog.NewConfig(
        &golog.DefaultLevels,
        golog.AllLevelsActive,
        sentryWriter,
    )

    logger := golog.NewLogger(config)

    // HTTP handler with logging
    http.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        
        logger.Info("Request started").
            Str("method", r.Method).
            Str("path", r.URL.Path).
            Str("ip", r.RemoteAddr).
            Log()

        // Process request...
        
        logger.Info("Request completed").
            Str("method", r.Method).
            Str("path", r.URL.Path).
            Duration("duration", time.Since(start)).
            Int("status", 200).
            Log()
    })

    logger.Info("Server starting").Str("port", "8080").Log()
    http.ListenAndServe(":8080", nil)
}
Microservice Integration
package main

import (
    "context"
    "os"

    "github.com/getsentry/sentry-go"
    "github.com/domonda/golog"
    "github.com/domonda/golog/logsentry"
)

type Service struct {
    logger *golog.Logger
}

func NewService() *Service {
    // Initialize Sentry with service-specific configuration
    sentry.Init(sentry.ClientOptions{
        Dsn: os.Getenv("SENTRY_DSN"),
        AttachStacktrace: true,
        Environment: os.Getenv("ENVIRONMENT"),
        Release: os.Getenv("VERSION"),
    })

    // Create service-specific logger
    sentryWriter := logsentry.NewWriterConfig(
        sentry.CurrentHub(),
        golog.NewDefaultFormat(),
        golog.WarnLevel().FilterOutBelow(), // Warnings and above
        false,
        map[string]any{
            "service": "user-service",
            "version": os.Getenv("VERSION"),
            "environment": os.Getenv("ENVIRONMENT"),
        },
    )

    config := golog.NewConfig(
        &golog.DefaultLevels,
        golog.AllLevelsActive,
        sentryWriter,
    )

    return &Service{
        logger: golog.NewLogger(config),
    }
}

func (s *Service) ProcessUser(ctx context.Context, userID string) error {
    s.logger.Info("Processing user").
        Str("user_id", userID).
        Log()

    // Process user...
    
    if err != nil {
        s.logger.Error("User processing failed").
            Str("user_id", userID).
            Err(err).
            Log()
        return err
    }

    s.logger.Info("User processed successfully").
        Str("user_id", userID).
        Log()

    return nil
}

License

This package is part of the golog project and follows the same MIT license. See the main golog repository for license details.

Documentation

Overview

Package logsentry provides Sentry integration for golog structured logging. It implements the golog.Writer and golog.WriterConfig interfaces to bridge golog's logging capabilities with Sentry's error tracking and monitoring system.

Index

Constants

This section is empty.

Variables

View Source
var (
	// UnknownLevel is the Sentry level used when a golog.Level cannot be mapped
	// to a known Sentry level. This typically happens with custom log levels
	// that don't match the standard golog levels (TRACE, DEBUG, INFO, WARN, ERROR, FATAL).
	// Defaults to sentry.LevelError to ensure unknown levels are treated as errors.
	UnknownLevel = sentry.LevelError

	// FlushTimeout specifies how long to wait when flushing Sentry events
	// before giving up. This is used when the application is shutting down
	// to ensure pending events are sent to Sentry before termination.
	// Defaults to 3 seconds, which should be sufficient for most network conditions.
	FlushTimeout time.Duration = 3 * time.Second
)

Functions

func ContextWithoutLogging

func ContextWithoutLogging(parent context.Context) context.Context

ContextWithoutLogging returns a new context derived from parent that disables Sentry logging for all log levels. When a logger uses this context, no log messages will be sent to Sentry regardless of their level.

This is useful for: - Testing scenarios where you don't want to pollute Sentry with test data - Background operations where Sentry logging is not desired - Sensitive operations where logging to external services should be avoided

The function is idempotent - if the parent context already has Sentry logging disabled, the same context is returned without modification.

Example:

ctx := logsentry.ContextWithoutLogging(context.Background())
logger.WithContext(ctx).Error("This won't go to Sentry").Log()

func IsContextWithoutLogging

func IsContextWithoutLogging(ctx context.Context) bool

IsContextWithoutLogging checks whether the given context has Sentry logging disabled. It returns true if the context was created by ContextWithoutLogging or if it contains the same context key that disables Sentry logging.

This function is used internally by the WriterConfig to determine whether to skip sending log messages to Sentry for a given context.

Returns false if ctx is nil or if Sentry logging is not disabled.

Example:

ctx := logsentry.ContextWithoutLogging(context.Background())
if logsentry.IsContextWithoutLogging(ctx) {
    // Sentry logging is disabled for this context
}

Types

type Writer

type Writer struct {
	// contains filtered or unexported fields
}

Writer implements golog.Writer and handles the actual writing of log messages to Sentry. It accumulates log data during the logging process and sends it as a Sentry event when CommitMessage() is called.

Writer instances are designed to be reused through object pooling to minimize memory allocations and improve performance. Each Writer accumulates:

  • Message text and timestamp
  • Sentry level (mapped from golog level)
  • Key-value pairs as Sentry event extra data
  • Optional stack trace information

The Writer automatically maps golog levels to Sentry levels:

TRACE/DEBUG -> DEBUG, INFO -> INFO, WARN -> WARNING, ERROR -> ERROR, FATAL -> FATAL

Example usage through golog:

logger.Error("Database error").Str("query", sql).Err(err).Log()
// Creates Sentry event with level=ERROR, message="Database error",
// and extra data: {"query": sql, "error": err.Error()}

func (*Writer) BeginMessage

func (w *Writer) BeginMessage(config golog.Config, timestamp time.Time, level golog.Level, prefix, text string)

func (*Writer) CommitMessage

func (w *Writer) CommitMessage()

CommitMessage implements golog.Writer and finalizes the log message by sending it to Sentry as an event. This method is called at the end of each log operation after all data has been written.

The method creates a Sentry event with:

  • The accumulated message text
  • The mapped Sentry level
  • The original timestamp
  • All key-value pairs as extra data (from both config.extra and values)
  • Optional stack trace (if enabled in Sentry options)
  • A fingerprint based on the message for grouping

After sending the event, the Writer is reset and returned to the object pool for reuse, ensuring efficient memory management.

func (*Writer) String

func (w *Writer) String() string

func (*Writer) WriteBool

func (w *Writer) WriteBool(val bool)

func (*Writer) WriteError

func (w *Writer) WriteError(val error)

func (*Writer) WriteFloat

func (w *Writer) WriteFloat(val float64)

func (*Writer) WriteInt

func (w *Writer) WriteInt(val int64)

func (*Writer) WriteJSON

func (w *Writer) WriteJSON(val []byte)

func (*Writer) WriteKey

func (w *Writer) WriteKey(key string)

func (*Writer) WriteNil

func (w *Writer) WriteNil()

func (*Writer) WriteSliceEnd

func (w *Writer) WriteSliceEnd()

func (*Writer) WriteSliceKey

func (w *Writer) WriteSliceKey(key string)

func (*Writer) WriteString

func (w *Writer) WriteString(val string)

func (*Writer) WriteUUID

func (w *Writer) WriteUUID(val [16]byte)

func (*Writer) WriteUint

func (w *Writer) WriteUint(val uint64)

type WriterConfig

type WriterConfig struct {
	// contains filtered or unexported fields
}

WriterConfig implements golog.WriterConfig and serves as a factory for creating Writer instances that send log messages to Sentry.

It maintains configuration for Sentry integration including the Sentry hub, message formatting, level filtering, and object pooling for efficient memory usage. WriterConfig instances are typically created once and reused across multiple log operations.

The config determines:

  • Which Sentry project receives events (via hub)
  • How messages are formatted (via format)
  • Which log levels are sent to Sentry (via filter)
  • Whether values appear in message text (via valsAsMsg)
  • Additional metadata included with every event (via extra)

Example usage:

config := logsentry.NewWriterConfig(
    sentry.CurrentHub(),
    golog.NewDefaultFormat(),
    golog.ErrorLevel().FilterOutBelow(),
    false,
    map[string]any{"service": "my-app"},
)

func NewWriterConfig

func NewWriterConfig(hub *sentry.Hub, format *golog.Format, filter golog.LevelFilter, valsAsMsg bool, extra map[string]any) *WriterConfig

NewWriterConfig returns a new WriterConfig for a sentry.Hub. Any values passed as extra will be added to every log messsage.

func (*WriterConfig) FlushUnderlying

func (c *WriterConfig) FlushUnderlying()

func (*WriterConfig) WriterForNewMessage

func (c *WriterConfig) WriterForNewMessage(ctx context.Context, level golog.Level) golog.Writer

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL