Files
2026-09-06 14:11:46 +02:00

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
}