logs.go 12 KB

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