github.com/shuguocloud/go-zero@v1.3.0/core/logx/logs.go (about) 1 package logx 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "io/ioutil" 10 "log" 11 "os" 12 "path" 13 "runtime" 14 "runtime/debug" 15 "strconv" 16 "strings" 17 "sync" 18 "sync/atomic" 19 "time" 20 21 "github.com/shuguocloud/go-zero/core/iox" 22 "github.com/shuguocloud/go-zero/core/sysx" 23 "github.com/shuguocloud/go-zero/core/timex" 24 ) 25 26 const ( 27 // InfoLevel logs everything 28 InfoLevel = iota 29 // ErrorLevel includes errors, slows, stacks 30 ErrorLevel 31 // SevereLevel only log severe messages 32 SevereLevel 33 ) 34 35 const ( 36 jsonEncodingType = iota 37 plainEncodingType 38 39 jsonEncoding = "json" 40 plainEncoding = "plain" 41 plainEncodingSep = '\t' 42 ) 43 44 const ( 45 accessFilename = "access.log" 46 errorFilename = "error.log" 47 severeFilename = "severe.log" 48 slowFilename = "slow.log" 49 statFilename = "stat.log" 50 51 consoleMode = "console" 52 volumeMode = "volume" 53 54 levelAlert = "alert" 55 levelInfo = "info" 56 levelError = "error" 57 levelSevere = "severe" 58 levelFatal = "fatal" 59 levelSlow = "slow" 60 levelStat = "stat" 61 62 backupFileDelimiter = "-" 63 callerInnerDepth = 5 64 flags = 0x0 65 ) 66 67 var ( 68 // ErrLogPathNotSet is an error that indicates the log path is not set. 69 ErrLogPathNotSet = errors.New("log path must be set") 70 // ErrLogNotInitialized is an error that log is not initialized. 71 ErrLogNotInitialized = errors.New("log not initialized") 72 // ErrLogServiceNameNotSet is an error that indicates that the service name is not set. 73 ErrLogServiceNameNotSet = errors.New("log service name must be set") 74 75 timeFormat = "2006-01-02T15:04:05.000Z07" 76 writeConsole bool 77 logLevel uint32 78 encoding = jsonEncodingType 79 // use uint32 for atomic operations 80 disableStat uint32 81 infoLog io.WriteCloser 82 errorLog io.WriteCloser 83 severeLog io.WriteCloser 84 slowLog io.WriteCloser 85 statLog io.WriteCloser 86 stackLog io.Writer 87 88 once sync.Once 89 initialized uint32 90 options logOptions 91 ) 92 93 type ( 94 logEntry struct { 95 Timestamp string `json:"@timestamp"` 96 Level string `json:"level"` 97 Duration string `json:"duration,omitempty"` 98 Content interface{} `json:"content"` 99 } 100 101 logOptions struct { 102 gzipEnabled bool 103 logStackCooldownMills int 104 keepDays int 105 } 106 107 // LogOption defines the method to customize the logging. 108 LogOption func(options *logOptions) 109 110 // A Logger represents a logger. 111 Logger interface { 112 Error(...interface{}) 113 Errorf(string, ...interface{}) 114 Errorv(interface{}) 115 Info(...interface{}) 116 Infof(string, ...interface{}) 117 Infov(interface{}) 118 Slow(...interface{}) 119 Slowf(string, ...interface{}) 120 Slowv(interface{}) 121 WithDuration(time.Duration) Logger 122 } 123 ) 124 125 // MustSetup sets up logging with given config c. It exits on error. 126 func MustSetup(c LogConf) { 127 Must(SetUp(c)) 128 } 129 130 // SetUp sets up the logx. If already set up, just return nil. 131 // we allow SetUp to be called multiple times, because for example 132 // we need to allow different service frameworks to initialize logx respectively. 133 // the same logic for SetUp 134 func SetUp(c LogConf) error { 135 if len(c.TimeFormat) > 0 { 136 timeFormat = c.TimeFormat 137 } 138 switch c.Encoding { 139 case plainEncoding: 140 encoding = plainEncodingType 141 default: 142 encoding = jsonEncodingType 143 } 144 145 switch c.Mode { 146 case consoleMode: 147 setupWithConsole(c) 148 return nil 149 case volumeMode: 150 return setupWithVolume(c) 151 default: 152 return setupWithFiles(c) 153 } 154 } 155 156 // Alert alerts v in alert level, and the message is written to error log. 157 func Alert(v string) { 158 outputText(errorLog, levelAlert, v) 159 } 160 161 // Close closes the logging. 162 func Close() error { 163 if writeConsole { 164 return nil 165 } 166 167 if atomic.LoadUint32(&initialized) == 0 { 168 return ErrLogNotInitialized 169 } 170 171 atomic.StoreUint32(&initialized, 0) 172 173 if infoLog != nil { 174 if err := infoLog.Close(); err != nil { 175 return err 176 } 177 } 178 179 if errorLog != nil { 180 if err := errorLog.Close(); err != nil { 181 return err 182 } 183 } 184 185 if severeLog != nil { 186 if err := severeLog.Close(); err != nil { 187 return err 188 } 189 } 190 191 if slowLog != nil { 192 if err := slowLog.Close(); err != nil { 193 return err 194 } 195 } 196 197 if statLog != nil { 198 if err := statLog.Close(); err != nil { 199 return err 200 } 201 } 202 203 return nil 204 } 205 206 // Disable disables the logging. 207 func Disable() { 208 once.Do(func() { 209 atomic.StoreUint32(&initialized, 1) 210 211 infoLog = iox.NopCloser(ioutil.Discard) 212 errorLog = iox.NopCloser(ioutil.Discard) 213 severeLog = iox.NopCloser(ioutil.Discard) 214 slowLog = iox.NopCloser(ioutil.Discard) 215 statLog = iox.NopCloser(ioutil.Discard) 216 stackLog = ioutil.Discard 217 }) 218 } 219 220 // DisableStat disables the stat logs. 221 func DisableStat() { 222 atomic.StoreUint32(&disableStat, 1) 223 } 224 225 // Error writes v into error log. 226 func Error(v ...interface{}) { 227 ErrorCaller(1, v...) 228 } 229 230 // ErrorCaller writes v with context into error log. 231 func ErrorCaller(callDepth int, v ...interface{}) { 232 errorTextSync(fmt.Sprint(v...), callDepth+callerInnerDepth) 233 } 234 235 // ErrorCallerf writes v with context in format into error log. 236 func ErrorCallerf(callDepth int, format string, v ...interface{}) { 237 errorTextSync(fmt.Errorf(format, v...).Error(), callDepth+callerInnerDepth) 238 } 239 240 // Errorf writes v with format into error log. 241 func Errorf(format string, v ...interface{}) { 242 ErrorCallerf(1, format, v...) 243 } 244 245 // ErrorStack writes v along with call stack into error log. 246 func ErrorStack(v ...interface{}) { 247 // there is newline in stack string 248 stackSync(fmt.Sprint(v...)) 249 } 250 251 // ErrorStackf writes v along with call stack in format into error log. 252 func ErrorStackf(format string, v ...interface{}) { 253 // there is newline in stack string 254 stackSync(fmt.Sprintf(format, v...)) 255 } 256 257 // Errorv writes v into error log with json content. 258 // No call stack attached, because not elegant to pack the messages. 259 func Errorv(v interface{}) { 260 errorAnySync(v) 261 } 262 263 // Info writes v into access log. 264 func Info(v ...interface{}) { 265 infoTextSync(fmt.Sprint(v...)) 266 } 267 268 // Infof writes v with format into access log. 269 func Infof(format string, v ...interface{}) { 270 infoTextSync(fmt.Sprintf(format, v...)) 271 } 272 273 // Infov writes v into access log with json content. 274 func Infov(v interface{}) { 275 infoAnySync(v) 276 } 277 278 // Must checks if err is nil, otherwise logs the err and exits. 279 func Must(err error) { 280 if err != nil { 281 msg := formatWithCaller(err.Error(), 3) 282 log.Print(msg) 283 outputText(severeLog, levelFatal, msg) 284 os.Exit(1) 285 } 286 } 287 288 // SetLevel sets the logging level. It can be used to suppress some logs. 289 func SetLevel(level uint32) { 290 atomic.StoreUint32(&logLevel, level) 291 } 292 293 // Severe writes v into severe log. 294 func Severe(v ...interface{}) { 295 severeSync(fmt.Sprint(v...)) 296 } 297 298 // Severef writes v with format into severe log. 299 func Severef(format string, v ...interface{}) { 300 severeSync(fmt.Sprintf(format, v...)) 301 } 302 303 // Slow writes v into slow log. 304 func Slow(v ...interface{}) { 305 slowTextSync(fmt.Sprint(v...)) 306 } 307 308 // Slowf writes v with format into slow log. 309 func Slowf(format string, v ...interface{}) { 310 slowTextSync(fmt.Sprintf(format, v...)) 311 } 312 313 // Slowv writes v into slow log with json content. 314 func Slowv(v interface{}) { 315 slowAnySync(v) 316 } 317 318 // Stat writes v into stat log. 319 func Stat(v ...interface{}) { 320 statSync(fmt.Sprint(v...)) 321 } 322 323 // Statf writes v with format into stat log. 324 func Statf(format string, v ...interface{}) { 325 statSync(fmt.Sprintf(format, v...)) 326 } 327 328 // WithCooldownMillis customizes logging on writing call stack interval. 329 func WithCooldownMillis(millis int) LogOption { 330 return func(opts *logOptions) { 331 opts.logStackCooldownMills = millis 332 } 333 } 334 335 // WithKeepDays customizes logging to keep logs with days. 336 func WithKeepDays(days int) LogOption { 337 return func(opts *logOptions) { 338 opts.keepDays = days 339 } 340 } 341 342 // WithGzip customizes logging to automatically gzip the log files. 343 func WithGzip() LogOption { 344 return func(opts *logOptions) { 345 opts.gzipEnabled = true 346 } 347 } 348 349 func createOutput(path string) (io.WriteCloser, error) { 350 if len(path) == 0 { 351 return nil, ErrLogPathNotSet 352 } 353 354 return NewLogger(path, DefaultRotateRule(path, backupFileDelimiter, options.keepDays, 355 options.gzipEnabled), options.gzipEnabled) 356 } 357 358 func errorAnySync(v interface{}) { 359 if shallLog(ErrorLevel) { 360 outputAny(errorLog, levelError, v) 361 } 362 } 363 364 func errorTextSync(msg string, callDepth int) { 365 if shallLog(ErrorLevel) { 366 outputError(errorLog, msg, callDepth) 367 } 368 } 369 370 func formatWithCaller(msg string, callDepth int) string { 371 var buf strings.Builder 372 373 caller := getCaller(callDepth) 374 if len(caller) > 0 { 375 buf.WriteString(caller) 376 buf.WriteByte(' ') 377 } 378 379 buf.WriteString(msg) 380 381 return buf.String() 382 } 383 384 func getCaller(callDepth int) string { 385 var buf strings.Builder 386 387 _, file, line, ok := runtime.Caller(callDepth) 388 if ok { 389 short := file 390 for i := len(file) - 1; i > 0; i-- { 391 if file[i] == '/' { 392 short = file[i+1:] 393 break 394 } 395 } 396 buf.WriteString(short) 397 buf.WriteByte(':') 398 buf.WriteString(strconv.Itoa(line)) 399 } 400 401 return buf.String() 402 } 403 404 func getTimestamp() string { 405 return timex.Time().Format(timeFormat) 406 } 407 408 func handleOptions(opts []LogOption) { 409 for _, opt := range opts { 410 opt(&options) 411 } 412 } 413 414 func infoAnySync(val interface{}) { 415 if shallLog(InfoLevel) { 416 outputAny(infoLog, levelInfo, val) 417 } 418 } 419 420 func infoTextSync(msg string) { 421 if shallLog(InfoLevel) { 422 outputText(infoLog, levelInfo, msg) 423 } 424 } 425 426 func outputAny(writer io.Writer, level string, val interface{}) { 427 switch encoding { 428 case plainEncodingType: 429 writePlainAny(writer, level, val) 430 default: 431 info := logEntry{ 432 Timestamp: getTimestamp(), 433 Level: level, 434 Content: val, 435 } 436 outputJson(writer, info) 437 } 438 } 439 440 func outputText(writer io.Writer, level, msg string) { 441 switch encoding { 442 case plainEncodingType: 443 writePlainText(writer, level, msg) 444 default: 445 info := logEntry{ 446 Timestamp: getTimestamp(), 447 Level: level, 448 Content: msg, 449 } 450 outputJson(writer, info) 451 } 452 } 453 454 func outputError(writer io.Writer, msg string, callDepth int) { 455 content := formatWithCaller(msg, callDepth) 456 outputText(writer, levelError, content) 457 } 458 459 func outputJson(writer io.Writer, info interface{}) { 460 if content, err := json.Marshal(info); err != nil { 461 log.Println(err.Error()) 462 } else if atomic.LoadUint32(&initialized) == 0 || writer == nil { 463 log.Println(string(content)) 464 } else { 465 writer.Write(append(content, '\n')) 466 } 467 } 468 469 func setupLogLevel(c LogConf) { 470 switch c.Level { 471 case levelInfo: 472 SetLevel(InfoLevel) 473 case levelError: 474 SetLevel(ErrorLevel) 475 case levelSevere: 476 SetLevel(SevereLevel) 477 } 478 } 479 480 func setupWithConsole(c LogConf) { 481 once.Do(func() { 482 atomic.StoreUint32(&initialized, 1) 483 writeConsole = true 484 setupLogLevel(c) 485 486 infoLog = newLogWriter(log.New(os.Stdout, "", flags)) 487 errorLog = newLogWriter(log.New(os.Stderr, "", flags)) 488 severeLog = newLogWriter(log.New(os.Stderr, "", flags)) 489 slowLog = newLogWriter(log.New(os.Stderr, "", flags)) 490 stackLog = newLessWriter(errorLog, options.logStackCooldownMills) 491 statLog = infoLog 492 }) 493 } 494 495 func setupWithFiles(c LogConf) error { 496 var opts []LogOption 497 var err error 498 499 if len(c.Path) == 0 { 500 return ErrLogPathNotSet 501 } 502 503 opts = append(opts, WithCooldownMillis(c.StackCooldownMillis)) 504 if c.Compress { 505 opts = append(opts, WithGzip()) 506 } 507 if c.KeepDays > 0 { 508 opts = append(opts, WithKeepDays(c.KeepDays)) 509 } 510 511 accessFile := path.Join(c.Path, accessFilename) 512 errorFile := path.Join(c.Path, errorFilename) 513 severeFile := path.Join(c.Path, severeFilename) 514 slowFile := path.Join(c.Path, slowFilename) 515 statFile := path.Join(c.Path, statFilename) 516 517 once.Do(func() { 518 atomic.StoreUint32(&initialized, 1) 519 handleOptions(opts) 520 setupLogLevel(c) 521 522 if infoLog, err = createOutput(accessFile); err != nil { 523 return 524 } 525 526 if errorLog, err = createOutput(errorFile); err != nil { 527 return 528 } 529 530 if severeLog, err = createOutput(severeFile); err != nil { 531 return 532 } 533 534 if slowLog, err = createOutput(slowFile); err != nil { 535 return 536 } 537 538 if statLog, err = createOutput(statFile); err != nil { 539 return 540 } 541 542 stackLog = newLessWriter(errorLog, options.logStackCooldownMills) 543 }) 544 545 return err 546 } 547 548 func setupWithVolume(c LogConf) error { 549 if len(c.ServiceName) == 0 { 550 return ErrLogServiceNameNotSet 551 } 552 553 c.Path = path.Join(c.Path, c.ServiceName, sysx.Hostname()) 554 return setupWithFiles(c) 555 } 556 557 func severeSync(msg string) { 558 if shallLog(SevereLevel) { 559 outputText(severeLog, levelSevere, fmt.Sprintf("%s\n%s", msg, string(debug.Stack()))) 560 } 561 } 562 563 func shallLog(level uint32) bool { 564 return atomic.LoadUint32(&logLevel) <= level 565 } 566 567 func shallLogStat() bool { 568 return atomic.LoadUint32(&disableStat) == 0 569 } 570 571 func slowAnySync(v interface{}) { 572 if shallLog(ErrorLevel) { 573 outputAny(slowLog, levelSlow, v) 574 } 575 } 576 577 func slowTextSync(msg string) { 578 if shallLog(ErrorLevel) { 579 outputText(slowLog, levelSlow, msg) 580 } 581 } 582 583 func stackSync(msg string) { 584 if shallLog(ErrorLevel) { 585 outputText(stackLog, levelError, fmt.Sprintf("%s\n%s", msg, string(debug.Stack()))) 586 } 587 } 588 589 func statSync(msg string) { 590 if shallLogStat() && shallLog(InfoLevel) { 591 outputText(statLog, levelStat, msg) 592 } 593 } 594 595 func writePlainAny(writer io.Writer, level string, val interface{}, fields ...string) { 596 switch v := val.(type) { 597 case string: 598 writePlainText(writer, level, v, fields...) 599 case error: 600 writePlainText(writer, level, v.Error(), fields...) 601 case fmt.Stringer: 602 writePlainText(writer, level, v.String(), fields...) 603 default: 604 var buf bytes.Buffer 605 buf.WriteString(getTimestamp()) 606 buf.WriteByte(plainEncodingSep) 607 buf.WriteString(level) 608 for _, item := range fields { 609 buf.WriteByte(plainEncodingSep) 610 buf.WriteString(item) 611 } 612 buf.WriteByte(plainEncodingSep) 613 if err := json.NewEncoder(&buf).Encode(val); err != nil { 614 log.Println(err.Error()) 615 return 616 } 617 buf.WriteByte('\n') 618 if atomic.LoadUint32(&initialized) == 0 || writer == nil { 619 log.Println(buf.String()) 620 return 621 } 622 623 if _, err := writer.Write(buf.Bytes()); err != nil { 624 log.Println(err.Error()) 625 } 626 } 627 } 628 629 func writePlainText(writer io.Writer, level, msg string, fields ...string) { 630 var buf bytes.Buffer 631 buf.WriteString(getTimestamp()) 632 buf.WriteByte(plainEncodingSep) 633 buf.WriteString(level) 634 for _, item := range fields { 635 buf.WriteByte(plainEncodingSep) 636 buf.WriteString(item) 637 } 638 buf.WriteByte(plainEncodingSep) 639 buf.WriteString(msg) 640 buf.WriteByte('\n') 641 if atomic.LoadUint32(&initialized) == 0 || writer == nil { 642 log.Println(buf.String()) 643 return 644 } 645 646 if _, err := writer.Write(buf.Bytes()); err != nil { 647 log.Println(err.Error()) 648 } 649 } 650 651 type logWriter struct { 652 logger *log.Logger 653 } 654 655 func newLogWriter(logger *log.Logger) logWriter { 656 return logWriter{ 657 logger: logger, 658 } 659 } 660 661 func (lw logWriter) Close() error { 662 return nil 663 } 664 665 func (lw logWriter) Write(data []byte) (int, error) { 666 lw.logger.Print(string(data)) 667 return len(data), nil 668 }