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 }