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  }