From b7a623c6f58011646a709aae3226c2cc283e49ec Mon Sep 17 00:00:00 2001 From: Utku Ufuk Date: Sun, 27 Mar 2022 00:40:36 +0300 Subject: [PATCH] Switch from YAML to JSON for configuration, remove Telegram integration (#46) * remove telegram & switch from YAML to JSON for configuration * fix .gitignore * allow reading config from an envar * fix readme * polish readme --- .editorconfig | 3 ++ .github/workflows/ci.yml | 5 +- .github/workflows/release.yml | 2 +- .gitignore | 2 +- README.md | 96 ++++++++++++++++------------------ cmd/entrello/main.go | 20 ++++--- cmd/entrello/source.go | 17 +++--- config.example.json | 43 +++++++++++++++ config.example.yml | 39 -------------- go.mod | 5 -- go.sum | 16 ------ internal/config/config.go | 53 ++++++++++--------- internal/config/config_test.go | 39 -------------- internal/logger/logger.go | 23 ++++++++ internal/syslog/syslog.go | 66 ----------------------- internal/syslog/syslog_test.go | 81 ---------------------------- 16 files changed, 166 insertions(+), 344 deletions(-) create mode 100644 config.example.json delete mode 100644 config.example.yml delete mode 100644 internal/config/config_test.go create mode 100644 internal/logger/logger.go delete mode 100644 internal/syslog/syslog.go delete mode 100644 internal/syslog/syslog_test.go diff --git a/.editorconfig b/.editorconfig index 28e59e2..d62dbfb 100644 --- a/.editorconfig +++ b/.editorconfig @@ -14,6 +14,9 @@ indent_style = space [*.md] trim_trailing_whitespace = false +[*.json] +indent_size = 2 + [*.yml] indent_size = 2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c6a11d2..a8bca39 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v1 with: - go-version: 1.14 + go-version: 1.16 - name: Check gofmt run: test -z "$(gofmt -s -d .)" @@ -22,6 +22,9 @@ jobs: - name: Make sure that go.mod has already been tidied run: go mod tidy && git diff --no-patch --exit-code + - name: Build + run: go build ./cmd/entrello + - name: Run tests run: go test -covermode=count -coverprofile=profile.cov ./... diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a89a40e..d696eb7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v1 with: - go-version: 1.14.x + go-version: 1.16.x - name: GoReleaser uses: goreleaser/goreleaser-action@v1 diff --git a/.gitignore b/.gitignore index b676cd4..dadc57c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -*config.yml *.json +!config.example.json diff --git a/README.md b/README.md index 225eb50..c490415 100644 --- a/README.md +++ b/README.md @@ -4,84 +4,76 @@ [![Go Report Card](https://goreportcard.com/badge/github.com/utkuufuk/entrello)](https://goreportcard.com/report/github.com/utkuufuk/entrello) [![Coverage Status](https://coveralls.io/repos/github/utkuufuk/entrello/badge.svg)](https://coveralls.io/github/utkuufuk/entrello) -Run this as a cron job to periodically poll your data sources via HTTP and automatically create/update/delete Trello cards based on fresh data. +Polls compatible data sources and keeps your Trello cards synchronized with fresh data. Meant to be run as a scheduled job. -An example use case could be to create a Trello card for each GitHub issue that's assigned to you. +Let's say you have an HTTP endpoint that returns GitHub issues assigned to you upon a `GET` request. +You can point `entrello` to that endpoint to keep your GitHub issues synchronized in your Trello board. -Your data sources must return a JSON array of Trello card objects upon a `GET` request. You can import and use the `NewCard` function from `pkg/trello/trello.go` in order to construct Trello card objects. +Each data source must return a JSON array of Trello card objects upon a `GET` request. You can import and use the `NewCard` function from `pkg/trello/trello.go` in order to construct Trello card objects. ## Configuration -Copy and rename `config.example.yml` as `config.yml` (default), then set your own values in `config.yml`. +Copy and rename `config.example.json` as `config.json` (default), then set your own values in `config.json`. You can also use a custom config file path using the `-c` flag: ```sh go run ./cmd/entrello -c /path/to/config/file ``` +Alternatively, you can store the configuration inside the `ENTRELLO_CONFIG` environment variable as a JSON string. + ### Trello You need to set your [Trello API key & token](https://trello.com/app-key) in the configuraiton file, as well as the Trello board ID. -### Telegram -You need a Telegram token & a chat ID in order to receive alerts in case of errors. - ### Data Sources -Each data source must have the following configuration parameters. Refer to `config.example.yml` for examples. +For each data source, the following parameters have to be specified. (See `config.example.json`) -#### **`name`** -Data source name. +- `name` — Data source name. -#### **`endpoint`** -Data source endpoint. `entrello` will make a `GET` request to this endpoint to fetch fresh cards from the data source. +- `endpoint` — Data source endpoint. `entrello` will make a `GET` request to this endpoint to fetch fresh cards from the data source. -#### **`strict`** -When strict mode is enabled, previously auto-generated cards that are no longer present in the fresh data will be deleted. +- `strict` — When strict mode is enabled, previously auto-generated cards that are no longer present in the fresh data will be deleted. For instance, with a GitHub data source, strict mode can be useful for automatically removing previously auto-generated cards for issues/PRs from the board when the corresponding issues/PRs are closed/merged. -For instance, with a GitHub data source, strict mode can be useful for automatically removing previously auto-generated cards for issues/PRs from the board when the corresponding issues/PRs are closed/merged. +- `label_id` — **Distinct** Trello label ID associated with the data source. -#### **`label_id`** -**Distinct** Trello label ID associated with the data source. +- `list_id` — Trello list ID for the data source to determine where to insert new cards. The selected list must be in the same board as configured by the `board_id` parameter. -#### **`list_id`** -Trello list ID for the data source to determine where to insert new cards. The selected list must be in the same board as configured by the `board_id` parameter. +- `period` — Polling period for the data source. Some examples: + ```json + // query at 3rd, 6th, 9th, ... of each month + "period": { + "type": "day", + "interval": 3 + } -#### **`period`** -Polling period for the data source. + // query at 00:00, 02:00, 04:00, ... every day + "period": { + "type": "hour", + "interval": 2 + } -Example configuration: -```yml -# query at 3rd, 6th, 9th, ... of each month -period: - type: day - interval: 3 + // query at XX:00, XX:15, XX:30 and XX:45 every hour + "period": { + "type": "minute", + "interval": 15 + } -# query at 00:00, 02:00, 04:00, ... every day -period: - type: hour - interval: 2 - -# query at XX:00, XX:15, XX:30 and XX:45 every hour -period: - type: minute - interval: 15 - -# query on each execution -period: - type: default - interval: -``` + // query on each execution + "period": { + "type": "default", + "interval": 0 + } + ``` ## Example Cron Job -It's important to make sure that the cron job runs frequently enough to accomodate the most frequent custom interval for a source. It wouldn't make sense to define a custom period of 15 minutes while the cron job only runs every hour. +Make sure that the cron job runs frequently enough to keep up with the most frequent custom interval in your configuration. + +For instance, it wouldn't make sense to define a custom period of 15 minutes while the cron job only runs every hour. -Both of the following jobs run every hour and both assume that `config.yml` is located in the current working directory. +Both of the following jobs run every hour and both assume that `config.json` is located in the current working directory, or its contents are stored within the `ENTRELLO_CONFIG` environment variable. ``` sh -# use "go run" -# 'config.yml' should be located in '/home/utku/git/entrello' -# your go executable may or may not be located in the same location (i.e. /usr/local/go/bin/) -0 * * * * cd /home/utku/git/entrello && /usr/local/go/bin/go run ./cmd/entrello - -# use binary executable -# see releases: https://github.com/utkuufuk/entrello/releases -# 'config.yml' should be located in '/path/to/binary' +# using "go run" +0 * * * * cd /home/you/git/entrello && /usr/local/go/bin/go run ./cmd/entrello + +# use binary executable (see releases: https://github.com/you/entrello/releases) 0 * * * * cd /path/to/binary && ./entrello ``` diff --git a/cmd/entrello/main.go b/cmd/entrello/main.go index 954fb1f..ebe036e 100644 --- a/cmd/entrello/main.go +++ b/cmd/entrello/main.go @@ -3,21 +3,18 @@ package main import ( "flag" "log" + "os" "sync" "time" "github.com/utkuufuk/entrello/internal/config" - "github.com/utkuufuk/entrello/internal/syslog" + "github.com/utkuufuk/entrello/internal/logger" "github.com/utkuufuk/entrello/pkg/trello" ) -var ( - logger syslog.Logger -) - func main() { var configFile string - flag.StringVar(&configFile, "c", "config.yml", "config file path") + flag.StringVar(&configFile, "c", "config.json", "config file path") flag.Parse() cfg, err := config.ReadConfig(configFile) @@ -25,11 +22,10 @@ func main() { log.Fatalf("Could not read configuration: %v", err) } - logger = syslog.NewLogger(cfg.Telegram) - loc, err := time.LoadLocation(cfg.TimezoneLocation) if err != nil { - logger.Fatalf("Invalid timezone location: %v", loc) + logger.Error("Invalid timezone location: %v", loc) + os.Exit(1) } sources, labels := getSources(cfg.Sources, time.Now().In(loc)) @@ -39,11 +35,13 @@ func main() { client, err := trello.NewClient(cfg.Trello) if err != nil { - logger.Fatalf("Could not create trello client: %v", err) + logger.Error("Could not create trello client: %v", err) + os.Exit(1) } if err := client.LoadBoard(labels); err != nil { - logger.Fatalf("Could not load existing cards from the board: %v", err) + logger.Error("Could not load existing cards from the board: %v", err) + os.Exit(1) } var wg sync.WaitGroup diff --git a/cmd/entrello/source.go b/cmd/entrello/source.go index c6f6442..44a3cda 100644 --- a/cmd/entrello/source.go +++ b/cmd/entrello/source.go @@ -9,6 +9,7 @@ import ( "time" "github.com/utkuufuk/entrello/internal/config" + "github.com/utkuufuk/entrello/internal/logger" "github.com/utkuufuk/entrello/pkg/trello" ) @@ -17,7 +18,7 @@ func getSources(srcArr []config.Source, now time.Time) (sources []config.Source, for _, src := range srcArr { if ok, err := shouldQuery(src, now); !ok { if err != nil { - logger.Errorf("could not check if '%s' should be queried or not, skipping", src.Name) + logger.Error("could not check if '%s' should be queried or not, skipping", src.Name) } continue } @@ -64,7 +65,7 @@ func process(src config.Source, client trello.Client, wg *sync.WaitGroup) { resp, err := http.Get(src.Endpoint) if err != nil { - logger.Errorf("could not make GET request to source '%s' endpoint: %v", src.Name, err) + logger.Error("could not make GET request to source '%s' endpoint: %v", src.Name, err) return } defer resp.Body.Close() @@ -75,23 +76,23 @@ func process(src config.Source, client trello.Client, wg *sync.WaitGroup) { if err != nil { msg = err.Error() } - logger.Errorf("could not retrieve cards from source '%s': %v", src.Name, msg) + logger.Error("could not retrieve cards from source '%s': %v", src.Name, msg) return } var cards []trello.Card if err = json.NewDecoder(resp.Body).Decode(&cards); err != nil { - logger.Errorf("could not decode cards received from source '%s': %v", src.Name, err) + logger.Error("could not decode cards received from source '%s': %v", src.Name, err) return } new, stale := client.FilterNewAndStale(cards, src.Label) for _, c := range new { if err := client.CreateCard(c, src.Label, src.List); err != nil { - logger.Errorf("could not create Trello card: %v", err) + logger.Error("could not create Trello card: %v", err) continue } - logger.Debugf("created new card: %s", c.Name) + logger.Info("created new card: %s", c.Name) } if !src.Strict { @@ -100,9 +101,9 @@ func process(src config.Source, client trello.Client, wg *sync.WaitGroup) { for _, c := range stale { if err := client.DeleteCard(c); err != nil { - logger.Errorf("could not delete Trello card: %v", err) + logger.Error("could not delete Trello card: %v", err) continue } - logger.Debugf("deleted stale card: %s", c.Name) + logger.Info("deleted stale card: %s", c.Name) } } diff --git a/config.example.json b/config.example.json new file mode 100644 index 0000000..63147f8 --- /dev/null +++ b/config.example.json @@ -0,0 +1,43 @@ +{ + "timezone_location": "Europe/Istanbul", + "trello": { + "api_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "api_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", + "board_id": "xxxxxxxxxxxxxxxxxxxxxxxx" + }, + "sources": [ + { + "name": "Github Issues", + "endpoint": "http://", + "strict": true, + "label_id": "xxxxxxxxxxxxxxxxxxxxxxxx", + "list_id": "xxxxxxxxxxxxxxxxxxxxxxxx", + "period": { + "type": "minute", + "interval": 15 + } + }, + { + "name": "Google Spreadsheet Habits", + "endpoint": "http://", + "strict": true, + "label_id": "xxxxxxxxxxxxxxxxxxxxxxxx", + "list_id": "xxxxxxxxxxxxxxxxxxxxxxxx", + "period": { + "type": "hour", + "interval": 1 + } + }, + { + "name": "TodoDock", + "endpoint": "http://", + "strict": true, + "label_id": "xxxxxxxxxxxxxxxxxxxxxxxx", + "list_id": "xxxxxxxxxxxxxxxxxxxxxxxx", + "period": { + "type": "default", + "interval": 0 + } + } + ] +} diff --git a/config.example.yml b/config.example.yml deleted file mode 100644 index e504eba..0000000 --- a/config.example.yml +++ /dev/null @@ -1,39 +0,0 @@ -timezone_location: Europe/Istanbul - -trello: - api_key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - api_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - board_id: xxxxxxxxxxxxxxxxxxxxxxxx - -telegram: - enabled: true - token: xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - chat_id: 1234567890 - -sources: - - name: "Github Issues" - endpoint: https://:/entrello - strict: true - label_id: xxxxxxxxxxxxxxxxxxxxxxxx - list_id: xxxxxxxxxxxxxxxxxxxxxxxx - period: - type: minute - interval: 15 - - - name: "TodoDock" - endpoint: https://:/entrello - strict: false - label_id: xxxxxxxxxxxxxxxxxxxxxxxx - list_id: xxxxxxxxxxxxxxxxxxxxxxxx - period: - type: hour - interval: 1 - - - name: "Google Spreadsheet Habits" - endpoint: https://:/entrello - strict: true - label_id: xxxxxxxxxxxxxxxxxxxxxxxx - list_id: xxxxxxxxxxxxxxxxxxxxxxxx - period: - type: hour - interval: 12 diff --git a/go.mod b/go.mod index c483994..ab64bb0 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,7 @@ go 1.13 require ( github.com/adlio/trello v1.8.0 - github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible github.com/google/go-cmp v0.5.4 - github.com/kr/text v0.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect - github.com/technoweenie/multipartstreamer v1.0.1 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect - gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect - gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 1d90fc6..349033d 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,10 @@ github.com/adlio/trello v1.8.0 h1:VU/1zwzuRzATsFC8WiK4f8R0HHQPWpf2H658KEchsmA= github.com/adlio/trello v1.8.0/go.mod h1:l2068AhUuUuQ9Vsb95ECMueHThYyAj4e85lWPmr2/LE= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible h1:2cauKuaELYAEARXRkq2LrJ0yDDv1rW7+wrTEdVL3uaU= -github.com/go-telegram-bot-api/telegram-bot-api v4.6.4+incompatible/go.mod h1:qf9acutJ8cwBUhm1bqgz6Bei9/C/c93FPDljKWwsOgM= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM= -github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= diff --git a/internal/config/config.go b/internal/config/config.go index 0837d44..1af1022 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -1,10 +1,11 @@ package config import ( + "encoding/json" "fmt" "os" - "gopkg.in/yaml.v2" + "github.com/utkuufuk/entrello/internal/logger" ) const ( @@ -15,47 +16,51 @@ const ( ) type Period struct { - Type string `yaml:"type"` - Interval int `yaml:"interval"` + Type string `json:"type"` + Interval int `json:"interval"` } type Source struct { - Name string `yaml:"name"` - Endpoint string `yaml:"endpoint"` - Strict bool `yaml:"strict"` - Label string `yaml:"label_id"` - List string `yaml:"list_id"` - Period Period `yaml:"period"` + Name string `json:"name"` + Endpoint string `json:"endpoint"` + Strict bool `json:"strict"` + Label string `json:"label_id"` + List string `json:"list_id"` + Period Period `json:"period"` } type Trello struct { - ApiKey string `yaml:"api_key"` - ApiToken string `yaml:"api_token"` - BoardId string `yaml:"board_id"` -} - -type Telegram struct { - Enabled bool `yaml:"enabled"` - Token string `yaml:"token"` - ChatId int64 `yaml:"chat_id"` + ApiKey string `json:"api_key"` + ApiToken string `json:"api_token"` + BoardId string `json:"board_id"` } type Config struct { - TimezoneLocation string `yaml:"timezone_location"` - Trello Trello `yaml:"trello"` - Sources []Source `yaml:"sources"` - Telegram Telegram `yaml:"telegram"` + TimezoneLocation string `json:"timezone_location"` + Trello Trello `json:"trello"` + Sources []Source `json:"sources"` } -// ReadConfig reads the YAML config file & decodes all parameters func ReadConfig(fileName string) (cfg Config, err error) { + jsonStr := os.Getenv("ENTRELLO_CONFIG") + if jsonStr != "" { + logger.Info("Attempting to read configuration from environment variable 'ENTRELLO_CONFIG'") + err = json.Unmarshal([]byte(jsonStr), &cfg) + if err != nil { + return cfg, fmt.Errorf("could not parse config stored in the environment variable: %s", err) + } + + return cfg, nil + } + + logger.Info("Attempting to read configuration from file '%s'", fileName) f, err := os.Open(fileName) if err != nil { return cfg, fmt.Errorf("could not open config file: %v", err) } defer f.Close() - decoder := yaml.NewDecoder(f) + decoder := json.NewDecoder(f) err = decoder.Decode(&cfg) return cfg, err } diff --git a/internal/config/config_test.go b/internal/config/config_test.go deleted file mode 100644 index c537e2f..0000000 --- a/internal/config/config_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package config - -import ( - "testing" -) - -func TestReadConfig(t *testing.T) { - tt := []struct { - name string - filename string - err bool - }{ - { - name: "valid file", - filename: "../../config.example.yml", - err: false, - }, - { - name: "non-existing file", - filename: "./no-way-this-file-exists.yml", - err: true, - }, - { - name: "illegal YAML syntax", - filename: "../../README.md", - err: true, - }, - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - _, err := ReadConfig(tc.filename) - - if (err != nil && !tc.err) || err == nil && tc.err { - t.Errorf("did not expect to get '%v' error", err) - } - }) - } -} diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..d52f57e --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,23 @@ +package logger + +import ( + "fmt" + "log" +) + +func Info(msg string, v ...interface{}) { + logf("[INFO]", msg, v...) +} + +func Warn(msg string, v ...interface{}) { + logf("[WARN]", msg, v...) +} + +func Error(msg string, v ...interface{}) { + logf("[ERROR]", msg, v...) +} + +func logf(prefix, msg string, v ...interface{}) { + msg = fmt.Sprintf(msg, v...) + log.Printf("%s %s\n", prefix, msg) +} diff --git a/internal/syslog/syslog.go b/internal/syslog/syslog.go deleted file mode 100644 index 9cd89e0..0000000 --- a/internal/syslog/syslog.go +++ /dev/null @@ -1,66 +0,0 @@ -package syslog - -import ( - "fmt" - "log" - "os" - - tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api" - "github.com/utkuufuk/entrello/internal/config" -) - -type Logger struct { - enabled bool - api *tgbotapi.BotAPI - chatId int64 -} - -// NewLogger creates a new system logger instance -func NewLogger(cfg config.Telegram) (l Logger) { - if !cfg.Enabled { - return l - } - - api, err := tgbotapi.NewBotAPI(cfg.Token) - if err != nil { - log.Printf("could not create Telegram Logger: %v", err) - return l - } - return Logger{true, api, cfg.ChatId} -} - -// Debugf logs an debug message to stdout, but doesn't send a Telegram notification -func (l Logger) Debugf(msg string, v ...interface{}) { - msg = fmt.Sprintf(msg, v...) - log.Println(msg) -} - -// Printf logs an informational message to stdout, and also sends a Telegram notification if enabled -func (l Logger) Printf(msg string, v ...interface{}) { - l.logf("Entrello:", msg, v...) -} - -// Errorf logs an error message to stdout, and also sends a Telegram notification if enabled -func (l Logger) Errorf(msg string, v ...interface{}) { - l.logf("Entrello Error:", msg, v...) -} - -// Fatalf works like Errorf, but it returns with a non-zero exit code after logging -func (l Logger) Fatalf(msg string, v ...interface{}) { - l.Errorf(msg, v...) - os.Exit(1) -} - -// logf prints the message to stdout, and after prepending the given prefix to the message, -// also sends a Telegram notification if enabled -func (l Logger) logf(prefix, msg string, v ...interface{}) { - msg = fmt.Sprintf(msg, v...) - log.Println(msg) - - if !l.enabled || l.api == nil || l.chatId == 0 { - return - } - msg = fmt.Sprintf("%s %s", prefix, msg) - m := tgbotapi.NewMessage(l.chatId, msg) - l.api.Send(m) -} diff --git a/internal/syslog/syslog_test.go b/internal/syslog/syslog_test.go deleted file mode 100644 index cb24805..0000000 --- a/internal/syslog/syslog_test.go +++ /dev/null @@ -1,81 +0,0 @@ -package syslog - -import ( - "bytes" - "log" - "os" - "strings" - "testing" - - "github.com/utkuufuk/entrello/internal/config" -) - -func TestSystemLog(t *testing.T) { - tt := []struct { - name string - cfg config.Telegram - }{ - { - name: "null config", - cfg: config.Telegram{Enabled: false, Token: "", ChatId: 0}, - }, - { - name: "psuedo-valid config but disabled", - cfg: config.Telegram{ - Enabled: false, - Token: "xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - ChatId: 1234567890, - }, - }, - { - name: "psuedo-valid config and enabled", - cfg: config.Telegram{ - Enabled: false, - Token: "xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - ChatId: 1234567890, - }, - }, - { - name: "enabled but invalid token", - cfg: config.Telegram{ - Enabled: true, - Token: "banana", - ChatId: 1234567890, - }, - }, - { - name: "enabled but invalid chat ID", - cfg: config.Telegram{ - Enabled: true, - Token: "xxxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", - ChatId: 0, - }, - }, - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - msg := "test" - logger := NewLogger(tc.cfg) - var i, e bytes.Buffer - - log.SetOutput(&i) - logger.Printf(msg) - - log.SetOutput(&e) - logger.Errorf(msg) - - log.SetOutput(os.Stderr) - - oi := i.String() - if !strings.Contains(oi, msg) { - t.Errorf("wanted '%s' to contain '%s'", oi, msg) - } - - oe := e.String() - if !strings.Contains(oe, msg) { - t.Errorf("wanted '%s' to contain '%s'", oe, msg) - } - }) - } -}