github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/logger/mlog_file.go (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 // File I/O and registry for mlogs. 18 19 package logger 20 21 import ( 22 "encoding/json" 23 "errors" 24 "fmt" 25 "math/big" 26 "os" 27 "os/user" 28 "path/filepath" 29 "strings" 30 "sync" 31 "time" 32 33 "github.com/ethereumproject/go-ethereum/common" 34 "github.com/ethereumproject/go-ethereum/logger/glog" 35 ) 36 37 type mlogFormatT uint 38 39 const ( 40 mLOGPlain mlogFormatT = iota + 1 41 mLOGKV 42 MLOGJSON 43 ) 44 45 var ( 46 // If non-empty, overrides the choice of directory in which to write logs. 47 // See createLogDirs for the full list of possible destinations. 48 mLogDir = new(string) 49 mLogFormat = MLOGJSON 50 51 errMLogComponentUnavailable = errors.New("provided component name is unavailable") 52 ErrUnkownMLogFormat = errors.New("unknown mlog format") 53 54 // MLogRegistryAvailable contains all available mlog components submitted by any package 55 // with MLogRegisterAvailable. 56 mLogRegistryAvailable = make(map[mlogComponent][]*MLogT) 57 // MLogRegistryActive contains all registered mlog component and their respective loggers. 58 mLogRegistryActive = make(map[mlogComponent]*Logger) 59 mlogRegLock sync.RWMutex 60 61 // Abstract literals (for documentation examples, labels) 62 mlogInterfaceExamples = map[string]interface{}{ 63 "INT": int(0), 64 "BIGINT": new(big.Int), 65 "STRING": "string", 66 "BOOL": true, 67 "QUOTEDSTRING": "string with spaces", 68 "STRING_OR_NULL": nil, 69 "DURATION": time.Minute + time.Second*3 + time.Millisecond*42, 70 "OBJECT": common.GetClientSessionIdentity(), 71 } 72 73 MLogStringToFormat = map[string]mlogFormatT{ 74 "plain": mLOGPlain, 75 "kv": mLOGKV, 76 "json": MLOGJSON, 77 } 78 79 // Global var set to false if "--mlog=off", used to simply/ 80 // speed-up checks to avoid performance penalty if mlog is 81 // off. 82 isMlogEnabled bool 83 ) 84 85 func (f mlogFormatT) String() string { 86 switch f { 87 case MLOGJSON: 88 return "json" 89 case mLOGKV: 90 return "kv" 91 case mLOGPlain: 92 return "plain" 93 } 94 panic(ErrUnkownMLogFormat) 95 } 96 97 // MLogT defines an mlog LINE 98 type MLogT struct { 99 sync.Mutex 100 // TODO: can remove these json tags, since we have a custom MarshalJSON fn 101 Description string `json:"-"` 102 Receiver string `json:"receiver"` 103 Verb string `json:"verb"` 104 Subject string `json:"subject"` 105 Details []MLogDetailT `json:"details"` 106 } 107 108 // MLogDetailT defines an mlog LINE DETAILS 109 type MLogDetailT struct { 110 Owner string `json:"owner"` 111 Key string `json:"key"` 112 Value interface{} `json:"value"` 113 } 114 115 // mlogComponent is used as a golang receiver type that can call Send(logLine). 116 type mlogComponent string 117 118 // The following vars and init() essentially duplicate those found in glog_file; 119 // the reason for the non-DRYness of that is that this allows us flexibility 120 // as we finalize the spec and format for the mlog lines, allowing customization 121 // of the establish system if desired, without exporting the vars from glog. 122 var ( 123 pid = os.Getpid() 124 program = filepath.Base(os.Args[0]) 125 host = "unknownhost" 126 userName = "unknownuser" 127 ) 128 129 func init() { 130 h, err := os.Hostname() 131 if err == nil { 132 host = shortHostname(h) 133 } 134 135 current, err := user.Current() 136 if err == nil { 137 userName = current.Username 138 } 139 140 // Sanitize userName since it may contain filepath separators on Windows. 141 userName = strings.Replace(userName, `\`, "_", -1) 142 } 143 144 func SetMlogEnabled(b bool) { 145 isMlogEnabled = b 146 } 147 148 func MlogEnabled() bool { 149 return isMlogEnabled 150 } 151 152 // MLogRegisterAvailable is called for each log component variable from a package/mlog.go file 153 // as they set up their mlog vars. 154 // It registers an mlog component as Available. 155 func MLogRegisterAvailable(name string, lines []*MLogT) mlogComponent { 156 c := mlogComponent(name) 157 mlogRegLock.Lock() 158 mLogRegistryAvailable[c] = lines 159 mlogRegLock.Unlock() 160 return c 161 } 162 163 // GetMlogRegistryAvailable returns copy of all registered components mapping 164 func GetMLogRegistryAvailable() map[mlogComponent][]*MLogT { 165 mlogRegLock.RLock() 166 defer mlogRegLock.RUnlock() 167 168 ret := make(map[mlogComponent][]*MLogT) 169 for k, v := range mLogRegistryAvailable { 170 ret[k] = make([]*MLogT, len(v)) 171 copy(ret[k], v) 172 } 173 return ret 174 } 175 176 // GetMlogRegistryActive returns copy of all active components mapping 177 func GetMLogRegistryActive() map[mlogComponent]*Logger { 178 mlogRegLock.RLock() 179 defer mlogRegLock.RUnlock() 180 181 ret := make(map[mlogComponent]*Logger) 182 for k, v := range mLogRegistryActive { 183 ret[k] = v 184 } 185 return ret 186 } 187 188 // MLogRegisterComponentsFromContext receives a comma-separated string of 189 // desired mlog components. 190 // It returns an error if the specified mlog component is unavailable. 191 // For each available component, the desires mlog components are registered as active, 192 // creating new loggers for each. 193 // If the string begins with '!', the function will remove the following components from the 194 // default list 195 func MLogRegisterComponentsFromContext(s string) error { 196 // negation 197 var negation bool 198 if strings.HasPrefix(s, "!") { 199 negation = true 200 s = strings.TrimPrefix(s, "!") 201 } 202 ss := strings.Split(s, ",") 203 204 registry := GetMLogRegistryAvailable() 205 206 if !negation { 207 for _, c := range ss { 208 ct := strings.TrimSpace(c) 209 if _, ok := registry[mlogComponent(ct)]; !ok { 210 return fmt.Errorf("%v: '%s'", errMLogComponentUnavailable, ct) 211 } 212 MLogRegisterActive(mlogComponent(ct)) 213 } 214 return nil 215 } 216 // Register all 217 for c := range registry { 218 MLogRegisterActive(c) 219 } 220 // then remove 221 for _, u := range ss { 222 ct := strings.TrimSpace(u) 223 mlogRegisterInactive(mlogComponent(ct)) 224 } 225 return nil 226 } 227 228 // MLogRegisterActive registers a component for mlogging. 229 // Only registered loggers will write to mlog file. 230 func MLogRegisterActive(component mlogComponent) { 231 mlogRegLock.Lock() 232 mLogRegistryActive[component] = NewLogger(string(component)) 233 mlogRegLock.Unlock() 234 } 235 236 func mlogRegisterInactive(component mlogComponent) { 237 mlogRegLock.Lock() 238 delete(mLogRegistryActive, component) // noop if nil 239 mlogRegLock.Unlock() 240 } 241 242 // SendMLog writes enabled component mlogs to file if the component is registered active. 243 func (msg *MLogT) Send(c mlogComponent) { 244 mlogRegLock.RLock() 245 if l, exists := mLogRegistryActive[c]; exists { 246 l.SendFormatted(GetMLogFormat(), 1, msg, c) 247 } 248 mlogRegLock.RUnlock() 249 } 250 251 func (l *Logger) SendFormatted(format mlogFormatT, level LogLevel, msg *MLogT, c mlogComponent) { 252 switch format { 253 case mLOGKV: 254 l.Sendln(level, msg.FormatKV()) 255 case MLOGJSON: 256 logMessageC <- stdMsg{level, string(msg.FormatJSON(c))} 257 case mLOGPlain: 258 l.Sendln(level, string(msg.FormatPlain())) 259 //case MLOGDocumentation: 260 // don't handle this because this is just for one-off help/usage printing documentation 261 default: 262 glog.Fatalf("Unknown mlog format: %v", format) 263 } 264 } 265 266 // SetMLogDir sets the mlog directory, into which one mlog file per session 267 // will be written. 268 func SetMLogDir(str string) { 269 *mLogDir = str 270 } 271 272 func GetMLogDir() string { 273 m := *mLogDir 274 return m 275 } 276 277 func SetMLogFormat(format mlogFormatT) { 278 mLogFormat = format 279 } 280 281 func GetMLogFormat() mlogFormatT { 282 return mLogFormat 283 } 284 285 func SetMLogFormatFromString(formatString string) error { 286 if f := MLogStringToFormat[formatString]; f < 1 { 287 return ErrUnkownMLogFormat 288 } else { 289 SetMLogFormat(f) 290 } 291 return nil 292 } 293 294 func createLogDirs() error { 295 if *mLogDir != "" { 296 return os.MkdirAll(*mLogDir, os.ModePerm) 297 } 298 return errors.New("createLogDirs received empty string") 299 } 300 301 // shortHostname returns its argument, truncating at the first period. 302 // For instance, given "www.google.com" it returns "www". 303 func shortHostname(hostname string) string { 304 if i := strings.Index(hostname, "."); i >= 0 { 305 return hostname[:i] 306 } 307 return hostname 308 } 309 310 // logName returns a new log file name containing tag, with start time t, and 311 // the name for the symlink for tag. 312 func logName(t time.Time) (name, link string) { 313 name = fmt.Sprintf("%s.mlog.%s.%04d%02d%02d-%02d%02d%02d.%d.log", 314 program, 315 common.SessionID, 316 t.Year(), 317 t.Month(), 318 t.Day(), 319 t.Hour(), 320 t.Minute(), 321 t.Second(), 322 pid) 323 return name, program + ".log" 324 } 325 326 // CreateMLogFile creates a new log file and returns the file and its filename, which 327 // contains tag ("INFO", "FATAL", etc.) and t. If the file is created 328 // successfully, create also attempts to update the symlink for that tag, ignoring 329 // errors. 330 func CreateMLogFile(t time.Time) (f *os.File, filename string, err error) { 331 332 if e := createLogDirs(); e != nil { 333 return nil, "", e 334 } 335 336 name, link := logName(t) 337 fname := filepath.Join(*mLogDir, name) 338 339 f, e := os.Create(fname) 340 if e != nil { 341 err = e 342 return nil, fname, err 343 } 344 345 symlink := filepath.Join(*mLogDir, link) 346 os.Remove(symlink) // ignore err 347 os.Symlink(name, symlink) // ignore err 348 349 return f, fname, nil 350 } 351 352 func (m *MLogT) placeholderize() { 353 placeholderEmpty := "-" 354 if m.Receiver == "" { 355 m.Receiver = placeholderEmpty 356 } 357 if m.Subject == "" { 358 m.Subject = placeholderEmpty 359 } 360 if m.Verb == "" { 361 m.Verb = placeholderEmpty 362 } 363 } 364 365 func (m *MLogT) FormatJSON(c mlogComponent) []byte { 366 b, _ := m.MarshalJSON(c) 367 return b 368 } 369 370 func (m *MLogT) FormatKV() (out string) { 371 m.Lock() 372 defer m.Unlock() 373 m.placeholderize() 374 out = fmt.Sprintf("%s %s %s session=%s", m.Receiver, m.Verb, m.Subject, common.SessionID) 375 for _, d := range m.Details { 376 v := fmt.Sprintf("%v", d.Value) 377 // quote strings which contains spaces 378 if strings.Contains(v, " ") { 379 v = fmt.Sprintf(`"%v"`, d.Value) 380 } 381 out += fmt.Sprintf(" %s=%v", d.EventName(), v) 382 } 383 return out 384 } 385 386 func (m *MLogT) FormatPlain() (out string) { 387 m.Lock() 388 defer m.Unlock() 389 m.placeholderize() 390 out = fmt.Sprintf("%s %s %s %s", m.Receiver, m.Verb, m.Subject, common.SessionID) 391 for _, d := range m.Details { 392 v := fmt.Sprintf("%v", d.Value) 393 // quote strings which contains spaces 394 if strings.Contains(v, " ") { 395 v = fmt.Sprintf(`"%v"`, d.Value) 396 } 397 out += fmt.Sprintf(" %v", v) 398 } 399 return out 400 } 401 402 func (m *MLogT) MarshalJSON(c mlogComponent) ([]byte, error) { 403 m.Lock() 404 defer m.Unlock() 405 var obj = make(map[string]interface{}) 406 obj["event"] = m.EventName() 407 obj["ts"] = time.Now() 408 obj["session"] = common.SessionID 409 obj["component"] = string(c) 410 for _, d := range m.Details { 411 obj[d.EventName()] = d.Value 412 } 413 return json.Marshal(obj) 414 } 415 416 func (m *MLogT) FormatJSONExample(c mlogComponent) []byte { 417 mm := &MLogT{ 418 Receiver: m.Receiver, 419 Verb: m.Verb, 420 Subject: m.Subject, 421 } 422 var dets []MLogDetailT 423 for _, d := range m.Details { 424 ex := mlogInterfaceExamples[d.Value.(string)] 425 // Type of var not matched to interface example 426 if ex == "" { 427 continue 428 } 429 dets = append(dets, MLogDetailT{ 430 Owner: d.Owner, 431 Key: d.Key, 432 Value: ex, 433 }) 434 } 435 mm.Details = dets 436 b, _ := mm.MarshalJSON(c) 437 return b 438 } 439 440 // FormatDocumentation prints wiki-ready documentation for all available component mlog LINES. 441 // Output should be in markdown. 442 func (m *MLogT) FormatDocumentation(cmp mlogComponent) (out string) { 443 444 // Get the json example before converting to abstract literal format, eg STRING -> $STRING 445 // This keeps the interface example dictionary as a separate concern. 446 exJSON := string(m.FormatJSONExample(cmp)) 447 448 // Set up arbitrary documentation abstract literal format 449 docDetails := []MLogDetailT{} 450 for _, d := range m.Details { 451 dd := d.AsDocumentation() 452 docDetails = append(docDetails, *dd) 453 } 454 m.Details = docDetails 455 456 exPlain := m.FormatPlain() 457 exKV := m.FormatKV() 458 459 t := time.Now() 460 lStandardHeaderDateTime := fmt.Sprintf("%4d/%02d/%02d %02d:%02d:%02d", 461 t.Year(), t.Month(), t.Day(), 462 t.Hour(), t.Minute(), t.Second()) 463 cmpS := fmt.Sprintf("[%s]", cmp) 464 465 out += fmt.Sprintf(` 466 #### %s %s %s 467 %s 468 469 __Key value__: 470 `+"```"+` 471 %s %s %s 472 `+"```"+` 473 474 __JSON__: 475 `+"```json"+` 476 %s 477 `+"```"+` 478 479 __Plain__: 480 `+"```"+` 481 %s %s %s 482 `+"```"+` 483 484 _%d detail values_: 485 486 `, m.Receiver, m.Verb, m.Subject, 487 m.Description, 488 lStandardHeaderDateTime, 489 cmpS, 490 exKV, 491 exJSON, 492 lStandardHeaderDateTime, 493 cmpS, 494 exPlain, 495 len(m.Details)) 496 497 var details string 498 for _, d := range m.Details { 499 details += fmt.Sprintf("- `%s`: %s\n", d.EventName(), d.Value) 500 } 501 details += "\n" 502 503 out += details 504 return out 505 } 506 507 // EventName implements the JsonMsg interface in case wanting to use existing half-established json logging system 508 func (m *MLogT) EventName() string { 509 r := strings.ToLower(m.Receiver) 510 v := strings.ToLower(m.Verb) 511 s := strings.ToLower(m.Subject) 512 return strings.Join([]string{r, v, s}, ".") 513 } 514 515 func (m *MLogDetailT) EventName() string { 516 o := strings.ToLower(m.Owner) 517 k := strings.ToLower(m.Key) 518 return strings.Join([]string{o, k}, ".") 519 } 520 521 func (m *MLogDetailT) AsDocumentation() *MLogDetailT { 522 m.Value = fmt.Sprintf("$%s", m.Value) 523 return m 524 } 525 526 // AssignDetails is a setter function for setting values for pre-existing details. 527 // It accepts a variadic number of empty interfaces. 528 // If the number of arguments does not match the number of established details 529 // for the receiving MLogT, it will fatal error. 530 // Arguments MUST be provided in the order in which they should be applied to the 531 // slice of existing details. 532 func (m *MLogT) AssignDetails(detailVals ...interface{}) *MLogT { 533 // Check for congruence between argument length and registered details. 534 if len(detailVals) != len(m.Details) { 535 glog.Fatal(m.EventName(), "wrong number of details set, want: ", len(m.Details), "got:", len(detailVals)) 536 } 537 538 m.Lock() 539 for i, detailval := range detailVals { 540 m.Details[i].Value = detailval 541 } 542 m.Unlock() 543 544 return m 545 }