-
Notifications
You must be signed in to change notification settings - Fork 476
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Post launch Log level control for the Server (#4880)
Signed-off-by: Edwin Buck <edwbuck@gmail.com>
- Loading branch information
Showing
28 changed files
with
1,969 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package logger | ||
|
||
import ( | ||
"context" | ||
"flag" | ||
"fmt" | ||
|
||
"github.com/mitchellh/cli" | ||
api "github.com/spiffe/spire-api-sdk/proto/spire/api/server/logger/v1" | ||
"github.com/spiffe/spire/cmd/spire-server/util" | ||
commoncli "github.com/spiffe/spire/pkg/common/cli" | ||
"github.com/spiffe/spire/pkg/common/cliprinter" | ||
) | ||
|
||
type getCommand struct { | ||
env *commoncli.Env | ||
printer cliprinter.Printer | ||
} | ||
|
||
// Returns a cli.command that gets the logger information using | ||
// the default cli environment. | ||
func NewGetCommand() cli.Command { | ||
return NewGetCommandWithEnv(commoncli.DefaultEnv) | ||
} | ||
|
||
// Returns a cli.command that gets the root logger information. | ||
func NewGetCommandWithEnv(env *commoncli.Env) cli.Command { | ||
return util.AdaptCommand(env, &getCommand{env: env}) | ||
} | ||
|
||
// The name of the command. | ||
func (*getCommand) Name() string { | ||
return "logger get" | ||
} | ||
|
||
// The help presented description of the command. | ||
func (*getCommand) Synopsis() string { | ||
return "Gets the logger details" | ||
} | ||
|
||
// Adds additional flags specific to the command. | ||
func (c *getCommand) AppendFlags(fs *flag.FlagSet) { | ||
cliprinter.AppendFlagWithCustomPretty(&c.printer, fs, c.env, c.prettyPrintLogger) | ||
} | ||
|
||
// The routine that executes the command | ||
func (c *getCommand) Run(ctx context.Context, _ *commoncli.Env, serverClient util.ServerClient) error { | ||
logger, err := serverClient.NewLoggerClient().GetLogger(ctx, &api.GetLoggerRequest{}) | ||
if err != nil { | ||
return fmt.Errorf("error fetching logger: %w", err) | ||
} | ||
|
||
return c.printer.PrintProto(logger) | ||
} | ||
|
||
// Formatting for the logger under pretty printing of output. | ||
func (c *getCommand) prettyPrintLogger(env *commoncli.Env, results ...any) error { | ||
return PrettyPrintLogger(env, results...) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
//go:build !windows | ||
|
||
package logger_test | ||
|
||
var ( | ||
getUsage = `Usage of logger get: | ||
-output value | ||
Desired output format (pretty, json); default: pretty. | ||
-socketPath string | ||
Path to the SPIRE Server API socket (default "/tmp/spire-server/private/api.sock") | ||
` | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
package logger_test | ||
|
||
import ( | ||
"errors" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/require" | ||
|
||
"github.com/spiffe/spire-api-sdk/proto/spire/api/types" | ||
"github.com/spiffe/spire/cmd/spire-server/cli/logger" | ||
) | ||
|
||
func TestGetHelp(t *testing.T) { | ||
test := setupCliTest(t, nil, logger.NewGetCommandWithEnv) | ||
test.client.Help() | ||
require.Equal(t, "", test.stdout.String()) | ||
require.Equal(t, getUsage, test.stderr.String()) | ||
} | ||
|
||
func TestGetSynopsis(t *testing.T) { | ||
cmd := logger.NewGetCommand() | ||
require.Equal(t, "Gets the logger details", cmd.Synopsis()) | ||
} | ||
|
||
func TestGet(t *testing.T) { | ||
for _, tt := range []struct { | ||
name string | ||
// server state | ||
server *mockLoggerServer | ||
// input | ||
args []string | ||
// expected items | ||
expectReturnCode int | ||
expectStdout string | ||
expectStderr string | ||
}{ | ||
{ | ||
name: "configured to info, set to info, using pretty output", | ||
args: []string{"-output", "pretty"}, | ||
server: &mockLoggerServer{ | ||
returnLogger: &types.Logger{ | ||
CurrentLevel: types.LogLevel_INFO, | ||
LaunchLevel: types.LogLevel_INFO, | ||
}, | ||
}, | ||
expectReturnCode: 0, | ||
expectStdout: `Logger Level : info | ||
Launch Level : info | ||
`, | ||
}, | ||
{ | ||
name: "configured to debug, set to warn, using pretty output", | ||
args: []string{"-output", "pretty"}, | ||
server: &mockLoggerServer{ | ||
returnLogger: &types.Logger{ | ||
CurrentLevel: types.LogLevel_WARN, | ||
LaunchLevel: types.LogLevel_DEBUG, | ||
}, | ||
}, | ||
expectReturnCode: 0, | ||
expectStdout: `Logger Level : warning | ||
Launch Level : debug | ||
`, | ||
}, | ||
{ | ||
name: "configured to error, set to trace, using pretty output", | ||
args: []string{"-output", "pretty"}, | ||
server: &mockLoggerServer{ | ||
returnLogger: &types.Logger{ | ||
CurrentLevel: types.LogLevel_TRACE, | ||
LaunchLevel: types.LogLevel_ERROR, | ||
}, | ||
}, | ||
expectReturnCode: 0, | ||
expectStdout: `Logger Level : trace | ||
Launch Level : error | ||
`, | ||
}, | ||
{ | ||
name: "configured to panic, set to fatal, using pretty output", | ||
args: []string{"-output", "pretty"}, | ||
server: &mockLoggerServer{ | ||
returnLogger: &types.Logger{ | ||
CurrentLevel: types.LogLevel_FATAL, | ||
LaunchLevel: types.LogLevel_PANIC, | ||
}, | ||
}, | ||
expectReturnCode: 0, | ||
expectStdout: `Logger Level : fatal | ||
Launch Level : panic | ||
`, | ||
}, | ||
{ | ||
name: "configured to info, set to info, using json output", | ||
args: []string{"-output", "json"}, | ||
server: &mockLoggerServer{ | ||
returnLogger: &types.Logger{ | ||
CurrentLevel: types.LogLevel_INFO, | ||
LaunchLevel: types.LogLevel_INFO, | ||
}, | ||
}, | ||
expectReturnCode: 0, | ||
expectStdout: `{"current_level":"INFO","launch_level":"INFO"} | ||
`, | ||
}, | ||
{ | ||
name: "configured to debug, set to warn, using json output", | ||
args: []string{"-output", "json"}, | ||
server: &mockLoggerServer{ | ||
returnLogger: &types.Logger{ | ||
CurrentLevel: types.LogLevel_WARN, | ||
LaunchLevel: types.LogLevel_DEBUG, | ||
}, | ||
}, | ||
expectReturnCode: 0, | ||
expectStdout: `{"current_level":"WARN","launch_level":"DEBUG"} | ||
`, | ||
}, | ||
{ | ||
name: "configured to error, set to trace, using json output", | ||
args: []string{"-output", "json"}, | ||
server: &mockLoggerServer{ | ||
returnLogger: &types.Logger{ | ||
CurrentLevel: types.LogLevel_TRACE, | ||
LaunchLevel: types.LogLevel_ERROR, | ||
}, | ||
}, | ||
expectReturnCode: 0, | ||
expectStdout: `{"current_level":"TRACE","launch_level":"ERROR"} | ||
`, | ||
}, | ||
{ | ||
name: "configured to panic, set to fatal, using json output", | ||
args: []string{"-output", "json"}, | ||
server: &mockLoggerServer{ | ||
returnLogger: &types.Logger{ | ||
CurrentLevel: types.LogLevel_FATAL, | ||
LaunchLevel: types.LogLevel_PANIC, | ||
}, | ||
}, | ||
expectReturnCode: 0, | ||
expectStdout: `{"current_level":"FATAL","launch_level":"PANIC"} | ||
`, | ||
}, | ||
{ | ||
name: "configured to info, set to info, server will error", | ||
args: []string{"-output", "pretty"}, | ||
server: &mockLoggerServer{ | ||
returnErr: errors.New("server is unavailable"), | ||
}, | ||
expectReturnCode: 1, | ||
expectStderr: `Error: error fetching logger: rpc error: code = Unknown desc = server is unavailable | ||
`, | ||
}, | ||
{ | ||
name: "bizzarro world, returns neither logger nor error", | ||
args: []string{"-output", "pretty"}, | ||
server: &mockLoggerServer{ | ||
returnLogger: nil, | ||
}, | ||
expectReturnCode: 1, | ||
expectStderr: `Error: internal error: returned current log level is undefined; please report this as a bug | ||
`, | ||
}, | ||
} { | ||
t.Run(tt.name, func(t *testing.T) { | ||
test := setupCliTest(t, tt.server, logger.NewGetCommandWithEnv) | ||
returnCode := test.client.Run(append(test.args, tt.args...)) | ||
require.Equal(t, tt.expectStdout, test.stdout.String()) | ||
require.Equal(t, tt.expectStderr, test.stderr.String()) | ||
require.Equal(t, tt.expectReturnCode, returnCode) | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
//go:build windows | ||
|
||
package logger_test | ||
|
||
var ( | ||
getUsage = `Usage of logger get: | ||
-namedPipeName string | ||
Pipe name of the SPIRE Server API named pipe (default "\\spire-server\\private\\api") | ||
-output value | ||
Desired output format (pretty, json); default: pretty. | ||
` | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package logger_test | ||
|
||
import ( | ||
"io" | ||
"testing" | ||
|
||
"github.com/spiffe/spire/test/spiretest" | ||
|
||
"bytes" | ||
"context" | ||
|
||
"github.com/mitchellh/cli" | ||
loggerv1 "github.com/spiffe/spire-api-sdk/proto/spire/api/server/logger/v1" | ||
"github.com/spiffe/spire-api-sdk/proto/spire/api/types" | ||
"github.com/spiffe/spire/cmd/spire-server/cli/common" | ||
commoncli "github.com/spiffe/spire/pkg/common/cli" | ||
"google.golang.org/grpc" | ||
) | ||
|
||
// an input/output capture struct | ||
type loggerTest struct { | ||
stdin *bytes.Buffer | ||
stdout *bytes.Buffer | ||
stderr *bytes.Buffer | ||
args []string | ||
server *mockLoggerServer | ||
client cli.Command | ||
} | ||
|
||
// serialization of capture | ||
func (l *loggerTest) afterTest(t *testing.T) { | ||
t.Logf("TEST:%s", t.Name()) | ||
t.Logf("STDOUT:\n%s", l.stdout.String()) | ||
t.Logf("STDIN:\n%s", l.stdin.String()) | ||
t.Logf("STDERR:\n%s", l.stderr.String()) | ||
} | ||
|
||
// setup of input/output capture | ||
func setupCliTest(t *testing.T, server *mockLoggerServer, newClient func(*commoncli.Env) cli.Command) *loggerTest { | ||
addr := spiretest.StartGRPCServer(t, func(s *grpc.Server) { | ||
loggerv1.RegisterLoggerServer(s, server) | ||
}) | ||
|
||
stdin := new(bytes.Buffer) | ||
stdout := new(bytes.Buffer) | ||
stderr := new(bytes.Buffer) | ||
|
||
client := newClient(&commoncli.Env{ | ||
Stdin: stdin, | ||
Stdout: stdout, | ||
Stderr: stderr, | ||
}) | ||
|
||
test := &loggerTest{ | ||
stdin: stdin, | ||
stdout: stdout, | ||
stderr: stderr, | ||
args: []string{common.AddrArg, common.GetAddr(addr)}, | ||
server: server, | ||
client: client, | ||
} | ||
|
||
t.Cleanup(func() { | ||
test.afterTest(t) | ||
}) | ||
|
||
return test | ||
} | ||
|
||
// a mock grpc logger server | ||
type mockLoggerServer struct { | ||
loggerv1.UnimplementedLoggerServer | ||
|
||
receivedSetValue *types.LogLevel | ||
returnLogger *types.Logger | ||
returnErr error | ||
} | ||
|
||
// mock implementation for GetLogger | ||
func (s *mockLoggerServer) GetLogger(_ context.Context, _ *loggerv1.GetLoggerRequest) (*types.Logger, error) { | ||
return s.returnLogger, s.returnErr | ||
} | ||
|
||
func (s *mockLoggerServer) SetLogLevel(_ context.Context, req *loggerv1.SetLogLevelRequest) (*types.Logger, error) { | ||
s.receivedSetValue = &req.NewLevel | ||
return s.returnLogger, s.returnErr | ||
} | ||
|
||
func (s *mockLoggerServer) ResetLogLevel(_ context.Context, _ *loggerv1.ResetLogLevelRequest) (*types.Logger, error) { | ||
s.receivedSetValue = nil | ||
return s.returnLogger, s.returnErr | ||
} | ||
|
||
var _ io.Writer = &errorWriter{} | ||
|
||
type errorWriter struct { | ||
ReturnError error | ||
Buffer bytes.Buffer | ||
} | ||
|
||
func (e *errorWriter) Write(p []byte) (n int, err error) { | ||
if e.ReturnError != nil { | ||
return 0, e.ReturnError | ||
} | ||
return e.Buffer.Write(p) | ||
} | ||
|
||
func (e *errorWriter) String() string { | ||
return e.Buffer.String() | ||
} |
Oops, something went wrong.