Skip to content

Commit 25018a6

Browse files
authored
Merge pull request #15 from PDOK/PDOK-17771-slack-integration
Pdok 17771 slack integration
2 parents bbabb2b + 144a584 commit 25018a6

File tree

3 files changed

+193
-0
lines changed

3 files changed

+193
-0
lines changed

pkg/integrations/logging/logging.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package logging
2+
3+
import (
4+
"github.com/pdok/smooth-operator/pkg/integrations/slack"
5+
zap "go.uber.org/zap"
6+
"go.uber.org/zap/zapcore"
7+
)
8+
9+
type StdoutWriter struct {
10+
}
11+
12+
func (s StdoutWriter) Write(p []byte) (n int, err error) {
13+
println(string(p))
14+
return len(p), nil
15+
}
16+
17+
func SetupLogger(operatorName string, slackWebhookUrl string, minLogLevel zapcore.LevelEnabler) (*zap.Logger, error) {
18+
// Standard output writer
19+
stdoutSyncer := zapcore.Lock(zapcore.AddSync(StdoutWriter{}))
20+
21+
// Slack writer for errors
22+
slackSyncer := zapcore.Lock(zapcore.AddSync(&slack.SlackZapWriter{
23+
OperatorName: operatorName,
24+
SlackWebhookUrl: slackWebhookUrl,
25+
}))
26+
27+
// Encoder configuration for human-readable output
28+
encoderConfig := zap.NewProductionEncoderConfig()
29+
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
30+
31+
// Create a core for stdout (all levels)
32+
stdoutCore := zapcore.NewCore(
33+
zapcore.NewConsoleEncoder(encoderConfig),
34+
stdoutSyncer,
35+
minLogLevel,
36+
)
37+
38+
// Create a core for Slack (errors only)
39+
slackCore := zapcore.NewCore(
40+
zapcore.NewConsoleEncoder(encoderConfig),
41+
slackSyncer,
42+
zapcore.ErrorLevel,
43+
)
44+
45+
// Combine cores
46+
combinedCore := zapcore.NewTee(stdoutCore, slackCore)
47+
48+
// Build the logger
49+
logger := zap.New(combinedCore, zap.AddCaller())
50+
return logger, nil
51+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package logging
2+
3+
import (
4+
"go.uber.org/zap/zapcore"
5+
"testing"
6+
)
7+
8+
func TestLogger(t *testing.T) {
9+
logger, err := SetupLogger("myOperator", "", zapcore.InfoLevel)
10+
if err != nil {
11+
panic(err)
12+
}
13+
14+
// This should log a message to stdout
15+
// If slackWebhookUrl is provided, it will use the URL to send a Slack message
16+
logger.Info("Foo")
17+
logger.Error("Bar")
18+
}

pkg/integrations/slack/slack.go

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package slack
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"net/http"
7+
"strings"
8+
)
9+
10+
type SlackRequest struct {
11+
Attachments []SlackElement `json:"attachments"`
12+
Text *string `json:"text,omitempty"`
13+
}
14+
15+
type SlackElement struct {
16+
Color string `json:"color"`
17+
Blocks []SlackBlock `json:"blocks"`
18+
}
19+
20+
type SlackBlock struct {
21+
Type string `json:"type"`
22+
Text SlackValueBlock `json:"text"`
23+
Fields []SlackValueBlock `json:"fields"`
24+
}
25+
26+
type SlackValueBlock struct {
27+
Type string `json:"type"`
28+
Text string `json:"text"`
29+
}
30+
31+
func GetSlackBlock(generalMessage string, jobUrl string, color string, datasetName string) SlackRequest {
32+
var slackBlock = SlackBlock{
33+
Type: "section",
34+
Text: SlackValueBlock{
35+
Type: "mrkdwn",
36+
Text: generalMessage,
37+
},
38+
Fields: []SlackValueBlock{{
39+
Type: "mrkdwn",
40+
Text: "*Dataset*",
41+
}, {
42+
Type: "mrkdwn",
43+
Text: "*Job*",
44+
}, {
45+
Type: "mrkdwn",
46+
Text: datasetName,
47+
}, {
48+
Type: "mrkdwn",
49+
Text: jobUrl,
50+
}},
51+
}
52+
53+
slackElement := SlackElement{
54+
Color: color,
55+
Blocks: []SlackBlock{slackBlock},
56+
}
57+
58+
return SlackRequest{
59+
Attachments: []SlackElement{slackElement},
60+
}
61+
}
62+
63+
func GetSlackErrorMessage(message string, bundle string, color string) SlackRequest {
64+
var slackBlock = SlackBlock{
65+
Type: "section",
66+
Text: SlackValueBlock{
67+
Type: "mrkdwn",
68+
Text: message,
69+
},
70+
Fields: []SlackValueBlock{{
71+
Type: "mrkdwn",
72+
Text: "*Bundle*",
73+
}, {
74+
Type: "mrkdwn",
75+
Text: bundle,
76+
}},
77+
}
78+
79+
slackElement := SlackElement{
80+
Color: color,
81+
Blocks: []SlackBlock{slackBlock},
82+
}
83+
84+
return SlackRequest{
85+
Attachments: []SlackElement{slackElement},
86+
}
87+
}
88+
89+
func GetSimpleSlackErrorMessage(message string) SlackRequest {
90+
return SlackRequest{
91+
Attachments: nil,
92+
Text: &message,
93+
}
94+
}
95+
96+
func SendSlackRequest(slackRequest SlackRequest, slackUrl string) error {
97+
marshalled, err := json.Marshal(slackRequest)
98+
if err != nil {
99+
return err
100+
}
101+
response, err := http.DefaultClient.Post(slackUrl, "application/json", strings.NewReader(string(marshalled)))
102+
if err != nil {
103+
return err
104+
}
105+
defer response.Body.Close()
106+
return nil
107+
}
108+
109+
type SlackZapWriter struct {
110+
OperatorName string
111+
SlackWebhookUrl string
112+
}
113+
114+
func (slackWriter *SlackZapWriter) Sync() error {
115+
return nil
116+
}
117+
118+
func (slackWriter *SlackZapWriter) Write(p []byte) (n int, err error) {
119+
if slackWriter.SlackWebhookUrl != "" {
120+
slackRequest := GetSimpleSlackErrorMessage(fmt.Sprintf("%s: %s", slackWriter.OperatorName, string(p)))
121+
SendSlackRequest(slackRequest, slackWriter.SlackWebhookUrl)
122+
}
123+
return len(p), nil
124+
}

0 commit comments

Comments
 (0)