logs.go 11 KB

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