diff --git a/cmd/tailscaled/tailscaled.go b/cmd/tailscaled/tailscaled.go index b851807e4..0fe0567c5 100644 --- a/cmd/tailscaled/tailscaled.go +++ b/cmd/tailscaled/tailscaled.go @@ -526,6 +526,9 @@ func getLocalBackend(ctx context.Context, logf logger.Logf, logid string) (_ *ip return nil, fmt.Errorf("ipnlocal.NewLocalBackend: %w", err) } lb.SetVarRoot(opts.VarRoot) + if logPol != nil { + lb.SetLogFlusher(logPol.Logtail.StartFlush) + } if root := lb.TailscaleVarRoot(); root != "" { dnsfallback.SetCachePath(filepath.Join(root, "derpmap.cached.json")) } diff --git a/ipn/ipnlocal/c2n.go b/ipn/ipnlocal/c2n.go index 0302ff156..508f99c2f 100644 --- a/ipn/ipnlocal/c2n.go +++ b/ipn/ipnlocal/c2n.go @@ -26,6 +26,16 @@ func (b *LocalBackend) handleC2N(w http.ResponseWriter, r *http.Request) { // Test handler. body, _ := io.ReadAll(r.Body) w.Write(body) + case "/logtail/flush": + if r.Method != "POST" { + http.Error(w, "bad method", http.StatusMethodNotAllowed) + return + } + if b.TryFlushLogs() { + w.WriteHeader(http.StatusNoContent) + } else { + http.Error(w, "no log flusher wired up", http.StatusInternalServerError) + } case "/debug/goroutines": w.Header().Set("Content-Type", "text/plain") w.Write(goroutines.ScrubbedGoroutineDump()) diff --git a/ipn/ipnlocal/local.go b/ipn/ipnlocal/local.go index 4a0e0e58c..d54de8592 100644 --- a/ipn/ipnlocal/local.go +++ b/ipn/ipnlocal/local.go @@ -140,6 +140,7 @@ type LocalBackend struct { gotPortPollRes chan struct{} // closed upon first readPoller result newDecompressor func() (controlclient.Decompressor, error) varRoot string // or empty if SetVarRoot never called + logFlushFunc func() // or nil if SetLogFlusher wasn't called sshAtomicBool atomic.Bool shutdownCalled bool // if Shutdown has been called @@ -3015,6 +3016,25 @@ func (b *LocalBackend) SetVarRoot(dir string) { b.varRoot = dir } +// SetLogFlusher sets a func to be called to flush log uploads. +// +// It should only be called before the LocalBackend is used. +func (b *LocalBackend) SetLogFlusher(flushFunc func()) { + b.logFlushFunc = flushFunc +} + +// TryFlushLogs calls the log flush function. It returns false if a log flush +// function was never initialized with SetLogFlusher. +// +// TryFlushLogs should not block. +func (b *LocalBackend) TryFlushLogs() bool { + if b.logFlushFunc == nil { + return false + } + b.logFlushFunc() + return true +} + // TailscaleVarRoot returns the root directory of Tailscale's writable // storage area. (e.g. "/var/lib/tailscale") // diff --git a/ipn/localapi/localapi.go b/ipn/localapi/localapi.go index 4f3e72439..fa5583c49 100644 --- a/ipn/localapi/localapi.go +++ b/ipn/localapi/localapi.go @@ -292,6 +292,7 @@ func (h *Handler) serveBugReport(w http.ResponseWriter, r *http.Request) { http.Error(w, "only POST allowed", http.StatusMethodNotAllowed) return } + defer h.b.TryFlushLogs() // kick off upload after bugreport's done logging logMarker := func() string { return fmt.Sprintf("BUG-%v-%v-%v", h.backendLogID, time.Now().UTC().Format("20060102150405Z"), randHex(8)) diff --git a/logtail/logtail.go b/logtail/logtail.go index fbccc28ea..aa6d2dddf 100644 --- a/logtail/logtail.go +++ b/logtail/logtail.go @@ -461,12 +461,24 @@ func (l *Logger) upload(ctx context.Context, body []byte, origlen int) (uploaded return true, nil } -// Flush uploads all logs to the server. -// It blocks until complete or there is an unrecoverable error. +// Flush uploads all logs to the server. It blocks until complete or there is an +// unrecoverable error. +// +// TODO(bradfitz): this apparently just returns nil, as of tailscale/corp@9c2ec35. +// Finish cleaning this up. func (l *Logger) Flush() error { return nil } +// StartFlush starts a log upload, if anything is pending. +// +// If l is nil, StartFlush is a no-op. +func (l *Logger) StartFlush() { + if l != nil { + l.tryDrainWake() + } +} + // logtailDisabled is whether logtail uploads to logcatcher are disabled. var logtailDisabled atomic.Bool diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 290151fd1..ffcd2173a 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -88,7 +88,8 @@ type CapabilityVersion int // - 49: 2022-11-03: Client understands EarlyNoise // - 50: 2022-11-14: Client understands CapabilityIngress // - 51: 2022-11-30: Client understands CapabilityTailnetLockAlpha -const CurrentCapabilityVersion CapabilityVersion = 51 +// - 52: 2023-01-05: client can handle c2n POST /logtail/flush +const CurrentCapabilityVersion CapabilityVersion = 52 type StableID string