glog_logger.go 12 KB

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