commit ebfa3f498c8cca2ab0fb95b8d6e837a9c6aa83c7
parent 432910f4614aefe13788398610e84e8d54da8759
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date: Mon, 28 Apr 2025 10:49:56 +0200
add getopt and logging
Diffstat:
4 files changed, 103 insertions(+), 44 deletions(-)
diff --git a/cmd/taldir-server/main.go b/cmd/taldir-server/main.go
@@ -35,6 +35,7 @@ import (
"gopkg.in/ini.v1"
"gorm.io/driver/postgres"
+ "rsc.io/getopt"
taldir "taler.net/taldir/pkg/rest"
)
@@ -42,21 +43,48 @@ var (
t taldir.Taldir
version string
taldirdatahome string
+ verbose bool // FIXME do something with this?
)
func handleRequests(t *taldir.Taldir) {
log.Fatal(http.ListenAndServe(t.Cfg.Ini.Section("taldir").Key("bind_to").MustString("localhost:11000"), t.Router))
}
+func printHelp() {
+ fmt.Print("taler-directory\n\n")
+ getopt.PrintDefaults()
+ fmt.Print("\nReport bugs to gnunet-developers@gnu.org.\n",
+ "Home page: https://taler.net\n",
+ "General help using GNU software: http://www.gnu.org/gethelp/\n")
+}
+
func main() {
var cfgFlag = flag.String("c", "", "Configuration file to use")
+ getopt.Alias("c", "config")
+ // FIXME use flags
+ loglevelStringOpt := flag.String("L", "INFO", "Log level to use. DEBUG, INFO, WARNING or ERROR")
+ getopt.Alias("L", "loglevel")
+ var verboseFlag = flag.Bool("v", false, "Verbose")
+ getopt.Alias("v", "verbose")
+ var helpFlag = flag.Bool("h", false, "Print help")
+ getopt.Alias("h", "help")
- flag.Parse()
- log.Println(version)
+ getopt.Parse()
cfgfile := "taldir.conf"
if len(*cfgFlag) != 0 {
cfgfile = *cfgFlag
}
+ if *helpFlag {
+ printHelp()
+ return
+ }
+ verbose = *verboseFlag
+ loglevel := taldir.LogInfo
+ for loglevelNum, loglevelString := range taldir.LoglevelStringMap {
+ if loglevelString == *loglevelStringOpt {
+ loglevel = loglevelNum
+ }
+ }
t := taldir.Taldir{}
cfg, err := ini.LooseLoad(cfgfile)
if err != nil {
@@ -76,6 +104,7 @@ func main() {
Version: version,
Datahome: taldirdatahome,
Db: db,
+ Loglevel: loglevel,
})
handleRequests(&t)
}
diff --git a/go.mod b/go.mod
@@ -12,6 +12,7 @@ require (
gorm.io/driver/postgres v1.5.11
gorm.io/driver/sqlite v1.5.7
gorm.io/gorm v1.26.0
+ rsc.io/getopt v0.0.0-20170811000552-20be20937449
)
require (
diff --git a/go.sum b/go.sum
@@ -59,3 +59,5 @@ gorm.io/driver/sqlite v1.5.7 h1:8NvsrhP0ifM7LX9G4zPB97NwovUakUxc+2V2uuf3Z1I=
gorm.io/driver/sqlite v1.5.7/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
gorm.io/gorm v1.26.0 h1:9lqQVPG5aNNS6AyHdRiwScAVnXHg/L/Srzx55G5fOgs=
gorm.io/gorm v1.26.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE=
+rsc.io/getopt v0.0.0-20170811000552-20be20937449 h1:UukjJOsjQH0DIuyyrcod6CXHS6cdaMMuJmrt+SN1j4A=
+rsc.io/getopt v0.0.0-20170811000552-20be20937449/go.mod h1:dhCdeqAxkyt5u3/sKRkUXuHaMXUu1Pt13GTQAM2xnig=
diff --git a/pkg/rest/taldir.go b/pkg/rest/taldir.go
@@ -68,6 +68,9 @@ type TaldirConfig struct {
// The merchant connection to use
Merchant merchant.Merchant
+
+ // The loglevel to use
+ Loglevel LogLevel
}
// Taldir is the primary object of the Taldir service
@@ -138,6 +141,9 @@ type Taldir struct {
// I18n
I18n *i18n.I18n
+
+ // Logger
+ Logger *log.Logger
}
type ValidatorType string
@@ -339,18 +345,17 @@ func (t *Taldir) isPMSValid(pms string) (err error) {
if t.ValidPMSRegex != "" {
matched, _ := regexp.MatchString(t.ValidPMSRegex, pms)
if !matched {
- return errors.New(fmt.Sprintf("Payment System Address `%s' invalid", pms)) // TODO i18n
+ return fmt.Errorf("payment System Address `%s' invalid", pms) // TODO i18n
}
}
return
}
func (v *Validator) isAliasValid(alias string) (err error) {
- log.Println(v.ValidAliasRegex)
if v.ValidAliasRegex != "" {
matched, _ := regexp.MatchString(v.ValidAliasRegex, alias)
if !matched {
- return errors.New(fmt.Sprintf("Alias `%s' invalid", alias)) // TODO i18n
+ return fmt.Errorf("alias `%s' invalid", alias) // TODO i18n
}
}
return
@@ -415,20 +420,20 @@ func (t *Taldir) validationRequest(w http.ResponseWriter, r *http.Request) {
return
}
} else {
- log.Println("New solution timeframe set.")
+ t.Logf(LogDebug, "New solution timeframe set.")
validation.LastSolutionTimeframeStart = time.Now()
validation.SolutionAttemptCount = 1
}
t.Db.Save(&validation)
expectedSolution := util.GenerateSolution(validation.TargetUri, validation.Challenge)
- log.Printf("Expected solution: `%s', given: `%s'\n", expectedSolution, confirm.Solution)
+ t.Logf(LogDebug, "Expected solution: `%s', given: `%s'\n", expectedSolution, confirm.Solution)
if confirm.Solution != expectedSolution {
w.WriteHeader(http.StatusForbidden)
return
}
err = t.Db.Delete(&validation).Error
if err != nil {
- log.Println("Error deleting validation")
+ t.Logf(LogError, "Error deleting validation")
w.WriteHeader(http.StatusInternalServerError)
return
}
@@ -439,7 +444,7 @@ func (t *Taldir) validationRequest(w http.ResponseWriter, r *http.Request) {
err = t.Db.First(&entry, "hs_address = ?", entry.HsAddress).Error
if err == nil {
if validation.TargetUri == "" {
- log.Printf("Deleted entry for '%s´\n", entry.HsAddress)
+ t.Logf(LogDebug, "Deleted entry for '%s´\n", entry.HsAddress)
err = t.Db.Delete(&entry).Error
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
@@ -450,7 +455,7 @@ func (t *Taldir) validationRequest(w http.ResponseWriter, r *http.Request) {
}
} else {
if validation.TargetUri == "" {
- log.Printf("Validated a deletion request but no entry found for `%s'\n", entry.HsAddress)
+ t.Logf(LogWarning, "Validated a deletion request but no entry found for `%s'\n", entry.HsAddress)
} else {
err = t.Db.Create(&entry).Error
if err != nil {
@@ -544,7 +549,7 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) {
}
rateLimited, err := t.isRateLimited(hAddress)
if nil != err {
- log.Printf("Error checking rate limit! %v", err)
+ t.Logf(LogError, "Error checking rate limit! %v", err)
w.WriteHeader(http.StatusInternalServerError)
return
} else if rateLimited {
@@ -602,7 +607,7 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) {
if paytoErr != nil {
fmt.Println(paytoErr)
w.WriteHeader(http.StatusInternalServerError)
- log.Println(paytoErr)
+ t.Logf(LogError, paytoErr.Error()+"\n")
return
}
if len(payto) != 0 {
@@ -615,23 +620,23 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) {
}
err = t.Db.Save(&validation).Error
if err != nil {
- log.Println(err)
+ t.Logf(LogError, err.Error()+"\n")
w.WriteHeader(500)
return
}
path, err := exec.LookPath(validator.Command)
if err != nil {
- log.Println(err)
+ t.Logf(LogError, err.Error()+"\n")
t.Db.Delete(&validation)
w.WriteHeader(500)
return
}
- log.Printf("Found `%s' in path as `%s'\n", validator.Command, path)
+ t.Logf(LogDebug, "Found `%s' in path as `%s'\n", validator.Command, path)
topic := t.I18n.GetLocale(r).GetMessage("taldirRegTopic")
link := t.Host + "/register/" + url.QueryEscape(validation.HAddress) + "/" + url.QueryEscape(validation.Challenge) + "?address=" + url.QueryEscape(req.Address)
message := t.I18n.GetLocale(r).GetMessage("taldirRegMessage", link)
out, err := exec.Command(path, req.Address, validation.Challenge, topic, message).Output()
- log.Printf("Executing `%s %s %s %s %s`\n", path, req.Address, validation.Challenge, topic, message)
+ t.Logf(LogDebug, "Executing `%s %s %s %s %s`\n", path, req.Address, validation.Challenge, topic, message)
if err != nil {
fmt.Printf("%s, %v\n", out, err)
t.Db.Delete(&validation)
@@ -679,7 +684,7 @@ func (t *Taldir) validationPage(w http.ResponseWriter, r *http.Request) {
return
}
if vars["challenge"] != validation.Challenge {
- log.Println("Solution does not match challenge!")
+ t.Logf(LogWarning, "Solution does not match challenge!\n")
w.WriteHeader(400)
return
}
@@ -696,14 +701,14 @@ func (t *Taldir) validationPage(w http.ResponseWriter, r *http.Request) {
expectedHAddress := util.Base32CrockfordEncode(h.Sum(nil))
if expectedHAddress != validation.HAddress {
- log.Println("Address does not match challenge!")
+ t.Logf(LogWarning, "Address does not match challenge!\n")
w.WriteHeader(400)
return
}
// FIXME: This is kind of broken and probably requires wallet support/integration first
if validation.RequiresPayment {
- log.Println("Validation requires payment")
+ t.Logf(LogWarning, "Validation requires payment\n")
walletLink = "taler://taldir/" + vars["h_address"] + "/" + vars["challenge"] + "-wallet"
png, err = qrcode.Encode(walletLink, qrcode.Medium, 256)
if err != nil {
@@ -772,7 +777,7 @@ func (t *Taldir) privacyResponse(w http.ResponseWriter, r *http.Request) {
func (t *Taldir) landingPage(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
translateFunc := t.I18n.GetLocale(r).GetMessage
- fullData := map[string]interface{}{
+ fullData := map[string]any{
"validators": t.Validators,
"version": t.Cfg.Version,
"lookupOrRegisterCardTitle": template.HTML(translateFunc("lookup")),
@@ -786,13 +791,12 @@ func (t *Taldir) landingPage(w http.ResponseWriter, r *http.Request) {
if err != nil {
fmt.Println(err)
}
- return
}
func (t *Taldir) imprintPage(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
translateFunc := t.I18n.GetLocale(r).GetMessage
- fullData := map[string]interface{}{
+ fullData := map[string]any{
"validators": t.Validators,
"version": t.Cfg.Version,
"productDisclaimerShort": template.HTML(translateFunc("productDisclaimerShort")),
@@ -803,13 +807,12 @@ func (t *Taldir) imprintPage(w http.ResponseWriter, r *http.Request) {
if err != nil {
fmt.Println(err)
}
- return
}
func (t *Taldir) aboutPage(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
translateFunc := t.I18n.GetLocale(r).GetMessage
- fullData := map[string]interface{}{
+ fullData := map[string]any{
"validators": t.Validators,
"version": t.Cfg.Version,
"productDisclaimerShort": template.HTML(translateFunc("productDisclaimerShort")),
@@ -821,7 +824,6 @@ func (t *Taldir) aboutPage(w http.ResponseWriter, r *http.Request) {
if err != nil {
fmt.Println(err)
}
- return
}
func (t *Taldir) methodLookupResultPage(w http.ResponseWriter, r *http.Request) {
@@ -842,7 +844,7 @@ func (t *Taldir) methodLookupResultPage(w http.ResponseWriter, r *http.Request)
emsg := ""
found := false
if nil != err {
- log.Printf("Not a valid alias\n")
+ t.Logf(LogWarning, "Not a valid alias\n")
emsg = t.I18n.GetLocale(r).GetMessage("aliasInvalid", alias)
http.Redirect(w, r, fmt.Sprintf("/landing/"+val.Name+"?error=%s", emsg), http.StatusSeeOther)
return
@@ -852,12 +854,12 @@ func (t *Taldir) methodLookupResultPage(w http.ResponseWriter, r *http.Request)
hsAddress := saltHAddress(hAddress, t.Salt)
err = t.Db.First(&entry, "hs_address = ?", hsAddress).Error
if err != nil {
- log.Printf("`%s` not found.\n", hAddress)
+ t.Logf(LogError, "`%s` not found.\n", hAddress)
} else {
found = true
}
}
- fullData := map[string]interface{}{
+ fullData := map[string]any{
"version": t.Cfg.Version,
"available": !found,
"method": val.Name,
@@ -940,7 +942,7 @@ func (t *Taldir) getFileName(relativeFileName string) string {
if errors.Is(err, os.ErrNotExist) {
_, err := os.Stat(t.Cfg.Datahome + "/" + relativeFileName)
if errors.Is(err, os.ErrNotExist) {
- log.Printf("Tried fallback not found %s\n", t.Cfg.Datahome+"/"+relativeFileName)
+ t.Logf(LogError, "Tried fallback not found %s\n", t.Cfg.Datahome+"/"+relativeFileName)
return ""
}
return t.Cfg.Datahome + "/" + relativeFileName
@@ -948,9 +950,34 @@ func (t *Taldir) getFileName(relativeFileName string) string {
return relativeFileName
}
+type LogLevel int
+
+const (
+ LogError LogLevel = iota
+ LogWarning
+ LogInfo
+ LogDebug
+)
+
+var LoglevelStringMap = map[LogLevel]string{
+ LogDebug: "DEBUG",
+ LogError: "ERROR",
+ LogWarning: "WARN",
+ LogInfo: "INFO",
+}
+
+func (t *Taldir) Logf(loglevel LogLevel, fmt string, args ...any) {
+ if loglevel < t.Cfg.Loglevel {
+ return
+ }
+ t.Logger.SetPrefix("taler-directory - " + LoglevelStringMap[loglevel] + " ")
+ t.Logger.Printf(fmt, args...)
+}
+
// Initialize the Taldir instance with cfgfile
func (t *Taldir) Initialize(cfg TaldirConfig) {
t.Cfg = cfg
+ t.Logger = log.New(os.Stdout, "taler-directory:", log.LstdFlags)
// FIXME localedir
i18n, err := i18n.New(i18n.Glob("./locales/*/*", i18n.LoaderConfig{
// Set custom functions per locale!
@@ -973,14 +1000,14 @@ func (t *Taldir) Initialize(cfg TaldirConfig) {
continue
}
if !sec.HasKey("type") {
- log.Printf("`type` key in section `[%s]` not found, disabling validator.\n", sec.Name())
+ t.Logger.Printf("`type` key in section `[%s]` not found, disabling validator.\n", sec.Name())
continue
}
vname := strings.TrimPrefix(sec.Name(), "taldir-validator-")
vlandingPageTplFile := sec.Key("registration_page").MustString(t.getFileName("web/templates/landing_" + vname + ".html"))
vlandingPageTpl, err := template.ParseFiles(vlandingPageTplFile, navTplFile, footerTplFile)
if err != nil {
- log.Printf("`%s` template not found, disabling validator `%s`.\n", vlandingPageTplFile, vname)
+ t.Logger.Printf("`%s` template not found, disabling validator `%s`.\n", vlandingPageTplFile, vname)
continue
}
t.Validators[vname] = Validator{
@@ -1001,14 +1028,14 @@ func (t *Taldir) Initialize(cfg TaldirConfig) {
t.ValidPMSRegex = cfg.Ini.Section("taldir").Key("valid_payment_system_address_regex").MustString(".*")
t.ValidationTimeframe, err = time.ParseDuration(validationTTLStr)
if err != nil {
- log.Fatal(err)
+ t.Logger.Fatal(err)
os.Exit(1)
}
retryTimeframeStr := cfg.Ini.Section("taldir").Key("solution_attempt_timeframe").MustString("1h")
t.SolutionTimeframe, err = time.ParseDuration(retryTimeframeStr)
if err != nil {
- log.Fatal(err)
+ t.Logger.Fatal(err)
os.Exit(1)
}
t.MonthlyFee = cfg.Ini.Section("taldir").Key("monthly_fee").MustString("KUDOS:0")
@@ -1027,52 +1054,52 @@ func (t *Taldir) Initialize(cfg TaldirConfig) {
panic(err)
}
if cfg.Ini.Section("taldir").Key("purge_mappings_on_startup_dangerous").MustBool(false) {
- log.Println("DANGER Purging mappings!")
+ t.Logger.Println("DANGER Purging mappings!")
tx := t.Db.Where("1 = 1").Delete(&Entry{})
- log.Printf("Deleted %d entries.\n", tx.RowsAffected)
+ t.Logger.Printf("Deleted %d entries.\n", tx.RowsAffected)
}
// Clean up validations
validationExpStr := cfg.Ini.Section("taldir").Key("validation_expiration").MustString("24h")
validationExp, err := time.ParseDuration(validationExpStr)
if err != nil {
- log.Fatal(err)
+ t.Logger.Fatal(err)
os.Exit(1)
}
go func() {
for {
tx := t.Db.Where("created_at < ?", time.Now().Add(-validationExp)).Delete(&Validation{})
- log.Printf("Cleaned up %d stale validations.\n", tx.RowsAffected)
+ t.Logger.Printf("Cleaned up %d stale validations.\n", tx.RowsAffected)
time.Sleep(validationExp)
}
}()
imprintTplFile := cfg.Ini.Section("taldir").Key("imprint_page").MustString(t.getFileName("web/templates/imprint.html"))
t.ImprintTpl, err = template.ParseFiles(imprintTplFile, navTplFile, footerTplFile)
if err != nil {
- log.Fatal(err)
+ t.Logger.Fatal(err)
os.Exit(1)
}
validationLandingTplFile := cfg.Ini.Section("taldir").Key("validation_landing").MustString(t.getFileName("web/templates/validation_landing.html"))
t.ValidationTpl, err = template.ParseFiles(validationLandingTplFile, navTplFile, footerTplFile)
if err != nil {
- log.Fatal(err)
+ t.Logger.Fatal(err)
os.Exit(1)
}
landingTplFile := cfg.Ini.Section("taldir").Key("landing_page").MustString(t.getFileName("web/templates/landing.html"))
t.LandingPageTpl, err = template.ParseFiles(landingTplFile, navTplFile, footerTplFile)
if err != nil {
- log.Fatal(err)
+ t.Logger.Fatal(err)
os.Exit(1)
}
lookupResultTplFile := cfg.Ini.Section("taldir").Key("lookup_result_page").MustString(t.getFileName("web/templates/lookup_result.html"))
t.LookupResultPageTpl, err = template.ParseFiles(lookupResultTplFile, navTplFile, footerTplFile)
if err != nil {
- log.Fatal(err)
+ t.Logger.Fatal(err)
os.Exit(1)
}
aboutTplFile := cfg.Ini.Section("taldir").Key("about_page").MustString(t.getFileName("web/templates/about.html"))
t.AboutPageTpl, err = template.ParseFiles(aboutTplFile, navTplFile, footerTplFile)
if err != nil {
- log.Fatal(err)
+ t.Logger.Fatal(err)
os.Exit(1)
}
t.Salt = os.Getenv("TALDIR_SALT")
@@ -1084,12 +1111,12 @@ func (t *Taldir) Initialize(cfg TaldirConfig) {
registrationCost, _ := talerutil.ParseAmount(t.MonthlyFee)
merchConfig, err := t.Merchant.GetConfig()
if err != nil {
- log.Fatal(err)
+ t.Logger.Fatal(err)
os.Exit(1)
}
currencySpec, currencySupported := merchConfig.Currencies[registrationCost.Currency]
for !currencySupported {
- log.Fatalf("Currency `%s' not supported by merchant!\n", registrationCost.Currency)
+ t.Logger.Fatalf("Currency `%s' not supported by merchant!\n", registrationCost.Currency)
os.Exit(1)
}
t.CurrencySpec = currencySpec