logs.go 11 KB

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