vitess.io/vitess@v0.16.2/go/streamlog/streamlog.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package streamlog provides a non-blocking message broadcaster. 18 package streamlog 19 20 import ( 21 "fmt" 22 "io" 23 "net/http" 24 "net/url" 25 "os" 26 "os/signal" 27 "strings" 28 "sync" 29 "syscall" 30 31 "github.com/spf13/pflag" 32 33 "vitess.io/vitess/go/acl" 34 "vitess.io/vitess/go/stats" 35 "vitess.io/vitess/go/vt/log" 36 "vitess.io/vitess/go/vt/servenv" 37 ) 38 39 var ( 40 sendCount = stats.NewCountersWithSingleLabel("StreamlogSend", "stream log send count", "logger_names") 41 deliveredCount = stats.NewCountersWithMultiLabels( 42 "StreamlogDelivered", 43 "Stream log delivered", 44 []string{"Log", "Subscriber"}) 45 deliveryDropCount = stats.NewCountersWithMultiLabels( 46 "StreamlogDeliveryDroppedMessages", 47 "Dropped messages by streamlog delivery", 48 []string{"Log", "Subscriber"}) 49 ) 50 51 var ( 52 redactDebugUIQueries bool 53 queryLogFilterTag string 54 queryLogRowThreshold uint64 55 queryLogFormat = "text" 56 ) 57 58 func GetRedactDebugUIQueries() bool { 59 return redactDebugUIQueries 60 } 61 62 func SetRedactDebugUIQueries(newRedactDebugUIQueries bool) { 63 redactDebugUIQueries = newRedactDebugUIQueries 64 } 65 66 func GetQueryLogFilterTag() string { 67 return queryLogFilterTag 68 } 69 70 func SetQueryLogFilterTag(newQueryLogFilterTag string) { 71 queryLogFilterTag = newQueryLogFilterTag 72 } 73 74 func GetQueryLogRowThreshold() uint64 { 75 return queryLogRowThreshold 76 } 77 78 func SetQueryLogRowThreshold(newQueryLogRowThreshold uint64) { 79 queryLogRowThreshold = newQueryLogRowThreshold 80 } 81 82 func GetQueryLogFormat() string { 83 return queryLogFormat 84 } 85 86 func SetQueryLogFormat(newQueryLogFormat string) { 87 queryLogFormat = newQueryLogFormat 88 } 89 90 func init() { 91 servenv.OnParseFor("vtcombo", registerStreamLogFlags) 92 servenv.OnParseFor("vttablet", registerStreamLogFlags) 93 servenv.OnParseFor("vtgate", registerStreamLogFlags) 94 } 95 96 func registerStreamLogFlags(fs *pflag.FlagSet) { 97 // RedactDebugUIQueries controls whether full queries and bind variables are suppressed from debug UIs. 98 fs.BoolVar(&redactDebugUIQueries, "redact-debug-ui-queries", redactDebugUIQueries, "redact full queries and bind variables from debug UI") 99 100 // QueryLogFormat controls the format of the query log (either text or json) 101 fs.StringVar(&queryLogFormat, "querylog-format", queryLogFormat, "format for query logs (\"text\" or \"json\")") 102 103 // QueryLogFilterTag contains an optional string that must be present in the query for it to be logged 104 fs.StringVar(&queryLogFilterTag, "querylog-filter-tag", queryLogFilterTag, "string that must be present in the query for it to be logged; if using a value as the tag, you need to disable query normalization") 105 106 // QueryLogRowThreshold only log queries returning or affecting this many rows 107 fs.Uint64Var(&queryLogRowThreshold, "querylog-row-threshold", queryLogRowThreshold, "Number of rows a query has to return or affect before being logged; not useful for streaming queries. 0 means all queries will be logged.") 108 109 } 110 111 const ( 112 // QueryLogFormatText is the format specifier for text querylog output 113 QueryLogFormatText = "text" 114 115 // QueryLogFormatJSON is the format specifier for json querylog output 116 QueryLogFormatJSON = "json" 117 ) 118 119 // StreamLogger is a non-blocking broadcaster of messages. 120 // Subscribers can use channels or HTTP. 121 type StreamLogger struct { 122 name string 123 size int 124 mu sync.Mutex 125 subscribed map[chan any]string 126 } 127 128 // LogFormatter is the function signature used to format an arbitrary 129 // message for the given output writer. 130 type LogFormatter func(out io.Writer, params url.Values, message any) error 131 132 // New returns a new StreamLogger that can stream events to subscribers. 133 // The size parameter defines the channel size for the subscribers. 134 func New(name string, size int) *StreamLogger { 135 return &StreamLogger{ 136 name: name, 137 size: size, 138 subscribed: make(map[chan any]string), 139 } 140 } 141 142 // Send sends message to all the writers subscribed to logger. Calling 143 // Send does not block. 144 func (logger *StreamLogger) Send(message any) { 145 logger.mu.Lock() 146 defer logger.mu.Unlock() 147 148 for ch, name := range logger.subscribed { 149 select { 150 case ch <- message: 151 deliveredCount.Add([]string{logger.name, name}, 1) 152 default: 153 deliveryDropCount.Add([]string{logger.name, name}, 1) 154 } 155 } 156 sendCount.Add(logger.name, 1) 157 } 158 159 // Subscribe returns a channel which can be used to listen 160 // for messages. 161 func (logger *StreamLogger) Subscribe(name string) chan any { 162 logger.mu.Lock() 163 defer logger.mu.Unlock() 164 165 ch := make(chan any, logger.size) 166 logger.subscribed[ch] = name 167 return ch 168 } 169 170 // Unsubscribe removes the channel from the subscription. 171 func (logger *StreamLogger) Unsubscribe(ch chan any) { 172 logger.mu.Lock() 173 defer logger.mu.Unlock() 174 175 delete(logger.subscribed, ch) 176 } 177 178 // Name returns the name of StreamLogger. 179 func (logger *StreamLogger) Name() string { 180 return logger.name 181 } 182 183 // ServeLogs registers the URL on which messages will be broadcast. 184 // It is safe to register multiple URLs for the same StreamLogger. 185 func (logger *StreamLogger) ServeLogs(url string, logf LogFormatter) { 186 http.HandleFunc(url, func(w http.ResponseWriter, r *http.Request) { 187 if err := acl.CheckAccessHTTP(r, acl.DEBUGGING); err != nil { 188 acl.SendError(w, err) 189 return 190 } 191 if err := r.ParseForm(); err != nil { 192 http.Error(w, err.Error(), http.StatusBadRequest) 193 } 194 ch := logger.Subscribe("ServeLogs") 195 defer logger.Unsubscribe(ch) 196 197 // Notify client that we're set up. Helpful to distinguish low-traffic streams from connection issues. 198 w.WriteHeader(http.StatusOK) 199 w.(http.Flusher).Flush() 200 201 for message := range ch { 202 if err := logf(w, r.Form, message); err != nil { 203 return 204 } 205 w.(http.Flusher).Flush() 206 } 207 }) 208 log.Infof("Streaming logs from %s at %v.", logger.Name(), url) 209 } 210 211 // LogToFile starts logging to the specified file path and will reopen the 212 // file in response to SIGUSR2. 213 // 214 // Returns the channel used for the subscription which can be used to close 215 // it. 216 func (logger *StreamLogger) LogToFile(path string, logf LogFormatter) (chan any, error) { 217 rotateChan := make(chan os.Signal, 1) 218 signal.Notify(rotateChan, syscall.SIGUSR2) 219 220 logChan := logger.Subscribe("FileLog") 221 formatParams := map[string][]string{"full": {}} 222 223 f, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) 224 if err != nil { 225 return nil, err 226 } 227 228 go func() { 229 for { 230 select { 231 case record := <-logChan: 232 logf(f, formatParams, record) // nolint:errcheck 233 case <-rotateChan: 234 f.Close() 235 f, _ = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644) 236 } 237 } 238 }() 239 240 return logChan, nil 241 } 242 243 // Formatter is a simple interface for objects that expose a Format function 244 // as needed for streamlog. 245 type Formatter interface { 246 Logf(io.Writer, url.Values) error 247 } 248 249 // GetFormatter returns a formatter function for objects conforming to the 250 // Formatter interface 251 func GetFormatter(logger *StreamLogger) LogFormatter { 252 return func(w io.Writer, params url.Values, val any) error { 253 fmter, ok := val.(Formatter) 254 if !ok { 255 _, err := fmt.Fprintf(w, "Error: unexpected value of type %T in %s!", val, logger.Name()) 256 return err 257 } 258 return fmter.Logf(w, params) 259 } 260 } 261 262 // ShouldEmitLog returns whether the log with the given SQL query 263 // should be emitted or filtered 264 func ShouldEmitLog(sql string, rowsAffected, rowsReturned uint64) bool { 265 if queryLogRowThreshold > maxUint64(rowsAffected, rowsReturned) && queryLogFilterTag == "" { 266 return false 267 } 268 if queryLogFilterTag != "" { 269 return strings.Contains(sql, queryLogFilterTag) 270 } 271 return true 272 } 273 274 func maxUint64(a, b uint64) uint64 { 275 if a < b { 276 return b 277 } 278 return a 279 }