go.dedis.ch/onet/v3@v3.2.11-0.20210930124529-e36530bca7ef/tracing/logger.go (about)

     1  package tracing
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"os"
     7  	"runtime"
     8  	"runtime/debug"
     9  	"strconv"
    10  	"strings"
    11  	"sync"
    12  	"time"
    13  
    14  	"github.com/shirou/gopsutil/disk"
    15  	"github.com/shirou/gopsutil/load"
    16  	"github.com/shirou/gopsutil/net"
    17  
    18  	"github.com/shirou/gopsutil/cpu"
    19  	"go.dedis.ch/onet/v3/network"
    20  
    21  	"go.dedis.ch/onet/v3"
    22  
    23  	"go.dedis.ch/onet/v3/log"
    24  )
    25  
    26  // The TraceLogger implements the interface Logger to be registered in onet/log.
    27  // It has also some convenience methods to connect to a standard cothority
    28  //installation, including putting the nodeName in the logging information.
    29  type TraceLogger struct {
    30  	// Don't create spans for calls that are not in entryPoints
    31  	NoSingleSpans bool
    32  	// Print spans that are not in entryPoints so that the slice can be updated.
    33  	PrintSingleSpans int
    34  	// LogLevel is the minimum log level that is sent to the tracing service
    35  	LogLevel int
    36  	// As the TraceLogger cannot use onet/log, turn on/off debug messages here.
    37  	Debug bool
    38  	// currently active traces - the keys are the traceID or the go-routine
    39  	// id of the current call
    40  	traces map[string]*traceWrapper
    41  	// used to create a new trace,
    42  	// so the TraceLogger can work with simulation, honeycomb,
    43  	// or your own extension.
    44  	hcn newTrace
    45  	// which pkgMath/methods are interpreted as entry-points that can have
    46  	// other go-routines and/or traceIDs linked to them.
    47  	entryPoints []string
    48  	// which log-message should be interpreted as closing the trace
    49  	doneMsgs []string
    50  	// these fields will be set in all traces
    51  	defaultFields map[string]string
    52  	// maps goroutine-ids to traceID, filled up by TraceLogger.TraceID
    53  	goToTraceID map[string]string
    54  	// telling onet/log what this TraceLogger needs
    55  	logInfo log.LoggerInfo
    56  	// tells the automatic stats-service to shut down
    57  	statsDone chan bool
    58  	// protects common fields
    59  	logMutex sync.Mutex
    60  	// our entry in onet/log
    61  	loggerID int
    62  }
    63  
    64  type newTrace = func(context.Context, string) (context.Context, Trace)
    65  
    66  // Log implements the Logger interface.
    67  // It calls the getTraceSpan with the appropriate callLvl to remove the stack
    68  // trace due to onet/log.
    69  func (logger *TraceLogger) Log(level int, msg string) {
    70  	logger.logMutex.Lock()
    71  	ok := level > logger.LogLevel
    72  	logger.logMutex.Unlock()
    73  	if !ok {
    74  		return
    75  	}
    76  	logger.getTraceSpan(level, msg, 7)
    77  }
    78  
    79  // Close implements the Logger interface.
    80  func (logger *TraceLogger) Close() {
    81  	close(logger.statsDone)
    82  }
    83  
    84  // GetLoggerInfo implements the Logger interface.
    85  func (logger *TraceLogger) GetLoggerInfo() *log.LoggerInfo {
    86  	return &logger.logInfo
    87  }
    88  
    89  // AddOnetDefaults sets a number of entry-points that are useful when
    90  //running a project using onet.
    91  // These entry-points allow to track quite accurately what is happening,
    92  //both for the servie-calls over websockets, as well as the protocol-messages.
    93  func (logger *TraceLogger) AddOnetDefaults(si *network.ServerIdentity) {
    94  	logger.AddEntryPoints(
    95  		"go.dedis.ch/onet/v3.wsHandler.ServeHTTP",
    96  		"go.dedis.ch/onet/v3/network.(*BlockingDispatcher).Dispatch",
    97  		"go.dedis.ch/onet/v3/network.(*RoutineDispatcher).Dispatch",
    98  		"go.dedis.ch/cothority/v3/blscosi/protocol.(*SubBlsCosi).dispatchLeaf",
    99  		"go.dedis.ch/onet/v3.(*TreeNodeInstance).dispatchMsgToProtocol",
   100  		"go.dedis.ch/onet/v3.(*Overlay).TransmitMsg",
   101  		"go.dedis.ch/onet/v3.(*TreeNodeInstance).dispatchMsgReader")
   102  	logger.AddDoneMsgs("ws close", "done tracing")
   103  
   104  	logger.defaultFields["nodeName"] = si.String()
   105  	logger.defaultFields["nodeDescription"] = si.Description
   106  }
   107  
   108  // AddEntryPoints takes all given entry points and adds them to the internal
   109  // list.
   110  // Empty entry points are discarded.
   111  func (logger *TraceLogger) AddEntryPoints(eps ...string) {
   112  	for _, ep := range eps {
   113  		if len(ep) > 0 {
   114  			logger.entryPoints = append(logger.entryPoints, ep)
   115  		}
   116  	}
   117  }
   118  
   119  // AddDoneMsgs takes all given done messages and adds them to the internal list.
   120  // Empty done messages are discarded.
   121  func (logger *TraceLogger) AddDoneMsgs(dms ...string) {
   122  	for _, dm := range dms {
   123  		if len(dm) > 0 {
   124  			logger.doneMsgs = append(logger.doneMsgs, dm)
   125  		}
   126  	}
   127  }
   128  
   129  // AddStats sends statistics of the current node on a regular basis to the
   130  //tracing service.
   131  // The sending stops once Logger.Close is called.
   132  func (logger *TraceLogger) AddStats(c *onet.Context, repeat time.Duration) {
   133  	go func() {
   134  		for {
   135  			select {
   136  			case <-time.After(repeat):
   137  				// Create a new trace that points to a dummy stackEntry,
   138  				//so the status can be sent to the service.
   139  				t, s := logger.newTrace(context.TODO(), "",
   140  					stackEntry{pkgPath: "go.dedis.ch/onet/v3/honeycomb",
   141  						method: "stats"})
   142  				logger.addDefaultFields(t, s)
   143  				t.add("status", c.ReportStatus())
   144  				var m runtime.MemStats
   145  				runtime.ReadMemStats(&m)
   146  				t.add("memstats", m)
   147  				var gc debug.GCStats
   148  				debug.ReadGCStats(&gc)
   149  				t.add("gcstats", gc)
   150  				ld, err := load.Avg()
   151  				if err == nil {
   152  					t.add("load", ld)
   153  				}
   154  				us, err := disk.Usage(".")
   155  				if err == nil {
   156  					t.add("disk-usage", us)
   157  				}
   158  				ioc, err := disk.IOCounters(".")
   159  				if err == nil {
   160  					t.add("disk-iostat", ioc)
   161  				}
   162  				netio, err := net.IOCounters(false)
   163  				if err == nil {
   164  					t.add("network-stat", netio)
   165  				}
   166  
   167  				t.send()
   168  			case <-logger.statsDone:
   169  				return
   170  			}
   171  		}
   172  	}()
   173  }
   174  
   175  // AddEnvironment reads the environment variables defined to initialize the
   176  // variables.
   177  // The following environmental variables are available:
   178  //   - TRACING_PRINT_SINGLE_SPANS - output a stack trace of single spans to
   179  //  let you trace where you could/should add a TRACING_ENTRY_POINT
   180  //   - TRACING_CREATE_SINGLE_SPANS - whenever there is no entry point found,
   181  //  the system can create single spans that are not linked together.
   182  //   This is a fallback to regular logging when we cannot simulate traces.
   183  //   - TRACING_LOG_LEVEL - sets the minimal log level that is sent to the
   184  //  tracing service
   185  //   - TRACING_DEBUG - if true, fmt.Println is used for some additional
   186  //  debugging messages, as onet/log cannot be used within the logger
   187  //   - TRACING_ENTRY_POINTS - a "::" separated list of entry points that can
   188  //  be used to refine the tracing.
   189  //  The name of the entry points are the same as given by
   190  //  TRACING_PRINT_SINGLE_SPAN
   191  //   - TRACING_DONE_MSGS - a "::" separated list of done msgs that will close
   192  //  the started trace. This is due to the simulation of actual traces,
   193  //  because we can't really know when the trace should end.
   194  func (logger *TraceLogger) AddEnvironment() error {
   195  	if envPSS := os.Getenv("TRACING_PRINT_SINGLE_SPANS"); envPSS != "" {
   196  		var err error
   197  		logger.PrintSingleSpans, err = strconv.Atoi(envPSS)
   198  		if err != nil {
   199  			return fmt.Errorf("while reading TRACING_PRINT_SINGLE_SPAN: %v",
   200  				err)
   201  		}
   202  	}
   203  	tcss := strings.ToLower(os.Getenv("TRACING_CREATE_SINGLE_SPANS"))
   204  	if tcss != "" {
   205  		if tcss != "true" && tcss != "false" {
   206  			return fmt.Errorf("while reading TRACING_CREATE_SINGLE_SPANS: can" +
   207  				" be only \"true\" or \"false\"")
   208  		}
   209  		logger.NoSingleSpans = tcss == "true"
   210  	}
   211  	tdb := strings.ToLower(os.Getenv("TRACING_DEBUG"))
   212  	if tdb != "" {
   213  		if tdb != "true" && tdb != "false" {
   214  			return fmt.Errorf("while reading TRACING_CREATE_SINGLE_SPANS: can" +
   215  				" be only \"true\" or \"false\"")
   216  		}
   217  		logger.Debug = tdb == "true"
   218  	}
   219  	if tcll := os.Getenv("TRACING_LOG_LEVEL"); tcll != "" {
   220  		var err error
   221  		logger.LogLevel, err = strconv.Atoi(tcll)
   222  		if err != nil {
   223  			return fmt.Errorf("while reading TRACING_LOG_LEVEL: %v",
   224  				err)
   225  		}
   226  	} else {
   227  		logger.LogLevel = 3
   228  	}
   229  	logger.AddEntryPoints(strings.Split(os.Getenv("TRACING_ENTRY_POINTS"), "::")...)
   230  	logger.AddDoneMsgs(strings.Split(os.Getenv("TRACING_DONE_MSGS"), "::")...)
   231  	return nil
   232  }
   233  
   234  // TraceID stores the go-routine ID / TraceID pair for help in merging new
   235  // go-routines.
   236  func (logger *TraceLogger) TraceID(id []byte) {
   237  	logger.logMutex.Lock()
   238  	defer logger.logMutex.Unlock()
   239  	logger.goToTraceID[getGoID(log.Stack())] = fmt.Sprintf("%x", id)
   240  }
   241  
   242  // getTraceSpan checks whether this trace is already known.
   243  // If it is known, it adds a new span to it to create the log.
   244  // If it is unknown, it checks if it should create a new span.
   245  // If it is not a span to be created,
   246  // it checks if it should create a singleTrace.
   247  func (logger *TraceLogger) getTraceSpan(lvl int, msg string, callLvl int) (*traceWrapper,
   248  	*spanWrapper) {
   249  	logger.logMutex.Lock()
   250  	defer logger.logMutex.Unlock()
   251  	ses := parseLogs(log.Stack(), logger.goToTraceID)
   252  	ses = ses[0 : len(ses)-callLvl]
   253  	ses = mergeLogs(logger.traces, ses)
   254  	for i, se := range ses {
   255  		tr, ok := logger.traces[se.traceID]
   256  		if ok {
   257  			for _, done := range logger.doneMsgs {
   258  				if strings.Contains(msg, done) {
   259  					if logger.Debug {
   260  						fmt.Println("-- found done for", se.uniqueID())
   261  					}
   262  					delete(logger.traces, se.traceID)
   263  					tr.send()
   264  					return nil, nil
   265  				}
   266  			}
   267  			if logger.Debug {
   268  				fmt.Println("-- adding log to", se.uniqueID(), msg)
   269  			}
   270  			sw := tr.stackToSpan(ses[i:])
   271  			sw.log(lvl, msg)
   272  			return tr, sw
   273  		}
   274  
   275  		// Check if there is a new trace to be generated
   276  		if se.checkEntryPoint(logger.entryPoints) != "" {
   277  			if logger.Debug {
   278  				fmt.Println("-- new trace", se.uniqueID())
   279  				for _, s := range ses[i:] {
   280  					fmt.Println("-- ", s.uniqueID())
   281  				}
   282  			}
   283  			var child *spanWrapper
   284  			tw, sw := logger.newTrace(context.TODO(), "", ses[i:]...)
   285  			logger.addDefaultFields(tw, sw)
   286  			logger.traces[se.traceID], child = tw, sw
   287  			child.log(lvl, msg)
   288  			return logger.traces[se.traceID], child
   289  		}
   290  	}
   291  	if logger.PrintSingleSpans > 0 {
   292  		fmt.Printf("Creating single trace for '%s' from:\n",
   293  			msg)
   294  		for i, se := range ses {
   295  			if i >= logger.PrintSingleSpans {
   296  				break
   297  			}
   298  			fmt.Println("\t", se.pkgMethod())
   299  		}
   300  	}
   301  	if !logger.NoSingleSpans {
   302  		tr, sw := logger.newTrace(context.TODO(), "", ses...)
   303  		logger.addDefaultFields(tr, sw)
   304  		tr.hcTrace.AddField("singleTrace", true)
   305  		sw.log(lvl, msg)
   306  		tr.send()
   307  	}
   308  	return nil, nil
   309  }
   310  
   311  func (logger *TraceLogger) addDefaultFields(tw *traceWrapper, sw *spanWrapper) {
   312  	for k, v := range logger.defaultFields {
   313  		tw.hcTrace.AddField(k, v)
   314  	}
   315  	ts, err := cpu.Times(false)
   316  	if err == nil {
   317  		for i, t := range ts {
   318  			sw.add("cpustat"+strconv.Itoa(i), t)
   319  		}
   320  	}
   321  }
   322  
   323  func (logger *TraceLogger) newTrace(ctx context.Context, str string,
   324  	ses ...stackEntry) (*traceWrapper,
   325  	*spanWrapper) {
   326  	ctx, newTr := logger.hcn(ctx, str)
   327  	return newTraceWrapper(ctx, newTr, ses)
   328  }
   329  
   330  // NewLogger returns a new TraceLogger,
   331  //already registered to the logging system.
   332  // You might want to use the NewHoneyCombLogger or newSimulLogger instead,
   333  //which make it easier to set up the tracing logger.
   334  func NewLogger(nt newTrace) *TraceLogger {
   335  	l := &TraceLogger{
   336  		logInfo:       log.LoggerInfo{DebugLvl: 5, RawMessage: true},
   337  		defaultFields: map[string]string{},
   338  		goToTraceID:   map[string]string{},
   339  		statsDone:     make(chan bool),
   340  		hcn:           nt,
   341  		traces:        make(map[string]*traceWrapper),
   342  	}
   343  	l.loggerID = log.RegisterLogger(l)
   344  	return l
   345  }