github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/cmd/geth/log_dispatch.go (about) 1 package main 2 3 import ( 4 "os" 5 "os/signal" 6 "strings" 7 "syscall" 8 "time" 9 10 "github.com/ethereumproject/go-ethereum/core" 11 "github.com/ethereumproject/go-ethereum/eth" 12 "github.com/ethereumproject/go-ethereum/eth/downloader" 13 "github.com/ethereumproject/go-ethereum/eth/fetcher" 14 "github.com/ethereumproject/go-ethereum/event" 15 "github.com/ethereumproject/go-ethereum/logger/glog" 16 "gopkg.in/urfave/cli.v1" 17 ) 18 19 // availableLogStatusFeatures stores state of implemented log STATUS features. 20 // New features should be registered here, and their status updates by dispatchStatusLogs if in use (to avoid dupe goroutine logging). 21 var availableLogStatusFeatures = map[string]time.Duration{ 22 "sync": time.Duration(0), 23 } 24 25 // lsMode represents the current behavior of the client. 26 type lsMode uint 27 28 const ( 29 lsModeDiscover lsMode = iota 30 lsModeFullSync 31 lsModeFastSync 32 lsModeImport 33 ) 34 35 var lsModeName = []string{ 36 "Discover", 37 "Sync", 38 "Fast", 39 "Import", 40 } 41 42 func (m lsMode) String() string { 43 return lsModeName[m] 44 } 45 46 // displayEventHandlerFn is a function that gets called when something happens; where that 'something' 47 // is decided by the displayEventHandler the fn belongs to. It's type accepts a standard interface signature and 48 // returns nothing. evData can be nil, and will be, particularly, when the handler is the "INTERVAL" callee. 49 type displayEventHandlerFn func(ctx *cli.Context, e *eth.Ethereum, evData interface{}, tickerInterval time.Duration) 50 type displayEventHandlerFns []displayEventHandlerFn 51 52 // displayEventHandler is a unit of "listening" that can be added to the display system handlers to configure 53 // what is listened for and how to respond to the given event. 'ev' is an event as received from the Ethereum Mux subscription, 54 // or nil in the case of INTERVAL. Note, as exemplified below, that in order to make use of the ev data it's required 55 // to use a (hacky) single switch to .(type) the event data 56 type displayEventHandler struct { 57 eventT logEventType // used for labeling events and matching to the switch statement 58 ev interface{} // which event to handle. if nil, will run on the ticker. 59 // (ctx *cli.Context, e *eth.Ethereum, evData interface{}, mode *lsMode, tickerInterval time.Duration, n *uint64) 60 handlers displayEventHandlerFns 61 } 62 type displayEventHandlers []displayEventHandler 63 64 // getByName looks up a handler by name to see if it's "registered" for a given display system. 65 func (hs displayEventHandlers) getByName(eventType logEventType) (*displayEventHandler, bool) { 66 for _, h := range hs { 67 if h.eventT == eventType { 68 return &h, true 69 } 70 } 71 return nil, false 72 } 73 74 var displaySystems = make(map[string]displayEventHandlers) 75 76 func init() { 77 displaySystems["basic"] = basicDisplaySystem 78 displaySystems["green"] = greenDisplaySystem 79 displaySystems["gitlike"] = gitDisplaySystem 80 displaySystems["dash"] = dashDisplaySystem 81 } 82 83 // mustGetDisplaySystemFromName parses the flag --display-fmt from context and returns an associated 84 // displayEventHandlers set. This can be considered a temporary solve for handling "registering" or 85 // "delegating" log interface systems. 86 func mustGetDisplaySystemFromName(s string) displayEventHandlers { 87 v, ok := displaySystems[s] 88 if !ok { 89 availables := []string{} 90 for k := range displaySystems { 91 availables = append(availables, k) 92 } 93 glog.Errorf("%v: --%v. Available values are: %s", ErrInvalidFlag, DisplayFormatFlag.Name, strings.Join(availables, ",")) 94 os.Exit(1) 95 } 96 return v 97 } 98 99 // runAllIfAny runs all configured fns for a given event, if registered. 100 func (hs *displayEventHandlers) runAllIfAny(ctx *cli.Context, e *eth.Ethereum, d interface{}, tickerInterval time.Duration, eventType logEventType) { 101 if h, ok := hs.getByName(eventType); ok { 102 for _, handler := range h.handlers { 103 handler(ctx, e, d, tickerInterval) 104 } 105 } 106 } 107 108 // dispatchStatusLogs handle parsing --log-status=argument and toggling appropriate goroutine status feature logging. 109 func dispatchStatusLogs(ctx *cli.Context, ethe *eth.Ethereum) { 110 flagName := aliasableName(LogStatusFlag.Name, ctx) 111 v := ctx.GlobalString(flagName) 112 if v == "" { 113 glog.Fatalf("%v: %v", flagName, ErrInvalidFlag) 114 } 115 116 parseStatusInterval := func(statusModule string, interval string) (tickerInterval time.Duration) { 117 upcaseModuleName := strings.ToUpper(statusModule) 118 if interval != "" { 119 if ti, err := parseDuration(interval); err != nil { 120 glog.Fatalf("%s %v: could not parse argument: %v", upcaseModuleName, err, interval) 121 } else { 122 tickerInterval = ti 123 } 124 } 125 //glog.V(logger.Info).Infof("Rolling %s log interval set: %v", upcaseModuleName, tickerInterval) 126 return tickerInterval 127 } 128 129 for _, p := range strings.Split(v, ",") { 130 // Ignore hanging or double commas 131 if p == "" { 132 continue 133 } 134 135 // If possible, split sync=60 into ["sync", "60"], otherwise yields ["sync"], ["60"], or ["someothernonsense"] 136 eqs := strings.Split(p, "=") 137 138 // If just given, eg. --log-status=60s, assume as default intended sync=60s, at least until 139 // there is another status module interval added. 140 if len(eqs) == 1 { 141 dur := eqs[0] 142 if _, err := parseDuration(dur); err == nil { 143 eqs = append([]string{"sync"}, dur) 144 } 145 } 146 if len(eqs) < 2 { 147 glog.Fatalf("%v: %v. Must be comma-separated pairs of module=interval.", ErrInvalidFlag, eqs) 148 } 149 150 // Catch unavailable and duplicate status feature logs 151 if status, ok := availableLogStatusFeatures[eqs[0]]; !ok { 152 glog.Fatalf("%v: %v: unavailable status feature by name of '%v'", flagName, ErrInvalidFlag, eqs[0]) 153 } else if status.Seconds() != 0 { 154 glog.Fatalf("%v: %v: duplicate status feature by name of '%v'", flagName, ErrInvalidFlag, eqs[0]) 155 } 156 157 // If user just uses "sync" instead of "sync=42", append empty string and delegate to each status log function how to handle it 158 if len(eqs) == 1 { 159 eqs = append(eqs, "") 160 } 161 162 // Parse interval from flag value. 163 d := parseStatusInterval(eqs[0], eqs[1]) 164 switch eqs[0] { 165 case "sync": 166 availableLogStatusFeatures["sync"] = d 167 dsys := mustGetDisplaySystemFromName(ctx.GlobalString(DisplayFormatFlag.Name)) 168 go runDisplayLogs(ctx, ethe, d, dsys) 169 } 170 } 171 } 172 173 // runDisplayLogs starts STATUS SYNC logging at a given interval. 174 // It should be run as a goroutine. 175 // eg. --log-status="sync=42" logs SYNC information every 42 seconds 176 func runDisplayLogs(ctx *cli.Context, e *eth.Ethereum, tickerInterval time.Duration, handles displayEventHandlers) { 177 // Listen for events. 178 var handledEvents []interface{} 179 for _, h := range handles { 180 if h.ev != nil { 181 handledEvents = append(handledEvents, h.ev) 182 } 183 } 184 var ethEvents event.Subscription 185 if len(handledEvents) > 0 { 186 ethEvents = e.EventMux().Subscribe(handledEvents...) 187 } 188 189 // Run any "setup" if configured 190 handles.runAllIfAny(ctx, e, nil, tickerInterval, logEventBefore) 191 192 if len(handledEvents) > 0 { 193 go func() { 194 for ev := range ethEvents.Chan() { 195 updateLogStatusModeHandler(ctx, e, nil, tickerInterval) 196 switch d := ev.Data.(type) { 197 198 // downloader events 199 case downloader.StartEvent: 200 handles.runAllIfAny(ctx, e, d, tickerInterval, logEventDownloaderStart) 201 case downloader.InsertChainEvent: 202 handles.runAllIfAny(ctx, e, d, tickerInterval, logEventDownloaderInsertChain) 203 case downloader.InsertReceiptChainEvent: 204 handles.runAllIfAny(ctx, e, d, tickerInterval, logEventDownloaderInsertReceiptChain) 205 case downloader.InsertHeaderChainEvent: 206 handles.runAllIfAny(ctx, e, d, tickerInterval, logEventDownloaderInsertHeaderChain) 207 case downloader.DoneEvent: 208 handles.runAllIfAny(ctx, e, d, tickerInterval, logEventDownloaderDone) 209 case downloader.FailedEvent: 210 handles.runAllIfAny(ctx, e, d, tickerInterval, logEventDownloaderFailed) 211 212 // core events 213 case core.ChainInsertEvent: 214 handles.runAllIfAny(ctx, e, d, tickerInterval, logEventCoreChainInsert) 215 case core.ChainSideEvent: 216 handles.runAllIfAny(ctx, e, d, tickerInterval, logEventCoreChainInsertSide) 217 case core.HeaderChainInsertEvent: 218 handles.runAllIfAny(ctx, e, d, tickerInterval, logEventCoreHeaderChainInsert) 219 case core.ReceiptChainInsertEvent: 220 handles.runAllIfAny(ctx, e, d, tickerInterval, logEventCoreReceiptChainInsert) 221 case core.NewMinedBlockEvent: 222 handles.runAllIfAny(ctx, e, ev.Data, tickerInterval, logEventCoreMinedBlock) 223 224 // eth/protocol handler events 225 case eth.PMHandlerAddEvent: 226 handles.runAllIfAny(ctx, e, ev.Data, tickerInterval, logEventPMHandlerAdd) 227 case eth.PMHandlerRemoveEvent: 228 handles.runAllIfAny(ctx, e, ev.Data, tickerInterval, logEventPMHandlerRemove) 229 230 // fetcher events 231 case fetcher.FetcherInsertBlockEvent: 232 handles.runAllIfAny(ctx, e, ev.Data, tickerInterval, logEventFetcherInsert) 233 234 default: 235 236 } 237 } 238 }() 239 } 240 241 // Set up ticker based on established interval. 242 if tickerInterval.Seconds() > 0 { 243 ticker := time.NewTicker(tickerInterval) 244 defer ticker.Stop() 245 go func() { 246 for { 247 select { 248 case <-ticker.C: 249 updateLogStatusModeHandler(ctx, e, nil, tickerInterval) 250 handles.runAllIfAny(ctx, e, nil, tickerInterval, logEventInterval) 251 } 252 } 253 }() 254 } 255 256 // Listen for interrupt 257 sigc := make(chan os.Signal, 1) 258 signal.Notify(sigc, os.Interrupt, syscall.SIGTERM) 259 defer signal.Stop(sigc) 260 for { 261 select { 262 case <-sigc: 263 handles.runAllIfAny(ctx, e, nil, tickerInterval, logEventAfter) 264 return 265 } 266 } 267 }