feat: add stackdriver and json log options to coder server (#5682)

This commit is contained in:
Colin Adler
2023-01-12 20:08:23 -06:00
committed by GitHub
parent 1229fda1a6
commit dcab87358e
12 changed files with 461 additions and 7 deletions
+20
View File
@@ -471,6 +471,26 @@ func newConfig() *codersdk.DeploymentConfig {
Default: false,
},
},
Logging: &codersdk.LoggingConfig{
Human: &codersdk.DeploymentConfigField[string]{
Name: "Human Log Location",
Usage: "Output human-readable logs to a given file.",
Flag: "log-human",
Default: "/dev/stderr",
},
JSON: &codersdk.DeploymentConfigField[string]{
Name: "JSON Log Location",
Usage: "Output JSON logs to a given file.",
Flag: "log-json",
Default: "",
},
Stackdriver: &codersdk.DeploymentConfigField[string]{
Name: "Stackdriver Log Location",
Usage: "Output Stackdriver compatible logs to a given file.",
Flag: "log-stackdriver",
Default: "",
},
},
}
}
+72 -6
View File
@@ -46,6 +46,8 @@ import (
"cdr.dev/slog"
"cdr.dev/slog/sloggers/sloghuman"
"cdr.dev/slog/sloggers/slogjson"
"cdr.dev/slog/sloggers/slogstackdriver"
"github.com/coder/coder/buildinfo"
"github.com/coder/coder/cli/cliui"
"github.com/coder/coder/cli/config"
@@ -122,13 +124,11 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
}
printLogo(cmd)
logger := slog.Make(sloghuman.Sink(cmd.ErrOrStderr()))
if ok, _ := cmd.Flags().GetBool(varVerbose); ok {
logger = logger.Leveled(slog.LevelDebug)
}
if cfg.Trace.CaptureLogs.Value {
logger = logger.AppendSinks(tracing.SlogSink{})
logger, logCloser, err := buildLogger(cmd, cfg)
if err != nil {
return xerrors.Errorf("make logger: %w", err)
}
defer logCloser()
// Register signals early on so that graceful shutdown can't
// be interrupted by additional signals. Note that we avoid
@@ -1145,6 +1145,11 @@ func newProvisionerDaemon(
// nolint: revive
func printLogo(cmd *cobra.Command) {
// Only print the logo in TTYs.
if !isTTYOut(cmd) {
return
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s - Software development on your infrastucture\n", cliui.Styles.Bold.Render("Coder "+buildinfo.Version()))
}
@@ -1512,3 +1517,64 @@ func redirectHTTPToAccessURL(handler http.Handler, accessURL *url.URL) http.Hand
func isLocalhost(host string) bool {
return host == "localhost" || host == "127.0.0.1" || host == "::1"
}
func buildLogger(cmd *cobra.Command, cfg *codersdk.DeploymentConfig) (slog.Logger, func(), error) {
var (
sinks = []slog.Sink{}
closers = []func() error{}
)
addSinkIfProvided := func(sinkFn func(io.Writer) slog.Sink, loc string) error {
switch loc {
case "":
case "/dev/stdout":
sinks = append(sinks, sinkFn(cmd.OutOrStdout()))
case "/dev/stderr":
sinks = append(sinks, sinkFn(cmd.ErrOrStderr()))
default:
fi, err := os.OpenFile(loc, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
if err != nil {
return xerrors.Errorf("open log file %q: %w", loc, err)
}
closers = append(closers, fi.Close)
sinks = append(sinks, sinkFn(fi))
}
return nil
}
err := addSinkIfProvided(sloghuman.Sink, cfg.Logging.Human.Value)
if err != nil {
return slog.Logger{}, nil, xerrors.Errorf("add human sink: %w", err)
}
err = addSinkIfProvided(slogjson.Sink, cfg.Logging.JSON.Value)
if err != nil {
return slog.Logger{}, nil, xerrors.Errorf("add json sink: %w", err)
}
err = addSinkIfProvided(slogstackdriver.Sink, cfg.Logging.Stackdriver.Value)
if err != nil {
return slog.Logger{}, nil, xerrors.Errorf("add stackdriver sink: %w", err)
}
if cfg.Trace.CaptureLogs.Value {
sinks = append(sinks, tracing.SlogSink{})
}
level := slog.LevelInfo
if ok, _ := cmd.Flags().GetBool(varVerbose); ok {
level = slog.LevelDebug
}
if len(sinks) == 0 {
return slog.Logger{}, nil, xerrors.New("no loggers provided")
}
return slog.Make(sinks...).Leveled(level), func() {
for _, closer := range closers {
_ = closer()
}
}, nil
}
+189
View File
@@ -35,6 +35,7 @@ import (
"github.com/coder/coder/coderd/database/postgres"
"github.com/coder/coder/coderd/telemetry"
"github.com/coder/coder/codersdk"
"github.com/coder/coder/cryptorand"
"github.com/coder/coder/pty/ptytest"
"github.com/coder/coder/testutil"
)
@@ -1122,6 +1123,194 @@ func TestServer(t *testing.T) {
<-serverErr
})
})
t.Run("Logging", func(t *testing.T) {
t.Parallel()
t.Run("CreatesFile", func(t *testing.T) {
t.Parallel()
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
random, err := cryptorand.String(5)
require.NoError(t, err)
fiName := fmt.Sprint(os.TempDir(), "/coder-logging-test-", random)
defer func() {
_ = os.Remove(fiName)
}()
root, _ := clitest.New(t,
"server",
"--verbose",
"--in-memory",
"--http-address", ":0",
"--access-url", "http://example.com",
"--log-human", fiName,
)
serverErr := make(chan error, 1)
go func() {
serverErr <- root.ExecuteContext(ctx)
}()
assert.Eventually(t, func() bool {
stat, err := os.Stat(fiName)
return err == nil && stat.Size() > 0
}, testutil.WaitShort, testutil.IntervalFast)
cancelFunc()
<-serverErr
})
t.Run("Human", func(t *testing.T) {
t.Parallel()
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
fi, err := os.CreateTemp("", "coder-logging-test-*")
require.NoError(t, err)
defer func() {
_ = os.Remove(fi.Name())
}()
root, _ := clitest.New(t,
"server",
"--verbose",
"--in-memory",
"--http-address", ":0",
"--access-url", "http://example.com",
"--log-human", fi.Name(),
)
serverErr := make(chan error, 1)
go func() {
serverErr <- root.ExecuteContext(ctx)
}()
assert.Eventually(t, func() bool {
stat, err := os.Stat(fi.Name())
return err == nil && stat.Size() > 0
}, testutil.WaitShort, testutil.IntervalFast)
cancelFunc()
<-serverErr
})
t.Run("JSON", func(t *testing.T) {
t.Parallel()
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
fi, err := os.CreateTemp("", "coder-logging-test-*")
require.NoError(t, err)
defer func() {
_ = os.Remove(fi.Name())
}()
root, _ := clitest.New(t,
"server",
"--verbose",
"--in-memory",
"--http-address", ":0",
"--access-url", "http://example.com",
"--log-json", fi.Name(),
)
serverErr := make(chan error, 1)
go func() {
serverErr <- root.ExecuteContext(ctx)
}()
assert.Eventually(t, func() bool {
stat, err := os.Stat(fi.Name())
return err == nil && stat.Size() > 0
}, testutil.WaitShort, testutil.IntervalFast)
cancelFunc()
<-serverErr
})
t.Run("Stackdriver", func(t *testing.T) {
t.Parallel()
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
fi, err := os.CreateTemp("", "coder-logging-test-*")
require.NoError(t, err)
defer func() {
_ = os.Remove(fi.Name())
}()
root, _ := clitest.New(t,
"server",
"--verbose",
"--in-memory",
"--http-address", ":0",
"--access-url", "http://example.com",
"--log-stackdriver", fi.Name(),
)
serverErr := make(chan error, 1)
go func() {
serverErr <- root.ExecuteContext(ctx)
}()
assert.Eventually(t, func() bool {
stat, err := os.Stat(fi.Name())
return err == nil && stat.Size() > 0
}, testutil.WaitLong, testutil.IntervalMedium)
cancelFunc()
<-serverErr
})
t.Run("Multiple", func(t *testing.T) {
t.Parallel()
ctx, cancelFunc := context.WithCancel(context.Background())
defer cancelFunc()
fi1, err := os.CreateTemp("", "coder-logging-test-*")
require.NoError(t, err)
defer func() {
_ = os.Remove(fi1.Name())
}()
fi2, err := os.CreateTemp("", "coder-logging-test-*")
require.NoError(t, err)
defer func() {
_ = os.Remove(fi2.Name())
}()
fi3, err := os.CreateTemp("", "coder-logging-test-*")
require.NoError(t, err)
defer func() {
_ = os.Remove(fi3.Name())
}()
root, _ := clitest.New(t,
"server",
"--verbose",
"--in-memory",
"--http-address", ":0",
"--access-url", "http://example.com",
"--log-human", fi1.Name(),
"--log-json", fi2.Name(),
"--log-stackdriver", fi3.Name(),
)
serverErr := make(chan error, 1)
go func() {
serverErr <- root.ExecuteContext(ctx)
}()
assert.Eventually(t, func() bool {
stat, err := os.Stat(fi1.Name())
return err == nil && stat.Size() > 0
}, testutil.WaitLong, testutil.IntervalMedium)
assert.Eventually(t, func() bool {
stat, err := os.Stat(fi2.Name())
return err == nil && stat.Size() > 0
}, testutil.WaitLong, testutil.IntervalMedium)
assert.Eventually(t, func() bool {
stat, err := os.Stat(fi3.Name())
return err == nil && stat.Size() > 0
}, testutil.WaitLong, testutil.IntervalMedium)
cancelFunc()
<-serverErr
})
})
}
func generateTLSCertificate(t testing.TB, commonName ...string) (certPath, keyPath string) {
+9
View File
@@ -70,6 +70,15 @@ Flags:
disable the HTTP endpoint.
Consumes $CODER_HTTP_ADDRESS (default
"127.0.0.1:3000")
--log-human string Output human-readable logs to a given
file.
Consumes $CODER_LOGGING_HUMAN (default
"/dev/stderr")
--log-json string Output JSON logs to a given file.
Consumes $CODER_LOGGING_JSON
--log-stackdriver string Output Stackdriver compatible logs to a
given file.
Consumes $CODER_LOGGING_STACKDRIVER
--max-token-lifetime duration The maximum lifetime duration for any
user creating a token.
Consumes $CODER_MAX_TOKEN_LIFETIME