logs.go 11 KB

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