-
Notifications
You must be signed in to change notification settings - Fork 207
Cvd logs subcommand #2498
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Cvd logs subcommand #2498
Changes from all commits
4ee4cc2
d7a8efb
d288ca4
1decfea
905829d
1177b9c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| #include "cuttlefish/host/commands/cvd/cli/commands/logs.h" | ||
|
|
||
| #include <algorithm> | ||
| #include <iostream> | ||
| #include <memory> | ||
| #include <string> | ||
| #include <vector> | ||
|
|
||
| #include <android-base/file.h> | ||
|
|
||
| #include "cuttlefish/common/libs/utils/files.h" | ||
| #include "cuttlefish/common/libs/utils/flag_parser.h" | ||
| #include "cuttlefish/common/libs/utils/subprocess.h" | ||
| #include "cuttlefish/host/commands/cvd/cli/command_request.h" | ||
| #include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h" | ||
| #include "cuttlefish/host/commands/cvd/cli/selector/selector.h" | ||
| #include "cuttlefish/host/commands/cvd/cli/types.h" | ||
| #include "cuttlefish/host/commands/cvd/instances/instance_manager.h" | ||
| #include "cuttlefish/result/result.h" | ||
|
|
||
| namespace cuttlefish { | ||
| namespace { | ||
|
|
||
| constexpr char kSummaryHelpText[] = "Print device logs"; | ||
|
|
||
| constexpr char kDetailedHelpText[] = R"( | ||
| usage: cvd [--group_name name [--instance_name name]] logs [-p|--print name] | ||
| )"; | ||
|
|
||
| struct LogsCmdOptions { | ||
| std::string print_target; | ||
|
|
||
| std::vector<Flag> Flags() { | ||
| return { | ||
| GflagsCompatFlag("print", print_target), | ||
| GflagsCompatFlag("p", print_target), | ||
| }; | ||
| } | ||
| }; | ||
|
|
||
| Result<void> PrintLogsList(const std::string& dir) { | ||
| auto callback = [](const std::string& filename) -> Result<void> { | ||
| constexpr int kMaxPadding = 30; | ||
| std::string basename = android::base::Basename(filename); | ||
| std::cout << basename; | ||
| std::cout << std::string(std::max(int(kMaxPadding - basename.length()), 1), ' '); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: While the padding might make it look nice for a human it will complicate using this in a script. For example,
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Assign that expression to a variable to make it more clear what it is. |
||
| std::cout << filename; | ||
| std::cout << std::endl; | ||
| return {}; | ||
| }; | ||
| return WalkDirectory(dir, callback); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
| } | ||
|
|
||
| Result<void> PrintLog(const std::string& filename) { | ||
| Command cat("cat"); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This adds an implicit dependency between If you still think calling |
||
| cat.AddParameter(filename); | ||
| int exit_code = cat.Start().Wait(); | ||
| CF_EXPECTF(exit_code == 0, "{} {} failed: exited with status {}", | ||
| cat.Executable(), filename, exit_code); | ||
| return {}; | ||
|
Comment on lines
+55
to
+60
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One of the suggestions in https://clig.dev/#output is
|
||
| } | ||
|
|
||
| class CvdLogsHandler : public CvdCommandHandler { | ||
| public: | ||
| CvdLogsHandler(InstanceManager& instance_manager) | ||
| : instance_manager_(instance_manager) {} | ||
|
|
||
| Result<void> Handle(const CommandRequest& request) override { | ||
| CF_EXPECT(CanHandle(request)); | ||
|
|
||
| auto [instance, _] = | ||
| CF_EXPECT(selector::SelectInstance(instance_manager_, request), | ||
| "Unable to select an instance"); | ||
|
|
||
| LogsCmdOptions opts; | ||
| std::vector<std::string> args = request.SubcommandArguments(); | ||
| CF_EXPECT(ConsumeFlags(opts.Flags(), args)); | ||
|
|
||
| std::string dir = instance.instance_dir(); | ||
| std::string logs_dir = dir + "/logs"; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Some logs in the instance dir are symlinks to the assembly dir that may not be created if the instance fails to boot. This code should avoid those symlinks and go for the actual files instead. There is also fetch.log in the artifacts dir.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. I noticed the following log names: assemble_cvd.log and cuttlefish_config.json and fetch.log are symlinks in the instance directory. Created b/509586405 to handle this in a different PR. |
||
| CF_EXPECT(FileExists(logs_dir)); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It should print a line informing the user that there are no logs available when no log files are found. If |
||
| CF_EXPECT(IsDirectory(logs_dir)); | ||
|
|
||
| if (opts.print_target.empty()) { | ||
| return PrintLogsList(logs_dir); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: otherwise |
||
| } | ||
|
|
||
| std::string print_target = logs_dir + "/" + opts.print_target; | ||
| CF_EXPECT(FileExists(print_target)); | ||
| return PrintLog(print_target); | ||
| } | ||
|
|
||
| cvd_common::Args CmdList() const override { return {"logs"}; } | ||
|
|
||
| Result<std::string> SummaryHelp() const override { return kSummaryHelpText; } | ||
|
|
||
| bool ShouldInterceptHelp() const override { return true; } | ||
|
|
||
| bool RequiresDeviceExists() const override { return true; } | ||
|
|
||
| Result<std::string> DetailedHelp(std::vector<std::string>&) const override { | ||
| return kDetailedHelpText; | ||
| } | ||
|
|
||
| private: | ||
| InstanceManager& instance_manager_; | ||
| }; | ||
|
|
||
| } // namespace | ||
|
|
||
| std::unique_ptr<CvdCommandHandler> NewCvdLogsHandler( | ||
| InstanceManager& instance_manager) { | ||
| return std::unique_ptr<CvdCommandHandler>( | ||
| new CvdLogsHandler(instance_manager)); | ||
| } | ||
|
|
||
| } // namespace cuttlefish | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| #pragma once | ||
|
|
||
| #include <memory> | ||
|
|
||
| #include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h" | ||
| #include "cuttlefish/host/commands/cvd/instances/instance_manager.h" | ||
|
|
||
| namespace cuttlefish { | ||
|
|
||
| std::unique_ptr<CvdCommandHandler> NewCvdLogsHandler( | ||
| InstanceManager& instance_manager); | ||
|
|
||
| } // namespace cuttlefish |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -79,13 +79,15 @@ type TestContext struct { | |
| usePodcvd bool | ||
| } | ||
|
|
||
| func runCmdWithContextEnv(ctx context.Context, command []string, envvars map[string]string) (string, error) { | ||
| func runCmdWithContextEnv(ctx context.Context, command []string, envvars map[string]string) (string, string, error) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe worth returning a structure with fields for stdout and stderr so code isn't reliant on the caller knowing what each string means? It could be reused for |
||
| cmd := exec.CommandContext(ctx, command[0], command[1:]...) | ||
|
|
||
| cmdOutputBuf := bytes.Buffer{} | ||
| cmdWriter := io.MultiWriter(&cmdOutputBuf, log.Writer()) | ||
| cmd.Stdout = cmdWriter | ||
| cmd.Stderr = cmdWriter | ||
| stdOutBuf := bytes.Buffer{} | ||
| stdErrBuf := bytes.Buffer{} | ||
| stdOutWriter := io.MultiWriter(&stdOutBuf, log.Writer()) | ||
| stdErrWriter := io.MultiWriter(&stdErrBuf, log.Writer()) | ||
| cmd.Stdout = stdOutWriter | ||
| cmd.Stderr = stdErrWriter | ||
|
|
||
| envvarPairs := []string{} | ||
| for k, v := range envvars { | ||
|
|
@@ -97,14 +99,14 @@ func runCmdWithContextEnv(ctx context.Context, command []string, envvars map[str | |
| log.Printf("Running `%s %s`\n", strings.Join(envvarPairs, " "), strings.Join(command, " ")) | ||
| err := cmd.Run() | ||
| if err != nil { | ||
| return "", fmt.Errorf("`%s` failed: %w", strings.Join(command, " "), err) | ||
| return "", "", fmt.Errorf("`%s` failed: %w", strings.Join(command, " "), err) | ||
| } | ||
|
|
||
| return cmdOutputBuf.String(), nil | ||
| return stdOutBuf.String(), stdErrBuf.String(), nil | ||
| } | ||
|
|
||
| // Runs the given command with the given set of envvars overrided. | ||
| func (tc *TestContext) RunCmdWithEnv(command []string, envvars map[string]string) (string, error) { | ||
| func (tc *TestContext) RunCmdWithEnv(command []string, envvars map[string]string) (string, string, error) { | ||
| return runCmdWithContextEnv(tc.context, command, envvars) | ||
| } | ||
|
|
||
|
|
@@ -117,14 +119,14 @@ func (tc *TestContext) RunAdbWaitForDevice() error { | |
| "adb", | ||
| "wait-for-device", | ||
| } | ||
| if _, err := tc.RunCmd(adbCommand...); err != nil { | ||
| if _, _, err := tc.RunCmd(adbCommand...); err != nil { | ||
| return fmt.Errorf("timed out waiting for Cuttlefish device to connect to adb: %w", err) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| // Runs the given command with the existing envvars. | ||
| func (tc *TestContext) RunCmd(args ...string) (string, error) { | ||
| func (tc *TestContext) RunCmd(args ...string) (string, string, error) { | ||
| command := []string{} | ||
| command = append(command, args...) | ||
| return tc.RunCmdWithEnv(command, map[string]string{}) | ||
|
|
@@ -174,7 +176,7 @@ func (tc *TestContext) CVDFetch(args FetchArgs) error { | |
| if credentialArg != "" { | ||
| fetchCmd = append(fetchCmd, fmt.Sprintf("--credential_source=%s", credentialArg)) | ||
| } | ||
| if _, err := tc.RunCmd(fetchCmd...); err != nil { | ||
| if _, _, err := tc.RunCmd(fetchCmd...); err != nil { | ||
| log.Printf("Failed to fetch: %w", err) | ||
| return err | ||
| } | ||
|
|
@@ -202,7 +204,7 @@ func (tc *TestContext) CVDCreate(args CreateArgs) error { | |
| if len(args.Args) > 0 { | ||
| createCmd = append(createCmd, args.Args...) | ||
| } | ||
| if _, err := tc.RunCmdWithEnv(createCmd, tempdirEnv); err != nil { | ||
| if _, _, err := tc.RunCmdWithEnv(createCmd, tempdirEnv); err != nil { | ||
| log.Printf("Failed to create instance(s): %w", err) | ||
| return err | ||
| } | ||
|
|
@@ -218,7 +220,7 @@ func (tc *TestContext) CVDStop() error { | |
| } | ||
|
|
||
| stopCmd := []string{tc.TargetBin(), "stop"} | ||
| if _, err := tc.RunCmdWithEnv(stopCmd, tempdirEnv); err != nil { | ||
| if _, _, err := tc.RunCmdWithEnv(stopCmd, tempdirEnv); err != nil { | ||
| log.Printf("Failed to stop instance(s): %w", err) | ||
| return err | ||
| } | ||
|
|
@@ -238,7 +240,7 @@ func (tc *TestContext) LaunchCVD(args CreateArgs) error { | |
| if len(args.Args) > 0 { | ||
| createCmd = append(createCmd, args.Args...) | ||
| } | ||
| if _, err := tc.RunCmdWithEnv(createCmd, tempdirEnv); err != nil { | ||
| if _, _, err := tc.RunCmdWithEnv(createCmd, tempdirEnv); err != nil { | ||
| log.Printf("Failed to create instance(s): %w", err) | ||
| return err | ||
| } | ||
|
|
@@ -254,7 +256,7 @@ func (tc *TestContext) StopCVD() error { | |
| } | ||
|
|
||
| stopCmd := []string{"bin/stop_cvd"} | ||
| if _, err := tc.RunCmdWithEnv(stopCmd, tempdirEnv); err != nil { | ||
| if _, _, err := tc.RunCmdWithEnv(stopCmd, tempdirEnv); err != nil { | ||
| log.Printf("Failed to stop instance(s): %w", err) | ||
| return err | ||
| } | ||
|
|
@@ -269,7 +271,7 @@ func (tc *TestContext) CVDPowerwash() error { | |
| } | ||
|
|
||
| createCmd := []string{tc.TargetBin(), "powerwash"} | ||
| if _, err := tc.RunCmdWithEnv(createCmd, tempdirEnv); err != nil { | ||
| if _, _, err := tc.RunCmdWithEnv(createCmd, tempdirEnv); err != nil { | ||
| log.Printf("Failed to powerwash instance(s): %w", err) | ||
| return err | ||
| } | ||
|
|
@@ -304,7 +306,7 @@ func (tc *TestContext) CVDLoad(load LoadArgs) error { | |
| if credentialArg != "" { | ||
| loadCmd = append(loadCmd, fmt.Sprintf("--credential_source=%s", credentialArg)) | ||
| } | ||
| if _, err := tc.RunCmd(loadCmd...); err != nil { | ||
| if _, _, err := tc.RunCmd(loadCmd...); err != nil { | ||
| log.Printf("Failed to perform `cvd load`: %w", err) | ||
| return err | ||
| } | ||
|
|
@@ -315,13 +317,13 @@ func (tc *TestContext) CVDLoad(load LoadArgs) error { | |
| } | ||
|
|
||
| func (tc *TestContext) GetMetricsDir() (string, error) { | ||
| output, err := tc.RunCmd("cvd", "fleet") | ||
| stdOut, _, err := tc.RunCmd("cvd", "fleet") | ||
| if err != nil { | ||
| return "", fmt.Errorf("failed to run `cvd fleet`") | ||
| } | ||
|
|
||
| re := regexp.MustCompile(`"metrics_dir" : "(.*)",`) | ||
| matches := re.FindStringSubmatch(output) | ||
| matches := re.FindStringSubmatch(stdOut) | ||
| if len(matches) != 2 { | ||
| return "", fmt.Errorf("failed to find metrics directory") | ||
| } | ||
|
|
@@ -344,7 +346,7 @@ func (tc *TestContext) SetUp(t *testing.T) { | |
| log.Printf("Initializing %s test...", tc.t.Name()) | ||
|
|
||
| log.Printf("Cleaning up any pre-existing instances...") | ||
| if _, err := tc.RunCmd(tc.TargetBin(), "reset", "-y"); err != nil { | ||
| if _, _, err := tc.RunCmd(tc.TargetBin(), "reset", "-y"); err != nil { | ||
| log.Printf("Failed to cleanup any pre-existing instances: %w", err) | ||
| } | ||
| log.Printf("Finished cleaning up any pre-existing instances!") | ||
|
|
@@ -426,7 +428,7 @@ func (tc *TestContext) TearDown() { | |
| matches, err := filepath.Glob(path.Join(tc.tempdir, pattern)) | ||
| if err == nil { | ||
| for _, file := range matches { | ||
| _, err := tc.RunCmd("cp", "--dereference", file, testoutdir) | ||
| _, _, err := tc.RunCmd("cp", "--dereference", file, testoutdir) | ||
| if err != nil { | ||
| log.Printf("failed to copy %s to %s: %w", file, testoutdir, err) | ||
| } | ||
|
|
@@ -445,7 +447,7 @@ func (tc *TestContext) TearDown() { | |
| err := os.MkdirAll(outinstancedir, os.ModePerm) | ||
| if err == nil { | ||
| logdir := path.Join(instancedir, "logs") | ||
| _, err := runCmdWithContextEnv(context.TODO(), []string{"cp", "-r", "--dereference", logdir, outinstancedir}, map[string]string{}) | ||
| _, _, err := runCmdWithContextEnv(context.TODO(), []string{"cp", "-r", "--dereference", logdir, outinstancedir}, map[string]string{}) | ||
| if err != nil { | ||
| log.Printf("failed to copy %s to %s: %w", logdir, outinstancedir, err) | ||
| } | ||
|
|
@@ -461,7 +463,7 @@ func (tc *TestContext) TearDown() { | |
| outmetricsdir := path.Join(testoutdir, "metrics_files") | ||
| err := os.MkdirAll(outmetricsdir, os.ModePerm) | ||
| if err == nil { | ||
| _, err := runCmdWithContextEnv(context.TODO(), []string{"cp", "-r", "--dereference", metricsdir, outmetricsdir}, map[string]string{}) | ||
| _, _, err := runCmdWithContextEnv(context.TODO(), []string{"cp", "-r", "--dereference", metricsdir, outmetricsdir}, map[string]string{}) | ||
| if err != nil { | ||
| log.Printf("failed to copy %s to %s: %w", metricsdir, outmetricsdir, err) | ||
| } | ||
|
|
@@ -586,7 +588,7 @@ func RunXts(t *testing.T, cuttlefishArgs FetchAndCreateArgs, xtsArgs XtsArgs) { | |
| "--log-level-display=INFO", | ||
| } | ||
| xtsCommand = append(xtsCommand, xtsArgs.XtsArgs...) | ||
| if _, err := tc.RunCmd(xtsCommand...); err != nil { | ||
| if _, _, err := tc.RunCmd(xtsCommand...); err != nil { | ||
| t.Fatalf("failed to fully run XTS: %w", err) | ||
| } | ||
| log.Printf("Finished running XTS!") | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This creates two different flags, instead of a single flag with two aliases. Do this instead: