handler_glog.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. // Copyright 2017 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package log
  17. import (
  18. "errors"
  19. "fmt"
  20. "regexp"
  21. "runtime"
  22. "strconv"
  23. "strings"
  24. "sync"
  25. "sync/atomic"
  26. )
  27. // errVmoduleSyntax is returned when a user vmodule pattern is invalid.
  28. var errVmoduleSyntax = errors.New("expect comma-separated list of filename=N")
  29. // errTraceSyntax is returned when a user backtrace pattern is invalid.
  30. var errTraceSyntax = errors.New("expect file.go:234")
  31. // GlogHandler is a log handler that mimics the filtering features of Google's
  32. // glog logger: setting global log levels; overriding with callsite pattern
  33. // matches; and requesting backtraces at certain positions.
  34. type GlogHandler struct {
  35. origin Handler // The origin handler this wraps
  36. level uint32 // Current log level, atomically accessible
  37. override uint32 // Flag whether overrides are used, atomically accessible
  38. backtrace uint32 // Flag whether backtrace location is set
  39. patterns []pattern // Current list of patterns to override with
  40. siteCache map[uintptr]Lvl // Cache of callsite pattern evaluations
  41. location string // file:line location where to do a stackdump at
  42. lock sync.RWMutex // Lock protecting the override pattern list
  43. }
  44. // NewGlogHandler creates a new log handler with filtering functionality similar
  45. // to Google's glog logger. The returned handler implements Handler.
  46. func NewGlogHandler(h Handler) *GlogHandler {
  47. return &GlogHandler{
  48. origin: h,
  49. }
  50. }
  51. // pattern contains a filter for the Vmodule option, holding a verbosity level
  52. // and a file pattern to match.
  53. type pattern struct {
  54. pattern *regexp.Regexp
  55. level Lvl
  56. }
  57. // Verbosity sets the glog verbosity ceiling. The verbosity of individual packages
  58. // and source files can be raised using Vmodule.
  59. func (h *GlogHandler) Verbosity(level Lvl) {
  60. atomic.StoreUint32(&h.level, uint32(level))
  61. }
  62. // Vmodule sets the glog verbosity pattern.
  63. //
  64. // The syntax of the argument is a comma-separated list of pattern=N, where the
  65. // pattern is a literal file name or "glob" pattern matching and N is a V level.
  66. //
  67. // For instance:
  68. //
  69. // pattern="gopher.go=3"
  70. // sets the V level to 3 in all Go files named "gopher.go"
  71. //
  72. // pattern="foo=3"
  73. // sets V to 3 in all files of any packages whose import path ends in "foo"
  74. //
  75. // pattern="foo/*=3"
  76. // sets V to 3 in all files of any packages whose import path contains "foo"
  77. func (h *GlogHandler) Vmodule(ruleset string) error {
  78. var filter []pattern
  79. for _, rule := range strings.Split(ruleset, ",") {
  80. // Empty strings such as from a trailing comma can be ignored
  81. if len(rule) == 0 {
  82. continue
  83. }
  84. // Ensure we have a pattern = level filter rule
  85. parts := strings.Split(rule, "=")
  86. if len(parts) != 2 {
  87. return errVmoduleSyntax
  88. }
  89. parts[0] = strings.TrimSpace(parts[0])
  90. parts[1] = strings.TrimSpace(parts[1])
  91. if len(parts[0]) == 0 || len(parts[1]) == 0 {
  92. return errVmoduleSyntax
  93. }
  94. // Parse the level and if correct, assemble the filter rule
  95. level, err := strconv.Atoi(parts[1])
  96. if err != nil {
  97. return errVmoduleSyntax
  98. }
  99. if level <= 0 {
  100. continue // Ignore. It's harmless but no point in paying the overhead.
  101. }
  102. // Compile the rule pattern into a regular expression
  103. matcher := ".*"
  104. for _, comp := range strings.Split(parts[0], "/") {
  105. if comp == "*" {
  106. matcher += "(/.*)?"
  107. } else if comp != "" {
  108. matcher += "/" + regexp.QuoteMeta(comp)
  109. }
  110. }
  111. if !strings.HasSuffix(parts[0], ".go") {
  112. matcher += "/[^/]+\\.go"
  113. }
  114. matcher = matcher + "$"
  115. re, _ := regexp.Compile(matcher)
  116. filter = append(filter, pattern{re, Lvl(level)})
  117. }
  118. // Swap out the vmodule pattern for the new filter system
  119. h.lock.Lock()
  120. defer h.lock.Unlock()
  121. h.patterns = filter
  122. h.siteCache = make(map[uintptr]Lvl)
  123. atomic.StoreUint32(&h.override, uint32(len(filter)))
  124. return nil
  125. }
  126. // BacktraceAt sets the glog backtrace location. When set to a file and line
  127. // number holding a logging statement, a stack trace will be written to the Info
  128. // log whenever execution hits that statement.
  129. //
  130. // Unlike with Vmodule, the ".go" must be present.
  131. func (h *GlogHandler) BacktraceAt(location string) error {
  132. // Ensure the backtrace location contains two non-empty elements
  133. parts := strings.Split(location, ":")
  134. if len(parts) != 2 {
  135. return errTraceSyntax
  136. }
  137. parts[0] = strings.TrimSpace(parts[0])
  138. parts[1] = strings.TrimSpace(parts[1])
  139. if len(parts[0]) == 0 || len(parts[1]) == 0 {
  140. return errTraceSyntax
  141. }
  142. // Ensure the .go prefix is present and the line is valid
  143. if !strings.HasSuffix(parts[0], ".go") {
  144. return errTraceSyntax
  145. }
  146. if _, err := strconv.Atoi(parts[1]); err != nil {
  147. return errTraceSyntax
  148. }
  149. // All seems valid
  150. h.lock.Lock()
  151. defer h.lock.Unlock()
  152. h.location = location
  153. atomic.StoreUint32(&h.backtrace, uint32(len(location)))
  154. return nil
  155. }
  156. // Log implements Handler.Log, filtering a log record through the global, local
  157. // and backtrace filters, finally emitting it if either allow it through.
  158. func (h *GlogHandler) Log(r *Record) error {
  159. // If backtracing is requested, check whether this is the callsite
  160. if atomic.LoadUint32(&h.backtrace) > 0 {
  161. // Everything below here is slow. Although we could cache the call sites the
  162. // same way as for vmodule, backtracing is so rare it's not worth the extra
  163. // complexity.
  164. h.lock.RLock()
  165. match := h.location == r.Call.String()
  166. h.lock.RUnlock()
  167. if match {
  168. // Callsite matched, raise the log level to info and gather the stacks
  169. r.Lvl = LvlInfo
  170. buf := make([]byte, 1024*1024)
  171. buf = buf[:runtime.Stack(buf, true)]
  172. r.Msg += "\n\n" + string(buf)
  173. }
  174. }
  175. // If the global log level allows, fast track logging
  176. if atomic.LoadUint32(&h.level) >= uint32(r.Lvl) {
  177. return h.origin.Log(r)
  178. }
  179. // If no local overrides are present, fast track skipping
  180. if atomic.LoadUint32(&h.override) == 0 {
  181. return nil
  182. }
  183. // Check callsite cache for previously calculated log levels
  184. h.lock.RLock()
  185. lvl, ok := h.siteCache[r.Call.PC()]
  186. h.lock.RUnlock()
  187. // If we didn't cache the callsite yet, calculate it
  188. if !ok {
  189. h.lock.Lock()
  190. for _, rule := range h.patterns {
  191. if rule.pattern.MatchString(fmt.Sprintf("%+s", r.Call)) {
  192. h.siteCache[r.Call.PC()], lvl, ok = rule.level, rule.level, true
  193. break
  194. }
  195. }
  196. // If no rule matched, remember to drop log the next time
  197. if !ok {
  198. h.siteCache[r.Call.PC()] = 0
  199. }
  200. h.lock.Unlock()
  201. }
  202. if lvl >= r.Lvl {
  203. return h.origin.Log(r)
  204. }
  205. return nil
  206. }