You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

285 lines
6.3 KiB

2 years ago
  1. package logger
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "gitea.baoapi.com/root/stu_uuos/config"
  7. rotatelogs "github.com/lestrrat-go/file-rotatelogs"
  8. "github.com/rifflock/lfshook"
  9. "github.com/sirupsen/logrus"
  10. "os"
  11. "path"
  12. "path/filepath"
  13. "regexp"
  14. "runtime"
  15. "strings"
  16. "time"
  17. )
  18. const (
  19. nocolor logColor = 0
  20. red logColor = 31
  21. green logColor = 32
  22. yellow logColor = 33
  23. blue logColor = 36
  24. gray logColor = 37
  25. )
  26. type TextLogFormatter struct {
  27. Conf *config.Config
  28. }
  29. type logOutTarget string
  30. type logFormatter string
  31. type logLevel string
  32. type logColor int
  33. const (
  34. Std logOutTarget = "std"
  35. File logOutTarget = "file"
  36. )
  37. const (
  38. Text logFormatter = "text"
  39. Json logFormatter = "json"
  40. )
  41. const (
  42. Unknown logLevel = "UNKNOWN"
  43. Trc logLevel = "TRC"
  44. Dbg logLevel = "DBG"
  45. Inf logLevel = "INF"
  46. Wrn logLevel = "WRN"
  47. Err logLevel = "ERR"
  48. Fat logLevel = "FAT"
  49. Pan logLevel = "PAN"
  50. )
  51. func newLogrusLogger() *logrus.Logger {
  52. conf := config.GetConfig()
  53. logger := logrus.New()
  54. lvl, err := logrus.ParseLevel(conf.Log.MinLevel)
  55. if err != nil {
  56. panic(err)
  57. }
  58. logger.Level = lvl
  59. logger.ReportCaller = conf.Log.ReportCaller
  60. if conf.Log.Formatter == string(Text) {
  61. logger.Formatter = &TextLogFormatter{conf}
  62. } else {
  63. logger.Formatter = &logrus.JSONFormatter{
  64. TimestampFormat: conf.Log.TimestampFormat,
  65. PrettyPrint: conf.Log.JsonPrettyPrint,
  66. CallerPrettyfier: func(f *runtime.Frame) (string, string) {
  67. filename, line, funcName := getRuntimeLocation()
  68. if len(filename) == 0 {
  69. _, filename = path.Split(f.File)
  70. } else {
  71. filename = fmt.Sprintf("%s:%d", filename, line)
  72. }
  73. if len(funcName) == 0 {
  74. s := strings.Split(f.Function, ".")
  75. funcName = s[len(s)-1]
  76. }
  77. return funcName, filename
  78. },
  79. }
  80. }
  81. if conf.Log.Out == string(File) {
  82. hook := newLfsHook(conf, logger.Formatter)
  83. logger.AddHook(hook)
  84. } else {
  85. logger.SetOutput(os.Stdout)
  86. }
  87. return logger
  88. }
  89. func (f *TextLogFormatter) Format(entry *logrus.Entry) ([]byte, error) {
  90. var b *bytes.Buffer
  91. if entry.Buffer != nil {
  92. b = entry.Buffer
  93. } else {
  94. b = &bytes.Buffer{}
  95. }
  96. lt, lc := parseLevel(entry)
  97. fn, line, funcName := getEntryRuntimeLocation(entry)
  98. caller := ""
  99. if entry.HasCaller() && f.Conf.Log.ReportCaller {
  100. caller = fmt.Sprintf("%s:%d %s", fn, line, funcName)
  101. }
  102. if f.Conf.Log.Out == string(Std) && f.Conf.Log.OutColor { //只有控制台输出才需要输出颜色,文件输出不需要颜色。
  103. _, err := fmt.Fprintf(b, "%s \x1b[%dm[%s] \x1b[0m%s - %s ",
  104. entry.Time.Format(f.Conf.Log.TimestampFormat), lc, lt, caller, entry.Message)
  105. if err != nil {
  106. panic(err)
  107. }
  108. } else {
  109. _, err := fmt.Fprintf(b, "%s [%s] %s | %-44s ",
  110. entry.Time.Format(f.Conf.Log.TimestampFormat), lt, caller, entry.Message)
  111. if err != nil {
  112. panic(err)
  113. }
  114. }
  115. if len(entry.Data) > 0 {
  116. outJsonEntry(b, entry)
  117. }
  118. b.WriteByte('\n')
  119. return b.Bytes(), nil
  120. }
  121. func parseLevel(entry *logrus.Entry) (logLevel, logColor) {
  122. lt := Unknown
  123. var lc logColor
  124. switch entry.Level {
  125. case logrus.TraceLevel:
  126. lt = Trc
  127. lc = gray
  128. case logrus.DebugLevel:
  129. lt = Dbg
  130. lc = gray
  131. case logrus.InfoLevel:
  132. lt = Inf
  133. lc = blue
  134. case logrus.WarnLevel:
  135. lt = Wrn
  136. lc = yellow
  137. case logrus.ErrorLevel:
  138. lt = Err
  139. lc = red
  140. case logrus.FatalLevel:
  141. lt = Fat
  142. lc = red
  143. case logrus.PanicLevel:
  144. lt = Pan
  145. lc = red
  146. }
  147. return lt, lc
  148. }
  149. func outJsonEntry(b *bytes.Buffer, entry *logrus.Entry) {
  150. jb, err := json.Marshal(entry.Data)
  151. if err != nil {
  152. panic(err)
  153. }
  154. if _, err = fmt.Fprint(b, string(jb)); err != nil {
  155. panic(err)
  156. }
  157. }
  158. func newLfsHook(conf *config.Config, formatter logrus.Formatter) logrus.Hook {
  159. filePathName := path.Join(conf.Log.FilePath, conf.Log.FileName)
  160. var opts []rotatelogs.Option
  161. // 生成软链,指向最新日志文件
  162. opts = append(opts, rotatelogs.WithLinkName(filePathName))
  163. // 日志切割时间间隔
  164. du1, err := time.ParseDuration(conf.Log.RotationTime)
  165. if err != nil {
  166. panic(err)
  167. }
  168. if du1.Minutes() < 1 {
  169. panic(fmt.Errorf("日志切割时间 %v 太短", du1))
  170. }
  171. opts = append(opts, rotatelogs.WithRotationTime(du1))
  172. du2, err := time.ParseDuration(conf.Log.MaxAge)
  173. if err != nil {
  174. panic(err)
  175. }
  176. if du2.Minutes() > 0 && du2.Minutes() < 1 {
  177. panic(fmt.Errorf("日志保留时间 %v 太短", du1))
  178. }
  179. // WithMaxAge 和 WithRotationCount 二者只能设置一个
  180. if du2.Minutes() > 0 {
  181. // 文件最长保存时间
  182. opts = append(opts, rotatelogs.WithMaxAge(du2))
  183. } else if conf.Log.MaxRemainCount > 0 {
  184. //最多保存文件个数
  185. opts = append(opts, rotatelogs.WithRotationCount(conf.Log.MaxRemainCount))
  186. } else {
  187. // 默认设置为文件最长保存时间一个月
  188. opts = append(opts, rotatelogs.WithMaxAge(time.Hour*24*30))
  189. }
  190. writer, err := rotatelogs.New(filePathName+".%Y%m%d%H%M", opts...)
  191. if err != nil {
  192. panic(err)
  193. }
  194. lfsHook := lfshook.NewHook(lfshook.WriterMap{
  195. logrus.TraceLevel: writer,
  196. logrus.DebugLevel: writer,
  197. logrus.InfoLevel: writer,
  198. logrus.WarnLevel: writer,
  199. logrus.ErrorLevel: writer,
  200. logrus.FatalLevel: writer,
  201. logrus.PanicLevel: writer,
  202. }, formatter)
  203. return lfsHook
  204. }
  205. func getEntryRuntimeLocation(entry *logrus.Entry) (filename string, line int, funcName string) {
  206. filename, line, funcName = getRuntimeLocation()
  207. if len(filename) == 0 && line == 0 && len(funcName) == 0 {
  208. filename = filepath.Base(entry.Caller.File)
  209. line = entry.Caller.Line
  210. funcName = filepath.Base(entry.Caller.Function)
  211. }
  212. return filename, line, funcName
  213. }
  214. var logRegexp = regexp.MustCompile(`config*|model*|entity*|db*|constant*|srv*|web*|handler*|router*|util*|micro*`)
  215. var excludeRegexp = regexp.MustCompile(`logger*|logrus*`)
  216. // 获取正在运行的文件名称、文件所在行、函数名
  217. func getRuntimeLocation() (filename string, line int, funcName string) {
  218. var pc uintptr
  219. var ok bool
  220. var i int
  221. for {
  222. pc, filename, line, ok = runtime.Caller(i)
  223. if ok && (logRegexp.MatchString(filename) && !excludeRegexp.MatchString(filename)) {
  224. funcName = runtime.FuncForPC(pc).Name()
  225. fns := strings.Split(funcName, "/")
  226. if len(fns) > 1 {
  227. dir := strings.Join(fns[1:len(fns)-1], "/")
  228. directPkg := strings.Split(fns[len(fns)-1], ".")[0]
  229. filename = fmt.Sprintf("%s/%s/%s", dir, directPkg, filepath.Base(filename))
  230. }
  231. funcName = filepath.Base(funcName)
  232. break
  233. }
  234. i++
  235. }
  236. return filename, line, funcName
  237. }