2020-02-05 22:16:58 +00:00
|
|
|
// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
2020-02-13 22:53:43 +00:00
|
|
|
// Package logpolicy manages the creation or reuse of logtail loggers,
|
|
|
|
// caching collection instance state on disk for use on future runs of
|
|
|
|
// programs on the same machine.
|
2020-02-05 22:16:58 +00:00
|
|
|
package logpolicy
|
|
|
|
|
|
|
|
import (
|
2020-03-26 03:40:24 +00:00
|
|
|
"bytes"
|
2020-02-05 22:16:58 +00:00
|
|
|
"context"
|
2020-04-04 15:20:54 +00:00
|
|
|
"crypto/tls"
|
2020-02-05 22:16:58 +00:00
|
|
|
"encoding/json"
|
2020-02-19 03:21:02 +00:00
|
|
|
"fmt"
|
2020-02-05 22:16:58 +00:00
|
|
|
"io/ioutil"
|
|
|
|
"log"
|
2020-04-04 15:20:54 +00:00
|
|
|
"net"
|
|
|
|
"net/http"
|
2020-02-05 22:16:58 +00:00
|
|
|
"os"
|
2020-04-05 00:34:25 +00:00
|
|
|
"os/exec"
|
2020-02-05 22:16:58 +00:00
|
|
|
"path/filepath"
|
|
|
|
"runtime"
|
2020-04-04 15:20:54 +00:00
|
|
|
"strconv"
|
2020-04-09 21:46:19 +00:00
|
|
|
"strings"
|
2020-04-04 15:20:54 +00:00
|
|
|
"time"
|
2020-02-05 22:16:58 +00:00
|
|
|
|
|
|
|
"golang.org/x/crypto/ssh/terminal"
|
|
|
|
"tailscale.com/atomicfile"
|
|
|
|
"tailscale.com/logtail"
|
|
|
|
"tailscale.com/logtail/filch"
|
2020-05-28 23:53:19 +00:00
|
|
|
"tailscale.com/net/netns"
|
2020-04-26 15:31:14 +00:00
|
|
|
"tailscale.com/net/tlsdial"
|
2020-07-02 18:26:33 +00:00
|
|
|
"tailscale.com/smallzstd"
|
2020-08-05 21:50:43 +00:00
|
|
|
"tailscale.com/types/logger"
|
2020-02-05 22:16:58 +00:00
|
|
|
"tailscale.com/version"
|
|
|
|
)
|
|
|
|
|
2020-02-13 22:53:43 +00:00
|
|
|
// Config represents an instance of logs in a collection.
|
2020-02-05 22:16:58 +00:00
|
|
|
type Config struct {
|
|
|
|
Collection string
|
|
|
|
PrivateID logtail.PrivateID
|
|
|
|
PublicID logtail.PublicID
|
|
|
|
}
|
|
|
|
|
2020-02-13 22:53:43 +00:00
|
|
|
// Policy is a logger and its public ID.
|
2020-02-05 22:16:58 +00:00
|
|
|
type Policy struct {
|
2020-02-13 22:53:43 +00:00
|
|
|
// Logtail is the logger.
|
|
|
|
Logtail logtail.Logger
|
|
|
|
// PublicID is the logger's instance identifier.
|
2020-02-05 22:16:58 +00:00
|
|
|
PublicID logtail.PublicID
|
|
|
|
}
|
|
|
|
|
2020-02-13 22:53:43 +00:00
|
|
|
// ToBytes returns the JSON representation of c.
|
2020-02-05 22:16:58 +00:00
|
|
|
func (c *Config) ToBytes() []byte {
|
|
|
|
data, err := json.MarshalIndent(c, "", "\t")
|
|
|
|
if err != nil {
|
2020-08-05 21:50:43 +00:00
|
|
|
log.Fatalf("logpolicy.Config marshal: %v", err)
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
return data
|
|
|
|
}
|
|
|
|
|
2020-02-13 22:53:43 +00:00
|
|
|
// Save writes the JSON representation of c to stateFile.
|
2020-02-19 03:21:02 +00:00
|
|
|
func (c *Config) save(stateFile string) error {
|
2020-02-05 22:16:58 +00:00
|
|
|
c.PublicID = c.PrivateID.Public()
|
2020-02-19 03:21:02 +00:00
|
|
|
if err := os.MkdirAll(filepath.Dir(stateFile), 0750); err != nil {
|
2020-02-13 22:53:43 +00:00
|
|
|
return err
|
|
|
|
}
|
2020-02-05 22:16:58 +00:00
|
|
|
data := c.ToBytes()
|
2020-02-13 22:53:43 +00:00
|
|
|
if err := atomicfile.WriteFile(stateFile, data, 0600); err != nil {
|
|
|
|
return err
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
2020-02-13 22:53:43 +00:00
|
|
|
return nil
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
|
2020-02-13 22:53:43 +00:00
|
|
|
// ConfigFromBytes parses a a Config from its JSON encoding.
|
|
|
|
func ConfigFromBytes(jsonEnc []byte) (*Config, error) {
|
2020-02-05 22:16:58 +00:00
|
|
|
c := &Config{}
|
2020-02-13 22:53:43 +00:00
|
|
|
if err := json.Unmarshal(jsonEnc, c); err != nil {
|
2020-02-05 22:16:58 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return c, nil
|
|
|
|
}
|
|
|
|
|
2020-02-13 22:53:43 +00:00
|
|
|
// stderrWriter is an io.Writer that always writes to the latest
|
|
|
|
// os.Stderr, even if os.Stderr changes during the lifetime of the
|
|
|
|
// stderrWriter value.
|
2020-02-05 22:16:58 +00:00
|
|
|
type stderrWriter struct{}
|
|
|
|
|
2020-02-13 22:53:43 +00:00
|
|
|
func (stderrWriter) Write(buf []byte) (int, error) {
|
2020-02-05 22:16:58 +00:00
|
|
|
return os.Stderr.Write(buf)
|
|
|
|
}
|
|
|
|
|
|
|
|
type logWriter struct {
|
|
|
|
logger *log.Logger
|
|
|
|
}
|
|
|
|
|
2020-02-13 22:53:43 +00:00
|
|
|
func (l logWriter) Write(buf []byte) (int, error) {
|
|
|
|
l.logger.Printf("%s", buf)
|
2020-02-05 22:16:58 +00:00
|
|
|
return len(buf), nil
|
|
|
|
}
|
|
|
|
|
2020-02-19 03:21:02 +00:00
|
|
|
// logsDir returns the directory to use for log configuration and
|
|
|
|
// buffer storage.
|
2020-08-05 21:50:43 +00:00
|
|
|
func logsDir(logf logger.Logf) string {
|
2020-04-05 00:34:25 +00:00
|
|
|
systemdStateDir := os.Getenv("STATE_DIRECTORY")
|
|
|
|
if systemdStateDir != "" {
|
2020-08-05 21:50:43 +00:00
|
|
|
logf("logpolicy: using $STATE_DIRECTORY, %q", systemdStateDir)
|
2020-04-05 00:34:25 +00:00
|
|
|
return systemdStateDir
|
2020-02-19 03:21:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cacheDir, err := os.UserCacheDir()
|
|
|
|
if err == nil {
|
2020-08-05 21:50:43 +00:00
|
|
|
d := filepath.Join(cacheDir, "Tailscale")
|
|
|
|
logf("logpolicy: using UserCacheDir, %q", d)
|
|
|
|
return d
|
2020-02-19 03:21:02 +00:00
|
|
|
}
|
|
|
|
|
2020-04-05 00:18:04 +00:00
|
|
|
// Use the current working directory, unless we're being run by a
|
|
|
|
// service manager that sets it to /.
|
|
|
|
wd, err := os.Getwd()
|
|
|
|
if err == nil && wd != "/" {
|
2020-08-05 21:50:43 +00:00
|
|
|
logf("logpolicy: using current directory, %q", wd)
|
2020-04-05 00:18:04 +00:00
|
|
|
return wd
|
|
|
|
}
|
|
|
|
|
|
|
|
// No idea where to put stuff. Try to create a temp dir. It'll
|
|
|
|
// mean we might lose some logs and rotate through log IDs, but
|
|
|
|
// it's something.
|
|
|
|
tmp, err := ioutil.TempDir("", "tailscaled-log-*")
|
|
|
|
if err != nil {
|
|
|
|
panic("no safe place found to store log state")
|
|
|
|
}
|
2020-08-05 21:50:43 +00:00
|
|
|
logf("logpolicy: using temp directory, %q", tmp)
|
2020-04-05 00:18:04 +00:00
|
|
|
return tmp
|
2020-02-19 03:21:02 +00:00
|
|
|
}
|
|
|
|
|
2020-03-26 03:40:24 +00:00
|
|
|
// runningUnderSystemd reports whether we're running under systemd.
|
|
|
|
func runningUnderSystemd() bool {
|
|
|
|
if runtime.GOOS == "linux" && os.Getppid() == 1 {
|
|
|
|
slurp, _ := ioutil.ReadFile("/proc/1/stat")
|
|
|
|
return bytes.HasPrefix(slurp, []byte("1 (systemd) "))
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2020-04-05 00:34:25 +00:00
|
|
|
// tryFixLogStateLocation is a temporary fixup for
|
|
|
|
// https://github.com/tailscale/tailscale/issues/247 . We accidentally
|
|
|
|
// wrote logging state files to /, and then later to $CACHE_DIRECTORY
|
|
|
|
// (which is incorrect because the log ID is not reconstructible if
|
|
|
|
// deleted - it's state, not cache data).
|
|
|
|
//
|
|
|
|
// If log state for cmdname exists in / or $CACHE_DIRECTORY, and no
|
|
|
|
// log state for that command exists in dir, then the log state is
|
|
|
|
// moved from whereever it does exist, into dir. Leftover logs state
|
|
|
|
// in / and $CACHE_DIRECTORY is deleted.
|
|
|
|
func tryFixLogStateLocation(dir, cmdname string) {
|
2020-07-30 14:47:19 +00:00
|
|
|
switch runtime.GOOS {
|
|
|
|
case "linux", "freebsd", "openbsd":
|
|
|
|
// These are the OSes where we might have written stuff into
|
|
|
|
// root. Others use different logic to find the logs storage
|
|
|
|
// dir.
|
|
|
|
default:
|
|
|
|
return
|
|
|
|
}
|
2020-04-05 00:34:25 +00:00
|
|
|
if cmdname == "" {
|
|
|
|
log.Printf("[unexpected] no cmdname given to tryFixLogStateLocation, please file a bug at https://github.com/tailscale/tailscale")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if dir == "/" {
|
|
|
|
// Trying to store things in / still. That's a bug, but don't
|
|
|
|
// abort hard.
|
|
|
|
log.Printf("[unexpected] storing logging config in /, please file a bug at https://github.com/tailscale/tailscale")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if os.Getuid() != 0 {
|
|
|
|
// Only root could have written log configs to weird places.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// We stored logs in 2 incorrect places: either /, or CACHE_DIR
|
|
|
|
// (aka /var/cache/tailscale). We want to move files into the
|
|
|
|
// provided dir, preferring those in CACHE_DIR over those in / if
|
|
|
|
// both exist. If files already exist in dir, don't
|
|
|
|
// overwrite. Finally, once we've maybe moved files around, we
|
|
|
|
// want to delete leftovers in / and CACHE_DIR, to clean up after
|
|
|
|
// our past selves.
|
|
|
|
|
|
|
|
files := []string{
|
|
|
|
fmt.Sprintf("%s.log.conf", cmdname),
|
|
|
|
fmt.Sprintf("%s.log1.txt", cmdname),
|
|
|
|
fmt.Sprintf("%s.log2.txt", cmdname),
|
|
|
|
}
|
|
|
|
|
|
|
|
// checks if any of the files above exist in d.
|
|
|
|
checkExists := func(d string) (bool, error) {
|
|
|
|
for _, file := range files {
|
|
|
|
p := filepath.Join(d, file)
|
|
|
|
_, err := os.Stat(p)
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
continue
|
|
|
|
} else if err != nil {
|
|
|
|
return false, fmt.Errorf("stat %q: %w", p, err)
|
|
|
|
}
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
return false, nil
|
|
|
|
}
|
|
|
|
// move files from d into dir, if they exist.
|
|
|
|
moveFiles := func(d string) error {
|
|
|
|
for _, file := range files {
|
|
|
|
src := filepath.Join(d, file)
|
|
|
|
_, err := os.Stat(src)
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
continue
|
|
|
|
} else if err != nil {
|
|
|
|
return fmt.Errorf("stat %q: %v", src, err)
|
|
|
|
}
|
|
|
|
dst := filepath.Join(dir, file)
|
|
|
|
bs, err := exec.Command("mv", src, dst).CombinedOutput()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("mv %q %q: %v (%s)", src, dst, err, bs)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
existsInRoot, err := checkExists("/")
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("checking for configs in /: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
existsInCache := false
|
|
|
|
cacheDir := os.Getenv("CACHE_DIRECTORY")
|
|
|
|
if cacheDir != "" {
|
|
|
|
existsInCache, err = checkExists("/var/cache/tailscale")
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("checking for configs in %s: %v", cacheDir, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
existsInDest, err := checkExists(dir)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("checking for configs in %s: %v", dir, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch {
|
|
|
|
case !existsInRoot && !existsInCache:
|
|
|
|
// No leftover files, nothing to do.
|
|
|
|
return
|
|
|
|
case existsInDest:
|
|
|
|
// Already have "canonical" configs, just delete any remnants
|
|
|
|
// (below).
|
|
|
|
case existsInCache:
|
|
|
|
// CACHE_DIRECTORY takes precedence over /, move files from
|
|
|
|
// there.
|
|
|
|
if err := moveFiles(cacheDir); err != nil {
|
|
|
|
log.Print(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case existsInRoot:
|
|
|
|
// Files from root is better than nothing.
|
|
|
|
if err := moveFiles("/"); err != nil {
|
|
|
|
log.Print(err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If moving succeeded, or we didn't need to move files, try to
|
|
|
|
// delete any leftover files, but it's okay if we can't delete
|
|
|
|
// them for some reason.
|
|
|
|
dirs := []string{}
|
|
|
|
if existsInCache {
|
|
|
|
dirs = append(dirs, cacheDir)
|
|
|
|
}
|
|
|
|
if existsInRoot {
|
|
|
|
dirs = append(dirs, "/")
|
|
|
|
}
|
|
|
|
for _, d := range dirs {
|
|
|
|
for _, file := range files {
|
|
|
|
p := filepath.Join(d, file)
|
|
|
|
_, err := os.Stat(p)
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
continue
|
|
|
|
} else if err != nil {
|
|
|
|
log.Printf("stat %q: %v", p, err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if err := os.Remove(p); err != nil {
|
|
|
|
log.Printf("rm %q: %v", p, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-13 22:53:43 +00:00
|
|
|
// New returns a new log policy (a logger and its instance ID) for a
|
2020-02-19 03:21:02 +00:00
|
|
|
// given collection name.
|
|
|
|
func New(collection string) *Policy {
|
2020-02-05 22:16:58 +00:00
|
|
|
var lflags int
|
|
|
|
if terminal.IsTerminal(2) || runtime.GOOS == "windows" {
|
|
|
|
lflags = 0
|
|
|
|
} else {
|
|
|
|
lflags = log.LstdFlags
|
|
|
|
}
|
2020-03-26 03:40:24 +00:00
|
|
|
if runningUnderSystemd() {
|
|
|
|
// If journalctl is going to prepend its own timestamp
|
|
|
|
// anyway, no need to add one.
|
|
|
|
lflags = 0
|
|
|
|
}
|
2020-02-13 22:53:43 +00:00
|
|
|
console := log.New(stderrWriter{}, "", lflags)
|
2020-02-05 22:16:58 +00:00
|
|
|
|
2020-08-05 21:50:43 +00:00
|
|
|
var earlyErrBuf bytes.Buffer
|
|
|
|
earlyLogf := func(format string, a ...interface{}) {
|
|
|
|
fmt.Fprintf(&earlyErrBuf, format, a...)
|
|
|
|
earlyErrBuf.WriteByte('\n')
|
|
|
|
}
|
|
|
|
|
|
|
|
dir := logsDir(earlyLogf)
|
2020-04-05 00:34:25 +00:00
|
|
|
|
2020-07-30 14:47:19 +00:00
|
|
|
cmdName := version.CmdName()
|
|
|
|
tryFixLogStateLocation(dir, cmdName)
|
2020-04-05 00:34:25 +00:00
|
|
|
|
2020-07-30 14:47:19 +00:00
|
|
|
cfgPath := filepath.Join(dir, fmt.Sprintf("%s.log.conf", cmdName))
|
2020-02-05 22:16:58 +00:00
|
|
|
var oldc *Config
|
2020-02-19 03:21:02 +00:00
|
|
|
data, err := ioutil.ReadFile(cfgPath)
|
2020-02-05 22:16:58 +00:00
|
|
|
if err != nil {
|
2020-08-05 21:50:43 +00:00
|
|
|
earlyLogf("logpolicy.Read %v: %v", cfgPath, err)
|
2020-02-05 22:16:58 +00:00
|
|
|
oldc = &Config{}
|
|
|
|
oldc.Collection = collection
|
|
|
|
} else {
|
|
|
|
oldc, err = ConfigFromBytes(data)
|
|
|
|
if err != nil {
|
2020-08-05 21:50:43 +00:00
|
|
|
earlyLogf("logpolicy.Config unmarshal: %v", err)
|
2020-02-05 22:16:58 +00:00
|
|
|
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 == (logtail.PrivateID{}) {
|
|
|
|
newc.PrivateID, err = logtail.NewPrivateID()
|
|
|
|
if err != nil {
|
|
|
|
log.Fatalf("logpolicy: NewPrivateID() should never fail")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
newc.PublicID = newc.PrivateID.Public()
|
|
|
|
if newc != *oldc {
|
2020-02-19 03:21:02 +00:00
|
|
|
if err := newc.save(cfgPath); err != nil {
|
2020-08-05 21:50:43 +00:00
|
|
|
earlyLogf("logpolicy.Config.Save: %v", err)
|
2020-02-13 22:53:43 +00:00
|
|
|
}
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
c := logtail.Config{
|
|
|
|
Collection: newc.Collection,
|
|
|
|
PrivateID: newc.PrivateID,
|
2020-02-13 22:53:43 +00:00
|
|
|
Stderr: logWriter{console},
|
2020-02-05 22:16:58 +00:00
|
|
|
NewZstdEncoder: func() logtail.Encoder {
|
2020-07-02 18:26:33 +00:00
|
|
|
w, err := smallzstd.NewEncoder(nil)
|
2020-02-05 22:16:58 +00:00
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return w
|
|
|
|
},
|
2020-04-26 15:31:14 +00:00
|
|
|
HTTPC: &http.Client{Transport: newLogtailTransport(logtail.DefaultHost)},
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
|
|
|
|
2020-07-30 14:47:19 +00:00
|
|
|
filchBuf, filchErr := filch.New(filepath.Join(dir, cmdName), filch.Options{})
|
2020-02-05 22:16:58 +00:00
|
|
|
if filchBuf != nil {
|
|
|
|
c.Buffer = filchBuf
|
|
|
|
}
|
Add tstest.PanicOnLog(), and fix various problems detected by this.
If a test calls log.Printf, 'go test' horrifyingly rearranges the
output to no longer be in chronological order, which makes debugging
virtually impossible. Let's stop that from happening by making
log.Printf panic if called from any module, no matter how deep, during
tests.
This required us to change the default error handler in at least one
http.Server, as well as plumbing a bunch of logf functions around,
especially in magicsock and wgengine, but also in logtail and backoff.
To add insult to injury, 'go test' also rearranges the output when a
parent test has multiple sub-tests (all the sub-test's t.Logf is always
printed after all the parent tests t.Logf), so we need to screw around
with a special Logf that can point at the "current" t (current_t.Logf)
in some places. Probably our entire way of using subtests is wrong,
since 'go test' would probably like to run them all in parallel if you
called t.Parallel(), but it definitely can't because the're all
manipulating the shared state created by the parent test. They should
probably all be separate toplevel tests instead, with common
setup/teardown logic. But that's a job for another time.
Signed-off-by: Avery Pennarun <apenwarr@tailscale.com>
2020-05-14 02:59:54 +00:00
|
|
|
lw := logtail.Log(c, log.Printf)
|
2020-02-05 22:16:58 +00:00
|
|
|
log.SetFlags(0) // other logflags are set on console, not here
|
|
|
|
log.SetOutput(lw)
|
|
|
|
|
2020-08-05 21:50:43 +00:00
|
|
|
log.Printf("Program starting: v%v, Go %v: %#v",
|
2020-04-09 21:46:19 +00:00
|
|
|
version.LONG,
|
|
|
|
strings.TrimPrefix(runtime.Version(), "go"),
|
|
|
|
os.Args)
|
2020-08-05 21:50:43 +00:00
|
|
|
log.Printf("LogID: %v", newc.PublicID)
|
2020-02-05 22:16:58 +00:00
|
|
|
if filchErr != nil {
|
2020-05-01 08:51:19 +00:00
|
|
|
log.Printf("filch failed: %v", filchErr)
|
2020-02-05 22:16:58 +00:00
|
|
|
}
|
2020-08-05 21:50:43 +00:00
|
|
|
if earlyErrBuf.Len() != 0 {
|
|
|
|
log.Printf("%s", earlyErrBuf.Bytes())
|
|
|
|
}
|
2020-02-05 22:16:58 +00:00
|
|
|
|
|
|
|
return &Policy{
|
|
|
|
Logtail: lw,
|
|
|
|
PublicID: newc.PublicID,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close immediately shuts down the logger.
|
|
|
|
func (p *Policy) Close() {
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
cancel()
|
|
|
|
p.Shutdown(ctx)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Shutdown gracefully shuts down the logger, finishing any current
|
|
|
|
// log upload if it can be done before ctx is canceled.
|
|
|
|
func (p *Policy) Shutdown(ctx context.Context) error {
|
|
|
|
if p.Logtail != nil {
|
2020-08-05 21:50:43 +00:00
|
|
|
log.Printf("flushing log.")
|
2020-02-05 22:16:58 +00:00
|
|
|
return p.Logtail.Shutdown(ctx)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
2020-04-04 15:20:54 +00:00
|
|
|
|
2020-04-26 15:31:14 +00:00
|
|
|
// newLogtailTransport returns the HTTP Transport we use for uploading
|
|
|
|
// logs to the given host name.
|
|
|
|
func newLogtailTransport(host string) *http.Transport {
|
2020-04-04 15:20:54 +00:00
|
|
|
// Start with a copy of http.DefaultTransport and tweak it a bit.
|
|
|
|
tr := http.DefaultTransport.(*http.Transport).Clone()
|
|
|
|
|
|
|
|
// We do our own zstd compression on uploads, and responses never contain any payload,
|
|
|
|
// so don't send "Accept-Encoding: gzip" to save a few bytes on the wire, since there
|
|
|
|
// will never be any body to decompress:
|
|
|
|
tr.DisableCompression = true
|
|
|
|
|
|
|
|
// Log whenever we dial:
|
|
|
|
tr.DialContext = func(ctx context.Context, netw, addr string) (net.Conn, error) {
|
2020-06-01 17:50:37 +00:00
|
|
|
nd := netns.FromDialer(&net.Dialer{
|
|
|
|
Timeout: 30 * time.Second,
|
|
|
|
KeepAlive: 30 * time.Second,
|
|
|
|
})
|
2020-04-04 15:20:54 +00:00
|
|
|
t0 := time.Now()
|
|
|
|
c, err := nd.DialContext(ctx, netw, addr)
|
|
|
|
d := time.Since(t0).Round(time.Millisecond)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("logtail: dial %q failed: %v (in %v)", addr, err, d)
|
|
|
|
} else {
|
|
|
|
log.Printf("logtail: dialed %q in %v", addr, d)
|
|
|
|
}
|
|
|
|
return c, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// We're contacting exactly 1 hostname, so the default's 100
|
|
|
|
// max idle conns is very high for our needs. Even 2 is
|
|
|
|
// probably double what we need:
|
|
|
|
tr.MaxIdleConns = 2
|
|
|
|
|
|
|
|
// Provide knob to force HTTP/1 for log uploads.
|
|
|
|
// TODO(bradfitz): remove this debug knob once we've decided
|
|
|
|
// to upload via HTTP/1 or HTTP/2 (probably HTTP/1). Or we might just enforce
|
|
|
|
// it server-side.
|
|
|
|
if h1, _ := strconv.ParseBool(os.Getenv("TS_DEBUG_FORCE_H1_LOGS")); h1 {
|
|
|
|
tr.TLSClientConfig = nil // DefaultTransport's was already initialized w/ h2
|
|
|
|
tr.ForceAttemptHTTP2 = false
|
|
|
|
tr.TLSNextProto = map[string]func(authority string, c *tls.Conn) http.RoundTripper{}
|
|
|
|
}
|
2020-04-26 15:31:14 +00:00
|
|
|
|
|
|
|
tr.TLSClientConfig = tlsdial.Config(host, tr.TLSClientConfig)
|
|
|
|
|
2020-04-04 15:20:54 +00:00
|
|
|
return tr
|
|
|
|
}
|