From 09e0ccf4c27514ecb25a82189bf03ad61fc54106 Mon Sep 17 00:00:00 2001 From: Will Norris Date: Fri, 10 Mar 2023 14:33:26 -0800 Subject: [PATCH] ipn: add c2n endpoint for sockstats logs Signed-off-by: Will Norris --- ipn/ipnlocal/c2n.go | 3 +++ ipn/ipnlocal/local.go | 2 +- log/sockstatlog/logger.go | 31 +++++++++++++++++++++++++++++-- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/ipn/ipnlocal/c2n.go b/ipn/ipnlocal/c2n.go index 4b2b94819..eea9e25a6 100644 --- a/ipn/ipnlocal/c2n.go +++ b/ipn/ipnlocal/c2n.go @@ -83,6 +83,9 @@ func (b *LocalBackend) handleC2N(w http.ResponseWriter, r *http.Request) { return } writeJSON(res) + case "/sockstats": + w.Header().Set("Content-Type", "text/plain") + b.sockstatLogger.WriteLogs(w) default: http.Error(w, "unknown c2n path", http.StatusBadRequest) } diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 516168195..fe22df1ad 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -310,7 +310,7 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, diale // for now, only log sockstats on unstable builds if version.IsUnstableBuild() { - b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf)) + b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf) if err != nil { log.Printf("error setting up sockstat logger: %v", err) } diff --git a/log/sockstatlog/logger.go b/log/sockstatlog/logger.go index 736ba3e38..8e4209709 100644 --- a/log/sockstatlog/logger.go +++ b/log/sockstatlog/logger.go @@ -6,12 +6,14 @@ package sockstatlog import ( "encoding/json" + "io" "os" "path/filepath" "time" "tailscale.com/logtail/filch" "tailscale.com/net/sockstats" + "tailscale.com/types/logger" "tailscale.com/util/mak" ) @@ -21,6 +23,7 @@ const pollPeriod = time.Second / 10 // Logger logs statistics about network sockets. type Logger struct { ticker time.Ticker + logf logger.Logf logbuffer *filch.Filch } @@ -46,7 +49,7 @@ type event struct { // NewLogger returns a new Logger that will store stats in logdir. // On platforms that do not support sockstat logging, a nil Logger will be returned. // The returned Logger is not yet running. -func NewLogger(logdir string) (*Logger, error) { +func NewLogger(logdir string, logf logger.Logf) (*Logger, error) { if !sockstats.IsAvailable { return nil, nil } @@ -62,6 +65,7 @@ func NewLogger(logdir string) (*Logger, error) { return &Logger{ ticker: *time.NewTicker(pollPeriod), + logf: logf, logbuffer: filch, }, nil } @@ -92,7 +96,9 @@ func (l *Logger) poll() { if stats.CurrentInterfaceCellular { e.IsCellularInterface = 1 } - enc.Encode(e) + if err := enc.Encode(e); err != nil { + l.logf("sockstatlog: error encoding log: %v", err) + } } } lastTime = t @@ -105,6 +111,27 @@ func (l *Logger) Shutdown() { l.logbuffer.Close() } +// WriteLogs reads local logs, combining logs into events, and writes them to w. +// Logs within eventWindow are combined into the same event. +func (l *Logger) WriteLogs(w io.Writer) { + if l == nil || l.logbuffer == nil { + return + } + for { + b, err := l.logbuffer.TryReadLine() + if err != nil { + l.logf("sockstatlog: error reading log: %v", err) + return + } + if b == nil { + // no more log messages + return + } + + w.Write(b) + } +} + // delta calculates the delta stats between two SockStats snapshots. // b is assumed to have occurred after a. // Zero values are omitted from the returned map, and an empty map is returned if no bytes were transferred.