From 9534dc34922f5f5aa45d6da5c6ae56a1cef4a3fc Mon Sep 17 00:00:00 2001 From: ChaoticByte Date: Sat, 14 Oct 2023 10:41:22 +0200 Subject: [PATCH] Implemented proper logging and added more log messages --- README.md | 5 ++++- datastore.go | 11 ++++++----- logging.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ mail.go | 8 +++++--- main.go | 31 ++++++++++++++++++++++--------- template.go | 5 ++--- widapi.go | 4 ++-- 7 files changed, 89 insertions(+), 23 deletions(-) create mode 100644 logging.go diff --git a/README.md b/README.md index 07e876d..7634851 100644 --- a/README.md +++ b/README.md @@ -31,11 +31,12 @@ Example: ```json { "api_fetch_interval": 600, + "datafile": "data", "enabled_api_endpoints": [ "bay", "bund" ], - "datafile": "data", + "loglevel": 2, "recipients": [ { "address": "guenther@example.org", @@ -59,6 +60,8 @@ Example: } ``` +To show debug messages, set the `loglevel` to `3`. + ## 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. diff --git a/datastore.go b/datastore.go index 2609e68..fbc09e4 100644 --- a/datastore.go +++ b/datastore.go @@ -5,7 +5,6 @@ package main import ( "encoding/json" "errors" - "fmt" "io/fs" "os" "time" @@ -15,6 +14,7 @@ type Config struct { ApiFetchInterval int `json:"api_fetch_interval"` // in seconds EnabledApiEndpoints []string `json:"enabled_api_endpoints"` PersistentDataFilePath string `json:"datafile"` + LogLevel int `json:"loglevel"` Recipients []Recipient `json:"recipients"` SmtpConfiguration SmtpSettings `json:"smtp"` Template MailTemplateConfig `json:"template"` @@ -26,6 +26,7 @@ func NewConfig() Config { ApiFetchInterval: 60 * 10, // every 10 minutes, EnabledApiEndpoints: []string{"bay", "bund"}, PersistentDataFilePath: "data", + LogLevel: 2, Recipients: []Recipient{}, SmtpConfiguration: SmtpSettings{ From: "from@example.org", @@ -43,21 +44,21 @@ func NewConfig() Config { func checkConfig(config Config) { if len(config.Recipients) < 1 { - fmt.Println("ERROR\tConfiguration is incomplete.") + logger.error("Configuration is incomplete") panic(errors.New("no recipients are configured")) } for _, r := range config.Recipients { 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")) } 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")) } } 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")) } } diff --git a/logging.go b/logging.go new file mode 100644 index 0000000..c35c96c --- /dev/null +++ b/logging.go @@ -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 +} diff --git a/mail.go b/mail.go index de3e841..62b53c6 100644 --- a/mail.go +++ b/mail.go @@ -64,11 +64,13 @@ func (r Recipient) filterAndSendNotices(notices []WidNotice, template MailTempla } } 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 { mailContent, err := template.generate(n) if err != nil { - fmt.Println("ERROR\tCould not create mail from template.") - fmt.Println(err) + logger.error("Could not create mail from template") + logger.error(err) } // serialize & send mail data := mailContent.serializeValidMail(smtpConfig.From, r.Address) @@ -82,8 +84,8 @@ func (r Recipient) filterAndSendNotices(notices []WidNotice, template MailTempla if err != nil { return err } - // fmt.Printf("%v", strings.ReplaceAll(string(data), "\r", "\\r")) } + logger.debug("Sent all mails for recipient " + r.Address) return nil } diff --git a/main.go b/main.go index b9db59a..a27b3a7 100644 --- a/main.go +++ b/main.go @@ -9,6 +9,8 @@ import ( "time" ) +var logger Logger + func main() { // get cli arguments args := os.Args @@ -17,8 +19,11 @@ func main() { os.Exit(1) } configFilePath := os.Args[1] + // create logger + logger = NewLogger(2) // init - println("INFO\tInitializing ...") + logger.info("Initializing ...") + defer logger.info("Exiting ...") config := NewDataStore( configFilePath, NewConfig(), @@ -30,15 +35,17 @@ func main() { NewPersistentData(config), false, 0640) - // exit handler - defer println("INFO\tExiting ...") - // check config + logger.LogLevel = config.LogLevel + logger.debug("Checking configuration file ...") checkConfig(config) // create mail template from mail template config + logger.debug("Parsing mail template ...") if config.Template.SubjectTemplate == "" { + logger.debug("Using default template for mail subject") config.Template.SubjectTemplate = DEFAULT_SUBJECT_TEMPLATE } if config.Template.BodyTemplate == "" { + logger.debug("Using default template for mail body") config.Template.BodyTemplate = DEFAULT_BODY_TEMPLATE } mailTemplate := NewTemplateFromTemplateConfig(config.Template) @@ -54,42 +61,48 @@ func main() { for _, a := range apiEndpoints { for _, b := range config.EnabledApiEndpoints { if a.Id == b { + logger.debug("Endpoint '" + b + "' is enabled") enabledApiEndpoints = append(enabledApiEndpoints, a) } } } // main loop + logger.debug("Entering main loop ...") for { t1 := time.Now().UnixMilli() newNotices := []WidNotice{} 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]) if err != nil { // 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]) } if err != nil { // ok then... - fmt.Print("ERROR\t", err) + logger.error("Couldn't query notices from API endpoint '" + a.Id + "'") + logger.error(err) } else { newNotices = append(newNotices, n...) persistent.data.(PersistentData).LastPublished[a.Id] = t persistent.save() } } + logger.debug(fmt.Sprintf("Got %v new notices", len(newNotices))) 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 for _, r := range config.Recipients { err := r.filterAndSendNotices(newNotices, mailTemplate, mailAuth, config.SmtpConfiguration) if err != nil { - fmt.Printf("ERROR\t%v\n", err) + logger.error(err) } else { 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() dt := int(t2 - t1) diff --git a/template.go b/template.go index ac75571..4d662b0 100644 --- a/template.go +++ b/template.go @@ -4,7 +4,6 @@ package main import ( "bytes" - "fmt" "text/template" ) @@ -58,12 +57,12 @@ func (t MailTemplate) generate(notice WidNotice) (MailContent, error) { func NewTemplateFromTemplateConfig(tc MailTemplateConfig) MailTemplate { subjectTemplate, err := template.New("subject").Parse(tc.SubjectTemplate) if err != nil { - fmt.Println("ERROR\tCould not parse template.") + logger.error("Could not parse template") panic(err) } bodyTemplate, err := template.New("body").Parse(tc.BodyTemplate) if err != nil { - fmt.Println("ERROR\tCould not parse template.") + logger.error("Could not parse template") panic(err) } return MailTemplate{ diff --git a/widapi.go b/widapi.go index 307ff04..41638a0 100644 --- a/widapi.go +++ b/widapi.go @@ -66,7 +66,7 @@ func (e ApiEndpoint) getNotices(since time.Time) ([]WidNotice, time.Time, error) } notices = parseApiResponse(decodedData, e) } 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 } } else { @@ -104,7 +104,7 @@ func parseApiResponse(data map[string]interface{}, apiEndpoint ApiEndpoint) []Wi } published, err := time.Parse(PUBLISHED_TIME_FORMAT, d["published"].(string)) if err != nil { - fmt.Println("ERROR\t", err) + logger.error(err) } notice.Published = published // optional fields