glog_logger.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393
  1. // Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
  2. //
  3. // This Source Code Form is subject to the terms of the MIT License.
  4. // If a copy of the MIT was not distributed with this file,
  5. // You can obtain one at https://github.com/gogf/gf.
  6. package glog
  7. import (
  8. "bytes"
  9. "context"
  10. "fmt"
  11. "io"
  12. "os"
  13. "strings"
  14. "time"
  15. "github.com/fatih/color"
  16. "github.com/gogf/gf/container/gtype"
  17. "github.com/gogf/gf/internal/intlog"
  18. "github.com/gogf/gf/os/gctx"
  19. "github.com/gogf/gf/os/gfpool"
  20. "github.com/gogf/gf/os/gmlock"
  21. "github.com/gogf/gf/os/gtimer"
  22. "go.opentelemetry.io/otel/trace"
  23. "github.com/gogf/gf/debug/gdebug"
  24. "github.com/gogf/gf/os/gfile"
  25. "github.com/gogf/gf/os/gtime"
  26. "github.com/gogf/gf/text/gregex"
  27. "github.com/gogf/gf/util/gconv"
  28. )
  29. // Logger is the struct for logging management.
  30. type Logger struct {
  31. ctx context.Context // Context for logging.
  32. init *gtype.Bool // Initialized.
  33. parent *Logger // Parent logger, if it is not empty, it means the logger is used in chaining function.
  34. config Config // Logger configuration.
  35. }
  36. const (
  37. defaultFileFormat = `{Y-m-d}.log`
  38. defaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND
  39. defaultFilePerm = os.FileMode(0666)
  40. defaultFileExpire = time.Minute
  41. pathFilterKey = "/os/glog/glog"
  42. memoryLockPrefixForPrintingToFile = "glog.printToFile:"
  43. )
  44. const (
  45. F_ASYNC = 1 << iota // Print logging content asynchronously。
  46. F_FILE_LONG // Print full file name and line number: /a/b/c/d.go:23.
  47. F_FILE_SHORT // Print final file name element and line number: d.go:23. overrides F_FILE_LONG.
  48. F_TIME_DATE // Print the date in the local time zone: 2009-01-23.
  49. F_TIME_TIME // Print the time in the local time zone: 01:23:23.
  50. F_TIME_MILLI // Print the time with milliseconds in the local time zone: 01:23:23.675.
  51. F_CALLER_FN // Print Caller function name and package: main.main
  52. F_TIME_STD = F_TIME_DATE | F_TIME_MILLI
  53. )
  54. // New creates and returns a custom logger.
  55. func New() *Logger {
  56. logger := &Logger{
  57. init: gtype.NewBool(),
  58. config: DefaultConfig(),
  59. }
  60. return logger
  61. }
  62. // NewWithWriter creates and returns a custom logger with io.Writer.
  63. func NewWithWriter(writer io.Writer) *Logger {
  64. l := New()
  65. l.SetWriter(writer)
  66. return l
  67. }
  68. // Clone returns a new logger, which is the clone the current logger.
  69. // It's commonly used for chaining operations.
  70. func (l *Logger) Clone() *Logger {
  71. newLogger := New()
  72. newLogger.ctx = l.ctx
  73. newLogger.config = l.config
  74. newLogger.parent = l
  75. return newLogger
  76. }
  77. // getFilePath returns the logging file path.
  78. // The logging file name must have extension name of "log".
  79. func (l *Logger) getFilePath(now time.Time) string {
  80. // Content containing "{}" in the file name is formatted using gtime.
  81. file, _ := gregex.ReplaceStringFunc(`{.+?}`, l.config.File, func(s string) string {
  82. return gtime.New(now).Format(strings.Trim(s, "{}"))
  83. })
  84. file = gfile.Join(l.config.Path, file)
  85. return file
  86. }
  87. // print prints `s` to defined writer, logging file or passed `std`.
  88. func (l *Logger) print(ctx context.Context, level int, values ...interface{}) {
  89. // Lazy initialize for rotation feature.
  90. // It uses atomic reading operation to enhance the performance checking.
  91. // It here uses CAP for performance and concurrent safety.
  92. p := l
  93. if p.parent != nil {
  94. p = p.parent
  95. }
  96. // It just initializes once for each logger.
  97. if p.config.RotateSize > 0 || p.config.RotateExpire > 0 {
  98. if !p.init.Val() && p.init.Cas(false, true) {
  99. gtimer.AddOnce(p.config.RotateCheckInterval, p.rotateChecksTimely)
  100. intlog.Printf(ctx, "logger rotation initialized: every %s", p.config.RotateCheckInterval.String())
  101. }
  102. }
  103. var (
  104. now = time.Now()
  105. input = &HandlerInput{
  106. Logger: l,
  107. Buffer: bytes.NewBuffer(nil),
  108. Ctx: ctx,
  109. Time: now,
  110. Color: defaultLevelColor[level],
  111. Level: level,
  112. handlerIndex: -1,
  113. }
  114. )
  115. if l.config.HeaderPrint {
  116. // Time.
  117. timeFormat := ""
  118. if l.config.Flags&F_TIME_DATE > 0 {
  119. timeFormat += "2006-01-02"
  120. }
  121. if l.config.Flags&F_TIME_TIME > 0 {
  122. if timeFormat != "" {
  123. timeFormat += " "
  124. }
  125. timeFormat += "15:04:05"
  126. }
  127. if l.config.Flags&F_TIME_MILLI > 0 {
  128. if timeFormat != "" {
  129. timeFormat += " "
  130. }
  131. timeFormat += "15:04:05.000"
  132. }
  133. if len(timeFormat) > 0 {
  134. input.TimeFormat = now.Format(timeFormat)
  135. }
  136. // Level string.
  137. input.LevelFormat = l.getLevelPrefixWithBrackets(level)
  138. // Caller path and Fn name.
  139. if l.config.Flags&(F_FILE_LONG|F_FILE_SHORT|F_CALLER_FN) > 0 {
  140. callerFnName, path, line := gdebug.CallerWithFilter([]string{pathFilterKey}, l.config.StSkip)
  141. if l.config.Flags&F_CALLER_FN > 0 {
  142. if len(callerFnName) > 2 {
  143. input.CallerFunc = fmt.Sprintf(`[%s]`, callerFnName)
  144. }
  145. }
  146. if line >= 0 && len(path) > 1 {
  147. if l.config.Flags&F_FILE_LONG > 0 {
  148. input.CallerPath = fmt.Sprintf(`%s:%d:`, path, line)
  149. }
  150. if l.config.Flags&F_FILE_SHORT > 0 {
  151. input.CallerPath = fmt.Sprintf(`%s:%d:`, gfile.Basename(path), line)
  152. }
  153. }
  154. }
  155. // Prefix.
  156. if len(l.config.Prefix) > 0 {
  157. input.Prefix = l.config.Prefix
  158. }
  159. }
  160. // Convert value to string.
  161. if ctx != nil {
  162. // Tracing values.
  163. spanCtx := trace.SpanContextFromContext(ctx)
  164. if traceId := spanCtx.TraceID(); traceId.IsValid() {
  165. input.CtxStr = traceId.String()
  166. }
  167. // Context values.
  168. if len(l.config.CtxKeys) > 0 {
  169. for _, ctxKey := range l.config.CtxKeys {
  170. var ctxValue interface{}
  171. if ctxValue = ctx.Value(ctxKey); ctxValue == nil {
  172. ctxValue = ctx.Value(gctx.StrKey(gconv.String(ctxKey)))
  173. }
  174. if ctxValue != nil {
  175. if input.CtxStr != "" {
  176. input.CtxStr += ", "
  177. }
  178. input.CtxStr += gconv.String(ctxValue)
  179. }
  180. }
  181. }
  182. if input.CtxStr != "" {
  183. input.CtxStr = "{" + input.CtxStr + "}"
  184. }
  185. }
  186. var tempStr string
  187. for _, v := range values {
  188. tempStr = gconv.String(v)
  189. if len(input.Content) > 0 {
  190. if input.Content[len(input.Content)-1] == '\n' {
  191. // Remove one blank line(\n\n).
  192. if tempStr[0] == '\n' {
  193. input.Content += tempStr[1:]
  194. } else {
  195. input.Content += tempStr
  196. }
  197. } else {
  198. input.Content += " " + tempStr
  199. }
  200. } else {
  201. input.Content = tempStr
  202. }
  203. }
  204. if l.config.Flags&F_ASYNC > 0 {
  205. input.IsAsync = true
  206. err := asyncPool.Add(func() {
  207. input.Next()
  208. })
  209. if err != nil {
  210. intlog.Error(ctx, err)
  211. }
  212. } else {
  213. input.Next()
  214. }
  215. }
  216. // doDefaultPrint outputs the logging content according configuration.
  217. func (l *Logger) doDefaultPrint(ctx context.Context, input *HandlerInput) *bytes.Buffer {
  218. var buffer *bytes.Buffer
  219. if l.config.Writer == nil {
  220. // Allow output to stdout?
  221. if l.config.StdoutPrint {
  222. if buf := l.printToStdout(ctx, input); buf != nil {
  223. buffer = buf
  224. }
  225. }
  226. // Output content to disk file.
  227. if l.config.Path != "" {
  228. if buf := l.printToFile(ctx, input.Time, input); buf != nil {
  229. buffer = buf
  230. }
  231. }
  232. } else {
  233. // Output to custom writer.
  234. if buf := l.printToWriter(ctx, input); buf != nil {
  235. buffer = buf
  236. }
  237. }
  238. return buffer
  239. }
  240. // printToWriter writes buffer to writer.
  241. func (l *Logger) printToWriter(ctx context.Context, input *HandlerInput) *bytes.Buffer {
  242. if l.config.Writer != nil {
  243. var (
  244. buffer = input.getRealBuffer(l.config.WriterColorEnable)
  245. )
  246. if _, err := l.config.Writer.Write(buffer.Bytes()); err != nil {
  247. intlog.Error(ctx, err)
  248. }
  249. return buffer
  250. }
  251. return nil
  252. }
  253. // printToStdout outputs logging content to stdout.
  254. func (l *Logger) printToStdout(ctx context.Context, input *HandlerInput) *bytes.Buffer {
  255. if l.config.StdoutPrint {
  256. var (
  257. buffer = input.getRealBuffer(true)
  258. )
  259. // This will lose color in Windows os system.
  260. // if _, err := os.Stdout.Write(input.getRealBuffer(true).Bytes()); err != nil {
  261. // This will print color in Windows os system.
  262. if _, err := fmt.Fprint(color.Output, buffer.String()); err != nil {
  263. intlog.Error(ctx, err)
  264. }
  265. return buffer
  266. }
  267. return nil
  268. }
  269. // printToFile outputs logging content to disk file.
  270. func (l *Logger) printToFile(ctx context.Context, t time.Time, in *HandlerInput) *bytes.Buffer {
  271. var (
  272. buffer = in.getRealBuffer(l.config.WriterColorEnable)
  273. logFilePath = l.getFilePath(t)
  274. memoryLockKey = memoryLockPrefixForPrintingToFile + logFilePath
  275. )
  276. gmlock.Lock(memoryLockKey)
  277. defer gmlock.Unlock(memoryLockKey)
  278. // Rotation file size checks.
  279. if l.config.RotateSize > 0 {
  280. if gfile.Size(logFilePath) > l.config.RotateSize {
  281. l.rotateFileBySize(t)
  282. }
  283. }
  284. // Logging content outputting to disk file.
  285. if file := l.getFilePointer(ctx, logFilePath); file == nil {
  286. intlog.Errorf(ctx, `got nil file pointer for: %s`, logFilePath)
  287. } else {
  288. if _, err := file.Write(buffer.Bytes()); err != nil {
  289. intlog.Error(ctx, err)
  290. }
  291. if err := file.Close(); err != nil {
  292. intlog.Error(ctx, err)
  293. }
  294. }
  295. return buffer
  296. }
  297. // getFilePointer retrieves and returns a file pointer from file pool.
  298. func (l *Logger) getFilePointer(ctx context.Context, path string) *gfpool.File {
  299. file, err := gfpool.Open(
  300. path,
  301. defaultFileFlags,
  302. defaultFilePerm,
  303. defaultFileExpire,
  304. )
  305. if err != nil {
  306. // panic(err)
  307. intlog.Error(ctx, err)
  308. }
  309. return file
  310. }
  311. // getCtx returns the context which is set through chaining operations.
  312. // It returns an empty context if no context set previously.
  313. func (l *Logger) getCtx() context.Context {
  314. if l.ctx != nil {
  315. return l.ctx
  316. }
  317. return context.TODO()
  318. }
  319. // printStd prints content `s` without stack.
  320. func (l *Logger) printStd(level int, value ...interface{}) {
  321. l.print(l.getCtx(), level, value...)
  322. }
  323. // printStd prints content `s` with stack check.
  324. func (l *Logger) printErr(level int, value ...interface{}) {
  325. if l.config.StStatus == 1 {
  326. if s := l.GetStack(); s != "" {
  327. value = append(value, "\nStack:\n"+s)
  328. }
  329. }
  330. // In matter of sequence, do not use stderr here, but use the same stdout.
  331. l.print(l.getCtx(), level, value...)
  332. }
  333. // format formats `values` using fmt.Sprintf.
  334. func (l *Logger) format(format string, value ...interface{}) string {
  335. return fmt.Sprintf(format, value...)
  336. }
  337. // PrintStack prints the caller stack,
  338. // the optional parameter `skip` specify the skipped stack offset from the end point.
  339. func (l *Logger) PrintStack(skip ...int) {
  340. if s := l.GetStack(skip...); s != "" {
  341. l.Println("Stack:\n" + s)
  342. } else {
  343. l.Println()
  344. }
  345. }
  346. // GetStack returns the caller stack content,
  347. // the optional parameter `skip` specify the skipped stack offset from the end point.
  348. func (l *Logger) GetStack(skip ...int) string {
  349. stackSkip := l.config.StSkip
  350. if len(skip) > 0 {
  351. stackSkip += skip[0]
  352. }
  353. filters := []string{pathFilterKey}
  354. if l.config.StFilter != "" {
  355. filters = append(filters, l.config.StFilter)
  356. }
  357. return gdebug.StackWithFilters(filters, stackSkip)
  358. }
  359. // GetConfig returns the configuration of current Logger.
  360. func (l *Logger) GetConfig() Config {
  361. return l.config
  362. }