glog_logger.go 11 KB

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