Skip to content

Commit ba05f7a

Browse files
giortzisgbitsandfoxes
authored andcommitted
feat(go): Structured logging support for slog & logrus (#14104)
1 parent becb9bf commit ba05f7a

File tree

4 files changed

+187
-44
lines changed

4 files changed

+187
-44
lines changed

docs/platforms/go/common/logs/index.mdx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,13 @@ slogger.Info("Implementing slog.Logger")
120120
Sentry UI.
121121
</Alert>
122122

123+
### Integrations
124+
- [Slog](/platforms/go/guides/slog)
125+
- [Logrus](/platforms/go/guides/logrus)
126+
123127
### Upcoming Integrations
124128

125-
We're actively working on adding more integration support for Logs. Currently we are looking at adding support for [`slog`](https://pkg.go.dev/log/slog), [`logrus`](https://pkg.go.dev/github.com/sirupsen/logrus), and [`zerolog`](https://pkg.go.dev/github.com/rs/zerolog). You can follow this [GitHub issue](https://github.com/getsentry/sentry-go/issues/1015) to track progress.
129+
We're actively working on adding more integration support for Logs. Currently, we are looking at adding support for [`zerolog`](https://pkg.go.dev/github.com/rs/zerolog). You can follow this [GitHub issue](https://github.com/getsentry/sentry-go/issues/1015) to track progress.
126130

127131
## Options
128132

docs/platforms/go/common/usage/index.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,7 @@ Another common operation is to capture a bare message. A message is textual info
3232
Messages show up as issues on your issue stream, with the message as the issue name.
3333

3434
<PlatformContent includePath="capture-message" />
35+
36+
## Capturing Logs
37+
38+
Another common operation is to capture logs. Check <PlatformLink to="/logs"> Logs </PlatformLink> for how to setup your logging integration to send log entries to Sentry.
Lines changed: 111 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Logrus
3-
description: "Logrus is a structured logger for Go, used to log messages in different formats and levels. This guide demonstrates how to integrate Logrus with Sentry to capture and send logs to Sentry."
3+
description: "Logrus is a structured logger for Go, used to log messages in different formats and levels. This guide demonstrates how to integrate Logrus with Sentry to capture and send both logs and events to Sentry."
44
---
55

66
For a quick reference, there is a [complete example](https://github.com/getsentry/sentry-go/tree/master/_examples/logrus) at the Go SDK source code repository.
@@ -17,6 +17,8 @@ go get github.com/getsentry/sentry-go/logrus
1717

1818
<SignInNote />
1919

20+
To integrate Sentry with Logrus, you can set up both log hooks and event hooks to capture different types of data at various log levels.
21+
2022
```go
2123
import (
2224
"fmt"
@@ -25,61 +27,147 @@ import (
2527
"time"
2628

2729
"github.com/sirupsen/logrus"
28-
2930
"github.com/getsentry/sentry-go"
3031
sentrylogrus "github.com/getsentry/sentry-go/logrus"
3132
)
3233

33-
logger := logrus.New()
34+
func main() {
35+
// Initialize Logrus
36+
logger := logrus.New()
3437

3538
// Log DEBUG and higher level logs to STDERR
3639
logger.Level = logrus.DebugLevel
3740
logger.Out = os.Stderr
3841

39-
// Send only ERROR and higher level logs to Sentry
40-
sentryLevels := []logrus.Level{logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel}
41-
42-
// Initialize Sentry
43-
sentryHook, err := sentrylogrus.New(sentryLevels, sentry.ClientOptions{
42+
// send logs on InfoLevel
43+
logHook, err := sentrylogrus.NewLogHook(
44+
[]logrus.Level{logrus.InfoLevel},
45+
sentry.ClientOptions{
46+
Dsn: "___PUBLIC_DSN___",
47+
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
48+
if hint.Context != nil {
49+
if req, ok := hint.Context.Value(sentry.RequestContextKey).(*http.Request); ok {
50+
// You have access to the original Request
51+
fmt.Println(req)
52+
}
53+
}
54+
fmt.Println(event)
55+
return event
56+
},
57+
// need to have logs enabled
58+
EnableLogs: true,
59+
AttachStacktrace: true,
60+
})
61+
62+
// send events on Error, Fatal, Panic levels
63+
eventHook, err := sentrylogrus.NewEventHook([]logrus.Level{
64+
logrus.ErrorLevel,
65+
logrus.FatalLevel,
66+
logrus.PanicLevel,
67+
}, sentry.ClientOptions{
4468
Dsn: "___PUBLIC_DSN___",
4569
BeforeSend: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
4670
if hint.Context != nil {
4771
if req, ok := hint.Context.Value(sentry.RequestContextKey).(*http.Request); ok {
48-
// Access the original Request here
72+
// You have access to the original Request
4973
fmt.Println(req)
5074
}
5175
}
5276
fmt.Println(event)
5377
return event
5478
},
55-
Debug: true,
5679
AttachStacktrace: true,
5780
})
5881
if err != nil {
5982
panic(err)
6083
}
61-
defer sentryHook.Flush(5 * time.Second)
62-
logger.AddHook(sentryHook)
84+
defer eventHook.Flush(5 * time.Second)
85+
defer logHook.Flush(5 * time.Second)
86+
logger.AddHook(eventHook)
87+
logger.AddHook(logHook)
6388

6489
// Flushes before calling os.Exit(1) when using logger.Fatal
6590
// (else all defers are not called, and Sentry does not have time to send the event)
66-
logrus.RegisterExitHandler(func() { sentryHook.Flush(5 * time.Second) })
67-
68-
// Log an InfoLevel entry to STDERR (not sent to Sentry)
69-
logger.Infof("Application has started")
91+
logrus.RegisterExitHandler(func() {
92+
eventHook.Flush(5 * time.Second)
93+
logHook.Flush(5 * time.Second)
94+
})
7095

71-
// Log an ErrorLevel entry to STDERR and Sentry
72-
logger.Errorf("oh no!")
96+
// Log a InfoLevel entry STDERR which is sent as a log to Sentry
97+
logger.Infof("Application has started")
7398

74-
// Log a FatalLevel entry to STDERR, send to Sentry, and terminate the application
75-
logger.Fatalf("can't continue...")
99+
// Log an error to STDERR which is also sent to Sentry
100+
logger.Errorf("oh no!")
76101

102+
// Log a fatal error to STDERR, which sends an event to Sentry and terminates the application
103+
logger.Fatalf("can't continue...")
104+
105+
// Example of logging with attributes
106+
logger.WithField("user", "test-user").Error("An error occurred")
107+
}
77108
```
78109

79110
## Configure
80111

81-
`sentrylogrus` allows configuration via the `New` function, which accepts the levels to log and `sentry.ClientOptions`. The levels to log are the logrus levels that should be sent to Sentry. The `sentry.ClientOptions` are the same as the ones used in the `sentry.Init` function.
112+
`sentrylogrus` provides two types of hooks to configure the integration with Sentry. Both hooks accept these options:
113+
- **Levels**: A slice of `logrus.Level` specifying which log levels to capture
114+
- **ClientOptions**: Standard `sentry.ClientOptions` for configuration
115+
116+
### LogHook
117+
118+
Use `sentrylogrus.NewLogHook()` to send structured logs to Sentry. This hook captures log entries and sends them to Sentry's structured logging system.
119+
120+
```go
121+
logHook, err := sentrylogrus.NewLogHook(
122+
[]logrus.Level{logrus.InfoLevel, logrus.WarnLevel},
123+
sentry.ClientOptions{
124+
Dsn: "___PUBLIC_DSN___",
125+
EnableLogs: true, // Required for log entries
126+
})
127+
```
128+
129+
### EventHook
130+
131+
Use `sentrylogrus.NewEventHook()` to send log entries as Sentry events. This is useful for error tracking and alerting.
132+
133+
```go
134+
eventHook, err := sentrylogrus.NewEventHook(
135+
[]logrus.Level{logrus.ErrorLevel, logrus.FatalLevel, logrus.PanicLevel},
136+
sentry.ClientOptions{
137+
Dsn: "___PUBLIC_DSN___",
138+
Debug: true,
139+
AttachStacktrace: true,
140+
})
141+
```
142+
143+
<Alert>
144+
When using both hooks, ensure you flush both of them before the application exits and register exit handlers for fatal logs to avoid losing pending events.
145+
</Alert>
146+
147+
## Correlating Logs with Traces
148+
149+
To correlate logs with transactions, you need to pass a `context.Context` that contains transaction information to your logger calls. The `sentryhttp` middleware automatically adds transaction information to the request's context.
150+
Here's an example of how to use `WithContext` in an HTTP handler to ensure logs are associated with the correct trace.
151+
152+
```go
153+
// Assume logger is initialized and Sentry hooks are added as shown above.
154+
// var logger *logrus.Logger
155+
156+
func myAsyncHandler(w http.ResponseWriter, r *http.Request) {
157+
// The sentryhttp middleware adds a Hub with transaction information to the request context.
158+
ctx := r.Context()
159+
// By using WithContext, the log entry will be associated with the transaction from the request.
160+
logger.WithContext(ctx).Info("Log inside handler")
161+
w.WriteHeader(http.StatusOK)
162+
fmt.Fprintln(w, "Handler finished, async task running in background.")
163+
}
164+
165+
// In your main function, or wherever you set up your routes:
166+
// Wrap your handler with sentryhttp to automatically start transactions for requests.
167+
sentryHandler := sentryhttp.New(sentryhttp.Options{})
168+
http.Handle("/async", sentryHandler.Handle(http.HandlerFunc(myAsyncHandler)))
169+
```
82170

83-
## Usage
171+
## Logs
84172

85-
Use `logrus` as you normally would, and it will automatically send logs at or above the specified levels to Sentry.
173+
For comprehensive logging setup with Logrus, including advanced configuration options and best practices, see the [Go Logs documentation](/platforms/go/logs/). The Logrus integration shown above provides seamless integration with Sentry's structured logging features.

docs/platforms/go/guides/slog/index.mdx

Lines changed: 67 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,20 @@ func main() {
3939
// Adds request headers and IP for users,
4040
// visit: https://docs.sentry.io/platforms/go/data-management/data-collected/ for more info
4141
SendDefaultPII: true,
42+
EnableLogs: true,
4243
})
4344
if err != nil {
4445
log.Fatal(err)
4546
}
4647
defer sentry.Flush(2 * time.Second)
4748

4849
// Configure `slog` to use Sentry as a handler
49-
logger := slog.New(sentryslog.Option{Level: slog.LevelDebug}.NewSentryHandler())
50+
ctx := context.Background()
51+
handler := sentryslog.Option{
52+
EventLevel: []slog.Level{slog.LevelError, sentryslog.LevelFatal}, // Only Error and Fatal as events
53+
LogLevel: []slog.Level{slog.LevelWarn, slog.LevelInfo}, // Only Warn and Info as logs
54+
}.NewSentryHandler(ctx)
55+
logger := slog.New(handler)
5056
logger = logger.With("release", "v1.0.0")
5157

5258
// Log messages with various attributes
@@ -59,7 +65,7 @@ func main() {
5965
).
6066
With("environment", "dev").
6167
With("error", fmt.Errorf("an error")).
62-
Error("a message")
68+
ErrorContext(ctx, "a message")
6369
}
6470
```
6571

@@ -68,9 +74,14 @@ func main() {
6874
`sentryslog` provides options to configure the integration with Sentry. It accepts a struct of `sentryslog.Options` that allows you to configure how the handler will behave. The options are:
6975

7076
```go
71-
// Level sets the minimum log level to capture and send to Sentry.
72-
// Logs at this level and above will be processed. The default level is debug.
73-
Level slog.Leveler
77+
// EventLevel specifies the exact log levels to capture and send to Sentry as Events.
78+
// Only logs at these specific levels will be processed as events.
79+
// Defaults to []slog.Level{slog.LevelError, LevelFatal}.
80+
EventLevel slog.Leveler
81+
// LogLevel specifies the exact log levels to capture and send to Sentry as Log entries
82+
// Only logs at these specific levels will be processed as log entries.
83+
// Defaults to []slog.Level{slog.LevelDebug, slog.LevelInfo, slog.LevelWarn, slog.LevelError, LevelFatal}.
84+
LogLevel slog.Leveler
7485
// Hub specifies the Sentry Hub to use for capturing events.
7586
// If not provided, the current Hub is used by default.
7687
Hub *sentry.Hub
@@ -92,33 +103,69 @@ ReplaceAttr func(groups []string, a slog.Attr) slog.Attr
92103

93104
## Usage
94105

106+
### Sending Logs vs Events
107+
108+
Sentry allows you to send logs either as log entries or as events. The minimum log level defaults to `slog.LevelDebug` and logs will be sent if the `EnableLogs` option is set. The minimum event level defaults to `slog.LevelError`.
109+
110+
### Example: Sending Logs as Events
111+
95112
```go
96-
logger := slog.New(sentryslog.Option{
97-
Level: slog.LevelDebug,
113+
logger := slog.New(sentryslog.Options
114+
EventLevel: []slog.Level{slog.LevelError, sentryslog.LevelFatal},
115+
LogLevel: []slog.Level{}, // disable log entries
98116
AttrFromContext: []func(ctx context.Context) []slog.Attr{
99117
func(ctx context.Context) []slog.Attr {
100118
return []slog.Attr{slog.String("request_id", "123")}
101119
},
102120
},
103121
}.NewSentryHandler())
104122

105-
logger = logger.With("release", "v1.0.0")
106-
107-
logger.
108-
With(
109-
slog.Group("user",
110-
slog.String("id", "user-123"),
111-
slog.Time("created_at", time.Now()),
112-
),
113-
).
114-
With("environment", "dev").
115-
With("error", fmt.Errorf("an error")).
116-
Error("a message")
123+
logger.Error("This log is sent as an event")
117124
```
118125

126+
### Example: Sending Logs as Logs
127+
128+
```go
129+
logger := slog.New(sentryslog.Options{
130+
EventLevel: []slog.Level{}, // disable events
131+
LogLevel: []slog.Level{slog.LevelError},
132+
},
133+
}.NewSentryHandler())
119134

135+
logger.Error("This log is sent as a log")
136+
```
120137

121-
<SignInNote />
138+
<Alert>
139+
In order to properly attach the correct trace with each Log entry, a
140+
`context.Context` is required. We recommend using `Context` functions to ensure your logs
141+
are connected to spans and errors in the Sentry UI.
142+
</Alert>
122143

144+
## Correlating Logs with Traces
145+
146+
To correlate logs with transactions, you need to pass a `context.Context` that contains transaction information to your logger calls. The `sentryhttp` middleware automatically adds transaction information to the request's context.
147+
Here's an example of how to use `InfoContext` in an HTTP handler to ensure logs are associated with the correct trace.
148+
149+
```go
150+
// Assume logger is initialized with the Sentry handler as shown above.
151+
// var logger *slog.Logger
152+
153+
func myAsyncHandler(w http.ResponseWriter, r *http.Request) {
154+
// The sentryhttp middleware adds a Hub with transaction information to the request context.
155+
ctx := r.Context()
156+
// By using InfoContext, the log entry will be associated with the transaction from the request.
157+
logger.InfoContext(ctx, "Log inside handler")
158+
w.WriteHeader(http.StatusOK)
159+
fmt.Fprintln(w, "Handler finished, async task running in background.")
160+
}
161+
162+
// Wrap your handler with sentryhttp to automatically start transactions for requests.
163+
sentryHandler := sentryhttp.New(sentryhttp.Options{})
164+
http.Handle("/async", sentryHandler.Handle(http.HandlerFunc(myAsyncHandler)))
165+
```
123166

124167
Note: Ensure Sentry is flushed before the application exits to avoid losing any pending events.
168+
169+
## Logs
170+
171+
For comprehensive logging setup with slog, including advanced configuration options and best practices, see the [Go Logs documentation](/platforms/go/logs/). The slog integration shown above provides seamless integration with Sentry's structured logging features.

0 commit comments

Comments
 (0)