mirror of
https://github.com/tailscale/tailscale.git
synced 2026-07-20 21:23:07 +08:00
feature/syslog, cmd/tailscaled, logpolicy: add optional --syslog flag
Add a new modular syslog feature providing a tailscaled --syslog flag that sends the daemon's logs to the system syslog daemon instead of stderr, which is useful when running as a daemon without a service manager that captures stderr (e.g. OpenWrt's procd). The feature package registers two new hooks: one to register its flag before flag parsing, and one that tailscaled calls early in main to redirect the standard library's default logger. Because logpolicy later points the default logger at logtail, whose local console copy writes to stderr, logpolicy now also consults the hook and sends its console copy to the same sink (with timestamps disabled, as syslog records its own). The feature is linked by default only on Linux, FreeBSD, and OpenBSD, and can be removed with the ts_omit_syslog build tag. If connecting to the syslog daemon fails at startup, tailscaled logs a warning and continues logging to stderr. Fixes #16270 Signed-off-by: Brad Fitzpatrick <bradfitz@tailscale.com> Change-Id: I8f3a92d4c1e6b70a5d29e4f61b3c874250a9de13
This commit is contained in:
parent
def265083b
commit
0433cc6929
@ -323,6 +323,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
tailscale.com/feature/runtimemetrics from tailscale.com/feature/condregister
|
||||
L tailscale.com/feature/sdnotify from tailscale.com/feature/condregister
|
||||
LD tailscale.com/feature/ssh from tailscale.com/cmd/tailscaled
|
||||
L tailscale.com/feature/syslog from tailscale.com/feature/condregister
|
||||
tailscale.com/feature/syspolicy from tailscale.com/feature/condregister
|
||||
tailscale.com/feature/taildrop from tailscale.com/feature/condregister
|
||||
tailscale.com/feature/tailnetlock from tailscale.com/feature/condregister
|
||||
@ -745,7 +746,7 @@ tailscale.com/cmd/tailscaled dependencies: (generated by github.com/tailscale/de
|
||||
iter from maps+
|
||||
log from expvar+
|
||||
log/internal from log
|
||||
LD log/syslog from tailscale.com/ssh/tailssh
|
||||
LD log/syslog from tailscale.com/ssh/tailssh+
|
||||
maps from tailscale.com/clientupdate+
|
||||
math from archive/tar+
|
||||
math/big from crypto/dsa+
|
||||
|
||||
@ -33,6 +33,21 @@ func TestOmitSSH(t *testing.T) {
|
||||
}.Check(t)
|
||||
}
|
||||
|
||||
func TestOmitSyslog(t *testing.T) {
|
||||
const msg = "unexpected syslog usage with ts_omit_syslog"
|
||||
deptest.DepChecker{
|
||||
GOOS: "linux",
|
||||
GOARCH: "amd64",
|
||||
// Tailscale SSH's incubator also uses log/syslog, so omit
|
||||
// SSH too to lock down the standard library package.
|
||||
Tags: "ts_omit_syslog,ts_omit_ssh,ts_include_cli",
|
||||
BadDeps: map[string]string{
|
||||
"log/syslog": msg,
|
||||
"tailscale.com/feature/syslog": msg,
|
||||
},
|
||||
}.Check(t)
|
||||
}
|
||||
|
||||
func TestOmitSyspolicy(t *testing.T) {
|
||||
const msg = "unexpected syspolicy usage with ts_omit_syspolicy"
|
||||
deptest.DepChecker{
|
||||
|
||||
@ -233,6 +233,9 @@ func main() {
|
||||
if f, ok := hookRegisterOutboundProxyFlags.GetOk(); ok {
|
||||
f()
|
||||
}
|
||||
if f, ok := feature.HookRegisterLogSinkFlags.GetOk(); ok {
|
||||
f()
|
||||
}
|
||||
|
||||
if runtime.GOOS == "plan9" && os.Getenv("_NETSHELL_CHILD_") != "" {
|
||||
os.Args = []string{"tailscaled", "be-child", "plan9-netshell"}
|
||||
@ -261,6 +264,10 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
if f, ok := feature.HookLogSink.GetOk(); ok {
|
||||
f() // redirects the default logger (e.g. to syslog) if requested by flags
|
||||
}
|
||||
|
||||
if fd, ok := envknob.LookupInt("TS_PARENT_DEATH_FD"); ok && fd > 2 {
|
||||
go dieOnPipeReadErrorOfFD(fd)
|
||||
}
|
||||
|
||||
13
feature/buildfeatures/feature_syslog_disabled.go
Normal file
13
feature/buildfeatures/feature_syslog_disabled.go
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build ts_omit_syslog
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasSyslog is whether the binary was built with support for modular feature "tailscaled --syslog flag support to send logs to the system syslog daemon".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_syslog" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasSyslog = false
|
||||
13
feature/buildfeatures/feature_syslog_enabled.go
Normal file
13
feature/buildfeatures/feature_syslog_enabled.go
Normal file
@ -0,0 +1,13 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Code generated by gen.go; DO NOT EDIT.
|
||||
|
||||
//go:build !ts_omit_syslog
|
||||
|
||||
package buildfeatures
|
||||
|
||||
// HasSyslog is whether the binary was built with support for modular feature "tailscaled --syslog flag support to send logs to the system syslog daemon".
|
||||
// Specifically, it's whether the binary was NOT built with the "ts_omit_syslog" build tag.
|
||||
// It's a const so it can be used for dead code elimination.
|
||||
const HasSyslog = true
|
||||
8
feature/condregister/maybe_syslog.go
Normal file
8
feature/condregister/maybe_syslog.go
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build (linux || freebsd || openbsd) && !ts_omit_syslog
|
||||
|
||||
package condregister
|
||||
|
||||
import _ "tailscale.com/feature/syslog"
|
||||
@ -267,6 +267,10 @@ type FeatureMeta struct {
|
||||
Sym: "Synology",
|
||||
Desc: "Synology NAS integration (applies to Linux builds only)",
|
||||
},
|
||||
"syslog": {
|
||||
Sym: "Syslog",
|
||||
Desc: "tailscaled --syslog flag support to send logs to the system syslog daemon",
|
||||
},
|
||||
"syspolicy": {Sym: "SystemPolicy", Desc: "System policy configuration (MDM) support"},
|
||||
"systray": {
|
||||
Sym: "SysTray",
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
package feature
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
@ -13,6 +14,21 @@
|
||||
"tailscale.com/types/persist"
|
||||
)
|
||||
|
||||
// HookRegisterLogSinkFlags is a hook for the syslog feature to register
|
||||
// its flags (such as tailscaled's --syslog) with the process's default
|
||||
// flag set. If set, tailscaled calls it before flag parsing.
|
||||
var HookRegisterLogSinkFlags Hook[func()]
|
||||
|
||||
// HookLogSink is a hook for the syslog feature to redirect the process's
|
||||
// logs to an alternate sink. If set, tailscaled calls it once early in
|
||||
// main, after flag parsing; on that first call, if the user requested an
|
||||
// alternate sink, it points the standard library's default logger at that
|
||||
// sink. It returns the sink, or nil if logs are not being redirected.
|
||||
// Later callers (such as logpolicy, which otherwise writes its console
|
||||
// copy of logs to stderr) use the returned writer to send their logs to
|
||||
// the same place.
|
||||
var HookLogSink Hook[func() io.Writer]
|
||||
|
||||
// HookCanAutoUpdate is a hook for the clientupdate package
|
||||
// to conditionally initialize.
|
||||
var HookCanAutoUpdate Hook[func() bool]
|
||||
|
||||
47
feature/syslog/syslog.go
Normal file
47
feature/syslog/syslog.go
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright (c) Tailscale Inc & contributors
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
//go:build unix
|
||||
|
||||
// Package syslog provides the tailscaled --syslog flag, which sends the
|
||||
// daemon's logs to the system syslog daemon instead of stderr.
|
||||
package syslog
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"io"
|
||||
"log"
|
||||
"log/syslog"
|
||||
"sync"
|
||||
|
||||
"tailscale.com/feature"
|
||||
)
|
||||
|
||||
func init() {
|
||||
feature.Register("syslog")
|
||||
feature.HookRegisterLogSinkFlags.Set(registerFlags)
|
||||
feature.HookLogSink.Set(logSink)
|
||||
}
|
||||
|
||||
var useSyslog bool
|
||||
|
||||
func registerFlags() {
|
||||
flag.BoolVar(&useSyslog, "syslog", false, "log to the system syslog daemon instead of stderr")
|
||||
}
|
||||
|
||||
// logSink returns the writer to which logs should be sent, pointing the
|
||||
// standard library's default logger at it on first call. It returns nil if
|
||||
// syslog logging was not requested or the syslog daemon is unreachable.
|
||||
var logSink = sync.OnceValue(func() io.Writer {
|
||||
if !useSyslog {
|
||||
return nil
|
||||
}
|
||||
w, err := syslog.New(syslog.LOG_DAEMON|syslog.LOG_INFO, "tailscaled")
|
||||
if err != nil {
|
||||
log.Printf("syslog: connecting to syslog daemon failed; continuing to log to stderr: %v", err)
|
||||
return nil
|
||||
}
|
||||
log.SetFlags(0) // syslog records its own timestamps
|
||||
log.SetOutput(w)
|
||||
return w
|
||||
})
|
||||
@ -546,7 +546,18 @@ func (opts Options) init(disableLogging bool) (*logtail.Config, *Policy) {
|
||||
// anyway, no need to add one.
|
||||
lflags = 0
|
||||
}
|
||||
console := log.New(stderrWriter{}, "", lflags)
|
||||
var conWriter io.Writer = stderrWriter{}
|
||||
if buildfeatures.HasSyslog {
|
||||
if f, ok := feature.HookLogSink.GetOk(); ok {
|
||||
if w := f(); w != nil {
|
||||
// Logs are being redirected elsewhere (e.g. to syslog,
|
||||
// which records its own timestamps).
|
||||
conWriter = w
|
||||
lflags = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
console := log.New(conWriter, "", lflags)
|
||||
|
||||
var earlyErrBuf bytes.Buffer
|
||||
earlyLogf := func(format string, a ...any) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user