(tested) complete replace logrus with zerolog

* remove cobra dependency and split execs to mochi and e2e

* add log init synchronization
This commit is contained in:
Lawrence, Rendall
2022-05-02 03:13:58 +03:00
parent 4d646f7c09
commit c50a532181
36 changed files with 753 additions and 707 deletions
+19 -51
View File
@@ -1,82 +1,50 @@
//go:build e2e
// +build e2e
package main
import (
"flag"
"fmt"
"log"
"math/rand"
"time"
"github.com/anacrolix/torrent/tracker"
"github.com/spf13/cobra"
"github.com/sot-tech/mochi/bittorrent"
"github.com/sot-tech/mochi/pkg/log"
)
func init() {
e2eCmd = &cobra.Command{
Use: "e2e",
Short: "exec e2e tests",
Long: "Execute the Conf end-to-end test suite",
RunE: EndToEndRunCmdFunc,
}
func main() {
httpAddress := flag.String("httpaddr", "http://127.0.0.1:6969/announce", "address of the HTTP tracker")
udpAddress := flag.String("udpaddr", "udp://127.0.0.1:6969", "address of the UDP tracker")
delay := flag.Duration("delay", time.Second, "delay between announces")
flag.Parse()
e2eCmd.Flags().String("httpaddr", "http://127.0.0.1:6969/announce", "address of the HTTP tracker")
e2eCmd.Flags().String("udpaddr", "udp://127.0.0.1:6969", "address of the UDP tracker")
e2eCmd.Flags().Duration("delay", time.Second, "delay between announces")
}
// EndToEndRunCmdFunc implements a Cobra command that runs the end-to-end test
// suite for a Conf build.
func EndToEndRunCmdFunc(cmd *cobra.Command, args []string) error {
delay, err := cmd.Flags().GetDuration("delay")
if err != nil {
return err
}
// Test the HTTP tracker
httpAddr, err := cmd.Flags().GetString("httpaddr")
if err != nil {
return err
}
if len(httpAddr) != 0 {
log.Info("testing HTTP...")
err := test(httpAddr, delay)
if len(*httpAddress) != 0 {
log.Println("testing HTTP...")
err := test(*httpAddress, *delay)
if err != nil {
return err
log.Fatal(err)
}
log.Info("success")
log.Println("success")
}
// Test the UDP tracker.
udpAddr, err := cmd.Flags().GetString("udpaddr")
if err != nil {
return err
}
if len(udpAddr) != 0 {
log.Info("testing UDP...")
err := test(udpAddr, delay)
if len(*udpAddress) != 0 {
log.Println("testing UDP...")
err := test(*udpAddress, *delay)
if err != nil {
return err
log.Fatal(err)
}
log.Info("success")
log.Println("success")
}
return nil
}
func test(addr string, delay time.Duration) error {
b := make([]byte, bittorrent.InfoHashV1Len)
rand.Read(b)
ih, _ := bittorrent.NewInfoHash(b)
return testWithInfohash(ih, addr, delay)
return testWithInfoHash(ih, addr, delay)
}
func testWithInfohash(infoHash bittorrent.InfoHash, url string, delay time.Duration) error {
func testWithInfoHash(infoHash bittorrent.InfoHash, url string, delay time.Duration) error {
var ih [bittorrent.InfoHashV1Len]byte
req := tracker.AnnounceRequest{
InfoHash: ih,
+21 -220
View File
@@ -1,182 +1,17 @@
package main
import (
"context"
"errors"
"fmt"
"flag"
"log"
"os"
"os/signal"
"runtime"
"strings"
"syscall"
"github.com/spf13/cobra"
"github.com/sot-tech/mochi/frontend/http"
"github.com/sot-tech/mochi/frontend/udp"
"github.com/sot-tech/mochi/middleware"
"github.com/sot-tech/mochi/pkg/conf"
"github.com/sot-tech/mochi/pkg/log"
"github.com/sot-tech/mochi/pkg/metrics"
_ "github.com/sot-tech/mochi/pkg/randseed"
"github.com/sot-tech/mochi/pkg/stop"
"github.com/sot-tech/mochi/storage"
l "github.com/sot-tech/mochi/pkg/log"
)
var e2eCmd *cobra.Command
// Run represents the state of a running instance of Conf.
type Run struct {
configFilePath string
storage storage.PeerStorage
logic *middleware.Logic
sg *stop.Group
}
// NewRun runs an instance of Conf.
func NewRun(configFilePath string) (*Run, error) {
r := &Run{
configFilePath: configFilePath,
}
return r, r.Start(nil)
}
// Start begins an instance of Conf.
// It is optional to provide an instance of the peer store to avoid the
// creation of a new one.
func (r *Run) Start(ps storage.PeerStorage) error {
configFile, err := ParseConfigFile(r.configFilePath)
if err != nil {
return fmt.Errorf("failed to read config: %w", err)
}
cfg := configFile.Conf
r.sg = stop.NewGroup()
if len(cfg.MetricsAddr) > 0 {
log.Info().Str("addr", cfg.MetricsAddr).Msg("starting metrics server")
r.sg.Add(metrics.NewServer(cfg.MetricsAddr))
} else {
log.Info().Msg("metrics disabled because of empty address")
}
if ps == nil {
log.Info().Str("name", cfg.Storage.Name).Msg("starting storage")
ps, err = storage.NewStorage(cfg.Storage.Name, cfg.Storage.Config)
if err != nil {
return fmt.Errorf("failed to create storage: %w", err)
}
log.Info().Object("config", ps).Msg("started storage")
}
r.storage = ps
preHooks, err := middleware.HooksFromHookConfigs(cfg.PreHooks, r.storage)
if err != nil {
return fmt.Errorf("failed to validate hook config: %w", err)
}
postHooks, err := middleware.HooksFromHookConfigs(cfg.PostHooks, r.storage)
if err != nil {
return fmt.Errorf("failed to validate hook config: %w", err)
}
r.logic = middleware.NewLogic(cfg.AnnounceInterval, cfg.MinAnnounceInterval, r.storage, preHooks, postHooks)
if len(cfg.HTTPConfig) > 0 {
log.Info().Object("config", cfg.HTTPConfig).Msg("starting HTTP frontend")
httpFE, err := http.NewFrontend(r.logic, cfg.HTTPConfig)
if err == nil {
r.sg.Add(httpFE)
} else if !errors.Is(err, conf.ErrNilConfigMap) {
return err
}
}
if len(cfg.UDPConfig) > 0 {
log.Info().Object("config", cfg.HTTPConfig).Msg("starting UDP frontend")
udpFE, err := udp.NewFrontend(r.logic, cfg.UDPConfig)
if err == nil {
r.sg.Add(udpFE)
} else if !errors.Is(err, conf.ErrNilConfigMap) {
return err
}
}
return nil
}
func combineErrors(prefix string, errs []error) error {
errStrs := make([]string, 0, len(errs))
for _, err := range errs {
errStrs = append(errStrs, err.Error())
}
return errors.New(prefix + ": " + strings.Join(errStrs, "; "))
}
// Stop shuts down an instance of Conf.
func (r *Run) Stop(keepPeerStore bool) (storage.PeerStorage, error) {
log.Debug().Msg("stopping frontends and metrics server")
if errs := r.sg.Stop().Wait(); len(errs) != 0 {
return nil, combineErrors("failed while shutting down frontends", errs)
}
log.Debug().Msg("stopping logic")
if errs := r.logic.Stop().Wait(); len(errs) != 0 {
return nil, combineErrors("failed while shutting down middleware", errs)
}
if !keepPeerStore {
log.Debug().Msg("stopping peer store")
if errs := r.storage.Stop().Wait(); len(errs) != 0 {
return nil, combineErrors("failed while shutting down peer store", errs)
}
r.storage = nil
}
return r.storage, nil
}
// RootRunCmdFunc implements a Cobra command that runs an instance of Conf
// and handles reloading and shutdown via process signals.
func RootRunCmdFunc(cmd *cobra.Command, _ []string) error {
configFilePath, err := cmd.Flags().GetString(configArg)
if err != nil {
return err
}
r, err := NewRun(configFilePath)
if err != nil {
return err
}
shutdown, _ := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
reload, _ := signal.NotifyContext(context.Background(), ReloadSignals...)
for {
select {
case <-reload.Done():
log.Info().Msg("reloading; received reload signal")
peerStore, err := r.Stop(true)
if err != nil {
return err
}
if err := r.Start(peerStore); err != nil {
return err
}
case <-shutdown.Done():
log.Info().Msg("shutting down; received shutdown signal")
if _, err := r.Stop(false); err != nil {
return err
}
return nil
}
}
}
const (
appName = "mochi"
logOutArg = "logOut"
logLevelArg = "logLevel"
logPrettyArg = "logPretty"
@@ -184,59 +19,25 @@ const (
configArg = "config"
)
// configureLogger handles command line flags for the logger.
func configureLogger(cmd *cobra.Command, _ []string) (err error) {
var out, lvl string
var pretty, colored bool
flags := cmd.Flags()
out, err = flags.GetString(logOutArg)
if err != nil {
return err
}
lvl, err = flags.GetString(logLevelArg)
if err != nil {
return err
}
pretty, err = flags.GetBool(logPrettyArg)
if err != nil {
return err
}
colored, err = cmd.Flags().GetBool(logColorsArg)
if err != nil {
return err
}
return log.ConfigureLogger(out, lvl, pretty, colored)
}
func main() {
rootCmd := &cobra.Command{
Use: appName,
Short: "BitTorrent Tracker",
Long: "A customizable, multi-protocol BitTorrent Tracker",
PersistentPreRunE: configureLogger,
RunE: RootRunCmdFunc,
var s Server
logOut := flag.String(logOutArg, "stderr", "output for logging, might be 'stderr', 'stdout' or file path")
logLevel := flag.String(logLevelArg, "warn", "logging level: trace, debug, info, warn, error, fatal, panic")
logPretty := flag.Bool(logPrettyArg, false, "enable log pretty print. used only if 'logOut' set to 'stdout' or 'stderr'. if not set, log outputs json")
logColored := flag.Bool(logColorsArg, runtime.GOOS == "windows", "enable log coloring. used only if set 'logPretty'")
configPath := flag.String(configArg, "/etc/mochi.yaml", "location of configuration file")
flag.Parse()
if err := l.ConfigureLogger(*logOut, *logLevel, *logPretty, *logColored); err != nil {
log.Fatal("unable to configure logger ", err)
}
flags := rootCmd.PersistentFlags()
flags.String(logOutArg, "", "output for logging, might be 'stderr', 'stdout' of file path. 'stderr' if not set")
flags.String(logLevelArg, "info", "logging level (trace, debug, info, warn, error, fatal, panic). 'warn' if not set")
flags.Bool(logPrettyArg, false, "enable log pretty print. used only if 'logOut' set to 'stdout' or 'stderr'. if not set, log outputs json)")
flags.Bool(logColorsArg, runtime.GOOS == "windows", "enable log coloring. used only if set 'logPretty'")
rootCmd.Flags().String("config", "/etc/mochi.yaml", "location of configuration file")
if e2eCmd != nil {
rootCmd.AddCommand(e2eCmd)
}
if err := rootCmd.Execute(); err != nil {
log.Fatal().Err(err).Msg("failed while executing root command")
if err := s.Run(*configPath); err != nil {
log.Fatal("unable to start server ", err)
}
defer s.Dispose()
ch := make(chan os.Signal, 2)
signal.Notify(ch, os.Interrupt, syscall.SIGTERM)
<-ch
}
+106
View File
@@ -0,0 +1,106 @@
package main
import (
"errors"
"fmt"
"github.com/sot-tech/mochi/frontend/http"
"github.com/sot-tech/mochi/frontend/udp"
"github.com/sot-tech/mochi/middleware"
"github.com/sot-tech/mochi/pkg/log"
"github.com/sot-tech/mochi/pkg/metrics"
"github.com/sot-tech/mochi/pkg/stop"
"github.com/sot-tech/mochi/storage"
)
// Server represents the state of a running instance.
type Server struct {
storage storage.PeerStorage
logic *middleware.Logic
sg *stop.Group
}
// Run begins an instance of Conf.
// It is optional to provide an instance of the peer store to avoid the
// creation of a new one.
func (r *Server) Run(configFilePath string) error {
configFile, err := ParseConfigFile(configFilePath)
if err != nil {
return fmt.Errorf("failed to read config: %w", err)
}
cfg := configFile.Conf
r.sg = stop.NewGroup()
if len(cfg.MetricsAddr) > 0 {
log.Info().Str("address", cfg.MetricsAddr).Msg("starting metrics server")
r.sg.Add(metrics.NewServer(cfg.MetricsAddr))
} else {
log.Info().Msg("metrics disabled because of empty address")
}
log.Info().Str("name", cfg.Storage.Name).Msg("starting storage")
r.storage, err = storage.NewStorage(cfg.Storage.Name, cfg.Storage.Config)
if err != nil {
return fmt.Errorf("failed to create storage: %w", err)
}
log.Info().Object("config", r.storage).Msg("started storage")
preHooks, err := middleware.NewHooks(cfg.PreHooks, r.storage)
if err != nil {
return fmt.Errorf("failed to validate hook config: %w", err)
}
postHooks, err := middleware.NewHooks(cfg.PostHooks, r.storage)
if err != nil {
return fmt.Errorf("failed to validate hook config: %w", err)
}
r.logic = middleware.NewLogic(cfg.AnnounceInterval, cfg.MinAnnounceInterval, r.storage, preHooks, postHooks)
var started bool
if len(cfg.HTTPConfig) > 0 {
log.Info().Object("config", cfg.HTTPConfig).Msg("starting HTTP frontend")
httpFE, err := http.NewFrontend(r.logic, cfg.HTTPConfig)
if err == nil {
r.sg.Add(httpFE)
started = true
} else {
return err
}
}
if len(cfg.UDPConfig) > 0 {
log.Info().Object("config", cfg.UDPConfig).Msg("starting UDP frontend")
udpFE, err := udp.NewFrontend(r.logic, cfg.UDPConfig)
if err == nil {
r.sg.Add(udpFE)
started = true
} else {
return err
}
}
if !started {
return errors.New("no frontends configured")
}
return nil
}
// Dispose shuts down an instance of Server.
func (r *Server) Dispose() {
log.Debug().Msg("stopping frontends and metrics server")
if errs := r.sg.Stop().Wait(); len(errs) > 0 {
log.Error().Errs("errors", errs).Msg("error occurred while shutting down frontends")
}
log.Debug().Msg("stopping logic")
if errs := r.logic.Stop().Wait(); len(errs) > 0 {
log.Error().Errs("errors", errs).Msg("error occurred while shutting down middlewares")
}
log.Debug().Msg("stopping peer store")
if errs := r.storage.Stop().Wait(); len(errs) != 0 {
log.Error().Errs("errors", errs).Msg("error occurred while shutting down peer store")
}
log.Close()
}
-14
View File
@@ -1,14 +0,0 @@
//go:build darwin || freebsd || linux || netbsd || openbsd || dragonfly || solaris
package main
import (
"os"
"syscall"
)
// ReloadSignals are the signals that the current OS will send to the process
// when a configuration reload is requested.
var ReloadSignals = []os.Signal{
syscall.SIGUSR1,
}
-14
View File
@@ -1,14 +0,0 @@
//go:build windows
package main
import (
"os"
"syscall"
)
// ReloadSignals are the signals that the current OS will send to the process
// when a configuration reload is requested.
var ReloadSignals = []os.Signal{
syscall.SIGHUP,
}