Skip to content

Commit 90da44a

Browse files
authored
feat: setup Sentry traces (#1107)
Setup Sentry traces for Amalthea: * Add more context when initializing Sentry * Send `Reconcile()` errors to Sentry -> We may want to filter out "expected errors" in the future. * Setup Sentry transactions in the `Reconcile()` function: * Record `reconcileID` (this ID is also printed in logs) * Record request (session namespace and name) * Record result (requeue) * HTTP sub-requests are also traced; e.g. `PUT` or `PATCH` requests
1 parent d7ed35d commit 90da44a

5 files changed

Lines changed: 85 additions & 8 deletions

File tree

cmd/amalthea/main.go

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"time"
2626

2727
"github.com/getsentry/sentry-go"
28+
sentryhttpclient "github.com/getsentry/sentry-go/httpclient"
2829

2930
// Import all Kubernetes client auth plugins (e.g. Azure, GCP, OIDC, etc.)
3031
// to ensure that exec-entrypoint and run can make use of them.
@@ -33,6 +34,7 @@ import (
3334
"k8s.io/apimachinery/pkg/runtime"
3435
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
3536
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
37+
"k8s.io/client-go/rest"
3638
ctrl "sigs.k8s.io/controller-runtime"
3739
"sigs.k8s.io/controller-runtime/pkg/cache"
3840
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -68,6 +70,7 @@ func main() {
6870
var sentryDsn string
6971
var sentryEnvironment string
7072
var sentryTracesSampleRate float64
73+
var sentryRelease string
7174
var secureMetrics bool
7275
var enableHTTP2 bool
7376
var tlsOpts []func(*tls.Config)
@@ -89,6 +92,7 @@ func main() {
8992
0,
9093
"The sample rate for Sentry performance monitoring.",
9194
)
95+
flag.StringVar(&sentryRelease, "sentry-release", "", "The release value used for Sentry.")
9296
opts := zap.Options{
9397
Development: true,
9498
}
@@ -103,18 +107,29 @@ func main() {
103107
setupLog.Info("Sentry config",
104108
"DSN", sentryDsn,
105109
"environment", sentryEnvironment,
110+
"release", sentryRelease,
106111
"tracesSampleRate", sentryTracesSampleRate,
107112
)
108113
err := sentry.Init(sentry.ClientOptions{
109114
Dsn: sentryDsn,
110115
SendDefaultPII: false,
116+
Environment: sentryEnvironment,
117+
Release: sentryRelease,
111118
EnableTracing: sentryTracesSampleRate > 0,
112119
TracesSampleRate: sentryTracesSampleRate,
113120
})
114121
if err != nil {
115122
setupLog.Error(err, "failed to initialize Sentry")
116123
os.Exit(1)
117124
}
125+
defer func() {
126+
err := recover()
127+
if err != nil {
128+
sentry.CurrentHub().Recover(err)
129+
sentry.Flush(2 * time.Second)
130+
panic(err)
131+
}
132+
}()
118133
defer sentry.Flush(2 * time.Second)
119134
}
120135

@@ -166,17 +181,24 @@ func main() {
166181
if releaseNamespace == "" {
167182
releaseNamespace = "default"
168183
}
184+
185+
config := ctrl.GetConfigOrDie()
186+
187+
httpClient, err := rest.HTTPClientFor(config)
188+
if err != nil {
189+
setupLog.Error(err, "unable to get http client for config")
190+
os.Exit(1)
191+
}
192+
httpClient.Transport = sentryhttpclient.NewSentryRoundTripper(httpClient.Transport)
193+
169194
cacheOptions := cache.Options{
195+
HTTPClient: httpClient,
170196
DefaultNamespaces: map[string]cache.Config{releaseNamespace: {}},
171197
}
172198
if clusterScoped {
173-
cacheOptions = cache.Options{
174-
DefaultNamespaces: nil,
175-
}
199+
cacheOptions.DefaultNamespaces = nil
176200
}
177201

178-
config := ctrl.GetConfigOrDie()
179-
180202
mgr, err := ctrl.NewManager(config, ctrl.Options{
181203
Scheme: scheme,
182204
Metrics: metricsServerOptions,
@@ -196,6 +218,9 @@ func main() {
196218
// after the manager stops then its usage might be unsafe.
197219
// LeaderElectionReleaseOnCancel: true,
198220
Cache: cacheOptions,
221+
Client: client.Options{
222+
HTTPClient: httpClient,
223+
},
199224
})
200225
if err != nil {
201226
setupLog.Error(err, "unable to start manager")

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/distribution/reference v0.6.0
99
github.com/elazarl/goproxy v1.7.2
1010
github.com/getkin/kin-openapi v0.132.0
11-
github.com/getsentry/sentry-go v0.36.2
11+
github.com/getsentry/sentry-go v0.45.1
1212
github.com/go-git/go-git/v5 v5.16.0
1313
github.com/go-logr/logr v1.4.2
1414
github.com/golang-jwt/jwt/v5 v5.3.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,8 @@ github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv
7474
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
7575
github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk=
7676
github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58=
77-
github.com/getsentry/sentry-go v0.36.2 h1:uhuxRPTrUy0dnSzTd0LrYXlBYygLkKY0hhlG5LXarzM=
78-
github.com/getsentry/sentry-go v0.36.2/go.mod h1:p5Im24mJBeruET8Q4bbcMfCQ+F+Iadc4L48tB1apo2c=
77+
github.com/getsentry/sentry-go v0.45.1 h1:9rfzJtGiJG+MGIaWZXidDGHcH5GU1Z5y0WVJGf9nysw=
78+
github.com/getsentry/sentry-go v0.45.1/go.mod h1:XDotiNZbgf5U8bPDUAfvcFmOnMQQceESxyKaObSssW0=
7979
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
8080
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
8181
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=

helm-chart/amalthea-sessions/templates/deployment.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ spec:
2828
- --sentry-dsn={{ .Values.controllerManager.manager.sentry.dsn | default "unset" }}
2929
- --sentry-environment={{ .Values.controllerManager.manager.sentry.environment | default "" }}
3030
- --sentry-traces-sample-rate={{ .Values.controllerManager.manager.sentry.tracesSampleRate | default 0.1 }}
31+
- --sentry-release={{ .Values.controllerManager.manager.image.tag | default "" }}
3132
{{- end }}
3233
command:
3334
- /manager

internal/controller/amaltheasession_controller.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,12 @@ package controller
1919
import (
2020
"context"
2121
"errors"
22+
"fmt"
2223
"reflect"
2324
"time"
2425

26+
"github.com/getsentry/sentry-go"
27+
2528
appsv1 "k8s.io/api/apps/v1"
2629
corev1 "k8s.io/api/core/v1"
2730
networkingv1 "k8s.io/api/networking/v1"
@@ -32,6 +35,7 @@ import (
3235

3336
ctrl "sigs.k8s.io/controller-runtime"
3437
"sigs.k8s.io/controller-runtime/pkg/client"
38+
"sigs.k8s.io/controller-runtime/pkg/controller"
3539
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
3640
"sigs.k8s.io/controller-runtime/pkg/log"
3741

@@ -71,6 +75,53 @@ const secretCleanupFinalizerName = "amalthea.dev/secrets-finalizer"
7175
// For more details, check Reconcile and its Result here:
7276
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.16.3/pkg/reconcile
7377
func (r *AmaltheaSessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
78+
// Sentry Trace
79+
hub := sentry.GetHubFromContext(ctx)
80+
if hub == nil {
81+
hub = sentry.CurrentHub().Clone()
82+
ctx = sentry.SetHubOnContext(ctx, hub)
83+
}
84+
// Capture panics
85+
defer func() {
86+
err := recover()
87+
if err != nil {
88+
hub.RecoverWithContext(ctx, err)
89+
panic(err)
90+
}
91+
}()
92+
// Start Sentry transaction
93+
options := []sentry.SpanOption{
94+
sentry.WithOpName("function.reconcile"),
95+
sentry.WithDescription(fmt.Sprintf("RECONCILE AmaltheaSession %s %s", req.Namespace, req.Name)),
96+
sentry.WithSpanOrigin(sentry.SpanOriginManual),
97+
sentry.WithTransactionSource(sentry.SourceComponent),
98+
}
99+
transaction := sentry.StartTransaction(ctx, "AmaltheaSessionReconciler.Reconcile", options...)
100+
transaction.SetData("controller.request.namespace", req.Namespace)
101+
transaction.SetData("controller.request.name", req.Name)
102+
reconcileID := controller.ReconcileIDFromContext(ctx)
103+
if reconcileID != "" {
104+
transaction.SetData("controller.reconcile_id", string(reconcileID))
105+
}
106+
107+
defer transaction.Finish()
108+
109+
res, err := r.reconcileInner(transaction.Context(), req)
110+
// Set transaction results
111+
if err == nil {
112+
transaction.Status = sentry.SpanStatusOK
113+
} else {
114+
transaction.Status = sentry.SpanStatusInternalError
115+
hub.CaptureException(err)
116+
}
117+
transaction.SetData("controller.result.requeue", res.Requeue || res.RequeueAfter > 0)
118+
if res.RequeueAfter != 0 {
119+
transaction.SetData("controller.result.requeue_after", res.RequeueAfter)
120+
}
121+
return res, err
122+
}
123+
124+
func (r *AmaltheaSessionReconciler) reconcileInner(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
74125
log := log.FromContext(ctx)
75126

76127
amaltheasession := &amaltheadevv1alpha1.AmaltheaSession{}

0 commit comments

Comments
 (0)