Skip to content
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4262307
initial logger impl
giortzisg May 7, 2025
67128ea
add attributes
giortzisg May 8, 2025
3e25a81
remove tests
giortzisg May 8, 2025
3e17902
add BeforeSendLog option
giortzisg May 8, 2025
bd907d9
fix metadata attributes
giortzisg May 8, 2025
67975c0
improve test coverage
giortzisg May 8, 2025
2b8e407
add sentry origin
giortzisg May 8, 2025
7b181b7
change flush timeout
giortzisg May 9, 2025
74ce60d
fix trace & span propagation
giortzisg May 9, 2025
bcac19d
add ctx on log functions & handle trace propagation
giortzisg May 9, 2025
14827a0
lint issues
giortzisg May 9, 2025
6325542
Merge branch 'master' into feat/logger
giortzisg May 9, 2025
bfa595d
Merge branch 'master' into feat/logger
giortzisg May 9, 2025
95f8ed9
review changes
giortzisg May 12, 2025
d2c5b2a
change severity and level json
giortzisg May 12, 2025
98cf9fe
add Printf like methods & docs
giortzisg May 12, 2025
5b2aa71
setAttributes doc comment
giortzisg May 12, 2025
e0e251d
add examples and fix setAttributes
giortzisg May 12, 2025
bd47723
fix setAttributes
giortzisg May 12, 2025
2c8665c
fix warn level name
giortzisg May 12, 2025
710be2f
write to stdout if debug true
giortzisg May 12, 2025
b4360c9
Merge remote-tracking branch 'origin/master' into feat/logger
giortzisg May 12, 2025
10d9ae0
ignore coverage
giortzisg May 12, 2025
d40ff0a
change server addr & beforeSendLog
giortzisg May 12, 2025
120a48f
update doc comments
giortzisg May 13, 2025
9b7b141
add attributes tests
giortzisg May 13, 2025
74a43a0
add comment for check-in type
giortzisg May 13, 2025
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
36 changes: 36 additions & 0 deletions _examples/logs/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"context"
"github.com/getsentry/sentry-go"
"github.com/getsentry/sentry-go/attribute"
"time"
)

func main() {
err := sentry.Init(sentry.ClientOptions{
Dsn: "",
EnableLogs: true,
})
if err != nil {
panic(err)
}
defer sentry.Flush(2 * time.Second)

ctx := context.Background()
logger := sentry.NewLogger(ctx)

// you can use the logger like [fmt.Print]
logger.Info(ctx, "Expecting ", 2, " params")
// or like [fmt.Printf]
logger.Infof(ctx, "format: %v", "value")

// and you can also set attributes on the log like this
logger.SetAttributes(
attribute.Int("key.int", 42),
attribute.Bool("key.boolean", true),
attribute.Float64("key.float", 42.4),
attribute.String("key.string", "string"),
)
logger.Warnf(ctx, "I have params %v and attributes", "example param")
}
94 changes: 94 additions & 0 deletions batch_logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package sentry

import (
"context"
"sync"
"time"
)

const (
batchSize = 100
batchTimeout = 5 * time.Second
)

type BatchLogger struct {
client *Client
logCh chan Log
cancel context.CancelFunc
wg sync.WaitGroup
startOnce sync.Once
}

func NewBatchLogger(client *Client) *BatchLogger {
return &BatchLogger{
client: client,
logCh: make(chan Log, batchSize),
}
}

func (l *BatchLogger) Start() {
l.startOnce.Do(func() {
ctx, cancel := context.WithCancel(context.Background())
l.cancel = cancel
l.wg.Add(1)
go l.run(ctx)
})
}

func (l *BatchLogger) Flush() {
if l.cancel != nil {
l.cancel()
l.wg.Wait()
}
}

func (l *BatchLogger) run(ctx context.Context) {
defer l.wg.Done()
var logs []Log
timer := time.NewTimer(batchTimeout)

for {
select {
case log := <-l.logCh:
logs = append(logs, log)
if len(logs) >= batchSize {
l.processEvent(logs)
logs = nil
if !timer.Stop() {
<-timer.C
}

Check warning on line 59 in batch_logger.go

View check run for this annotation

Codecov / codecov/patch

batch_logger.go#L58-L59

Added lines #L58 - L59 were not covered by tests
timer.Reset(batchTimeout)
}
case <-timer.C:
if len(logs) > 0 {
l.processEvent(logs)
logs = nil
}
timer.Reset(batchTimeout)

Check warning on line 67 in batch_logger.go

View check run for this annotation

Codecov / codecov/patch

batch_logger.go#L62-L67

Added lines #L62 - L67 were not covered by tests
case <-ctx.Done():
// Drain remaining logs from channel
drain:
for {
select {
case log := <-l.logCh:
logs = append(logs, log)
default:
break drain
}
}

if len(logs) > 0 {
l.processEvent(logs)
}
return
}
}
}

func (l *BatchLogger) processEvent(logs []Log) {
event := NewEvent()
event.Timestamp = time.Now()
event.Type = logType
event.Logs = logs
l.client.CaptureEvent(event, nil, nil)
}
50 changes: 38 additions & 12 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,11 @@ type ClientOptions struct {
// By default, no such data is sent.
SendDefaultPII bool
// BeforeSend is called before error events are sent to Sentry.
// Use it to mutate the event or return nil to discard the event.
// Use it to mutate the event or return nil to discard it.
BeforeSend func(event *Event, hint *EventHint) *Event
// BeforeSendLong is called before log events are sent to Sentry.
// Use it to mutate the log event or return nil to discard it.
BeforeSendLog func(event *Event, hint *EventHint) *Event
// BeforeSendTransaction is called before transaction events are sent to Sentry.
// Use it to mutate the transaction or return nil to discard the transaction.
BeforeSendTransaction func(event *Event, hint *EventHint) *Event
Expand Down Expand Up @@ -223,6 +226,9 @@ type ClientOptions struct {
MaxErrorDepth int
// Default event tags. These are overridden by tags set on a scope.
Tags map[string]string
// EnableLogs is a boolean flag to control if log envelopes will be generated and
// sent to Sentry via the logging API.
EnableLogs bool
}

// Client is the underlying processor that is used by the main API and Hub
Expand All @@ -237,7 +243,8 @@ type Client struct {
sdkVersion string
// Transport is read-only. Replacing the transport of an existing client is
// not supported, create a new client instead.
Transport Transport
Transport Transport
batchLogger *BatchLogger
}

// NewClient creates and returns an instance of Client configured using
Expand Down Expand Up @@ -337,6 +344,11 @@ func NewClient(options ClientOptions) (*Client, error) {
sdkVersion: SDKVersion,
}

if options.EnableLogs {
client.batchLogger = NewBatchLogger(&client)
client.batchLogger.Start()
}

client.setupTransport()
client.setupIntegrations()

Expand Down Expand Up @@ -507,6 +519,9 @@ func (client *Client) RecoverWithContext(
// the network synchronously, configure it to use the HTTPSyncTransport in the
// call to Init.
func (client *Client) Flush(timeout time.Duration) bool {
if client.batchLogger != nil {
client.batchLogger.Flush()
}
return client.Transport.Flush(timeout)
}

Expand Down Expand Up @@ -617,17 +632,28 @@ func (client *Client) processEvent(event *Event, hint *EventHint, scope EventMod
if hint == nil {
hint = &EventHint{}
}
if event.Type == transactionType && client.options.BeforeSendTransaction != nil {
// Transaction events
if event = client.options.BeforeSendTransaction(event, hint); event == nil {
DebugLogger.Println("Transaction dropped due to BeforeSendTransaction callback.")
return nil
switch event.Type {
case transactionType:
if client.options.BeforeSendTransaction != nil {
if event = client.options.BeforeSendTransaction(event, hint); event == nil {
DebugLogger.Println("Transaction dropped due to BeforeSendTransaction callback.")
return nil
}
}
} else if event.Type != transactionType && event.Type != checkInType && client.options.BeforeSend != nil {
// All other events
if event = client.options.BeforeSend(event, hint); event == nil {
DebugLogger.Println("Event dropped due to BeforeSend callback.")
return nil
case logType:
if client.options.BeforeSendLog != nil {
if event = client.options.BeforeSendLog(event, hint); event == nil {
DebugLogger.Println("Log dropped due to BeforeSendLog callback.")
return nil
}
}
case checkInType:
default:
if client.options.BeforeSend != nil {
if event = client.options.BeforeSend(event, hint); event == nil {
DebugLogger.Println("Event dropped due to BeforeSend callback.")
return nil
}
}
}

Expand Down
87 changes: 83 additions & 4 deletions interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,20 @@ import (
"slices"
"strings"
"time"

"github.com/getsentry/sentry-go/attribute"
)

// eventType is the type of an error event.
// eventType is the type of error event.
const eventType = "event"

// transactionType is the type of a transaction event.
// transactionType is the type of transaction event.
const transactionType = "transaction"

// checkInType is the type of a check in event.
// logType is the type of log event.
const logType = "log"

// checkInType is the type of check in event.
const checkInType = "check_in"

// Level marks the severity of the event.
Expand All @@ -34,7 +39,7 @@ const (
LevelFatal Level = "fatal"
)

// SdkInfo contains all metadata about about the SDK being used.
// SdkInfo contains all metadata about the SDK being used.
type SdkInfo struct {
Name string `json:"name,omitempty"`
Version string `json:"version,omitempty"`
Expand Down Expand Up @@ -96,6 +101,61 @@ func (b *Breadcrumb) MarshalJSON() ([]byte, error) {
return json.Marshal((*breadcrumb)(b))
}

// Logger allows sending structured logs to sentry.
type Logger interface {
// Write implements the io.Writer interface. Currently, the [sentry.Hub] is
// context aware, in order to get the correct trace details. Using this
// might result in incorrect span association on logs. If you need to use
// Write it is recommended to create a NewLogger so that the associated context
// is passed correctly.
Write(p []byte) (n int, err error)
// Trace sends a [LogLevelTrace] log to Sentry.
// Arguments are handled in the manner of [fmt.Print].
Trace(ctx context.Context, v ...interface{})
// Debug sends a [LogLevelDebug] log to Sentry.
// Arguments are handled in the manner of [fmt.Print].
Debug(ctx context.Context, v ...interface{})
// Info sends a [LogLevelInfo] log to Sentry.
// Arguments are handled in the manner of [fmt.Print].
Info(ctx context.Context, v ...interface{})
// Warn sends a [LogLevelWarning] log to Sentry.
// Arguments are handled in the manner of [fmt.Print].
Warn(ctx context.Context, v ...interface{})
// Error sends a [LogLevelError] log to Sentry.
// Arguments are handled in the manner of [fmt.Print].
Error(ctx context.Context, v ...interface{})
// Fatal sends a [LogLevelFatal] log to Sentry followed by a call to [os.Exit](1).
// Arguments are handled in the manner of [fmt.Print].
Fatal(ctx context.Context, v ...interface{})
// Panic sends a [LogLevelFatal] log to Sentry followed by a call to panic().
// Arguments are handled in the manner of [fmt.Print].
Panic(ctx context.Context, v ...interface{})

// Tracef sends a [LogLevelTrace] log to Sentry.
// Arguments are handled in the manner of [fmt.Printf].
Tracef(ctx context.Context, format string, v ...interface{})
// Debugf sends a [LogLevelDebug] log to Sentry.
// Arguments are handled in the manner of [fmt.Printf].
Debugf(ctx context.Context, format string, v ...interface{})
// Infof sends a [LogLevelInfo] log to Sentry.
// Arguments are handled in the manner of [fmt.Printf].
Infof(ctx context.Context, format string, v ...interface{})
// Warnf sends a [LogLevelWarning] log to Sentry.
// Arguments are handled in the manner of [fmt.Printf].
Warnf(ctx context.Context, format string, v ...interface{})
// Errorf sends a [LogLevelError] log to Sentry.
// Arguments are handled in the manner of [fmt.Printf].
Errorf(ctx context.Context, format string, v ...interface{})
// Fatalf sends a [LogLevelFatal] log to Sentry followed by a call to [os.Exit](1).
// Arguments are handled in the manner of [fmt.Printf].
Fatalf(ctx context.Context, format string, v ...interface{})
// Panicf sends a [LogLevelFatal] log to Sentry followed by a call to panic().
// Arguments are handled in the manner of [fmt.Printf].
Panicf(ctx context.Context, format string, v ...interface{})
// SetAttributes allows attaching parameters to the log message using the attribute API.
SetAttributes(...attribute.Builder)
}

// Attachment allows associating files with your events to aid in investigation.
// An event may contain one or more attachments.
type Attachment struct {
Expand Down Expand Up @@ -325,6 +385,9 @@ type Event struct {
CheckIn *CheckIn `json:"check_in,omitempty"`
MonitorConfig *MonitorConfig `json:"monitor_config,omitempty"`

// The fields below are only relevant for logs
Logs []Log `json:"items,omitempty"`

// The fields below are not part of the final JSON payload.

sdkMetaData SDKMetaData
Expand Down Expand Up @@ -547,3 +610,19 @@ type EventHint struct {
Request *http.Request
Response *http.Response
}

// Log contains information about a logging event sent to Sentry.
type Log struct {
Timestamp time.Time `json:"timestamp,omitempty"`
TraceID TraceID `json:"trace_id,omitempty"`
Level Level `json:"level"`
Severity int `json:"severity_number,omitempty"`
Body string `json:"body,omitempty"`
Attributes map[string]Attribute `json:"attributes,omitempty"`
}

// Attribute is a log attribute.
type Attribute struct {
Value any `json:"value"`
Type string `json:"type"`
}
Loading
Loading