handler_glog.go 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  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. // SetHandler updates the handler to write records to the specified sub-handler.
  52. func (h *GlogHandler) SetHandler(nh Handler) {
  53. h.origin = nh
  54. }
  55. // pattern contains a filter for the Vmodule option, holding a verbosity level
  56. // and a file pattern to match.
  57. type pattern struct {
  58. pattern *regexp.Regexp
  59. level Lvl
  60. }
  61. // Verbosity sets the glog verbosity ceiling. The verbosity of individual packages
  62. // and source files can be raised using Vmodule.
  63. func (h *GlogHandler) Verbosity(level Lvl) {
  64. atomic.StoreUint32(&h.level, uint32(level))
  65. }
  66. // Vmodule sets the glog verbosity pattern.
  67. //
  68. // The syntax of the argument is a comma-separated list of pattern=N, where the
  69. // pattern is a literal file name or "glob" pattern matching and N is a V level.
  70. //
  71. // For instance:
  72. //
  73. // pattern="gopher.go=3"
  74. // sets the V level to 3 in all Go files named "gopher.go"
  75. //
  76. // pattern="foo=3"
  77. // sets V to 3 in all files of any packages whose import path ends in "foo"
  78. //
  79. // pattern="foo/*=3"
  80. // sets V to 3 in all files of any packages whose import path contains "foo"
  81. func (h *GlogHandler) Vmodule(ruleset string) error {
  82. var filter []pattern
  83. for _, rule := range strings.Split(ruleset, ",") {
  84. // Empty strings such as from a trailing comma can be ignored
  85. if len(rule) == 0 {
  86. continue
  87. }
  88. // Ensure we have a pattern = level filter rule
  89. parts := strings.Split(rule, "=")
  90. if len(parts) != 2 {
  91. return errVmoduleSyntax
  92. }
  93. parts[0] = strings.TrimSpace(parts[0])
  94. parts[1] = strings.TrimSpace(parts[1])
  95. if len(parts[0]) == 0 || len(parts[1]) == 0 {
  96. return errVmoduleSyntax
  97. }
  98. // Parse the level and if correct, assemble the filter rule
  99. level, err := strconv.Atoi(parts[1])
  100. if err != nil {
  101. return errVmoduleSyntax
  102. }
  103. if level <= 0 {
  104. continue // Ignore. It's harmless but no point in paying the overhead.
  105. }
  106. // Compile the rule pattern into a regular expression
  107. matcher := ".*"
  108. for _, comp := range strings.Split(parts[0], "/") {
  109. if comp == "*" {
  110. matcher += "(/.*)?"
  111. } else if comp != "" {
  112. matcher += "/" + regexp.QuoteMeta(comp)
  113. }
  114. }
  115. if !strings.HasSuffix(parts[0], ".go") {
  116. matcher += "/[^/]+\\.go"
  117. }
  118. matcher = matcher + "$"
  119. re, _ := regexp.Compile(matcher)
  120. filter = append(filter, pattern{re, Lvl(level)})
  121. }
  122. // Swap out the vmodule pattern for the new filter system
  123. h.lock.Lock()
  124. defer h.lock.Unlock()
  125. h.patterns = filter
  126. h.siteCache = make(map[uintptr]Lvl)
  127. atomic.StoreUint32(&h.override, uint32(len(filter)))
  128. return nil
  129. }
  130. // BacktraceAt sets the glog backtrace location. When set to a file and line
  131. // number holding a logging statement, a stack trace will be written to the Info
  132. // log whenever execution hits that statement.
  133. //
  134. // Unlike with Vmodule, the ".go" must be present.
  135. func (h *GlogHandler) BacktraceAt(location string) error {
  136. // Ensure the backtrace location contains two non-empty elements
  137. parts := strings.Split(location, ":")
  138. if len(parts) != 2 {
  139. return errTraceSyntax
  140. }
  141. parts[0] = strings.TrimSpace(parts[0])
  142. parts[1] = strings.TrimSpace(parts[1])
  143. if len(parts[0]) == 0 || len(parts[1]) == 0 {
  144. return errTraceSyntax
  145. }
  146. // Ensure the .go prefix is present and the line is valid
  147. if !strings.HasSuffix(parts[0], ".go") {
  148. return errTraceSyntax
  149. }
  150. if _, err := strconv.Atoi(parts[1]); err != nil {
  151. return errTraceSyntax
  152. }
  153. // All seems valid
  154. h.lock.Lock()
  155. defer h.lock.Unlock()
  156. h.location = location
  157. atomic.StoreUint32(&h.backtrace, uint32(len(location)))
  158. return nil
  159. }
  160. // Log implements Handler.Log, filtering a log record through the global, local
  161. // and backtrace filters, finally emitting it if either allow it through.
  162. func (h *GlogHandler) Log(r *Record) error {
  163. // If backtracing is requested, check whether this is the callsite
  164. if atomic.LoadUint32(&h.backtrace) > 0 {
  165. // Everything below here is slow. Although we could cache the call sites the
  166. // same way as for vmodule, backtracing is so rare it's not worth the extra
  167. // complexity.
  168. h.lock.RLock()
  169. match := h.location == r.Call.String()
  170. h.lock.RUnlock()
  171. if match {
  172. // Callsite matched, raise the log level to info and gather the stacks
  173. r.Lvl = LvlInfo
  174. buf := make([]byte, 1024*1024)
  175. buf = buf[:runtime.Stack(buf, true)]
  176. r.Msg += "\n\n" + string(buf)
  177. }
  178. }
  179. // If the global log level allows, fast track logging
  180. if atomic.LoadUint32(&h.level) >= uint32(r.Lvl) {
  181. return h.origin.Log(r)
  182. }
  183. // If no local overrides are present, fast track skipping
  184. if atomic.LoadUint32(&h.override) == 0 {
  185. return nil
  186. }
  187. // Check callsite cache for previously calculated log levels
  188. h.lock.RLock()
  189. lvl, ok := h.siteCache[r.Call.Frame().PC]
  190. h.lock.RUnlock()
  191. // If we didn't cache the callsite yet, calculate it
  192. if !ok {
  193. h.lock.Lock()
  194. for _, rule := range h.patterns {
  195. if rule.pattern.MatchString(fmt.Sprintf("%+s", r.Call)) {
  196. h.siteCache[r.Call.Frame().PC], lvl, ok = rule.level, rule.level, true
  197. break
  198. }
  199. }
  200. // If no rule matched, remember to drop log the next time
  201. if !ok {
  202. h.siteCache[r.Call.Frame().PC] = 0
  203. }
  204. h.lock.Unlock()
  205. }
  206. if lvl >= r.Lvl {
  207. return h.origin.Log(r)
  208. }
  209. return nil
  210. }