logs.go 14 KB

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