Skip to content

Commit

Permalink
chore: pipe slog into sentry breadcrumbs
Browse files Browse the repository at this point in the history
  • Loading branch information
aldy505 committed Jun 1, 2024
1 parent eeb96e9 commit 43318e1
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 13 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ WORKDIR /build

COPY . .

RUN go build -o brassite ./cmd/brassite/main.go
RUN go build -o brassite -ldflags="-X main.version=$(git rev-parse HEAD)" ./cmd/brassite/

FROM alpine:3.20 AS runtime

Expand Down
38 changes: 26 additions & 12 deletions cmd/brassite/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ import (

"github.com/getsentry/sentry-go"
"github.com/mmcdole/gofeed"
slogmulti "github.com/samber/slog-multi"
"github.com/teknologi-umum/brassite"
)

var version string
var environment = os.Getenv("ENVIRONMENT")

func main() {
// This is a very simple program, you can extend this to any extend you'd like.
// 1. Read configuration file
Expand Down Expand Up @@ -57,16 +61,24 @@ func main() {
slogLevel = slog.LevelError
}
if logPretty {
slog.SetDefault(slog.New(slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slogLevel})))
slog.SetDefault(slog.New(slogmulti.Fanout(
slog.NewTextHandler(os.Stderr, &slog.HandlerOptions{Level: slogLevel}),
NewSlogSentryBreadcrumbsHandler(),
)))
} else {
slog.SetDefault(slog.New(slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slogLevel})))
slog.SetDefault(slog.New(slogmulti.Fanout(
slog.NewJSONHandler(os.Stderr, &slog.HandlerOptions{Level: slogLevel}),
NewSlogSentryBreadcrumbsHandler(),
)))
}

if err := sentry.Init(sentry.ClientOptions{
Dsn: sentryDsn,
SampleRate: 1.0,
EnableTracing: true,
TracesSampleRate: 0.2,
Environment: environment,
Release: version,
}); err != nil {
slog.Error("Failed to initialize Sentry", slog.Any("error", err))
os.Exit(70)
Expand Down Expand Up @@ -106,8 +118,6 @@ func main() {

func runWorker(feed brassite.Feed) {
for {
slog.Debug("Starting worker", slog.String("feed_name", feed.Name), slog.String("url", feed.URL), slog.Duration("interval", feed.Interval))

ctx, cancel := context.WithTimeout(context.Background(), time.Minute*5)
hub := sentry.CurrentHub().Clone()
hub.Scope().SetTag("feed_name", feed.Name)
Expand All @@ -119,10 +129,12 @@ func runWorker(feed brassite.Feed) {
})
ctx = sentry.SetHubOnContext(ctx, hub)

slog.DebugContext(ctx, "Starting worker", slog.String("feed_name", feed.Name), slog.String("url", feed.URL), slog.Duration("interval", feed.Interval))

// Call the feed parser
request, err := http.NewRequestWithContext(ctx, http.MethodGet, feed.URL, nil)
if err != nil {
slog.Error("Failed to create request", slog.Any("error", err), slog.String("feed_name", feed.Name))
slog.ErrorContext(ctx, "Failed to create request", slog.Any("error", err), slog.String("feed_name", feed.Name))
cancel()
sentry.GetHubFromContext(ctx).CaptureException(err)
time.Sleep(feed.Interval)
Expand All @@ -142,17 +154,19 @@ func runWorker(feed brassite.Feed) {

response, err := http.DefaultClient.Do(request)
if err != nil {
slog.Error("Failed to send request", slog.Any("error", err), slog.String("feed_name", feed.Name))
slog.ErrorContext(ctx, "Failed to send request", slog.Any("error", err), slog.String("feed_name", feed.Name))
cancel()
sentry.GetHubFromContext(ctx).CaptureException(err)
time.Sleep(feed.Interval)
continue
}

slog.DebugContext(ctx, "Received response", slog.String("feed_name", feed.Name), slog.Int("status_code", response.StatusCode), slog.String("content_type", response.Header.Get("Content-Type")))

parser := gofeed.NewParser()
remoteFeed, err := parser.Parse(response.Body)
if err != nil {
slog.Error("Failed to parse feed", slog.Any("error", err), slog.String("feed_name", feed.Name))
slog.ErrorContext(ctx, "Failed to parse feed", slog.Any("error", err), slog.String("feed_name", feed.Name))
_ = response.Body.Close()
cancel()
sentry.GetHubFromContext(ctx).CaptureException(err)
Expand All @@ -166,26 +180,26 @@ func runWorker(feed brassite.Feed) {
// Only select the new items by using now - interval
var newItems []*gofeed.Item
for _, item := range remoteFeed.Items {
slog.Debug("Parsing item", slog.String("feed_name", feed.Name), slog.String("item_title", item.Title), slog.String("item_link", item.Link))
slog.DebugContext(ctx, "Parsing item", slog.String("feed_name", feed.Name), slog.String("item_title", item.Title), slog.String("item_link", item.Link))

if item.PublishedParsed != nil {
slog.Debug("Published parsed value", slog.String("feed_name", feed.Name), slog.Time("published_parsed", *item.PublishedParsed), slog.Time("now", time.Now().UTC()))
slog.DebugContext(ctx, "Published parsed value", slog.String("feed_name", feed.Name), slog.Time("published_parsed", *item.PublishedParsed), slog.Time("now", time.Now().UTC()))
if item.PublishedParsed.After(time.Now().UTC().Add(-feed.Interval)) {
newItems = append(newItems, item)
continue
}
}

if item.UpdatedParsed != nil {
slog.Debug("Updated parsed value", slog.String("feed_name", feed.Name), slog.Time("updated_parsed", *item.UpdatedParsed), slog.Time("now", time.Now().UTC()))
slog.DebugContext(ctx, "Updated parsed value", slog.String("feed_name", feed.Name), slog.Time("updated_parsed", *item.UpdatedParsed), slog.Time("now", time.Now().UTC()))
if item.UpdatedParsed.After(time.Now().UTC().Add(-feed.Interval)) {
newItems = append(newItems, item)
continue
}
}
}

slog.Debug("Found new items", slog.String("feed_name", feed.Name), slog.Int("new_items", len(newItems)))
slog.DebugContext(ctx, "Found new items", slog.String("feed_name", feed.Name), slog.Int("new_items", len(newItems)))

// Deliver it
for _, item := range newItems {
Expand Down Expand Up @@ -213,7 +227,7 @@ func runWorker(feed brassite.Feed) {
for _, url := range feed.Delivery.DiscordWebhookUrl.Values {
err := brassite.DeliverToDiscord(ctx, url, feedItem, feed.Logo)
if err != nil {
slog.Error("Failed to deliver to Discord", slog.String("feed_name", feed.Name), slog.Any("error", err))
slog.ErrorContext(ctx, "Failed to deliver to Discord", slog.String("feed_name", feed.Name), slog.Any("error", err))

sentry.GetHubFromContext(ctx).CaptureException(err)
}
Expand Down
93 changes: 93 additions & 0 deletions cmd/brassite/slog_breadcrumbs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright 2024 Teknologi Umum <[email protected]>
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package main

import (
"context"
"log/slog"
"strings"
"time"

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

type slogSentryBreadcrumbs struct {
attr []slog.Attr
group string
}

// Enabled implements slog.Handler.
func (s *slogSentryBreadcrumbs) Enabled(context.Context, slog.Level) bool {
return true
}

// Handle implements slog.Handler.
func (s *slogSentryBreadcrumbs) Handle(ctx context.Context, r slog.Record) error {
if ctx == nil {
return nil
}

hub := sentry.GetHubFromContext(ctx)
if hub == nil {
return nil
}

var timestamp = r.Time
if timestamp.IsZero() {
timestamp = time.Now()
}

var data = make(map[string]any)
r.Attrs(func(a slog.Attr) bool {
data[a.Key] = a.Value
return true
})

var group = s.group
if group == "" {
group = "console"
}

var additionalData = make(sentry.BreadcrumbHint)
for _, a := range s.attr {
additionalData[a.Key] = a.Value
}

hub.AddBreadcrumb(&sentry.Breadcrumb{
Type: "default",
Category: group,
Message: r.Message,
Data: data,
Level: sentry.Level(strings.ToLower(r.Level.String())),
Timestamp: timestamp,
}, &additionalData)

return nil
}

// WithAttrs implements slog.Handler.
func (s *slogSentryBreadcrumbs) WithAttrs(attrs []slog.Attr) slog.Handler {
return &slogSentryBreadcrumbs{attr: append(s.attr, attrs...), group: s.group}
}

// WithGroup implements slog.Handler.
func (s *slogSentryBreadcrumbs) WithGroup(name string) slog.Handler {
return &slogSentryBreadcrumbs{group: name, attr: s.attr}
}

// NewSlogSentryBreadcrumbsHandler creates a new slog handler that sends breadcrumbs to Sentry.
func NewSlogSentryBreadcrumbsHandler() slog.Handler {
return &slogSentryBreadcrumbs{}
}
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,22 @@ require (
github.com/JohannesKaufmann/html-to-markdown v1.6.0
github.com/getsentry/sentry-go v0.28.0
github.com/mmcdole/gofeed v1.3.0
github.com/samber/slog-multi v1.0.3
github.com/titanous/json5 v1.0.0
gopkg.in/yaml.v3 v3.0.1
)

require (
github.com/PuerkitoBio/goquery v1.9.2 // indirect
github.com/andybalholm/cascadia v1.3.2 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mmcdole/goxpp v1.1.1-0.20240225020742-a0c311522b23 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/samber/lo v1.38.1 // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3Bop
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
Expand Down Expand Up @@ -43,6 +47,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0=
github.com/robertkrimen/otto v0.2.1/go.mod h1:UPwtJ1Xu7JrLcZjNWN8orJaM5n5YEtqL//farB5FlRY=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/samber/slog-multi v1.0.3 h1:8wlX8ioZE38h91DwoJBVnC7JfhgwERwlekY+NHsVsv0=
github.com/samber/slog-multi v1.0.3/go.mod h1:TvwgIK4XPBb8Dn18as5uiTHf7in8gN/AtUXsT57UYuo=
github.com/sebdah/goldie/v2 v2.5.3 h1:9ES/mNN+HNUbNWpVAlrzuZ7jE+Nrczbj8uFRjM7624Y=
github.com/sebdah/goldie/v2 v2.5.3/go.mod h1:oZ9fp0+se1eapSRjfYbsV/0Hqhbuu3bJVvKI/NNtssI=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
Expand All @@ -63,6 +71,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
Expand Down

0 comments on commit 43318e1

Please sign in to comment.