Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit dcab873

Browse files
authored
feat: add stackdriver and json log options to coder server (#5682)
1 parent 1229fda commit dcab873

File tree

12 files changed

+461
-7
lines changed

12 files changed

+461
-7
lines changed

cli/deployment/config.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -471,6 +471,26 @@ func newConfig() *codersdk.DeploymentConfig {
471471
Default: false,
472472
},
473473
},
474+
Logging: &codersdk.LoggingConfig{
475+
Human: &codersdk.DeploymentConfigField[string]{
476+
Name: "Human Log Location",
477+
Usage: "Output human-readable logs to a given file.",
478+
Flag: "log-human",
479+
Default: "/dev/stderr",
480+
},
481+
JSON: &codersdk.DeploymentConfigField[string]{
482+
Name: "JSON Log Location",
483+
Usage: "Output JSON logs to a given file.",
484+
Flag: "log-json",
485+
Default: "",
486+
},
487+
Stackdriver: &codersdk.DeploymentConfigField[string]{
488+
Name: "Stackdriver Log Location",
489+
Usage: "Output Stackdriver compatible logs to a given file.",
490+
Flag: "log-stackdriver",
491+
Default: "",
492+
},
493+
},
474494
}
475495
}
476496

cli/server.go

Lines changed: 72 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ import (
4646

4747
"cdr.dev/slog"
4848
"cdr.dev/slog/sloggers/sloghuman"
49+
"cdr.dev/slog/sloggers/slogjson"
50+
"cdr.dev/slog/sloggers/slogstackdriver"
4951
"github.com/coder/coder/buildinfo"
5052
"github.com/coder/coder/cli/cliui"
5153
"github.com/coder/coder/cli/config"
@@ -122,13 +124,11 @@ func Server(vip *viper.Viper, newAPI func(context.Context, *coderd.Options) (*co
122124
}
123125

124126
printLogo(cmd)
125-
logger := slog.Make(sloghuman.Sink(cmd.ErrOrStderr()))
126-
if ok, _ := cmd.Flags().GetBool(varVerbose); ok {
127-
logger = logger.Leveled(slog.LevelDebug)
128-
}
129-
if cfg.Trace.CaptureLogs.Value {
130-
logger = logger.AppendSinks(tracing.SlogSink{})
127+
logger, logCloser, err := buildLogger(cmd, cfg)
128+
if err != nil {
129+
return xerrors.Errorf("make logger: %w", err)
131130
}
131+
defer logCloser()
132132

133133
// Register signals early on so that graceful shutdown can't
134134
// be interrupted by additional signals. Note that we avoid
@@ -1145,6 +1145,11 @@ func newProvisionerDaemon(
11451145

11461146
// nolint: revive
11471147
func printLogo(cmd *cobra.Command) {
1148+
// Only print the logo in TTYs.
1149+
if !isTTYOut(cmd) {
1150+
return
1151+
}
1152+
11481153
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "%s - Software development on your infrastucture\n", cliui.Styles.Bold.Render("Coder "+buildinfo.Version()))
11491154
}
11501155

@@ -1512,3 +1517,64 @@ func redirectHTTPToAccessURL(handler http.Handler, accessURL *url.URL) http.Hand
15121517
func isLocalhost(host string) bool {
15131518
return host == "localhost" || host == "127.0.0.1" || host == "::1"
15141519
}
1520+
1521+
func buildLogger(cmd *cobra.Command, cfg *codersdk.DeploymentConfig) (slog.Logger, func(), error) {
1522+
var (
1523+
sinks = []slog.Sink{}
1524+
closers = []func() error{}
1525+
)
1526+
1527+
addSinkIfProvided := func(sinkFn func(io.Writer) slog.Sink, loc string) error {
1528+
switch loc {
1529+
case "":
1530+
1531+
case "/dev/stdout":
1532+
sinks = append(sinks, sinkFn(cmd.OutOrStdout()))
1533+
1534+
case "/dev/stderr":
1535+
sinks = append(sinks, sinkFn(cmd.ErrOrStderr()))
1536+
1537+
default:
1538+
fi, err := os.OpenFile(loc, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0644)
1539+
if err != nil {
1540+
return xerrors.Errorf("open log file %q: %w", loc, err)
1541+
}
1542+
1543+
closers = append(closers, fi.Close)
1544+
sinks = append(sinks, sinkFn(fi))
1545+
}
1546+
return nil
1547+
}
1548+
1549+
err := addSinkIfProvided(sloghuman.Sink, cfg.Logging.Human.Value)
1550+
if err != nil {
1551+
return slog.Logger{}, nil, xerrors.Errorf("add human sink: %w", err)
1552+
}
1553+
err = addSinkIfProvided(slogjson.Sink, cfg.Logging.JSON.Value)
1554+
if err != nil {
1555+
return slog.Logger{}, nil, xerrors.Errorf("add json sink: %w", err)
1556+
}
1557+
err = addSinkIfProvided(slogstackdriver.Sink, cfg.Logging.Stackdriver.Value)
1558+
if err != nil {
1559+
return slog.Logger{}, nil, xerrors.Errorf("add stackdriver sink: %w", err)
1560+
}
1561+
1562+
if cfg.Trace.CaptureLogs.Value {
1563+
sinks = append(sinks, tracing.SlogSink{})
1564+
}
1565+
1566+
level := slog.LevelInfo
1567+
if ok, _ := cmd.Flags().GetBool(varVerbose); ok {
1568+
level = slog.LevelDebug
1569+
}
1570+
1571+
if len(sinks) == 0 {
1572+
return slog.Logger{}, nil, xerrors.New("no loggers provided")
1573+
}
1574+
1575+
return slog.Make(sinks...).Leveled(level), func() {
1576+
for _, closer := range closers {
1577+
_ = closer()
1578+
}
1579+
}, nil
1580+
}

cli/server_test.go

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import (
3535
"github.com/coder/coder/coderd/database/postgres"
3636
"github.com/coder/coder/coderd/telemetry"
3737
"github.com/coder/coder/codersdk"
38+
"github.com/coder/coder/cryptorand"
3839
"github.com/coder/coder/pty/ptytest"
3940
"github.com/coder/coder/testutil"
4041
)
@@ -1122,6 +1123,194 @@ func TestServer(t *testing.T) {
11221123
<-serverErr
11231124
})
11241125
})
1126+
1127+
t.Run("Logging", func(t *testing.T) {
1128+
t.Parallel()
1129+
1130+
t.Run("CreatesFile", func(t *testing.T) {
1131+
t.Parallel()
1132+
ctx, cancelFunc := context.WithCancel(context.Background())
1133+
defer cancelFunc()
1134+
1135+
random, err := cryptorand.String(5)
1136+
require.NoError(t, err)
1137+
fiName := fmt.Sprint(os.TempDir(), "/coder-logging-test-", random)
1138+
defer func() {
1139+
_ = os.Remove(fiName)
1140+
}()
1141+
1142+
root, _ := clitest.New(t,
1143+
"server",
1144+
"--verbose",
1145+
"--in-memory",
1146+
"--http-address", ":0",
1147+
"--access-url", "http://example.com",
1148+
"--log-human", fiName,
1149+
)
1150+
serverErr := make(chan error, 1)
1151+
go func() {
1152+
serverErr <- root.ExecuteContext(ctx)
1153+
}()
1154+
1155+
assert.Eventually(t, func() bool {
1156+
stat, err := os.Stat(fiName)
1157+
return err == nil && stat.Size() > 0
1158+
}, testutil.WaitShort, testutil.IntervalFast)
1159+
cancelFunc()
1160+
<-serverErr
1161+
})
1162+
1163+
t.Run("Human", func(t *testing.T) {
1164+
t.Parallel()
1165+
ctx, cancelFunc := context.WithCancel(context.Background())
1166+
defer cancelFunc()
1167+
1168+
fi, err := os.CreateTemp("", "coder-logging-test-*")
1169+
require.NoError(t, err)
1170+
defer func() {
1171+
_ = os.Remove(fi.Name())
1172+
}()
1173+
1174+
root, _ := clitest.New(t,
1175+
"server",
1176+
"--verbose",
1177+
"--in-memory",
1178+
"--http-address", ":0",
1179+
"--access-url", "http://example.com",
1180+
"--log-human", fi.Name(),
1181+
)
1182+
serverErr := make(chan error, 1)
1183+
go func() {
1184+
serverErr <- root.ExecuteContext(ctx)
1185+
}()
1186+
1187+
assert.Eventually(t, func() bool {
1188+
stat, err := os.Stat(fi.Name())
1189+
return err == nil && stat.Size() > 0
1190+
}, testutil.WaitShort, testutil.IntervalFast)
1191+
cancelFunc()
1192+
<-serverErr
1193+
})
1194+
1195+
t.Run("JSON", func(t *testing.T) {
1196+
t.Parallel()
1197+
ctx, cancelFunc := context.WithCancel(context.Background())
1198+
defer cancelFunc()
1199+
1200+
fi, err := os.CreateTemp("", "coder-logging-test-*")
1201+
require.NoError(t, err)
1202+
defer func() {
1203+
_ = os.Remove(fi.Name())
1204+
}()
1205+
1206+
root, _ := clitest.New(t,
1207+
"server",
1208+
"--verbose",
1209+
"--in-memory",
1210+
"--http-address", ":0",
1211+
"--access-url", "http://example.com",
1212+
"--log-json", fi.Name(),
1213+
)
1214+
serverErr := make(chan error, 1)
1215+
go func() {
1216+
serverErr <- root.ExecuteContext(ctx)
1217+
}()
1218+
1219+
assert.Eventually(t, func() bool {
1220+
stat, err := os.Stat(fi.Name())
1221+
return err == nil && stat.Size() > 0
1222+
}, testutil.WaitShort, testutil.IntervalFast)
1223+
cancelFunc()
1224+
<-serverErr
1225+
})
1226+
1227+
t.Run("Stackdriver", func(t *testing.T) {
1228+
t.Parallel()
1229+
ctx, cancelFunc := context.WithCancel(context.Background())
1230+
defer cancelFunc()
1231+
1232+
fi, err := os.CreateTemp("", "coder-logging-test-*")
1233+
require.NoError(t, err)
1234+
defer func() {
1235+
_ = os.Remove(fi.Name())
1236+
}()
1237+
1238+
root, _ := clitest.New(t,
1239+
"server",
1240+
"--verbose",
1241+
"--in-memory",
1242+
"--http-address", ":0",
1243+
"--access-url", "http://example.com",
1244+
"--log-stackdriver", fi.Name(),
1245+
)
1246+
serverErr := make(chan error, 1)
1247+
go func() {
1248+
serverErr <- root.ExecuteContext(ctx)
1249+
}()
1250+
1251+
assert.Eventually(t, func() bool {
1252+
stat, err := os.Stat(fi.Name())
1253+
return err == nil && stat.Size() > 0
1254+
}, testutil.WaitLong, testutil.IntervalMedium)
1255+
cancelFunc()
1256+
<-serverErr
1257+
})
1258+
1259+
t.Run("Multiple", func(t *testing.T) {
1260+
t.Parallel()
1261+
ctx, cancelFunc := context.WithCancel(context.Background())
1262+
defer cancelFunc()
1263+
1264+
fi1, err := os.CreateTemp("", "coder-logging-test-*")
1265+
require.NoError(t, err)
1266+
defer func() {
1267+
_ = os.Remove(fi1.Name())
1268+
}()
1269+
1270+
fi2, err := os.CreateTemp("", "coder-logging-test-*")
1271+
require.NoError(t, err)
1272+
defer func() {
1273+
_ = os.Remove(fi2.Name())
1274+
}()
1275+
1276+
fi3, err := os.CreateTemp("", "coder-logging-test-*")
1277+
require.NoError(t, err)
1278+
defer func() {
1279+
_ = os.Remove(fi3.Name())
1280+
}()
1281+
1282+
root, _ := clitest.New(t,
1283+
"server",
1284+
"--verbose",
1285+
"--in-memory",
1286+
"--http-address", ":0",
1287+
"--access-url", "http://example.com",
1288+
"--log-human", fi1.Name(),
1289+
"--log-json", fi2.Name(),
1290+
"--log-stackdriver", fi3.Name(),
1291+
)
1292+
serverErr := make(chan error, 1)
1293+
go func() {
1294+
serverErr <- root.ExecuteContext(ctx)
1295+
}()
1296+
1297+
assert.Eventually(t, func() bool {
1298+
stat, err := os.Stat(fi1.Name())
1299+
return err == nil && stat.Size() > 0
1300+
}, testutil.WaitLong, testutil.IntervalMedium)
1301+
assert.Eventually(t, func() bool {
1302+
stat, err := os.Stat(fi2.Name())
1303+
return err == nil && stat.Size() > 0
1304+
}, testutil.WaitLong, testutil.IntervalMedium)
1305+
assert.Eventually(t, func() bool {
1306+
stat, err := os.Stat(fi3.Name())
1307+
return err == nil && stat.Size() > 0
1308+
}, testutil.WaitLong, testutil.IntervalMedium)
1309+
1310+
cancelFunc()
1311+
<-serverErr
1312+
})
1313+
})
11251314
}
11261315

11271316
func generateTLSCertificate(t testing.TB, commonName ...string) (certPath, keyPath string) {

cli/testdata/coder_server_--help.golden

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,15 @@ Flags:
7070
disable the HTTP endpoint.
7171
Consumes $CODER_HTTP_ADDRESS (default
7272
"127.0.0.1:3000")
73+
--log-human string Output human-readable logs to a given
74+
file.
75+
Consumes $CODER_LOGGING_HUMAN (default
76+
"/dev/stderr")
77+
--log-json string Output JSON logs to a given file.
78+
Consumes $CODER_LOGGING_JSON
79+
--log-stackdriver string Output Stackdriver compatible logs to a
80+
given file.
81+
Consumes $CODER_LOGGING_STACKDRIVER
7382
--max-token-lifetime duration The maximum lifetime duration for any
7483
user creating a token.
7584
Consumes $CODER_MAX_TOKEN_LIFETIME

coderd/apidoc/docs.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5362,6 +5362,9 @@ const docTemplate = `{
53625362
"in_memory_database": {
53635363
"$ref": "#/definitions/codersdk.DeploymentConfigField-bool"
53645364
},
5365+
"logging": {
5366+
"$ref": "#/definitions/codersdk.LoggingConfig"
5367+
},
53655368
"max_token_lifetime": {
53665369
"$ref": "#/definitions/codersdk.DeploymentConfigField-time_Duration"
53675370
},
@@ -5881,6 +5884,20 @@ const docTemplate = `{
58815884
"LogSourceProvisioner"
58825885
]
58835886
},
5887+
"codersdk.LoggingConfig": {
5888+
"type": "object",
5889+
"properties": {
5890+
"human": {
5891+
"$ref": "#/definitions/codersdk.DeploymentConfigField-string"
5892+
},
5893+
"json": {
5894+
"$ref": "#/definitions/codersdk.DeploymentConfigField-string"
5895+
},
5896+
"stackdriver": {
5897+
"$ref": "#/definitions/codersdk.DeploymentConfigField-string"
5898+
}
5899+
}
5900+
},
58845901
"codersdk.LoginType": {
58855902
"type": "string",
58865903
"enum": [

0 commit comments

Comments
 (0)