glog_logger.go 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. // Copyright GoFrame Author(https://github.com/gogf/gf). 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. "github.com/gogf/gf/container/gtype"
  12. "github.com/gogf/gf/internal/intlog"
  13. "github.com/gogf/gf/os/gfpool"
  14. "github.com/gogf/gf/os/gmlock"
  15. "github.com/gogf/gf/os/gtimer"
  16. "io"
  17. "os"
  18. "strings"
  19. "time"
  20. "github.com/gogf/gf/debug/gdebug"
  21. "github.com/gogf/gf/os/gfile"
  22. "github.com/gogf/gf/os/gtime"
  23. "github.com/gogf/gf/text/gregex"
  24. "github.com/gogf/gf/util/gconv"
  25. )
  26. // Logger is the struct for logging management.
  27. type Logger struct {
  28. ctx context.Context // Context for logging.
  29. init *gtype.Bool // Initialized.
  30. parent *Logger // Parent logger, if it is not empty, it means the logger is used in chaining function.
  31. config Config // Logger configuration.
  32. }
  33. const (
  34. defaultFileFormat = `{Y-m-d}.log`
  35. defaultFileFlags = os.O_CREATE | os.O_WRONLY | os.O_APPEND
  36. defaultFilePerm = os.FileMode(0666)
  37. defaultFileExpire = time.Minute
  38. pathFilterKey = "/os/glog/glog"
  39. )
  40. const (
  41. F_ASYNC = 1 << iota // Print logging content asynchronously。
  42. F_FILE_LONG // Print full file name and line number: /a/b/c/d.go:23.
  43. F_FILE_SHORT // Print final file name element and line number: d.go:23. overrides F_FILE_LONG.
  44. F_TIME_DATE // Print the date in the local time zone: 2009-01-23.
  45. F_TIME_TIME // Print the time in the local time zone: 01:23:23.
  46. F_TIME_MILLI // Print the time with milliseconds in the local time zone: 01:23:23.675.
  47. F_CALLER_FN // Print Caller function name and package: main.main
  48. F_TIME_STD = F_TIME_DATE | F_TIME_MILLI
  49. )
  50. // New creates and returns a custom logger.
  51. func New() *Logger {
  52. logger := &Logger{
  53. init: gtype.NewBool(),
  54. config: DefaultConfig(),
  55. }
  56. return logger
  57. }
  58. // NewWithWriter creates and returns a custom logger with io.Writer.
  59. func NewWithWriter(writer io.Writer) *Logger {
  60. l := New()
  61. l.SetWriter(writer)
  62. return l
  63. }
  64. // Clone returns a new logger, which is the clone the current logger.
  65. // It's commonly used for chaining operations.
  66. func (l *Logger) Clone() *Logger {
  67. logger := New()
  68. logger.ctx = l.ctx
  69. logger.config = l.config
  70. logger.parent = l
  71. return logger
  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(std io.Writer, lead string, values ...interface{}) {
  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. p := l
  89. if p.parent != nil {
  90. p = p.parent
  91. }
  92. if !p.init.Val() && p.init.Cas(false, true) {
  93. // It just initializes once for each logger.
  94. if p.config.RotateSize > 0 || p.config.RotateExpire > 0 {
  95. gtimer.AddOnce(p.config.RotateCheckInterval, p.rotateChecksTimely)
  96. intlog.Printf("logger rotation initialized: every %s", p.config.RotateCheckInterval.String())
  97. }
  98. }
  99. var (
  100. now = time.Now()
  101. buffer = bytes.NewBuffer(nil)
  102. )
  103. if l.config.HeaderPrint {
  104. // Time.
  105. timeFormat := ""
  106. if l.config.Flags&F_TIME_DATE > 0 {
  107. timeFormat += "2006-01-02 "
  108. }
  109. if l.config.Flags&F_TIME_TIME > 0 {
  110. timeFormat += "15:04:05 "
  111. }
  112. if l.config.Flags&F_TIME_MILLI > 0 {
  113. timeFormat += "15:04:05.000 "
  114. }
  115. if len(timeFormat) > 0 {
  116. buffer.WriteString(now.Format(timeFormat))
  117. }
  118. // Lead string.
  119. if len(lead) > 0 {
  120. buffer.WriteString(lead)
  121. if len(values) > 0 {
  122. buffer.WriteByte(' ')
  123. }
  124. }
  125. // Caller path and Fn name.
  126. if l.config.Flags&(F_FILE_LONG|F_FILE_SHORT|F_CALLER_FN) > 0 {
  127. callerPath := ""
  128. callerFnName, path, line := gdebug.CallerWithFilter(pathFilterKey, l.config.StSkip)
  129. if l.config.Flags&F_CALLER_FN > 0 {
  130. buffer.WriteString(fmt.Sprintf(`[%s] `, callerFnName))
  131. }
  132. if l.config.Flags&F_FILE_LONG > 0 {
  133. callerPath = fmt.Sprintf(`%s:%d: `, path, line)
  134. }
  135. if l.config.Flags&F_FILE_SHORT > 0 {
  136. callerPath = fmt.Sprintf(`%s:%d: `, gfile.Basename(path), line)
  137. }
  138. buffer.WriteString(callerPath)
  139. }
  140. // Prefix.
  141. if len(l.config.Prefix) > 0 {
  142. buffer.WriteString(l.config.Prefix + " ")
  143. }
  144. }
  145. // Convert value to string.
  146. var (
  147. tempStr = ""
  148. valueStr = ""
  149. )
  150. // Context values.
  151. if l.ctx != nil && len(l.config.CtxKeys) > 0 {
  152. ctxStr := ""
  153. for _, key := range l.config.CtxKeys {
  154. if v := l.ctx.Value(key); v != nil {
  155. if ctxStr != "" {
  156. ctxStr += ", "
  157. }
  158. ctxStr += fmt.Sprintf("%s: %+v", key, v)
  159. }
  160. }
  161. if ctxStr != "" {
  162. buffer.WriteString(fmt.Sprintf("{%s} ", ctxStr))
  163. }
  164. }
  165. for _, v := range values {
  166. tempStr = gconv.String(v)
  167. if len(valueStr) > 0 {
  168. if valueStr[len(valueStr)-1] == '\n' {
  169. // Remove one blank line(\n\n).
  170. if tempStr[0] == '\n' {
  171. valueStr += tempStr[1:]
  172. } else {
  173. valueStr += tempStr
  174. }
  175. } else {
  176. valueStr += " " + tempStr
  177. }
  178. } else {
  179. valueStr = tempStr
  180. }
  181. }
  182. buffer.WriteString(valueStr + "\n")
  183. if l.config.Flags&F_ASYNC > 0 {
  184. err := asyncPool.Add(func() {
  185. l.printToWriter(now, std, buffer)
  186. })
  187. if err != nil {
  188. intlog.Error(err)
  189. }
  190. } else {
  191. l.printToWriter(now, std, buffer)
  192. }
  193. }
  194. // printToWriter writes buffer to writer.
  195. func (l *Logger) printToWriter(now time.Time, std io.Writer, buffer *bytes.Buffer) {
  196. if l.config.Writer == nil {
  197. // Output content to disk file.
  198. if l.config.Path != "" {
  199. l.printToFile(now, buffer)
  200. }
  201. // Allow output to stdout?
  202. if l.config.StdoutPrint {
  203. if _, err := std.Write(buffer.Bytes()); err != nil {
  204. intlog.Error(err)
  205. }
  206. }
  207. } else {
  208. if _, err := l.config.Writer.Write(buffer.Bytes()); err != nil {
  209. // panic(err)
  210. intlog.Error(err)
  211. }
  212. }
  213. }
  214. // printToFile outputs logging content to disk file.
  215. func (l *Logger) printToFile(now time.Time, buffer *bytes.Buffer) {
  216. var (
  217. logFilePath = l.getFilePath(now)
  218. memoryLockKey = "glog.printToFile:" + logFilePath
  219. )
  220. gmlock.Lock(memoryLockKey)
  221. defer gmlock.Unlock(memoryLockKey)
  222. // Rotation file size checks.
  223. if l.config.RotateSize > 0 {
  224. if gfile.Size(logFilePath) > l.config.RotateSize {
  225. l.rotateFileBySize(now)
  226. }
  227. }
  228. // Logging content outputting to disk file.
  229. if file := l.getFilePointer(logFilePath); file == nil {
  230. intlog.Errorf(`got nil file pointer for: %s`, logFilePath)
  231. } else {
  232. if _, err := file.Write(buffer.Bytes()); err != nil {
  233. intlog.Error(err)
  234. }
  235. if err := file.Close(); err != nil {
  236. intlog.Error(err)
  237. }
  238. }
  239. }
  240. // getFilePointer retrieves and returns a file pointer from file pool.
  241. func (l *Logger) getFilePointer(path string) *gfpool.File {
  242. file, err := gfpool.Open(
  243. path,
  244. defaultFileFlags,
  245. defaultFilePerm,
  246. defaultFileExpire,
  247. )
  248. if err != nil {
  249. // panic(err)
  250. intlog.Error(err)
  251. }
  252. return file
  253. }
  254. // printStd prints content <s> without stack.
  255. func (l *Logger) printStd(lead string, value ...interface{}) {
  256. l.print(os.Stdout, lead, value...)
  257. }
  258. // printStd prints content <s> with stack check.
  259. func (l *Logger) printErr(lead string, value ...interface{}) {
  260. if l.config.StStatus == 1 {
  261. if s := l.GetStack(); s != "" {
  262. value = append(value, "\nStack:\n"+s)
  263. }
  264. }
  265. // In matter of sequence, do not use stderr here, but use the same stdout.
  266. l.print(os.Stdout, lead, value...)
  267. }
  268. // format formats <values> using fmt.Sprintf.
  269. func (l *Logger) format(format string, value ...interface{}) string {
  270. return fmt.Sprintf(format, value...)
  271. }
  272. // PrintStack prints the caller stack,
  273. // the optional parameter <skip> specify the skipped stack offset from the end point.
  274. func (l *Logger) PrintStack(skip ...int) {
  275. if s := l.GetStack(skip...); s != "" {
  276. l.Println("Stack:\n" + s)
  277. } else {
  278. l.Println()
  279. }
  280. }
  281. // GetStack returns the caller stack content,
  282. // the optional parameter <skip> specify the skipped stack offset from the end point.
  283. func (l *Logger) GetStack(skip ...int) string {
  284. stackSkip := l.config.StSkip
  285. if len(skip) > 0 {
  286. stackSkip += skip[0]
  287. }
  288. filters := []string{pathFilterKey}
  289. if l.config.StFilter != "" {
  290. filters = append(filters, l.config.StFilter)
  291. }
  292. return gdebug.StackWithFilters(filters, stackSkip)
  293. }