github.com/loomnetwork/gamechain@v0.0.0-20200406110549-36c47eb97a92/tools/gamechain-logger/cmd/root.go (about)

     1  package cmd
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"log"
     7  	"net/url"
     8  	"os"
     9  	"os/signal"
    10  	"path/filepath"
    11  	"strings"
    12  	"syscall"
    13  	"time"
    14  
    15  	raven "github.com/getsentry/raven-go"
    16  	_ "github.com/go-sql-driver/mysql"
    17  	"github.com/gogo/protobuf/jsonpb"
    18  	"github.com/jinzhu/gorm"
    19  	"github.com/loomnetwork/gamechain/types/zb/zb_data"
    20  	"github.com/pkg/errors"
    21  	"github.com/spf13/cobra"
    22  	"github.com/spf13/viper"
    23  )
    24  
    25  var rootCmd = &cobra.Command{
    26  	Use:          "gamechain-logger",
    27  	Short:        "Loom Gamechain logger",
    28  	Long:         `A logger that captures events from Gamechain and creates game metadata`,
    29  	Example:      `  gamechain-logger`,
    30  	SilenceUsage: true,
    31  	RunE: func(cmd *cobra.Command, args []string) error {
    32  		return run()
    33  	},
    34  }
    35  
    36  func init() {
    37  	cobra.OnInitialize(initConfig)
    38  	rootCmd.PersistentFlags().String("db-url", "", "MySQL Connection URL")
    39  	rootCmd.PersistentFlags().String("db-host", "127.0.0.1", "MySQL host")
    40  	rootCmd.PersistentFlags().String("db-port", "3306", "MySQL port")
    41  	rootCmd.PersistentFlags().String("db-name", "loomauth", "MySQL database name")
    42  	rootCmd.PersistentFlags().String("db-user", "root", "MySQL database user")
    43  	rootCmd.PersistentFlags().String("db-password", "", "MySQL database password")
    44  	rootCmd.PersistentFlags().String("replay-dir", "replay", "replay directory")
    45  	rootCmd.PersistentFlags().String("chain-id", "default", "Chain Id")
    46  	rootCmd.PersistentFlags().String("read-uri", "http://localhost:46658/query", "URI for quering app state")
    47  	rootCmd.PersistentFlags().String("contract-name", "zombiebattleground:1.0.0", "Contract Name")
    48  	rootCmd.PersistentFlags().Int("reconnect-interval", 10, "Reconnect interval in seconds")
    49  	rootCmd.PersistentFlags().Int("poll-interval", 10, "Poll interval in seconds")
    50  	rootCmd.PersistentFlags().Int("block-interval", 20, "Amount of blocks to fetch")
    51  	rootCmd.PersistentFlags().String("sentry-dsn", "", "sentry DSN, blank locally cause we dont want to send errors locally")
    52  	rootCmd.PersistentFlags().String("sentry-environment", "", "sentry environment, leave it blank for localhost")
    53  
    54  	viper.BindPFlag("db-url", rootCmd.PersistentFlags().Lookup("db-url"))
    55  	viper.BindPFlag("db-host", rootCmd.PersistentFlags().Lookup("db-host"))
    56  	viper.BindPFlag("db-port", rootCmd.PersistentFlags().Lookup("db-port"))
    57  	viper.BindPFlag("db-name", rootCmd.PersistentFlags().Lookup("db-name"))
    58  	viper.BindPFlag("db-user", rootCmd.PersistentFlags().Lookup("db-user"))
    59  	viper.BindPFlag("db-password", rootCmd.PersistentFlags().Lookup("db-password"))
    60  	viper.BindPFlag("replay-dir", rootCmd.PersistentFlags().Lookup("replay-dir"))
    61  	viper.BindPFlag("chain-id", rootCmd.PersistentFlags().Lookup("chain-id"))
    62  	viper.BindPFlag("read-uri", rootCmd.PersistentFlags().Lookup("read-uri"))
    63  	viper.BindPFlag("contract-name", rootCmd.PersistentFlags().Lookup("contract-name"))
    64  	viper.BindPFlag("poll-interval", rootCmd.PersistentFlags().Lookup("poll-interval"))
    65  	viper.BindPFlag("reconnect-interval", rootCmd.PersistentFlags().Lookup("reconnect-interval"))
    66  	viper.BindPFlag("block-interval", rootCmd.PersistentFlags().Lookup("block-interval"))
    67  	viper.BindPFlag("sentry-dsn", rootCmd.PersistentFlags().Lookup("sentry-dsn"))
    68  	viper.BindPFlag("sentry-environment", rootCmd.PersistentFlags().Lookup("sentry-environment"))
    69  }
    70  
    71  func initConfig() {
    72  	viper.AutomaticEnv() // read in environment variables that match
    73  	viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
    74  
    75  	sentryDsn := viper.GetString("sentry-dsn")
    76  	sentryEnvironment := viper.GetString("sentry-environment")
    77  
    78  	raven.SetEnvironment(sentryEnvironment)
    79  	raven.SetDSN(sentryDsn)
    80  }
    81  
    82  func Execute() {
    83  	if err := rootCmd.Execute(); err != nil {
    84  		raven.CaptureErrorAndWait(err, map[string]string{})
    85  		log.Println(err)
    86  		os.Exit(1)
    87  	}
    88  }
    89  
    90  func run() error {
    91  	var (
    92  		dbURL             = viper.GetString("db-url")
    93  		dbHost            = viper.GetString("db-host")
    94  		dbPort            = viper.GetString("db-port")
    95  		dbName            = viper.GetString("db-name")
    96  		dbUser            = viper.GetString("db-user")
    97  		dbPassword        = viper.GetString("db-password")
    98  		chainID           = viper.GetString("chain-id")
    99  		readURI           = viper.GetString("read-uri")
   100  		contractName      = viper.GetString("contract-name")
   101  		pollInterval      = viper.GetInt("poll-interval")
   102  		reconnectInterval = viper.GetInt("reconnect-interval")
   103  		blockInterval     = viper.GetInt("block-interval")
   104  	)
   105  
   106  	var parsedURL *url.URL
   107  	var err error
   108  
   109  	dbConnStr := dbURL
   110  	if dbURL == "" {
   111  		dbConnStr = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=true", dbUser, dbPassword, dbHost, dbPort, dbName)
   112  	}
   113  	log.Printf("connecting to database host %s", dbHost)
   114  
   115  	db, err := connectDb(dbConnStr)
   116  	if err != nil {
   117  		return errors.Wrapf(err, "fail to connect to database")
   118  	}
   119  	log.Printf("connected to database host %s", dbHost)
   120  	defer db.Close()
   121  
   122  	parsedURL, err = url.Parse(readURI)
   123  	if err != nil {
   124  		return errors.Wrapf(err, "Error parsing url %s", readURI)
   125  	}
   126  
   127  	// control channels
   128  	doneC := make(chan struct{})
   129  	sigC := make(chan os.Signal, 1)
   130  	signal.Notify(sigC, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
   131  	defer signal.Stop(sigC)
   132  
   133  	config := &Config{
   134  		ReadURI:           parsedURL.String(),
   135  		ChainID:           chainID,
   136  		ReconnectInterval: time.Duration(int64(reconnectInterval)) * time.Second,
   137  		PollInterval:      time.Duration(int64(pollInterval)) * time.Second,
   138  		ContractName:      contractName,
   139  		BlockInterval:     blockInterval,
   140  	}
   141  
   142  	r := NewRunner(db, config)
   143  	go r.Start()
   144  	go func() {
   145  		select {
   146  		case <-sigC:
   147  			log.Println("stopping logger...")
   148  			r.Stop()
   149  			close(doneC)
   150  		}
   151  	}()
   152  
   153  	<-doneC
   154  
   155  	return nil
   156  }
   157  
   158  func connectDb(dbURL string) (*gorm.DB, error) {
   159  	db, err := gorm.Open("mysql", dbURL)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  	return db, nil
   164  }
   165  
   166  func writeReplayFile(topic string, event zb_data.PlayerActionEvent) ([]byte, error) {
   167  	dir := viper.GetString("replay-dir")
   168  	if _, err := os.Stat(dir); os.IsNotExist(err) {
   169  		if e := os.MkdirAll(dir, os.ModePerm); e != nil {
   170  			return nil, e
   171  		}
   172  	}
   173  
   174  	filename := fmt.Sprintf("%s.json", topic)
   175  	path := filepath.Join(dir, filename)
   176  
   177  	fmt.Println("Writing to file: ", path)
   178  
   179  	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, os.ModePerm)
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	defer f.Close()
   184  
   185  	if event.Block == nil {
   186  		return nil, nil
   187  	}
   188  
   189  	var replay zb_data.GameReplay
   190  	if fi, _ := f.Stat(); fi.Size() > 0 {
   191  		if err := jsonpb.Unmarshal(f, &replay); err != nil {
   192  			log.Println(err)
   193  			return nil, err
   194  		}
   195  	}
   196  
   197  	if event.PlayerAction != nil {
   198  		replay.Blocks = append(replay.Blocks, event.Block.List...)
   199  		replay.Actions = append(replay.Actions, event.PlayerAction)
   200  	} else {
   201  		replay.Blocks = append(replay.Blocks, event.Block.List...)
   202  	}
   203  
   204  	m := jsonpb.Marshaler{}
   205  	result, err := m.MarshalToString(&replay)
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  
   210  	if err := ioutil.WriteFile(path, []byte(result), os.ModePerm); err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	return []byte(result), nil
   215  }