-
Notifications
You must be signed in to change notification settings - Fork 154
/
Copy pathdiagnostics.go
136 lines (113 loc) · 4.94 KB
/
diagnostics.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
// Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
// or more contributor license agreements. Licensed under the Elastic License 2.0;
// you may not use this file except in compliance with the Elastic License 2.0.
package cmd
import (
"context"
"fmt"
"os"
"path"
"time"
"github.com/elastic/elastic-agent/pkg/control/v2/client"
"github.com/elastic/elastic-agent/pkg/control/v2/cproto"
"github.com/spf13/cobra"
"github.com/elastic/elastic-agent/internal/pkg/agent/application/paths"
"github.com/elastic/elastic-agent/internal/pkg/cli"
"github.com/elastic/elastic-agent/internal/pkg/diagnostics"
)
func newDiagnosticsCommand(_ []string, streams *cli.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "diagnostics",
Short: "Gather diagnostics information from the Elastic Agent and write it to a zip archive",
Long: "This command gathers diagnostics information from the Elastic Agent and writes it to a zip archive.",
Run: func(c *cobra.Command, args []string) {
if err := diagnosticCmd(streams, c); err != nil {
fmt.Fprintf(streams.Err, "Error: %v\n%s\n", err, troubleshootMessage())
os.Exit(1)
}
},
}
cmd.Flags().StringP("file", "f", "", "name of the output diagnostics zip archive")
cmd.Flags().BoolP("cpu-profile", "p", false, "wait to collect a CPU profile")
cmd.Flags().BoolP("skip-conn", "", false, "Skip connection request diagnostics")
cmd.Flags().Bool("exclude-events", false, "do not collect events log file")
return cmd
}
func diagnosticCmd(streams *cli.IOStreams, cmd *cobra.Command) error {
filepath, _ := cmd.Flags().GetString("file")
if filepath == "" {
ts := time.Now().UTC()
filepath = "elastic-agent-diagnostics-" + ts.Format("2006-01-02T15-04-05Z07-00") + ".zip" // RFC3339 format that replaces : with -, so it will work on Windows
}
excludeEvents, err := cmd.Flags().GetBool("exclude-events")
if err != nil {
return fmt.Errorf("cannot get 'exclude-events' flag: %w", err)
}
ctx := handleSignal(context.Background())
// 1st create the file to store the diagnostics, if it fails, anything else
// is pointless.
f, err := createFile(filepath)
if err != nil {
return fmt.Errorf("could not create diagnostics file %q: %w", filepath, err)
}
defer f.Close()
cpuProfile, _ := cmd.Flags().GetBool("cpu-profile")
connSkip, _ := cmd.Flags().GetBool("skip-conn")
agentDiag, unitDiags, compDiags, err := collectDiagnostics(ctx, streams, cpuProfile, connSkip)
if err != nil {
return fmt.Errorf("failed collecting diagnostics: %w", err)
}
if err := diagnostics.ZipArchive(streams.Err, f, paths.Top(), agentDiag, unitDiags, compDiags, excludeEvents); err != nil {
return fmt.Errorf("unable to create archive %q: %w", filepath, err)
}
fmt.Fprintf(streams.Out, "Created diagnostics archive %q\n", filepath)
fmt.Fprintln(streams.Out, "***** WARNING *****\nCreated archive may contain plain text credentials.\nEnsure that files in archive are redacted before sharing.\n*******************")
return nil
}
func collectDiagnostics(ctx context.Context, streams *cli.IOStreams, cpuProfile, connSkip bool) ([]client.DiagnosticFileResult, []client.DiagnosticUnitResult, []client.DiagnosticComponentResult, error) {
daemon := client.New()
err := daemon.Connect(ctx)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to connect to daemon: %w", err)
}
defer daemon.Disconnect()
var additionalDiags []cproto.AdditionalDiagnosticRequest
if !connSkip {
additionalDiags = append(additionalDiags, cproto.AdditionalDiagnosticRequest_CONN)
}
if cpuProfile {
// console will just hang while we wait for the CPU profile; print something so user doesn't get confused
fmt.Fprintf(streams.Out, "Creating diagnostics archive, waiting for CPU profile...\n")
additionalDiags = append(additionalDiags, cproto.AdditionalDiagnosticRequest_CPU)
}
agentDiag, err := daemon.DiagnosticAgent(ctx, additionalDiags)
if err != nil {
fmt.Fprintf(streams.Err, "[WARNING]: failed to fetch agent diagnostics: %s", err)
}
unitDiags, err := daemon.DiagnosticUnits(ctx)
if err != nil {
fmt.Fprintf(streams.Err, "[WARNING]: failed to fetch unit diagnostics: %s", err)
}
compDiags, err := daemon.DiagnosticComponents(ctx, additionalDiags)
if err != nil {
fmt.Fprintf(streams.Err, "[WARNING]: failed to fetch component diagnostics: %s", err)
}
if len(compDiags) == 0 && len(unitDiags) == 0 && len(agentDiag) == 0 {
return nil, nil, nil, fmt.Errorf("no diags could be fetched")
}
return agentDiag, unitDiags, compDiags, nil
}
func createFile(filepath string) (*os.File, error) {
// Ensure all the folders on filepath exist as os.Create does not do so.
// 0777 is the same permission, before unmask, os.Create uses.
dir := path.Dir(filepath)
if err := os.MkdirAll(dir, 0777); err != nil {
return nil, fmt.Errorf("could not create folders to save diagnostics on %q: %w",
dir, err)
}
f, err := os.Create(filepath)
if err != nil {
return nil, fmt.Errorf("error creating .zip file: %w", err)
}
return f, nil
}