formatter_pretty.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. package logger
  2. import (
  3. "log/slog"
  4. "time"
  5. )
  6. var (
  7. prettyGroupOpenToken = []byte("{")
  8. prettyGroupCloseToken = []byte(" }")
  9. prettyColorDebug = []byte("\x1b[37m") // Gray
  10. prettyColorDebugBold = []byte("\x1b[1;37m") // Bold Gray
  11. prettyColorInfo = []byte("\x1b[36m") // Cyan
  12. prettyColorInfoBold = []byte("\x1b[1;36m") // Bold Cyan
  13. prettyColorWarn = []byte("\x1b[33m") // Yellow
  14. prettyColorWarnBold = []byte("\x1b[1;33m") // Bold Yellow
  15. prettyColorError = []byte("\x1b[31m") // Red
  16. prettyColorErrorBold = []byte("\x1b[1;31m") // Bold Red
  17. prettyColorReset = []byte("\x1b[0m")
  18. )
  19. // formatterPretty is a pretty printer for log records.
  20. type formatterPretty struct {
  21. formatterCommon
  22. colorThin []byte
  23. colorBold []byte
  24. groupsOpened int
  25. }
  26. // newFormatterPretty creates a new instance of formatterPretty.
  27. func newFormatterPretty(groups []attrGroup, buf *buffer) *formatterPretty {
  28. return &formatterPretty{
  29. formatterCommon: newFormatterCommon(groups, buf),
  30. }
  31. }
  32. // format formats a log record as a pretty-printed string.
  33. func (s *formatterPretty) format(r slog.Record) {
  34. s.colorThin, s.colorBold = s.getColor(r.Level)
  35. // Append timestamp
  36. s.buf.appendStringRaw(r.Time.Format(time.DateTime))
  37. s.buf.append(' ')
  38. // Append level marker
  39. s.buf.append(s.colorBold...)
  40. s.buf.appendStringRaw(s.levelName(r.Level))
  41. s.buf.append(prettyColorReset...)
  42. s.buf.append(' ')
  43. // Append message
  44. s.buf.appendStringRaw(r.Message)
  45. // Append groups added with [Handler.WithAttrs] and [Handler.WithGroup]
  46. for _, g := range s.groups {
  47. if g.name != "" {
  48. s.openGroup(g.name)
  49. }
  50. s.appendAttributes(g.attrs)
  51. }
  52. // Append attributes from the record
  53. r.Attrs(func(attr slog.Attr) bool {
  54. s.appendAttribute(attr)
  55. return true
  56. })
  57. // Close all opened groups.
  58. for s.groupsOpened > 0 {
  59. s.closeGroup()
  60. }
  61. // Append error, source, and stack if present
  62. if s.error.Key != "" {
  63. s.appendKey(s.error.Key)
  64. s.appendValue(s.error.Value, false)
  65. }
  66. if s.source.Key != "" {
  67. s.appendKey(s.source.Key)
  68. s.appendValue(s.source.Value, false)
  69. }
  70. if s.stack.Key != "" {
  71. s.buf.append('\n')
  72. s.buf.append(prettyColorDebug...)
  73. s.buf.appendStringRaw(s.stack.Value.String())
  74. s.buf.append(prettyColorReset...)
  75. }
  76. }
  77. // getColor returns the terminal color sequences for a given log level.
  78. func (s *formatterPretty) getColor(lvl slog.Level) ([]byte, []byte) {
  79. switch {
  80. case lvl < slog.LevelInfo:
  81. return prettyColorDebug, prettyColorDebugBold
  82. case lvl < slog.LevelWarn:
  83. return prettyColorInfo, prettyColorInfoBold
  84. case lvl < slog.LevelError:
  85. return prettyColorWarn, prettyColorWarnBold
  86. default:
  87. return prettyColorError, prettyColorErrorBold
  88. }
  89. }
  90. // levelName returns the string representation of a log level.
  91. func (s *formatterPretty) levelName(lvl slog.Level) string {
  92. switch {
  93. case lvl < slog.LevelInfo:
  94. return "[DBG]"
  95. case lvl < slog.LevelWarn:
  96. return "[INF]"
  97. case lvl < slog.LevelError:
  98. return "[WRN]"
  99. case lvl < LevelCritical:
  100. return "[ERR]"
  101. default:
  102. return "[CRT]"
  103. }
  104. }
  105. // appendAttributes appends a list of attributes to the buffer.
  106. func (s *formatterPretty) appendAttributes(attrs []slog.Attr) {
  107. for _, attr := range attrs {
  108. s.appendAttribute(attr)
  109. }
  110. }
  111. // appendAttribute appends a single attribute to the buffer.
  112. func (s *formatterPretty) appendAttribute(attr slog.Attr) {
  113. // Resolve [slog.LogValuer] values
  114. attr.Value = attr.Value.Resolve()
  115. // If there are no groups opened, save special attributes for later
  116. if s.groupsOpened == 0 && s.saveSpecialAttr(attr) {
  117. return
  118. }
  119. // Groups need special handling
  120. if attr.Value.Kind() == slog.KindGroup {
  121. s.appendGroup(attr.Key, attr.Value.Group())
  122. return
  123. }
  124. s.appendKey(attr.Key)
  125. s.appendValue(attr.Value, false)
  126. }
  127. // appendKey appends an attribute key to the buffer.
  128. func (s *formatterPretty) appendKey(key string) {
  129. s.buf.append(' ')
  130. s.buf.append(s.colorThin...)
  131. s.buf.appendString(key)
  132. s.buf.append(prettyColorReset...)
  133. s.buf.append('=')
  134. }
  135. // appendGroup appends a group of attributes to the buffer.
  136. func (s *formatterPretty) appendGroup(name string, attrs []slog.Attr) {
  137. // Ignore empty groups
  138. if len(attrs) == 0 {
  139. return
  140. }
  141. if len(name) > 0 {
  142. // If the group has a name, open it and defer closing it.
  143. // Unnamed groups should be treated as sets of regular attributes.
  144. s.openGroup(name)
  145. defer s.closeGroup()
  146. }
  147. s.appendAttributes(attrs)
  148. }
  149. // openGroup opens a new group of attributes.
  150. func (s *formatterPretty) openGroup(name string) {
  151. s.groupsOpened++
  152. s.appendKey(name)
  153. s.buf.append(prettyGroupOpenToken...)
  154. }
  155. // closeGroup closes the most recently opened group of attributes.
  156. func (s *formatterPretty) closeGroup() {
  157. s.groupsOpened--
  158. s.buf.append(prettyGroupCloseToken...)
  159. }