Skip to content

Commit

Permalink
refactor: use slog for logging
Browse files Browse the repository at this point in the history
Add structured logging for greater viability into the transaction
mappings. Also use a decoder for the YNAB cleared status, simply done to
have less log statements.

commit-id:2fc69e1c
  • Loading branch information
martinohansen committed Sep 16, 2024
1 parent b57073c commit a92746a
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 78 deletions.
51 changes: 27 additions & 24 deletions cmd/ynabber/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package main
import (
"fmt"
"log"
"log/slog"
"os"
"strings"
"time"

"github.com/carlmjohnson/versioninfo"
Expand All @@ -15,27 +15,29 @@ import (
"github.com/martinohansen/ynabber/writer/ynab"
)

func main() {
log.Println("Version:", versioninfo.Short())
func setupLogging(debug bool) {
programLevel := slog.LevelInfo
if debug {
programLevel = slog.LevelDebug
}
logger := slog.New(slog.NewTextHandler(
os.Stderr, &slog.HandlerOptions{
Level: programLevel,
}))
slog.SetDefault(logger)
}

func main() {
// Read config from env
var cfg ynabber.Config
err := envconfig.Process("", &cfg)
if err != nil {
log.Fatal(err.Error())
}

// Check that some values are valid
cfg.YNAB.Cleared = strings.ToLower(cfg.YNAB.Cleared)
if cfg.YNAB.Cleared != "cleared" &&
cfg.YNAB.Cleared != "uncleared" &&
cfg.YNAB.Cleared != "reconciled" {
log.Fatal("YNAB_CLEARED must be one of cleared, uncleared or reconciled")
}

if cfg.Debug {
log.Printf("Config: %+v\n", cfg)
}
setupLogging(cfg.Debug)
slog.Info("starting...", "version", versioninfo.Short())
slog.Debug("", "config", cfg)

ynabber := ynabber.Ynabber{}
for _, reader := range cfg.Readers {
Expand All @@ -49,7 +51,7 @@ func main() {
for _, writer := range cfg.Writers {
switch writer {
case "ynab":
ynabber.Writers = append(ynabber.Writers, ynab.Writer{Config: &cfg})
ynabber.Writers = append(ynabber.Writers, ynab.NewWriter(&cfg))
case "json":
ynabber.Writers = append(ynabber.Writers, json.Writer{})
default:
Expand All @@ -58,22 +60,23 @@ func main() {
}

for {
err = run(ynabber, cfg.Interval)
start := time.Now()
err = run(ynabber)
if err != nil {
panic(err)
} else {
log.Printf("Run succeeded")
}
if cfg.Interval > 0 {
log.Printf("Waiting %s before running again...", cfg.Interval)
time.Sleep(cfg.Interval)
} else {
os.Exit(0)
slog.Info("run succeeded", "in", time.Since(start))
if cfg.Interval > 0 {
slog.Info("waiting for next run", "in", cfg.Interval)
time.Sleep(cfg.Interval)
} else {
os.Exit(0)
}
}
}
}

func run(y ynabber.Ynabber, interval time.Duration) error {
func run(y ynabber.Ynabber) error {
var transactions []ynabber.Transaction

// Read transactions from all readers
Expand Down
28 changes: 27 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package ynabber

import (
"encoding/json"
"fmt"
"strings"
"time"
)

Expand Down Expand Up @@ -30,6 +32,30 @@ func (accountMap *AccountMap) Decode(value string) error {
return nil
}

type TransactionStatus string

const (
Cleared TransactionStatus = "cleared"
Uncleared TransactionStatus = "uncleared"
Reconciled TransactionStatus = "reconciled"
)

// Decode implements `envconfig.Decoder` for TransactionStatus
func (cs *TransactionStatus) Decode(value string) error {
lowered := strings.ToLower(value)
switch lowered {
case string(Cleared), string(Uncleared), string(Reconciled):
*cs = TransactionStatus(lowered)
return nil
default:
return fmt.Errorf("unknown value %s", value)
}
}

func (cs TransactionStatus) String() string {
return string(cs)
}

// Config is loaded from the environment during execution with cmd/ynabber
type Config struct {
// DataDir is the path for storing files
Expand Down Expand Up @@ -115,7 +141,7 @@ type YNAB struct {
// Default is uncleared for historical reasons but recommend setting this
// to cleared because ynabber transactions are cleared by bank.
// They'd still be unapproved until approved in YNAB.
Cleared string `envconfig:"YNAB_CLEARED" default:"uncleared"`
Cleared TransactionStatus `envconfig:"YNAB_CLEARED" default:"uncleared"`

// SwapFlow changes inflow to outflow and vice versa for any account with a
// IBAN number in the list. This maybe be relevant for credit card accounts.
Expand Down
25 changes: 11 additions & 14 deletions reader/nordigen/nordigen.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package nordigen

import (
"fmt"
"log"
"log/slog"
"regexp"
"strings"

Expand All @@ -12,8 +12,8 @@ import (

type Reader struct {
Config *ynabber.Config

Client *nordigen.Client
logger *slog.Logger
}

// NewReader returns a new nordigen reader or panics
Expand All @@ -26,6 +26,10 @@ func NewReader(cfg *ynabber.Config) Reader {
return Reader{
Config: cfg,
Client: client,
logger: slog.Default().With(
"reader", "nordigen",
"bank_id", cfg.Nordigen.BankID,
),
}
}

Expand Down Expand Up @@ -54,6 +58,7 @@ func (r Reader) toYnabbers(a ynabber.Account, t nordigen.AccountTransactions) ([
y := []ynabber.Transaction{}
for _, v := range t.Transactions.Booked {
transaction, err := r.toYnabber(a, v)
r.logger.Debug("mapping transaction", "from", v, "to", transaction)
if err != nil {
return nil, err
}
Expand All @@ -70,8 +75,9 @@ func (r Reader) Bulk() (t []ynabber.Transaction, err error) {
return nil, fmt.Errorf("failed to authorize: %w", err)
}

log.Printf("Found %v accounts", len(req.Accounts))
r.logger.Info("bulk reading", "accounts", len(req.Accounts))
for _, account := range req.Accounts {
logger := r.logger.With("account", account)
accountMetadata, err := r.Client.GetAccountMetadata(account)
if err != nil {
return nil, fmt.Errorf("failed to get account metadata: %w", err)
Expand All @@ -81,11 +87,7 @@ func (r Reader) Bulk() (t []ynabber.Transaction, err error) {
// requisition.
switch accountMetadata.Status {
case "EXPIRED", "SUSPENDED":
log.Printf(
"Account: %s is %s. Going to recreate the requisition...",
account,
accountMetadata.Status,
)
logger.Info("recreating requisition", "status", accountMetadata.Status)
r.createRequisition()
}

Expand All @@ -95,17 +97,12 @@ func (r Reader) Bulk() (t []ynabber.Transaction, err error) {
IBAN: accountMetadata.Iban,
}

log.Printf("Reading transactions from account: %s", account.Name)

logger.Info("reading transactions")
transactions, err := r.Client.GetAccountTransactions(string(account.ID))
if err != nil {
return nil, fmt.Errorf("failed to get transactions: %w", err)
}

if r.Config.Debug {
log.Printf("Transactions received from Nordigen: %+v", transactions)
}

x, err := r.toYnabbers(account, transactions)
if err != nil {
return nil, fmt.Errorf("failed to convert transaction: %w", err)
Expand Down
8 changes: 4 additions & 4 deletions reader/nordigen/nordigen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ func TestToYnabber(t *testing.T) {
args: args{
account: ynabber.Account{Name: "foo", IBAN: "bar"},
t: nordigen.Transaction{
TransactionId: "foobar",
EntryReference: "",
BookingDate: "2023-02-24",
ValueDate: "2023-02-24",
InternalTransactionId: "foobar",
EntryReference: "",
BookingDate: "2023-02-24",
ValueDate: "2023-02-24",
TransactionAmount: struct {
Amount string "json:\"amount,omitempty\""
Currency string "json:\"currency,omitempty\""
Expand Down
Loading

0 comments on commit a92746a

Please sign in to comment.