vitess.io/vitess@v0.16.2/go/vt/vttablet/tabletmanager/vreplication/vrlog.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 /* 18 * A human readable streaming log of vreplication events for vttablets available at /debug/vrlog 19 */ 20 21 package vreplication 22 23 import ( 24 "net/http" 25 "strconv" 26 "text/template" 27 "time" 28 29 "vitess.io/vitess/go/streamlog" 30 "vitess.io/vitess/go/vt/log" 31 ) 32 33 var ( 34 vrLogStatsLogger = streamlog.New("VReplication", 50) 35 vrLogStatsTemplate = template.Must(template.New("vrlog"). 36 Parse("{{.Type}} Event {{.Detail}} {{.LogTime}} {{.DurationNs}}\n")) 37 ) 38 39 // VrLogStats collects attributes of a vreplication event for logging 40 type VrLogStats struct { 41 Type string 42 Detail string 43 StartTime time.Time 44 LogTime string 45 DurationNs int64 46 } 47 48 // NewVrLogStats should be called at the start of the event to be logged 49 func NewVrLogStats(eventType string) *VrLogStats { 50 return &VrLogStats{Type: eventType, StartTime: time.Now()} 51 } 52 53 // Send records the log event, should be called on a stats object constructed by NewVrLogStats() 54 func (stats *VrLogStats) Send(detail string) { 55 if stats.StartTime.IsZero() { 56 stats.Type = "Error: Type not specified" 57 stats.StartTime = time.Now() 58 } 59 stats.LogTime = stats.StartTime.Format("2006-01-02T15:04:05") 60 stats.Detail = detail 61 stats.DurationNs = time.Since(stats.StartTime).Nanoseconds() 62 vrLogStatsLogger.Send(stats) 63 } 64 65 func init() { 66 http.HandleFunc("/debug/vrlog", func(w http.ResponseWriter, r *http.Request) { 67 ch := vrLogStatsLogger.Subscribe("vrlogstats") 68 defer vrLogStatsLogger.Unsubscribe(ch) 69 vrlogStatsHandler(ch, w, r) 70 }) 71 } 72 73 func vrlogStatsHandler(ch chan any, w http.ResponseWriter, r *http.Request) { 74 timeout, limit := parseTimeoutLimitParams(r) 75 tmr := time.NewTimer(timeout) 76 defer tmr.Stop() 77 for i := 0; i < limit; i++ { 78 select { 79 case out := <-ch: 80 select { 81 case <-tmr.C: 82 return 83 default: 84 } 85 stats, ok := out.(*VrLogStats) 86 if !ok { 87 log.Error("Log received is not of type VrLogStats") 88 continue 89 } 90 if err := vrLogStatsTemplate.Execute(w, stats); err != nil { 91 log.Errorf("vrlog: couldn't execute template: %v", err) 92 } 93 if f, ok := w.(http.Flusher); ok { 94 f.Flush() 95 } 96 case <-tmr.C: 97 return 98 } 99 } 100 } 101 102 func parseTimeoutLimitParams(req *http.Request) (time.Duration, int) { 103 timeout := 1000 104 limit := 300 105 if ts, ok := req.URL.Query()["timeout"]; ok { 106 if t, err := strconv.Atoi(ts[0]); err == nil { 107 timeout = adjustValue(t, 0, 60) 108 } 109 } 110 if l, ok := req.URL.Query()["limit"]; ok { 111 if lim, err := strconv.Atoi(l[0]); err == nil { 112 limit = adjustValue(lim, 1, 200000) 113 } 114 } 115 return time.Duration(timeout) * time.Second, limit 116 } 117 118 func adjustValue(val int, lower int, upper int) int { 119 if val < lower { 120 return lower 121 } else if val > upper { 122 return upper 123 } 124 return val 125 }