114 lines
3.3 KiB
Go
114 lines
3.3 KiB
Go
// log.go — the event log of a vCenter, on screen and by mail.
|
|
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/vmware/govmomi/event"
|
|
"github.com/vmware/govmomi/vim25/types"
|
|
"gopkg.in/gomail.v2"
|
|
)
|
|
|
|
// vmlog shows what happened on one vCenter over the last so many minutes, and
|
|
// mails it when there is something to report and -m was given.
|
|
func vmlog(cfg Config, vc VCenter, minutes int, mail bool) error {
|
|
if mail {
|
|
// Asked before the query, not after it: finding out that the mail
|
|
// cannot be sent is of no use once the events have scrolled past.
|
|
if err := cfg.mailReady(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
s, err := connect(vc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer s.close()
|
|
|
|
now := time.Now().UTC()
|
|
// One minute further back than asked: run every hour with a window of
|
|
// exactly an hour and the events falling between two runs are never seen.
|
|
begin := now.Add(-time.Duration(minutes+1) * time.Minute)
|
|
|
|
events, err := event.NewManager(s.client.Client).QueryEvents(s.ctx, types.EventFilterSpec{
|
|
Time: &types.EventFilterSpecByTime{BeginTime: &begin, EndTime: &now},
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("%s: cannot read the event log: %w", vc.Name, err)
|
|
}
|
|
|
|
// No heading and no rule: the lines below are comma-separated on purpose —
|
|
// they are what the mail carries and what a script would cut up — and a
|
|
// pipe-separated heading above them promised a table that never came.
|
|
|
|
msg := ""
|
|
for _, e := range events {
|
|
if skipEvent(e) {
|
|
continue
|
|
}
|
|
base := e.GetEvent()
|
|
message := base.FullFormattedMessage
|
|
if message == "" {
|
|
message = fmt.Sprintf("Event: %T", e)
|
|
}
|
|
|
|
line := fmt.Sprintf("%s,%s,%s\n",
|
|
base.CreatedTime.Local().Format("2006-01-02 15:04:05"),
|
|
strings.ToUpper(severity(e)),
|
|
message)
|
|
PN(line)
|
|
msg += line
|
|
}
|
|
|
|
if msg == "" || !mail {
|
|
return nil
|
|
}
|
|
return sendmail(cfg, SF("vmware messages (%s)", vc.Name), "<pre>"+msg+"</pre>")
|
|
}
|
|
|
|
// skipEvent drops the session noise — logins, logouts, failed logins, a service
|
|
// account retrying a stale password.
|
|
//
|
|
// A Go type switch matches the exact type, never an embedded one, and vCenter
|
|
// only ever sends the concrete subtypes. The old `case *types.SessionEvent` was
|
|
// therefore dead, and everything that was not literally a login or a logout came
|
|
// through and was mailed out with the rest. types.BaseSessionEvent is the
|
|
// interface govmomi generates for precisely this, and it covers the subtypes
|
|
// that do not exist yet as well.
|
|
func skipEvent(e types.BaseEvent) bool {
|
|
_, ok := e.(types.BaseSessionEvent)
|
|
return ok
|
|
}
|
|
|
|
// severity is read off the event where it is carried and guessed from the type
|
|
// where it is not.
|
|
func severity(e types.BaseEvent) string {
|
|
switch ev := e.(type) {
|
|
case *types.EventEx:
|
|
if ev.Severity != "" {
|
|
return ev.Severity
|
|
}
|
|
case *types.VmFailedToPowerOnEvent, *types.HostConnectionLostEvent:
|
|
return "error"
|
|
}
|
|
return "info"
|
|
}
|
|
|
|
// sendmail hands one message to the configured relay.
|
|
func sendmail(cfg Config, subject, html string) error {
|
|
m := gomail.NewMessage()
|
|
m.SetHeader("From", cfg.MailFrom)
|
|
m.SetHeader("To", cfg.MailTo)
|
|
m.SetHeader("Subject", subject)
|
|
m.SetBody("text/html", html)
|
|
|
|
if err := gomail.NewDialer(cfg.SMTPHost, cfg.smtpPort(), "", "").DialAndSend(m); err != nil {
|
|
return fmt.Errorf("cannot send mail via %s: %w", cfg.SMTPHost, err)
|
|
}
|
|
PO("mail sent to " + cfg.MailTo)
|
|
return nil
|
|
}
|