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  }