From 53e243f3a5fc956bdaf187c3816f49994cf9205d Mon Sep 17 00:00:00 2001 From: Doug MacEachern Date: Thu, 8 Dec 2016 21:53:48 -0800 Subject: [PATCH] Add '-f' flag to logs command - Support logs '-n' > VC/ESX BrowseLog max - Add DiagnosticLog helper - Add doc for logs commands --- CHANGELOG.md | 2 + govc/CHANGELOG.md | 2 + govc/USAGE.md | 27 +++++++++++-- govc/emacs/README.md | 1 + govc/emacs/govc.el | 11 +++++- govc/logs/command.go | 38 +++++++++++------- govc/logs/download.go | 12 ++++++ govc/logs/ls.go | 8 ++++ govc/test/logs.bats | 6 ++- object/diagnostic_log.go | 76 ++++++++++++++++++++++++++++++++++++ object/diagnostic_manager.go | 8 ++++ 11 files changed, 172 insertions(+), 19 deletions(-) create mode 100644 object/diagnostic_log.go diff --git a/CHANGELOG.md b/CHANGELOG.md index c138ba5de..a5a099478 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ### unreleased +* Add DiagnosticLog helper + * Add DatastorePath helper ### 0.12.0 (2016-12-01) diff --git a/govc/CHANGELOG.md b/govc/CHANGELOG.md index a0d75be6f..d889d664b 100644 --- a/govc/CHANGELOG.md +++ b/govc/CHANGELOG.md @@ -2,6 +2,8 @@ ### unreleased +* Add '-f' flag to logs command + * Add storage support to vm.migrate * Add support for file backed serialport devices diff --git a/govc/USAGE.md b/govc/USAGE.md index 84cdfe859..55b55f4ff 100644 --- a/govc/USAGE.md +++ b/govc/USAGE.md @@ -1604,13 +1604,18 @@ View VPX and ESX logs. The '-log' option defaults to "hostd" when connected directly to a host or when connected to VirtualCenter and a '-host' option is given. Otherwise, the '-log' option defaults to "vpxd:vpxd.log". The '-host' option is ignored -when connected directly to host. -See 'govc logs.ls' for other '-log' options. +when connected directly to a host. See 'govc logs.ls' for other '-log' options. + +Examples: + govc logs -n 1000 -f + govc logs -host esx1 + govc logs -host esx1 -log vmkernel Options: + -f=false Follow log file changes -host= Host system [GOVC_HOST] -log= Log file key - -n=25 Output the last N logs + -n=25 Output the last N log lines ``` ## logs.download @@ -1618,6 +1623,16 @@ Options: ``` Usage: govc logs.download [OPTIONS] [PATH]... +Generate diagnostic bundles. + +A diagnostic bundle includes log files and other configuration information. + +Use PATH to include a specific set of hosts to include. + +Examples: + govc logs.download + govc logs.download host-a host-b + Options: -default=true Specifies if the bundle should include the default server ``` @@ -1627,6 +1642,12 @@ Options: ``` Usage: govc logs.ls [OPTIONS] +List diagnostic log keys. + +Examples: + govc logs.ls + govc logs.ls -host host-a + Options: -host= Host system [GOVC_HOST] ``` diff --git a/govc/emacs/README.md b/govc/emacs/README.md index 1cc0e4956..7dcb78247 100644 --- a/govc/emacs/README.md +++ b/govc/emacs/README.md @@ -119,6 +119,7 @@ during initialization. Keybinding | Description ---------------|------------------------------------------------------------ E | Events via govc events -n `govc-max-events` +L | Logs via govc logs -n `govc-max-events` J | JSON via govc host N | Netstat via `govc-esxcli-netstat-info` with current host id c | Connect new session for the current govc mode diff --git a/govc/emacs/govc.el b/govc/emacs/govc.el index 07c6d424d..508c1f7ad 100644 --- a/govc/emacs/govc.el +++ b/govc/emacs/govc.el @@ -466,7 +466,7 @@ Also fixes the case where user contains an '@'." (with-current-buffer (get-buffer current-prefix-arg) (setq govc-session-url url)))) -(defcustom govc-max-events 50 +(defcustom govc-max-events 100 "Limit events output to the last N events." :type 'integer :group 'govc) @@ -478,6 +478,14 @@ Also fixes the case where user contains an '@'." (govc-command "events" (list "-n" govc-max-events (if current-prefix-arg "-f") (govc-selection))))) +(defun govc-logs () + "Logs via govc logs -n `govc-max-events'." + (interactive) + (govc-shell-command + (let ((host (govc-selection))) + (govc-command "logs" + (list "-n" govc-max-events (if current-prefix-arg "-f") (if host (list "-host" host))))))) + (defun govc-parse-info (output) "Parse govc info command OUTPUT." (let* ((entries) @@ -656,6 +664,7 @@ Also fixes the case where user contains an '@'." (defvar govc-host-mode-map (let ((map (make-sparse-keymap))) (define-key map "E" 'govc-events) + (define-key map "L" 'govc-logs) (define-key map "J" 'govc-host-json-info) (define-key map "N" 'govc-host-esxcli-netstat) (define-key map "c" 'govc-mode-new-session) diff --git a/govc/logs/command.go b/govc/logs/command.go index 7ea54f2a1..a92143298 100644 --- a/govc/logs/command.go +++ b/govc/logs/command.go @@ -19,8 +19,7 @@ package logs import ( "context" "flag" - "fmt" - "math" + "time" "github.com/vmware/govmomi/govc/cli" "github.com/vmware/govmomi/govc/flags" @@ -32,6 +31,8 @@ type logs struct { Max int32 Key string + + follow bool } func init() { @@ -43,8 +44,9 @@ func (cmd *logs) Register(ctx context.Context, f *flag.FlagSet) { cmd.HostSystemFlag.Register(ctx, f) cmd.Max = 25 // default - f.Var(flags.NewInt32(&cmd.Max), "n", "Output the last N logs") + f.Var(flags.NewInt32(&cmd.Max), "n", "Output the last N log lines") f.StringVar(&cmd.Key, "log", "", "Log file key") + f.BoolVar(&cmd.follow, "f", false, "Follow log file changes") } func (cmd *logs) Process(ctx context.Context) error { @@ -60,8 +62,12 @@ func (cmd *logs) Description() string { The '-log' option defaults to "hostd" when connected directly to a host or when connected to VirtualCenter and a '-host' option is given. Otherwise, the '-log' option defaults to "vpxd:vpxd.log". The '-host' option is ignored -when connected directly to host. -See 'govc logs.ls' for other '-log' options.` +when connected directly to a host. See 'govc logs.ls' for other '-log' options. + +Examples: + govc logs -n 1000 -f + govc logs -host esx1 + govc logs -host esx1 -log vmkernel` } func (cmd *logs) Run(ctx context.Context, f *flag.FlagSet) error { @@ -91,20 +97,24 @@ func (cmd *logs) Run(ctx context.Context, f *flag.FlagSet) error { key = defaultKey } - // get LineEnd without any LineText - h, err := m.BrowseLog(ctx, host, key, math.MaxInt32, 0) - if err != nil { - return err - } + l := m.Log(ctx, host, key) - start := h.LineEnd - cmd.Max - h, err = m.BrowseLog(ctx, host, key, start, 0) + err = l.Seek(ctx, cmd.Max) if err != nil { return err } - for _, line := range h.LineText { - fmt.Println(line) + for { + _, err = l.Copy(ctx, cmd.Out) + if err != nil { + return nil + } + + if !cmd.follow { + break + } + + <-time.After(time.Second) } return nil diff --git a/govc/logs/download.go b/govc/logs/download.go index 1c775501f..62f95a507 100644 --- a/govc/logs/download.go +++ b/govc/logs/download.go @@ -58,6 +58,18 @@ func (cmd *download) Usage() string { return "[PATH]..." } +func (cmd *download) Description() string { + return `Generate diagnostic bundles. + +A diagnostic bundle includes log files and other configuration information. + +Use PATH to include a specific set of hosts to include. + +Examples: + govc logs.download + govc logs.download host-a host-b` +} + func (cmd *download) DownloadFile(c *vim25.Client, b string) error { u, err := c.Client.ParseURL(b) if err != nil { diff --git a/govc/logs/ls.go b/govc/logs/ls.go index fe6d0ae9b..49a2aa369 100644 --- a/govc/logs/ls.go +++ b/govc/logs/ls.go @@ -48,6 +48,14 @@ func (cmd *ls) Process(ctx context.Context) error { return nil } +func (cmd *ls) Description() string { + return `List diagnostic log keys. + +Examples: + govc logs.ls + govc logs.ls -host host-a` +} + func (cmd *ls) Run(ctx context.Context, f *flag.FlagSet) error { c, err := cmd.Client() if err != nil { diff --git a/govc/test/logs.bats b/govc/test/logs.bats index 733cd8f9c..f03489b34 100755 --- a/govc/test/logs.bats +++ b/govc/test/logs.bats @@ -10,7 +10,7 @@ load test_helper [ $nlogs -ge 1 ] # test -n flag - run govc logs -n $((nlogs - 1)) + run govc logs -n $((nlogs - 10)) assert_success [ ${#lines[@]} -le $nlogs ] @@ -20,6 +20,10 @@ load test_helper # there should be plenty more than 1 line of vmkernel logs [ $nlogs -ge 1 ] + # test > 1 call to BrowseLog() + run govc logs -n 2002 + assert_success + # -host ignored against ESX run govc logs -host enoent assert_success diff --git a/object/diagnostic_log.go b/object/diagnostic_log.go new file mode 100644 index 000000000..466d0ee91 --- /dev/null +++ b/object/diagnostic_log.go @@ -0,0 +1,76 @@ +/* +Copyright (c) 2015 VMware, Inc. All Rights Reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package object + +import ( + "context" + "fmt" + "io" + "math" +) + +// DiagnosticLog wraps DiagnosticManager.BrowseLog +type DiagnosticLog struct { + m DiagnosticManager + + Key string + Host *HostSystem + + Start int32 +} + +// Seek to log position starting at the last nlines of the log +func (l *DiagnosticLog) Seek(ctx context.Context, nlines int32) error { + h, err := l.m.BrowseLog(ctx, l.Host, l.Key, math.MaxInt32, 0) + if err != nil { + return err + } + + l.Start = h.LineEnd - nlines + + return nil +} + +// Copy log starting from l.Start to the given io.Writer +// Returns on error or when end of log is reached. +func (l *DiagnosticLog) Copy(ctx context.Context, w io.Writer) (int, error) { + const max = 500 // VC max == 500, ESX max == 1000 + written := 0 + + for { + h, err := l.m.BrowseLog(ctx, l.Host, l.Key, l.Start, max) + if err != nil { + return 0, err + } + + for _, line := range h.LineText { + n, err := fmt.Fprintln(w, line) + written += n + if err != nil { + return written, err + } + } + + l.Start += int32(len(h.LineText)) + + if l.Start >= h.LineEnd { + break + } + } + + return written, nil +} diff --git a/object/diagnostic_manager.go b/object/diagnostic_manager.go index 548a9338e..5baf1ad90 100644 --- a/object/diagnostic_manager.go +++ b/object/diagnostic_manager.go @@ -36,6 +36,14 @@ func NewDiagnosticManager(c *vim25.Client) *DiagnosticManager { return &m } +func (m DiagnosticManager) Log(ctx context.Context, host *HostSystem, key string) *DiagnosticLog { + return &DiagnosticLog{ + m: m, + Key: key, + Host: host, + } +} + func (m DiagnosticManager) BrowseLog(ctx context.Context, host *HostSystem, key string, start, lines int32) (*types.DiagnosticManagerLogHeader, error) { req := types.BrowseDiagnosticLog{ This: m.Reference(),