Skip to content

Commit c159843

Browse files
committed
add untracked files
1 parent a67f991 commit c159843

2 files changed

Lines changed: 177 additions & 0 deletions

File tree

src/logging/notify.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package logging
2+
3+
import (
4+
"context"
5+
"explo/src/config"
6+
"fmt"
7+
"log/slog"
8+
"strings"
9+
10+
"github.com/nikoksr/notify"
11+
"github.com/nikoksr/notify/service/discord"
12+
"github.com/nikoksr/notify/service/matrix"
13+
"github.com/nikoksr/notify/service/http"
14+
"maunium.net/go/mautrix/id"
15+
)
16+
17+
// TODO: reuse notifier instead of creating a new one every time. right now it's fine cause Explo sends 1 message per run
18+
19+
20+
type NotificationClient struct {
21+
Cfg config.NotifyConfig
22+
}
23+
24+
func InitNotify(cfg config.NotifyConfig) NotificationClient {
25+
return NotificationClient{
26+
Cfg: cfg,
27+
}
28+
}
29+
30+
func sendMatrix(cfg config.MatrixNotif, msg string) error {
31+
// UserID and RoomID need to be cast as specific types
32+
srvc, err := matrix.New(id.UserID(cfg.UserID), id.RoomID(cfg.RoomID), cfg.HomeServer, cfg.AccessToken)
33+
if err != nil {
34+
return fmt.Errorf("failed to create new Matrix notification service: %s", err.Error())
35+
}
36+
37+
notifier := notify.New()
38+
notifier.UseServices(srvc)
39+
40+
err = notifier.Send(context.Background(), "Explo", msg)
41+
if err != nil {
42+
return err
43+
}
44+
45+
return nil
46+
}
47+
48+
/* discordgo module (which notify uses) doesn't handle errors correctly
49+
no errors are given even when authentication fails*/
50+
func sendDiscord(cfg config.DiscordNotif, msg string) error {
51+
srvc := discord.New()
52+
if err := srvc.AuthenticateWithBotToken(cfg.BotToken); err != nil {
53+
return fmt.Errorf("failed to authenticate against Discord: %s", err.Error())
54+
}
55+
56+
srvc.AddReceivers(cfg.ChannelIDs...)
57+
58+
notifier := notify.New()
59+
notifier.UseServices(srvc)
60+
61+
if err := notifier.Send(context.Background(), "**Explo**", msg); err != nil {
62+
return fmt.Errorf("failed to send Discord notification: %s", err.Error())
63+
}
64+
65+
return nil
66+
}
67+
68+
func sendHttp(cfg config.HttpNotif, msg string) error {
69+
httpNotify := http.New()
70+
71+
httpNotify.AddReceiversURLs(cfg.ReceiverURL)
72+
httpNotify.AddReceivers(&http.Webhook{
73+
URL: cfg.ReceiverURL,
74+
})
75+
notifier := notify.NewWithServices(httpNotify)
76+
77+
if err := notifier.Send(context.Background(), "Explo", msg); err != nil {
78+
return fmt.Errorf("failed to send HTTP notification: %s", err.Error())
79+
}
80+
return nil
81+
}
82+
83+
// format notification to JSON
84+
func formatNotification(r slog.Record) string {
85+
var attrs []string
86+
87+
r.Attrs(func(a slog.Attr) bool {
88+
attrs = append(attrs, fmt.Sprintf("%s=%v", a.Key, a.Value))
89+
return true
90+
})
91+
92+
93+
return fmt.Sprintf(
94+
"[%s] %s\n%s",
95+
r.Level,
96+
r.Message,
97+
strings.Join(attrs, ", "),
98+
)
99+
}
100+
101+
func (c NotificationClient) SendNotification(r slog.Record) {
102+
var err error
103+
msg := formatNotification(r)
104+
switch c.Cfg.Service {
105+
case "matrix":
106+
err = sendMatrix(c.Cfg.Matrix, msg)
107+
108+
case "discord":
109+
err = sendDiscord(c.Cfg.Discord, msg)
110+
111+
case "http":
112+
err = sendHttp(c.Cfg.Http, msg)
113+
114+
case "": // no system defined
115+
return
116+
default:
117+
err = fmt.Errorf("wrong system defined for notifications: %s", c.Cfg.Service)
118+
}
119+
if err != nil {
120+
slog.Error(err.Error())
121+
} else {
122+
slog.Info("notification sent", "service", c.Cfg.Service)
123+
}
124+
}

src/logging/notify_handler.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package logging
2+
3+
import (
4+
"log/slog"
5+
"context"
6+
)
7+
// slog handler that checks whether to send notifications
8+
9+
type notifyHandler struct {
10+
handler slog.Handler
11+
notify NotificationClient
12+
}
13+
14+
func (h *notifyHandler) Enabled(ctx context.Context, level slog.Level) bool {
15+
return h.handler.Enabled(ctx, level)
16+
}
17+
18+
func (h *notifyHandler) Handle(ctx context.Context, r slog.Record) error {
19+
if shouldNotify(r) {
20+
// send notification in another goroutine
21+
rec := r
22+
go h.notify.SendNotification(rec)
23+
}
24+
return h.handler.Handle(ctx, r)
25+
}
26+
27+
func (h *notifyHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
28+
return &notifyHandler{
29+
handler: h.handler.WithAttrs(attrs),
30+
notify: h.notify,
31+
}
32+
}
33+
34+
func (h *notifyHandler) WithGroup(name string) slog.Handler {
35+
return &notifyHandler{
36+
handler: h.handler.WithGroup(name),
37+
notify: h.notify,
38+
}
39+
}
40+
41+
func shouldNotify(r slog.Record) bool {
42+
notify := false
43+
44+
r.Attrs(func(a slog.Attr) bool {
45+
if a.Key == "notify" && a.Value.Kind() == slog.KindBool && a.Value.Bool() {
46+
notify = true
47+
return false // stop scanning
48+
}
49+
return true
50+
})
51+
52+
return notify
53+
}

0 commit comments

Comments
 (0)