mirror of
https://github.com/tailscale/tailscale.git
synced 2024-11-29 04:55:31 +00:00
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>
This commit is contained in:
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 @@
|
|||||||
|
|
||||||
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"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -26,9 +32,10 @@ type Logger struct {
|
|||||||
ctx context.Context
|
ctx context.Context
|
||||||
cancelFn context.CancelFunc
|
cancelFn context.CancelFunc
|
||||||
|
|
||||||
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
|
||||||
}
|
}
|
||||||
@ -69,12 +84,31 @@ func NewLogger(logdir string, logf logger.Logf) (*Logger, error) {
|
|||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
logger := &Logger{
|
logger := &Logger{
|
||||||
ctx: ctx,
|
ctx: ctx,
|
||||||
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.
|
// Flush sends pending logs to the log server and flushes them from the local buffer.
|
||||||
// Logs within eventWindow are combined into the same event.
|
func (l *Logger) Flush() {
|
||||||
func (l *Logger) WriteLogs(w io.Writer) {
|
l.logger.StartFlush()
|
||||||
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)
|
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…
Reference in New Issue
Block a user