Implemented proper logging and added more log messages
This commit is contained in:
parent
5a90f9736b
commit
9534dc3492
7 changed files with 89 additions and 23 deletions
|
@ -31,11 +31,12 @@ Example:
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"api_fetch_interval": 600,
|
"api_fetch_interval": 600,
|
||||||
|
"datafile": "data",
|
||||||
"enabled_api_endpoints": [
|
"enabled_api_endpoints": [
|
||||||
"bay",
|
"bay",
|
||||||
"bund"
|
"bund"
|
||||||
],
|
],
|
||||||
"datafile": "data",
|
"loglevel": 2,
|
||||||
"recipients": [
|
"recipients": [
|
||||||
{
|
{
|
||||||
"address": "guenther@example.org",
|
"address": "guenther@example.org",
|
||||||
|
@ -59,6 +60,8 @@ Example:
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To show debug messages, set the `loglevel` to `3`.
|
||||||
|
|
||||||
## Filters
|
## Filters
|
||||||
|
|
||||||
You define filters for notices to be sent per recipient. Multiple filters can be set per recipient and multiple criteria can be used per filter. The configuration field for those filters is `include`. See [Configuration](#configuration) for an example.
|
You define filters for notices to be sent per recipient. Multiple filters can be set per recipient and multiple criteria can be used per filter. The configuration field for those filters is `include`. See [Configuration](#configuration) for an example.
|
||||||
|
|
11
datastore.go
11
datastore.go
|
@ -5,7 +5,6 @@ package main
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
@ -15,6 +14,7 @@ type Config struct {
|
||||||
ApiFetchInterval int `json:"api_fetch_interval"` // in seconds
|
ApiFetchInterval int `json:"api_fetch_interval"` // in seconds
|
||||||
EnabledApiEndpoints []string `json:"enabled_api_endpoints"`
|
EnabledApiEndpoints []string `json:"enabled_api_endpoints"`
|
||||||
PersistentDataFilePath string `json:"datafile"`
|
PersistentDataFilePath string `json:"datafile"`
|
||||||
|
LogLevel int `json:"loglevel"`
|
||||||
Recipients []Recipient `json:"recipients"`
|
Recipients []Recipient `json:"recipients"`
|
||||||
SmtpConfiguration SmtpSettings `json:"smtp"`
|
SmtpConfiguration SmtpSettings `json:"smtp"`
|
||||||
Template MailTemplateConfig `json:"template"`
|
Template MailTemplateConfig `json:"template"`
|
||||||
|
@ -26,6 +26,7 @@ func NewConfig() Config {
|
||||||
ApiFetchInterval: 60 * 10, // every 10 minutes,
|
ApiFetchInterval: 60 * 10, // every 10 minutes,
|
||||||
EnabledApiEndpoints: []string{"bay", "bund"},
|
EnabledApiEndpoints: []string{"bay", "bund"},
|
||||||
PersistentDataFilePath: "data",
|
PersistentDataFilePath: "data",
|
||||||
|
LogLevel: 2,
|
||||||
Recipients: []Recipient{},
|
Recipients: []Recipient{},
|
||||||
SmtpConfiguration: SmtpSettings{
|
SmtpConfiguration: SmtpSettings{
|
||||||
From: "from@example.org",
|
From: "from@example.org",
|
||||||
|
@ -43,21 +44,21 @@ func NewConfig() Config {
|
||||||
|
|
||||||
func checkConfig(config Config) {
|
func checkConfig(config Config) {
|
||||||
if len(config.Recipients) < 1 {
|
if len(config.Recipients) < 1 {
|
||||||
fmt.Println("ERROR\tConfiguration is incomplete.")
|
logger.error("Configuration is incomplete")
|
||||||
panic(errors.New("no recipients are configured"))
|
panic(errors.New("no recipients are configured"))
|
||||||
}
|
}
|
||||||
for _, r := range config.Recipients {
|
for _, r := range config.Recipients {
|
||||||
if !mailAddressIsValid(r.Address) {
|
if !mailAddressIsValid(r.Address) {
|
||||||
fmt.Println("ERROR\tConfiguration includes invalid data.")
|
logger.error("Configuration includes invalid data")
|
||||||
panic(errors.New("'" + r.Address + "' is not a valid e-mail address"))
|
panic(errors.New("'" + r.Address + "' is not a valid e-mail address"))
|
||||||
}
|
}
|
||||||
if len(r.Filters) < 1 {
|
if len(r.Filters) < 1 {
|
||||||
fmt.Println("ERROR\tConfiguration is incomplete.")
|
logger.error("Configuration is incomplete")
|
||||||
panic(errors.New("recipient " + r.Address + " has no filter defined - at least {'any': true/false} should be configured"))
|
panic(errors.New("recipient " + r.Address + " has no filter defined - at least {'any': true/false} should be configured"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !mailAddressIsValid(config.SmtpConfiguration.From) {
|
if !mailAddressIsValid(config.SmtpConfiguration.From) {
|
||||||
fmt.Println("ERROR\tConfiguration includes invalid data.")
|
logger.error("Configuration includes invalid data")
|
||||||
panic(errors.New("'" + config.SmtpConfiguration.From + "' is not a valid e-mail address"))
|
panic(errors.New("'" + config.SmtpConfiguration.From + "' is not a valid e-mail address"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
48
logging.go
Normal file
48
logging.go
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// Copyright (c) 2023 Julian Müller (ChaoticByte)
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
LogLevel int
|
||||||
|
ErrorLogger *log.Logger // 0
|
||||||
|
WarningLogger *log.Logger // 1
|
||||||
|
InfoLogger *log.Logger // 2
|
||||||
|
DebugLogger *log.Logger // 3
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) error(msg any) {
|
||||||
|
l.ErrorLogger.Println(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) warn(msg any) {
|
||||||
|
if l.LogLevel >= 1 {
|
||||||
|
l.WarningLogger.Println(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) info(msg any) {
|
||||||
|
if l.LogLevel >= 2 {
|
||||||
|
l.InfoLogger.Println(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) debug(msg any) {
|
||||||
|
if l.LogLevel >= 3 {
|
||||||
|
l.DebugLogger.Println(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLogger(loglevel int) Logger {
|
||||||
|
l := Logger{}
|
||||||
|
l.LogLevel = loglevel
|
||||||
|
l.ErrorLogger = log.New(os.Stderr, "ERROR ", log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile)
|
||||||
|
l.WarningLogger = log.New(os.Stderr, "WARN ", log.Ldate|log.Ltime|log.Lmicroseconds|log.Lshortfile)
|
||||||
|
l.InfoLogger = log.New(os.Stderr, "INFO ", log.Ldate|log.Ltime|log.Lmicroseconds)
|
||||||
|
l.DebugLogger = log.New(os.Stderr, "DEBUG ", log.Ldate|log.Ltime|log.Lmicroseconds)
|
||||||
|
return l
|
||||||
|
}
|
8
mail.go
8
mail.go
|
@ -64,11 +64,13 @@ func (r Recipient) filterAndSendNotices(notices []WidNotice, template MailTempla
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
slices.Reverse(filteredNotices)
|
slices.Reverse(filteredNotices)
|
||||||
|
logger.debug(fmt.Sprintf("Including %v of %v notices for recipient %v", len(filteredNotices), len(notices), r.Address))
|
||||||
|
logger.debug("Templating and sending mails for recipient " + r.Address + " ...")
|
||||||
for _, n := range filteredNotices {
|
for _, n := range filteredNotices {
|
||||||
mailContent, err := template.generate(n)
|
mailContent, err := template.generate(n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("ERROR\tCould not create mail from template.")
|
logger.error("Could not create mail from template")
|
||||||
fmt.Println(err)
|
logger.error(err)
|
||||||
}
|
}
|
||||||
// serialize & send mail
|
// serialize & send mail
|
||||||
data := mailContent.serializeValidMail(smtpConfig.From, r.Address)
|
data := mailContent.serializeValidMail(smtpConfig.From, r.Address)
|
||||||
|
@ -82,8 +84,8 @@ func (r Recipient) filterAndSendNotices(notices []WidNotice, template MailTempla
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// fmt.Printf("%v", strings.ReplaceAll(string(data), "\r", "\\r"))
|
|
||||||
}
|
}
|
||||||
|
logger.debug("Sent all mails for recipient " + r.Address)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
31
main.go
31
main.go
|
@ -9,6 +9,8 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var logger Logger
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// get cli arguments
|
// get cli arguments
|
||||||
args := os.Args
|
args := os.Args
|
||||||
|
@ -17,8 +19,11 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
configFilePath := os.Args[1]
|
configFilePath := os.Args[1]
|
||||||
|
// create logger
|
||||||
|
logger = NewLogger(2)
|
||||||
// init
|
// init
|
||||||
println("INFO\tInitializing ...")
|
logger.info("Initializing ...")
|
||||||
|
defer logger.info("Exiting ...")
|
||||||
config := NewDataStore(
|
config := NewDataStore(
|
||||||
configFilePath,
|
configFilePath,
|
||||||
NewConfig(),
|
NewConfig(),
|
||||||
|
@ -30,15 +35,17 @@ func main() {
|
||||||
NewPersistentData(config),
|
NewPersistentData(config),
|
||||||
false,
|
false,
|
||||||
0640)
|
0640)
|
||||||
// exit handler
|
logger.LogLevel = config.LogLevel
|
||||||
defer println("INFO\tExiting ...")
|
logger.debug("Checking configuration file ...")
|
||||||
// check config
|
|
||||||
checkConfig(config)
|
checkConfig(config)
|
||||||
// create mail template from mail template config
|
// create mail template from mail template config
|
||||||
|
logger.debug("Parsing mail template ...")
|
||||||
if config.Template.SubjectTemplate == "" {
|
if config.Template.SubjectTemplate == "" {
|
||||||
|
logger.debug("Using default template for mail subject")
|
||||||
config.Template.SubjectTemplate = DEFAULT_SUBJECT_TEMPLATE
|
config.Template.SubjectTemplate = DEFAULT_SUBJECT_TEMPLATE
|
||||||
}
|
}
|
||||||
if config.Template.BodyTemplate == "" {
|
if config.Template.BodyTemplate == "" {
|
||||||
|
logger.debug("Using default template for mail body")
|
||||||
config.Template.BodyTemplate = DEFAULT_BODY_TEMPLATE
|
config.Template.BodyTemplate = DEFAULT_BODY_TEMPLATE
|
||||||
}
|
}
|
||||||
mailTemplate := NewTemplateFromTemplateConfig(config.Template)
|
mailTemplate := NewTemplateFromTemplateConfig(config.Template)
|
||||||
|
@ -54,42 +61,48 @@ func main() {
|
||||||
for _, a := range apiEndpoints {
|
for _, a := range apiEndpoints {
|
||||||
for _, b := range config.EnabledApiEndpoints {
|
for _, b := range config.EnabledApiEndpoints {
|
||||||
if a.Id == b {
|
if a.Id == b {
|
||||||
|
logger.debug("Endpoint '" + b + "' is enabled")
|
||||||
enabledApiEndpoints = append(enabledApiEndpoints, a)
|
enabledApiEndpoints = append(enabledApiEndpoints, a)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// main loop
|
// main loop
|
||||||
|
logger.debug("Entering main loop ...")
|
||||||
for {
|
for {
|
||||||
t1 := time.Now().UnixMilli()
|
t1 := time.Now().UnixMilli()
|
||||||
newNotices := []WidNotice{}
|
newNotices := []WidNotice{}
|
||||||
for _, a := range enabledApiEndpoints {
|
for _, a := range enabledApiEndpoints {
|
||||||
fmt.Printf("INFO\t%v Querying endpoint '%v' for new notices ...\n", time.Now().Format(time.RFC3339Nano), a.Id)
|
logger.info("Querying endpoint '" + a.Id + "' for new notices ...")
|
||||||
n, t, err := a.getNotices(persistent.data.(PersistentData).LastPublished[a.Id])
|
n, t, err := a.getNotices(persistent.data.(PersistentData).LastPublished[a.Id])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// retry
|
// retry
|
||||||
|
logger.warn("Couldn't query notices from API endpoint '" + a.Id + "'. Retrying ...")
|
||||||
|
logger.warn(err)
|
||||||
n, t, err = a.getNotices(persistent.data.(PersistentData).LastPublished[a.Id])
|
n, t, err = a.getNotices(persistent.data.(PersistentData).LastPublished[a.Id])
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// ok then...
|
// ok then...
|
||||||
fmt.Print("ERROR\t", err)
|
logger.error("Couldn't query notices from API endpoint '" + a.Id + "'")
|
||||||
|
logger.error(err)
|
||||||
} else {
|
} else {
|
||||||
newNotices = append(newNotices, n...)
|
newNotices = append(newNotices, n...)
|
||||||
persistent.data.(PersistentData).LastPublished[a.Id] = t
|
persistent.data.(PersistentData).LastPublished[a.Id] = t
|
||||||
persistent.save()
|
persistent.save()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
logger.debug(fmt.Sprintf("Got %v new notices", len(newNotices)))
|
||||||
if len(newNotices) > 0 {
|
if len(newNotices) > 0 {
|
||||||
fmt.Printf("INFO\t%v Sending email notifications ...\n", time.Now().Format(time.RFC3339Nano))
|
logger.info("Sending email notifications ...")
|
||||||
recipientsNotified := 0
|
recipientsNotified := 0
|
||||||
for _, r := range config.Recipients {
|
for _, r := range config.Recipients {
|
||||||
err := r.filterAndSendNotices(newNotices, mailTemplate, mailAuth, config.SmtpConfiguration)
|
err := r.filterAndSendNotices(newNotices, mailTemplate, mailAuth, config.SmtpConfiguration)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("ERROR\t%v\n", err)
|
logger.error(err)
|
||||||
} else {
|
} else {
|
||||||
recipientsNotified++
|
recipientsNotified++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Printf("INFO\t%v Email notifications sent to %v of %v recipients.\n", time.Now().Format(time.RFC3339Nano), recipientsNotified, len(config.Recipients))
|
logger.info(fmt.Sprintf("Email notifications sent to %v of %v recipients", recipientsNotified, len(config.Recipients)))
|
||||||
}
|
}
|
||||||
t2 := time.Now().UnixMilli()
|
t2 := time.Now().UnixMilli()
|
||||||
dt := int(t2 - t1)
|
dt := int(t2 - t1)
|
||||||
|
|
|
@ -4,7 +4,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -58,12 +57,12 @@ func (t MailTemplate) generate(notice WidNotice) (MailContent, error) {
|
||||||
func NewTemplateFromTemplateConfig(tc MailTemplateConfig) MailTemplate {
|
func NewTemplateFromTemplateConfig(tc MailTemplateConfig) MailTemplate {
|
||||||
subjectTemplate, err := template.New("subject").Parse(tc.SubjectTemplate)
|
subjectTemplate, err := template.New("subject").Parse(tc.SubjectTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("ERROR\tCould not parse template.")
|
logger.error("Could not parse template")
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
bodyTemplate, err := template.New("body").Parse(tc.BodyTemplate)
|
bodyTemplate, err := template.New("body").Parse(tc.BodyTemplate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("ERROR\tCould not parse template.")
|
logger.error("Could not parse template")
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return MailTemplate{
|
return MailTemplate{
|
||||||
|
|
|
@ -66,7 +66,7 @@ func (e ApiEndpoint) getNotices(since time.Time) ([]WidNotice, time.Time, error)
|
||||||
}
|
}
|
||||||
notices = parseApiResponse(decodedData, e)
|
notices = parseApiResponse(decodedData, e)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("ERROR\tGet \"%v\": %v\n", url, res.Status)
|
logger.error(fmt.Sprintf("Get \"%v\": %v\n", url, res.Status))
|
||||||
return nil, time.Time{}, err
|
return nil, time.Time{}, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -104,7 +104,7 @@ func parseApiResponse(data map[string]interface{}, apiEndpoint ApiEndpoint) []Wi
|
||||||
}
|
}
|
||||||
published, err := time.Parse(PUBLISHED_TIME_FORMAT, d["published"].(string))
|
published, err := time.Parse(PUBLISHED_TIME_FORMAT, d["published"].(string))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("ERROR\t", err)
|
logger.error(err)
|
||||||
}
|
}
|
||||||
notice.Published = published
|
notice.Published = published
|
||||||
// optional fields
|
// optional fields
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue