From 09363064b544cd00e56fd4142e457119292e2588 Mon Sep 17 00:00:00 2001 From: Will Norris Date: Tue, 7 Jun 2022 10:09:21 -0700 Subject: [PATCH] tsnet: use proper log ID refactor logpolicy config loading to make it easier to reuse from outside the package. Within tsnet, setup a basic logtail config. Signed-off-by: Will Norris --- logpolicy/logpolicy.go | 80 +++++++++++++++++++++++++----------------- tsnet/tsnet.go | 52 +++++++++++++++++++++++++-- 2 files changed, 96 insertions(+), 36 deletions(-) diff --git a/logpolicy/logpolicy.go b/logpolicy/logpolicy.go index d1a7987d4..558a9d642 100644 --- a/logpolicy/logpolicy.go +++ b/logpolicy/logpolicy.go @@ -95,6 +95,33 @@ type Policy struct { PublicID logtail.PublicID } +// NewConfig creates a Config with collection and a newly generated PrivateID. +func NewConfig(collection string) *Config { + id, err := logtail.NewPrivateID() + if err != nil { + panic("logtail.NewPrivateID should never fail") + } + return &Config{ + Collection: collection, + PrivateID: id, + PublicID: id.Public(), + } +} + +// Validate verifies that the Config matches the collection, +// and that the PrivateID and PublicID pair are sensible. +func (c *Config) Validate(collection string) error { + switch { + case c.Collection != collection: + return fmt.Errorf("config collection %q does not match %q", c.Collection, collection) + case c.PrivateID.IsZero(): + return errors.New("config has zero PrivateID") + case c.PrivateID.Public() != c.PublicID: + return errors.New("config PrivateID does not match PublicID") + } + return nil +} + // ToBytes returns the JSON representation of c. func (c *Config) ToBytes() []byte { data, err := json.MarshalIndent(c, "", "\t") @@ -105,7 +132,7 @@ func (c *Config) ToBytes() []byte { } // Save writes the JSON representation of c to stateFile. -func (c *Config) save(stateFile string) error { +func (c *Config) Save(stateFile string) error { c.PublicID = c.PrivateID.Public() if err := os.MkdirAll(filepath.Dir(stateFile), 0750); err != nil { return err @@ -117,7 +144,16 @@ func (c *Config) save(stateFile string) error { return nil } -// ConfigFromBytes parses a a Config from its JSON encoding. +// ConfigFromFile reads a Config from a JSON file. +func ConfigFromFile(statefile string) (*Config, error) { + b, err := os.ReadFile(statefile) + if err != nil { + return nil, err + } + return ConfigFromBytes(b) +} + +// ConfigFromBytes parses a Config from its JSON encoding. func ConfigFromBytes(jsonEnc []byte) (*Config, error) { c := &Config{} if err := json.Unmarshal(jsonEnc, c); err != nil { @@ -470,38 +506,16 @@ func New(collection string) *Policy { } } - var oldc *Config - data, err := ioutil.ReadFile(cfgPath) + newc, err := ConfigFromFile(cfgPath) if err != nil { - earlyLogf("logpolicy.Read %v: %v", cfgPath, err) - oldc = &Config{} - oldc.Collection = collection - } else { - oldc, err = ConfigFromBytes(data) - if err != nil { - earlyLogf("logpolicy.Config unmarshal: %v", err) - oldc = &Config{} - } - } - - newc := *oldc - if newc.Collection != collection { - log.Printf("logpolicy.Config: config collection %q does not match %q", newc.Collection, collection) - // We picked up an incompatible config file. - // Regenerate the private ID. - newc.PrivateID = logtail.PrivateID{} - newc.Collection = collection - } - if newc.PrivateID.IsZero() { - newc.PrivateID, err = logtail.NewPrivateID() - if err != nil { - log.Fatalf("logpolicy: NewPrivateID() should never fail") - } - } - newc.PublicID = newc.PrivateID.Public() - if newc != *oldc { - if err := newc.save(cfgPath); err != nil { - earlyLogf("logpolicy.Config.Save: %v", err) + earlyLogf("logpolicy.ConfigFromFile %v: %v", cfgPath, err) + newc = NewConfig(collection) + } + if err := newc.Validate(collection); err != nil { + earlyLogf("logpolicy.Config.Validate for %q: %v", cfgPath, err) + newc := NewConfig(collection) + if err := newc.Save(cfgPath); err != nil { + earlyLogf("logpolicy.Config.Save for %v: %v", cfgPath, err) } } diff --git a/tsnet/tsnet.go b/tsnet/tsnet.go index ddde30a83..41e733564 100644 --- a/tsnet/tsnet.go +++ b/tsnet/tsnet.go @@ -10,6 +10,7 @@ package tsnet import ( "context" "fmt" + "io/ioutil" "log" "net" "net/http" @@ -29,6 +30,9 @@ import ( "tailscale.com/ipn/localapi" "tailscale.com/ipn/store" "tailscale.com/ipn/store/mem" + "tailscale.com/logpolicy" + "tailscale.com/logtail" + "tailscale.com/logtail/filch" "tailscale.com/net/nettest" "tailscale.com/net/tsdial" "tailscale.com/smallzstd" @@ -76,6 +80,7 @@ type Server struct { shutdownCtx context.Context shutdownCancel context.CancelFunc localClient *tailscale.LocalClient + logtail *logtail.Logger mu sync.Mutex listeners map[listenKey]*listener @@ -127,6 +132,11 @@ func (s *Server) Close() error { } s.listeners = nil + // Perform a best-effort final flush. + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + s.logtail.Shutdown(ctx) + return nil } @@ -178,8 +188,42 @@ func (s *Server) start() error { return fmt.Errorf("%v is not a directory", s.rootPath) } - // TODO(bradfitz): start logtail? don't use filch, perhaps? - // only upload plumbed Logf? + cfgPath := filepath.Join(s.rootPath, "tsnet.log.conf") + + lpc, err := logpolicy.ConfigFromFile(cfgPath) + switch { + case os.IsNotExist(err): + lpc = logpolicy.NewConfig(logtail.CollectionNode) + if err := lpc.Save(cfgPath); err != nil { + return fmt.Errorf("logpolicy.Config.Save for %v: %w", cfgPath, err) + } + case err != nil: + return fmt.Errorf("logpolicy.LoadConfig for %v: %w", cfgPath, err) + } + if err := lpc.Validate(logtail.CollectionNode); err != nil { + return fmt.Errorf("logpolicy.Config.Validate for %v: %w", cfgPath, err) + } + logid := lpc.PublicID.String() + + f, err := filch.New(filepath.Join(s.rootPath, "tsnet"), filch.Options{ReplaceStderr: false}) + if err != nil { + return fmt.Errorf("error creating filch: %w", err) + } + c := logtail.Config{ + Collection: lpc.Collection, + PrivateID: lpc.PrivateID, + Stderr: ioutil.Discard, // log everything to Buffer + Buffer: f, + NewZstdEncoder: func() logtail.Encoder { + w, err := smallzstd.NewEncoder(nil) + if err != nil { + panic(err) + } + return w + }, + HTTPC: &http.Client{Transport: logpolicy.NewLogtailTransport(logtail.DefaultHost)}, + } + s.logtail = logtail.NewLogger(c, logf) s.linkMon, err = monitor.New(logf) if err != nil { @@ -226,7 +270,6 @@ func (s *Server) start() error { return err } } - logid := "tsnet-TODO" // https://github.com/tailscale/tailscale/issues/3866 loginFlags := controlclient.LoginDefault if s.Ephemeral { @@ -283,6 +326,9 @@ func (s *Server) start() error { } func (s *Server) logf(format string, a ...interface{}) { + if s.logtail != nil { + s.logtail.Logf(format, a...) + } if s.Logf != nil { s.Logf(format, a...) return