log: use logtail to log and upload sockstat logs

Switch to using logtail for logging sockstat logs. Always log locally
(on supported platforms), but disable automatic uploading.  Change
existing c2n sockstats request to trigger upload to log server and
return log ID.

Signed-off-by: Will Norris <will@tailscale.com>
pull/7660/head
Will Norris 2 years ago committed by Will Norris
parent 731688e5cc
commit f13b8bf0cf

@ -84,8 +84,13 @@ func (b *LocalBackend) handleC2N(w http.ResponseWriter, r *http.Request) {
} }
writeJSON(res) writeJSON(res)
case "/sockstats": case "/sockstats":
if r.Method != "POST" {
http.Error(w, "bad method", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Type", "text/plain")
b.sockstatLogger.WriteLogs(w) b.sockstatLogger.Flush()
fmt.Fprintln(w, b.sockstatLogger.LogID())
default: default:
http.Error(w, "unknown c2n path", http.StatusBadRequest) http.Error(w, "unknown c2n path", http.StatusBadRequest)
} }

@ -310,7 +310,7 @@ func NewLocalBackend(logf logger.Logf, logid string, store ipn.StateStore, diale
// for now, only log sockstats on unstable builds // for now, only log sockstats on unstable builds
if version.IsUnstableBuild() { if version.IsUnstableBuild() {
b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf) b.sockstatLogger, err = sockstatlog.NewLogger(logpolicy.LogsDir(logf), logf, logid)
if err != nil { if err != nil {
log.Printf("error setting up sockstat logger: %v", err) log.Printf("error setting up sockstat logger: %v", err)
} }

@ -6,15 +6,21 @@ package sockstatlog
import ( import (
"context" "context"
"crypto/sha256"
"encoding/json" "encoding/json"
"io" "io"
"net/http"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
"tailscale.com/logpolicy"
"tailscale.com/logtail"
"tailscale.com/logtail/filch" "tailscale.com/logtail/filch"
"tailscale.com/net/sockstats" "tailscale.com/net/sockstats"
"tailscale.com/smallzstd"
"tailscale.com/types/logger" "tailscale.com/types/logger"
"tailscale.com/types/logid"
"tailscale.com/util/mak" "tailscale.com/util/mak"
) )
@ -28,7 +34,8 @@ type Logger struct {
ticker *time.Ticker ticker *time.Ticker
logf logger.Logf logf logger.Logf
logbuffer *filch.Filch
logger *logtail.Logger
} }
// deltaStat represents the bytes transferred during a time period. // deltaStat represents the bytes transferred during a time period.
@ -50,10 +57,18 @@ type event struct {
Stats map[sockstats.Label]deltaStat `json:"s"` Stats map[sockstats.Label]deltaStat `json:"s"`
} }
// SockstatLogID reproducibly derives a new logid.PrivateID for sockstat logging from a node's public backend log ID.
// The returned PrivateID is the sha256 sum of id + "sockstat".
// If a node's public log ID becomes known, it is trivial to spoof sockstat logs for that node.
// Given the this is just for debugging, we're not too concerned about that.
func SockstatLogID(id string) logid.PrivateID {
return logid.PrivateID(sha256.Sum256([]byte(id + "sockstat")))
}
// NewLogger returns a new Logger that will store stats in logdir. // 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. // On platforms that do not support sockstat logging, a nil Logger will be returned.
// The returned Logger must be shut down with Shutdown when it is no longer needed. // The returned Logger must be shut down with Shutdown when it is no longer needed.
func NewLogger(logdir string, logf logger.Logf) (*Logger, error) { func NewLogger(logdir string, logf logger.Logf, backendLogID string) (*Logger, error) {
if !sockstats.IsAvailable { if !sockstats.IsAvailable {
return nil, nil return nil, nil
} }
@ -73,8 +88,27 @@ func NewLogger(logdir string, logf logger.Logf) (*Logger, error) {
cancelFn: cancel, cancelFn: cancel,
ticker: time.NewTicker(pollPeriod), ticker: time.NewTicker(pollPeriod),
logf: logf, logf: logf,
logbuffer: filch,
} }
logger.logger = logtail.NewLogger(logtail.Config{
BaseURL: logpolicy.LogURL(),
PrivateID: SockstatLogID(backendLogID),
Collection: "sockstats.log.tailscale.io",
Buffer: filch,
NewZstdEncoder: func() logtail.Encoder {
w, err := smallzstd.NewEncoder(nil)
if err != nil {
panic(err)
}
return w
},
FlushDelayFn: func() time.Duration {
// set flush delay to 100 years so it never flushes automatically
return 100 * 365 * 24 * time.Hour
},
Stderr: io.Discard, // don't log to stderr
HTTPC: &http.Client{Transport: logpolicy.NewLogtailTransport(logtail.DefaultHost)},
}, logf)
go logger.poll() go logger.poll()
@ -89,7 +123,7 @@ func (l *Logger) poll() {
var lastStats *sockstats.SockStats var lastStats *sockstats.SockStats
var lastTime time.Time var lastTime time.Time
enc := json.NewEncoder(l.logbuffer) enc := json.NewEncoder(l.logger)
for { for {
select { select {
case <-l.ctx.Done(): case <-l.ctx.Done():
@ -118,31 +152,22 @@ func (l *Logger) poll() {
} }
} }
func (l *Logger) Shutdown() { func (l *Logger) LogID() string {
l.ticker.Stop() if l.logger == nil {
l.logbuffer.Close() return ""
l.cancelFn()
} }
return l.logger.PrivateID().Public().String()
// 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) // Flush sends pending logs to the log server and flushes them from the local buffer.
func (l *Logger) Flush() {
l.logger.StartFlush()
} }
func (l *Logger) Shutdown() {
l.ticker.Stop()
l.logger.Shutdown(context.Background())
l.cancelFn()
} }
// delta calculates the delta stats between two SockStats snapshots. // delta calculates the delta stats between two SockStats snapshots.

Loading…
Cancel
Save