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 }