tsnet: split user facing and backend logging

This adds a new `UserLogf` field to the `Server` struct.
When set this any logs generated by Server are logged using
`UserLogf` and all spammy backend logs are logged to `Logf`.

If it `UserLogf` is unset, we default to `log.Printf` and
if `Logf` is unset we discard all the spammy logs.

Fixes #12094

Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
Maisem Ali 2024-05-10 13:12:11 -07:00 committed by Maisem Ali
parent 7209c4f91e
commit 486a423716
7 changed files with 50 additions and 50 deletions

View File

@ -28,7 +28,6 @@
"tailscale.com/metrics" "tailscale.com/metrics"
"tailscale.com/tsnet" "tailscale.com/tsnet"
"tailscale.com/tsweb" "tailscale.com/tsweb"
"tailscale.com/types/logger"
) )
var ( var (
@ -58,8 +57,6 @@ func main() {
ts := &tsnet.Server{ ts := &tsnet.Server{
Dir: *tailscaleDir, Dir: *tailscaleDir,
Hostname: *hostname, Hostname: *hostname,
// Make the stdout logs a clean audit log of connections.
Logf: logger.Discard,
} }
if os.Getenv("TS_AUTHKEY") == "" { if os.Getenv("TS_AUTHKEY") == "" {

View File

@ -8,6 +8,7 @@
"encoding/json" "encoding/json"
"flag" "flag"
"fmt" "fmt"
"log"
"net" "net"
"net/http/httptest" "net/http/httptest"
"net/netip" "net/netip"
@ -99,8 +100,8 @@ func startNode(t *testing.T, ctx context.Context, controlURL, hostname string) (
Store: new(mem.Store), Store: new(mem.Store),
Ephemeral: true, Ephemeral: true,
} }
if !*verboseNodes { if *verboseNodes {
s.Logf = logger.Discard s.Logf = log.Printf
} }
t.Cleanup(func() { s.Close() }) t.Cleanup(func() { s.Close() })

View File

@ -40,7 +40,6 @@
"tailscale.com/tsnet" "tailscale.com/tsnet"
"tailscale.com/types/key" "tailscale.com/types/key"
"tailscale.com/types/lazy" "tailscale.com/types/lazy"
"tailscale.com/types/logger"
"tailscale.com/types/views" "tailscale.com/types/views"
"tailscale.com/util/mak" "tailscale.com/util/mak"
"tailscale.com/util/must" "tailscale.com/util/must"
@ -95,8 +94,8 @@ func main() {
ts := &tsnet.Server{ ts := &tsnet.Server{
Hostname: "idp", Hostname: "idp",
} }
if !*flagVerbose { if *flagVerbose {
ts.Logf = logger.Discard ts.Logf = log.Printf
} }
st, err = ts.Up(ctx) st, err = ts.Up(ctx)
if err != nil { if err != nil {

View File

@ -16,14 +16,12 @@
"net/http" "net/http"
"tailscale.com/tsnet" "tailscale.com/tsnet"
"tailscale.com/types/logger"
) )
func main() { func main() {
flag.Parse() flag.Parse()
s := &tsnet.Server{ s := &tsnet.Server{
Dir: "./funnel-demo-config", Dir: "./funnel-demo-config",
Logf: logger.Discard,
Hostname: "fun", Hostname: "fun",
} }
defer s.Close() defer s.Close()

View File

@ -89,15 +89,6 @@ func ExampleServer_multipleInstances() {
}() }()
} }
// ExampleServer_ignoreLogs shows you how to ignore all of the log messages written
// by a tsnet instance.
func ExampleServer_ignoreLogs() {
srv := &tsnet.Server{
Logf: func(string, ...any) {},
}
_ = srv
}
// ExampleServer_ignoreLogsSometimes shows you how to ignore all of the log messages // ExampleServer_ignoreLogsSometimes shows you how to ignore all of the log messages
// written by a tsnet instance, but allows you to opt-into them if a command-line // written by a tsnet instance, but allows you to opt-into them if a command-line
// flag is set. // flag is set.
@ -107,7 +98,6 @@ func ExampleServer_ignoreLogsSometimes() {
srv := &tsnet.Server{ srv := &tsnet.Server{
Hostname: *hostname, Hostname: *hostname,
Logf: func(string, ...any) {},
} }
if *tsnetVerbose { if *tsnetVerbose {

View File

@ -89,8 +89,14 @@ type Server struct {
// If empty, the binary name is used. // If empty, the binary name is used.
Hostname string Hostname string
// Logf, if non-nil, specifies the logger to use. By default, // UserLogf, if non-nil, specifies the logger to use for logs generated by
// log.Printf is used. // the Server itself intended to be seen by the user such as the AuthURL for
// login and status updates. If unset, log.Printf is used.
UserLogf logger.Logf
// Logf, if set is used for logs generated by the backend such as the
// LocalBackend and MagicSock. It is verbose and intended for debugging.
// If unset, logs are discarded.
Logf logger.Logf Logf logger.Logf
// Ephemeral, if true, specifies that the instance should register // Ephemeral, if true, specifies that the instance should register
@ -244,15 +250,15 @@ func (s *Server) Loopback() (addr string, proxyCred, localAPICred string, err er
s.logf("localapi tcp serve error: %v", err) s.logf("localapi tcp serve error: %v", err)
} }
}() }()
s5l := logger.WithPrefix(s.logf, "socks5: ")
s5s := &socks5.Server{ s5s := &socks5.Server{
Logf: logger.WithPrefix(s.logf, "socks5: "), Logf: s5l,
Dialer: s.dialer.UserDial, Dialer: s.dialer.UserDial,
Username: "tsnet", Username: "tsnet",
Password: s.proxyCred, Password: s.proxyCred,
} }
go func() { go func() {
s.logf("SOCKS5 server exited: %v", s5s.Serve(socksLn)) s5l("SOCKS5 server exited: %v", s5s.Serve(socksLn))
}() }()
} }
@ -484,14 +490,12 @@ func (s *Server) start() (reterr error) {
} }
} }
logf := s.logf
if s.rootPath == "" { if s.rootPath == "" {
confDir, err := os.UserConfigDir() confDir, err := os.UserConfigDir()
if err != nil { if err != nil {
return err return err
} }
s.rootPath, err = getTSNetDir(logf, confDir, prog) s.rootPath, err = getTSNetDir(s.logf, confDir, prog)
if err != nil { if err != nil {
return err return err
} }
@ -505,19 +509,29 @@ func (s *Server) start() (reterr error) {
return fmt.Errorf("%v is not a directory", s.rootPath) return fmt.Errorf("%v is not a directory", s.rootPath)
} }
tsLogf := func(format string, a ...any) {
if s.logtail != nil {
s.logtail.Logf(format, a...)
}
if s.Logf == nil {
return
}
s.Logf(format, a...)
}
sys := new(tsd.System) sys := new(tsd.System)
if err := s.startLogger(&closePool, sys.HealthTracker()); err != nil { if err := s.startLogger(&closePool, sys.HealthTracker(), tsLogf); err != nil {
return err return err
} }
s.netMon, err = netmon.New(logf) s.netMon, err = netmon.New(tsLogf)
if err != nil { if err != nil {
return err return err
} }
closePool.add(s.netMon) closePool.add(s.netMon)
s.dialer = &tsdial.Dialer{Logf: logf} // mutated below (before used) s.dialer = &tsdial.Dialer{Logf: tsLogf} // mutated below (before used)
eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ eng, err := wgengine.NewUserspaceEngine(tsLogf, wgengine.Config{
ListenPort: s.Port, ListenPort: s.Port,
NetMon: s.netMon, NetMon: s.netMon,
Dialer: s.dialer, Dialer: s.dialer,
@ -532,7 +546,7 @@ func (s *Server) start() (reterr error) {
sys.Set(eng) sys.Set(eng)
// TODO(oxtoacart): do we need to support Taildrive on tsnet, and if so, how? // TODO(oxtoacart): do we need to support Taildrive on tsnet, and if so, how?
ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), s.dialer, sys.DNSManager.Get(), sys.ProxyMapper(), nil) ns, err := netstack.Create(tsLogf, sys.Tun.Get(), eng, sys.MagicSock.Get(), s.dialer, sys.DNSManager.Get(), sys.ProxyMapper(), nil)
if err != nil { if err != nil {
return fmt.Errorf("netstack.Create: %w", err) return fmt.Errorf("netstack.Create: %w", err)
} }
@ -559,8 +573,8 @@ func (s *Server) start() (reterr error) {
if s.Store == nil { if s.Store == nil {
stateFile := filepath.Join(s.rootPath, "tailscaled.state") stateFile := filepath.Join(s.rootPath, "tailscaled.state")
logf("tsnet running state path %s", stateFile) s.logf("tsnet running state path %s", stateFile)
s.Store, err = store.New(logf, stateFile) s.Store, err = store.New(tsLogf, stateFile)
if err != nil { if err != nil {
return err return err
} }
@ -571,13 +585,13 @@ func (s *Server) start() (reterr error) {
if s.Ephemeral { if s.Ephemeral {
loginFlags = controlclient.LoginEphemeral loginFlags = controlclient.LoginEphemeral
} }
lb, err := ipnlocal.NewLocalBackend(logf, s.logid, sys, loginFlags|controlclient.LocalBackendStartKeyOSNeutral) lb, err := ipnlocal.NewLocalBackend(tsLogf, s.logid, sys, loginFlags|controlclient.LocalBackendStartKeyOSNeutral)
if err != nil { if err != nil {
return fmt.Errorf("NewLocalBackend: %v", err) return fmt.Errorf("NewLocalBackend: %v", err)
} }
lb.SetTCPHandlerForFunnelFlow(s.getTCPHandlerForFunnelFlow) lb.SetTCPHandlerForFunnelFlow(s.getTCPHandlerForFunnelFlow)
lb.SetVarRoot(s.rootPath) lb.SetVarRoot(s.rootPath)
logf("tsnet starting with hostname %q, varRoot %q", s.hostname, s.rootPath) s.logf("tsnet starting with hostname %q, varRoot %q", s.hostname, s.rootPath)
s.lb = lb s.lb = lb
if err := ns.Start(lb); err != nil { if err := ns.Start(lb); err != nil {
return fmt.Errorf("failed to start netstack: %w", err) return fmt.Errorf("failed to start netstack: %w", err)
@ -598,17 +612,17 @@ func (s *Server) start() (reterr error) {
} }
st := lb.State() st := lb.State()
if st == ipn.NeedsLogin || envknob.Bool("TSNET_FORCE_LOGIN") { if st == ipn.NeedsLogin || envknob.Bool("TSNET_FORCE_LOGIN") {
logf("LocalBackend state is %v; running StartLoginInteractive...", st) s.logf("LocalBackend state is %v; running StartLoginInteractive...", st)
if err := s.lb.StartLoginInteractive(s.shutdownCtx); err != nil { if err := s.lb.StartLoginInteractive(s.shutdownCtx); err != nil {
return fmt.Errorf("StartLoginInteractive: %w", err) return fmt.Errorf("StartLoginInteractive: %w", err)
} }
} else if authKey != "" { } else if authKey != "" {
logf("Authkey is set; but state is %v. Ignoring authkey. Re-run with TSNET_FORCE_LOGIN=1 to force use of authkey.", st) s.logf("Authkey is set; but state is %v. Ignoring authkey. Re-run with TSNET_FORCE_LOGIN=1 to force use of authkey.", st)
} }
go s.printAuthURLLoop() go s.printAuthURLLoop()
// Run the localapi handler, to allow fetching LetsEncrypt certs. // Run the localapi handler, to allow fetching LetsEncrypt certs.
lah := localapi.NewHandler(lb, logf, s.logid) lah := localapi.NewHandler(lb, tsLogf, s.logid)
lah.PermitWrite = true lah.PermitWrite = true
lah.PermitRead = true lah.PermitRead = true
@ -621,14 +635,14 @@ func (s *Server) start() (reterr error) {
s.lb.ConfigureWebClient(s.localClient) s.lb.ConfigureWebClient(s.localClient)
go func() { go func() {
if err := s.localAPIServer.Serve(lal); err != nil { if err := s.localAPIServer.Serve(lal); err != nil {
logf("localapi serve error: %v", err) s.logf("localapi serve error: %v", err)
} }
}() }()
closePool.add(s.localAPIListener) closePool.add(s.localAPIListener)
return nil return nil
} }
func (s *Server) startLogger(closePool *closeOnErrorPool, health *health.Tracker) error { func (s *Server) startLogger(closePool *closeOnErrorPool, health *health.Tracker, tsLogf logger.Logf) error {
if testenv.InTest() { if testenv.InTest() {
return nil return nil
} }
@ -659,10 +673,10 @@ func (s *Server) startLogger(closePool *closeOnErrorPool, health *health.Tracker
Stderr: io.Discard, // log everything to Buffer Stderr: io.Discard, // log everything to Buffer
Buffer: s.logbuffer, Buffer: s.logbuffer,
CompressLogs: true, CompressLogs: true,
HTTPC: &http.Client{Transport: logpolicy.NewLogtailTransport(logtail.DefaultHost, s.netMon, health, s.logf)}, HTTPC: &http.Client{Transport: logpolicy.NewLogtailTransport(logtail.DefaultHost, s.netMon, health, tsLogf)},
MetricsDelta: clientmetric.EncodeLogTailMetricsDelta, MetricsDelta: clientmetric.EncodeLogTailMetricsDelta,
} }
s.logtail = logtail.NewLogger(c, s.logf) s.logtail = logtail.NewLogger(c, tsLogf)
closePool.addFunc(func() { s.logtail.Shutdown(context.Background()) }) closePool.addFunc(func() { s.logtail.Shutdown(context.Background()) })
return nil return nil
} }
@ -683,8 +697,8 @@ func (s *Server) logf(format string, a ...any) {
if s.logtail != nil { if s.logtail != nil {
s.logtail.Logf(format, a...) s.logtail.Logf(format, a...)
} }
if s.Logf != nil { if s.UserLogf != nil {
s.Logf(format, a...) s.UserLogf(format, a...)
return return
} }
log.Printf(format, a...) log.Printf(format, a...)
@ -697,8 +711,8 @@ func (s *Server) printAuthURLLoop() {
if s.shutdownCtx.Err() != nil { if s.shutdownCtx.Err() != nil {
return return
} }
if st := s.lb.State(); st != ipn.NeedsLogin { if st := s.lb.State(); st != ipn.NeedsLogin && st != ipn.NoState {
s.logf("printAuthURLLoop: state is %v; stopping", st) s.logf("AuthLoop: state is %v; done", st)
return return
} }
st := s.lb.StatusWithoutPeers() st := s.lb.StatusWithoutPeers()

View File

@ -16,6 +16,7 @@
"flag" "flag"
"fmt" "fmt"
"io" "io"
"log"
"math/big" "math/big"
"net" "net"
"net/http" "net/http"
@ -214,8 +215,8 @@ func startServer(t *testing.T, ctx context.Context, controlURL, hostname string)
Ephemeral: true, Ephemeral: true,
getCertForTesting: testCertRoot.getCert, getCertForTesting: testCertRoot.getCert,
} }
if !*verboseNodes { if *verboseNodes {
s.Logf = logger.Discard s.Logf = log.Printf
} }
t.Cleanup(func() { s.Close() }) t.Cleanup(func() { s.Close() })