diff --git a/internal/watchdog/check_subnet.go b/internal/watchdog/check_subnet.go index 4638791..fafb2ff 100644 --- a/internal/watchdog/check_subnet.go +++ b/internal/watchdog/check_subnet.go @@ -24,7 +24,7 @@ const ( func subnetIfaceNames(subnetName string) (hostVeth, nsVeth, bridge string, err error) { parts := strings.SplitN(subnetName, "-", 2) if len(parts) < 2 || parts[1] == "" { - return "", "", "", fmt.Errorf("nom de subnet %q sans identifiant après le tiret, interfaces indéductibles", subnetName) + return "", "", "", fmt.Errorf("subnet name %q has no identifier after the dash, interface names cannot be derived", subnetName) } id := parts[1] return "v-" + id + "-e", "v-" + id + "-i", "br-" + id, nil @@ -37,13 +37,13 @@ func dnsmasqName(vpc, bridge string) string { func CheckSubnets(db *badger.DB, u unitChecker, n notify.Notifier) error { pairs, err := kv.ListByPrefix(db, prefixSubnet) if err != nil { - return fmt.Errorf("watchdog: lecture des subnets: %w", err) + return fmt.Errorf("watchdog: listing subnets: %w", err) } for _, name := range resourceNames(pairs, prefixSubnet) { st, err := state.Get(db, prefixSubnet+name) if err != nil { - n.Notify(kindSubnet, name, fmt.Sprintf("état illisible en base: %v", err)) + n.Notify(kindSubnet, name, fmt.Sprintf("state unreadable in database: %v", err)) continue } if st != state.Running { @@ -63,13 +63,13 @@ func checkSubnet(db *badger.DB, name string, u unitChecker, n notify.Notifier) { vpc, err := kv.GetFromDB(db, prefixSubnet+name+"/vpc") if err != nil { - n.Notify(kindSubnet, name, fmt.Sprintf("vpc illisible en base: %v", err)) + n.Notify(kindSubnet, name, fmt.Sprintf("vpc unreadable in database: %v", err)) return } mode, err := kv.GetFromDB(db, prefixSubnet+name+"/mode") if err != nil { - n.Notify(kindSubnet, name, fmt.Sprintf("mode illisible en base: %v", err)) + n.Notify(kindSubnet, name, fmt.Sprintf("mode unreadable in database: %v", err)) return } @@ -85,7 +85,7 @@ func checkSubnet(db *badger.DB, name string, u unitChecker, n notify.Notifier) { checkVxlanIface(db, name, n) case modeBridge: default: - n.Notify(kindSubnet, name, fmt.Sprintf("mode inconnu %q", mode)) + n.Notify(kindSubnet, name, fmt.Sprintf("unknown mode %q", mode)) } checkSubnetNetns(name, vpc, nsVeth, bridge, n) @@ -93,7 +93,7 @@ func checkSubnet(db *badger.DB, name string, u unitChecker, n notify.Notifier) { dnsName := dnsmasqName(vpc, bridge) conf := filepath.Join(dhcp.DefaultConfDir, dnsName+".conf") if _, err := os.Stat(conf); err != nil { - n.Notify(kindSubnet, name, fmt.Sprintf("config dnsmasq absente (%s): %v", conf, err)) + n.Notify(kindSubnet, name, fmt.Sprintf("dnsmasq config missing (%s): %v", conf, err)) } checkUnit(kindSubnet, name, "dnsmasq@"+dnsName+".service", u, n) @@ -102,12 +102,12 @@ func checkSubnet(db *badger.DB, name string, u unitChecker, n notify.Notifier) { func checkVxlanIface(db *badger.DB, name string, n notify.Notifier) { raw, err := kv.GetFromDB(db, prefixSubnet+name+"/vxlan_id") if err != nil { - n.Notify(kindSubnet, name, fmt.Sprintf("vxlan_id illisible en base: %v", err)) + n.Notify(kindSubnet, name, fmt.Sprintf("vxlan_id unreadable in database: %v", err)) return } id, err := strconv.Atoi(raw) if err != nil { - n.Notify(kindSubnet, name, fmt.Sprintf("vxlan_id invalide %q: %v", raw, err)) + n.Notify(kindSubnet, name, fmt.Sprintf("invalid vxlan_id %q: %v", raw, err)) return } if p := linkProblem(fmt.Sprintf("vxlan-%d", id)); p != "" { @@ -117,18 +117,18 @@ func checkVxlanIface(db *badger.DB, name string, n notify.Notifier) { func checkSubnetNetns(name, vpc, nsVeth, bridge string, n notify.Notifier) { if !netns.Exist(vpc) { - n.Notify(kindSubnet, name, "netns "+vpc+" absent (/var/run/netns/"+vpc+")") + n.Notify(kindSubnet, name, "netns "+vpc+" missing (/var/run/netns/"+vpc+")") return } if err := netns.Call(vpc, func() error { for _, iface := range []string{nsVeth, bridge} { if p := linkProblem(iface); p != "" { - n.Notify(kindSubnet, name, p+" (dans le netns "+vpc+")") + n.Notify(kindSubnet, name, p+" (in netns "+vpc+")") } } return nil }); err != nil { - n.Notify(kindSubnet, name, fmt.Sprintf("entrée dans le netns %s impossible: %v", vpc, err)) + n.Notify(kindSubnet, name, fmt.Sprintf("cannot enter netns %s: %v", vpc, err)) } } diff --git a/internal/watchdog/check_subnet_test.go b/internal/watchdog/check_subnet_test.go index dbcd19b..6928e6f 100644 --- a/internal/watchdog/check_subnet_test.go +++ b/internal/watchdog/check_subnet_test.go @@ -85,7 +85,7 @@ func TestCheckSubnets_VPCManquantEnBase(t *testing.T) { if len(got) != 1 { t.Fatalf("attendu 1 notification, obtenu %d : %v", len(got), got) } - if !strings.Contains(got[0].problem, "vpc illisible") { + if !strings.Contains(got[0].problem, "vpc unreadable") { t.Errorf("problem = %q, devrait porter sur le vpc", got[0].problem) } } @@ -100,7 +100,7 @@ func TestCheckSubnets_ModeManquantEnBase(t *testing.T) { t.Fatalf("erreur inattendue: %v", err) } - if !r.hasProblemContaining("mode illisible") { + if !r.hasProblemContaining("mode unreadable") { t.Errorf("devrait signaler un mode illisible, obtenu %v", r.calls) } } @@ -114,7 +114,7 @@ func TestCheckSubnets_ModeInconnu(t *testing.T) { t.Fatalf("erreur inattendue: %v", err) } - if !r.hasProblemContaining(`mode inconnu "macvlan"`) { + if !r.hasProblemContaining(`unknown mode "macvlan"`) { t.Errorf("devrait signaler un mode inconnu, obtenu %v", r.calls) } } @@ -147,7 +147,7 @@ func TestCheckSubnets_ModeVxlanSansVxlanID(t *testing.T) { t.Fatalf("erreur inattendue: %v", err) } - if !r.hasProblemContaining("vxlan_id illisible") { + if !r.hasProblemContaining("vxlan_id unreadable") { t.Errorf("devrait signaler un vxlan_id illisible, obtenu %v", r.calls) } } @@ -162,7 +162,7 @@ func TestCheckSubnets_ModeVxlanVxlanIDInvalide(t *testing.T) { t.Fatalf("erreur inattendue: %v", err) } - if !r.hasProblemContaining("vxlan_id invalide") { + if !r.hasProblemContaining("invalid vxlan_id") { t.Errorf("devrait signaler un vxlan_id invalide, obtenu %v", r.calls) } } @@ -191,7 +191,7 @@ func TestCheckSubnets_ConfigDnsmasqAbsente(t *testing.T) { t.Fatalf("erreur inattendue: %v", err) } - if !r.hasProblemContaining("config dnsmasq absente") { + if !r.hasProblemContaining("dnsmasq config missing") { t.Errorf("devrait signaler la config dnsmasq absente, obtenu %v", r.calls) } if !r.hasProblemContaining("vp-admin_br-000042.conf") { @@ -242,7 +242,7 @@ func TestCheckSubnets_UnitIllisible(t *testing.T) { t.Fatalf("erreur inattendue: %v", err) } - if !r.hasProblemContaining("unit dnsmasq@vp-admin_br-000042.service illisible") { + if !r.hasProblemContaining("unit dnsmasq@vp-admin_br-000042.service unreadable") { t.Errorf("devrait signaler l'unit illisible, obtenu %v", r.calls) } } @@ -271,7 +271,7 @@ func TestCheckSubnets_EtatCorrompuNInterrompPasLaBoucle(t *testing.T) { t.Fatalf("un état corrompu ne doit pas faire échouer CheckSubnets: %v", err) } - if !strings.Contains(strings.Join(problems(r.forName("br-corrompu")), " "), "état illisible") { + if !strings.Contains(strings.Join(problems(r.forName("br-corrompu")), " "), "state unreadable") { t.Errorf("devrait signaler l'état corrompu, obtenu %v", r.calls) } if len(r.forName("br-000042")) == 0 { diff --git a/internal/watchdog/check_vm.go b/internal/watchdog/check_vm.go index 8660eb1..3c850a3 100644 --- a/internal/watchdog/check_vm.go +++ b/internal/watchdog/check_vm.go @@ -23,18 +23,18 @@ func tapName(tapID int) string { func CheckVMs(db *badger.DB, cfg *configuration.Config, u unitChecker, n notify.Notifier) error { if cfg == nil { - return errors.New("watchdog: configuration requise pour vérifier les VMs") + return errors.New("watchdog: configuration required to check vms") } pairs, err := kv.ListByPrefix(db, prefixVM) if err != nil { - return fmt.Errorf("watchdog: lecture des vm: %w", err) + return fmt.Errorf("watchdog: listing vms: %w", err) } for _, name := range resourceNames(pairs, prefixVM) { st, err := state.Get(db, prefixVM+name) if err != nil { - n.Notify(kindVM, name, fmt.Sprintf("état illisible en base: %v", err)) + n.Notify(kindVM, name, fmt.Sprintf("state unreadable in database: %v", err)) continue } if st != state.Running { @@ -48,13 +48,13 @@ func CheckVMs(db *badger.DB, cfg *configuration.Config, u unitChecker, n notify. func checkVM(db *badger.DB, cfg *configuration.Config, name string, u unitChecker, n notify.Notifier) { subnetName, err := kv.GetFromDB(db, prefixVM+name+"/subnet") if err != nil { - n.Notify(kindVM, name, fmt.Sprintf("subnet illisible en base: %v", err)) + n.Notify(kindVM, name, fmt.Sprintf("subnet unreadable in database: %v", err)) return } vpc, err := kv.GetFromDB(db, prefixSubnet+subnetName+"/vpc") if err != nil { - n.Notify(kindVM, name, fmt.Sprintf("vpc du subnet %s illisible en base: %v", subnetName, err)) + n.Notify(kindVM, name, fmt.Sprintf("vpc of subnet %s unreadable in database: %v", subnetName, err)) return } @@ -67,33 +67,33 @@ func checkVM(db *badger.DB, cfg *configuration.Config, name string, u unitChecke func checkVMTap(db *badger.DB, name, vpc string, n notify.Notifier) { raw, err := kv.GetFromDB(db, prefixVM+name+"/tap_id") if err != nil { - n.Notify(kindVM, name, fmt.Sprintf("tap_id illisible en base: %v", err)) + n.Notify(kindVM, name, fmt.Sprintf("tap_id unreadable in database: %v", err)) return } tapID, err := strconv.Atoi(raw) if err != nil { - n.Notify(kindVM, name, fmt.Sprintf("tap_id invalide %q: %v", raw, err)) + n.Notify(kindVM, name, fmt.Sprintf("invalid tap_id %q: %v", raw, err)) return } if !netns.Exist(vpc) { - n.Notify(kindVM, name, "netns "+vpc+" absent (/var/run/netns/"+vpc+")") + n.Notify(kindVM, name, "netns "+vpc+" missing (/var/run/netns/"+vpc+")") return } if err := netns.Call(vpc, func() error { if p := linkProblem(tapName(tapID)); p != "" { - n.Notify(kindVM, name, p+" (dans le netns "+vpc+")") + n.Notify(kindVM, name, p+" (in netns "+vpc+")") } return nil }); err != nil { - n.Notify(kindVM, name, fmt.Sprintf("entrée dans le netns %s impossible: %v", vpc, err)) + n.Notify(kindVM, name, fmt.Sprintf("cannot enter netns %s: %v", vpc, err)) } } func checkVMQemu(cfg *configuration.Config, name string, n notify.Notifier) { sock := filepath.Join(cfg.QEMU.QMPDir, name+".sock") if _, err := qmp.Send(sock, nil); err != nil { - n.Notify(kindVM, name, fmt.Sprintf("qemu ne répond pas sur %s: %v", sock, err)) + n.Notify(kindVM, name, fmt.Sprintf("qemu not responding on %s: %v", sock, err)) } } diff --git a/internal/watchdog/check_vm_test.go b/internal/watchdog/check_vm_test.go index 8bf24a4..eb62b59 100644 --- a/internal/watchdog/check_vm_test.go +++ b/internal/watchdog/check_vm_test.go @@ -86,7 +86,7 @@ func TestCheckVMs_SubnetManquantEnBase(t *testing.T) { if len(got) != 1 { t.Fatalf("attendu 1 notification, obtenu %d : %v", len(got), got) } - if !strings.Contains(got[0].problem, "subnet illisible") { + if !strings.Contains(got[0].problem, "subnet unreadable") { t.Errorf("problem = %q, devrait porter sur le subnet", got[0].problem) } } @@ -101,7 +101,7 @@ func TestCheckVMs_VPCDuSubnetManquant(t *testing.T) { t.Fatalf("erreur inattendue: %v", err) } - if !r.hasProblemContaining("vpc du subnet br-000042 illisible") { + if !r.hasProblemContaining("vpc of subnet br-000042 unreadable") { t.Errorf("devrait signaler le vpc introuvable, obtenu %v", r.calls) } } @@ -115,7 +115,7 @@ func TestCheckVMs_TapIDManquant(t *testing.T) { t.Fatalf("erreur inattendue: %v", err) } - if !r.hasProblemContaining("tap_id illisible") { + if !r.hasProblemContaining("tap_id unreadable") { t.Errorf("devrait signaler un tap_id illisible, obtenu %v", r.calls) } } @@ -129,7 +129,7 @@ func TestCheckVMs_TapIDInvalide(t *testing.T) { t.Fatalf("erreur inattendue: %v", err) } - if !r.hasProblemContaining("tap_id invalide") { + if !r.hasProblemContaining("invalid tap_id") { t.Errorf("devrait signaler un tap_id invalide, obtenu %v", r.calls) } } @@ -144,7 +144,7 @@ func TestCheckVMs_QemuNeRepondPas(t *testing.T) { t.Fatalf("erreur inattendue: %v", err) } - if !r.hasProblemContaining("qemu ne répond pas") { + if !r.hasProblemContaining("qemu not responding") { t.Errorf("devrait signaler que qemu ne répond pas, obtenu %v", r.calls) } if !r.hasProblemContaining("i-test1.sock") { @@ -220,7 +220,7 @@ func TestCheckVMs_EtatCorrompuNInterrompPasLaBoucle(t *testing.T) { t.Fatalf("un état corrompu ne doit pas faire échouer CheckVMs: %v", err) } - if !strings.Contains(strings.Join(problems(r.forName("i-corrompu")), " "), "état illisible") { + if !strings.Contains(strings.Join(problems(r.forName("i-corrompu")), " "), "state unreadable") { t.Errorf("devrait signaler l'état corrompu, obtenu %v", r.calls) } if len(r.forName("i-test1")) == 0 { diff --git a/internal/watchdog/check_vpc.go b/internal/watchdog/check_vpc.go index 5cf252d..c344561 100644 --- a/internal/watchdog/check_vpc.go +++ b/internal/watchdog/check_vpc.go @@ -17,7 +17,7 @@ const vpcBridge = "br-public" func vpcIfaceNames(vpcName string) (host, ns string, err error) { parts := strings.SplitN(vpcName, "-", 2) if len(parts) < 2 || parts[1] == "" { - return "", "", fmt.Errorf("nom de VPC %q sans identifiant après le tiret, interfaces indéductibles", vpcName) + return "", "", fmt.Errorf("vpc name %q has no identifier after the dash, interface names cannot be derived", vpcName) } return "vp-" + parts[1] + "-e", "vp-" + parts[1] + "-i", nil } @@ -25,13 +25,13 @@ func vpcIfaceNames(vpcName string) (host, ns string, err error) { func CheckVPCs(db *badger.DB, n notify.Notifier) error { pairs, err := kv.ListByPrefix(db, prefixVPC) if err != nil { - return fmt.Errorf("watchdog: lecture des vpc: %w", err) + return fmt.Errorf("watchdog: listing vpcs: %w", err) } for _, name := range resourceNames(pairs, prefixVPC) { st, err := state.Get(db, prefixVPC+name) if err != nil { - n.Notify(kindVPC, name, fmt.Sprintf("état illisible en base: %v", err)) + n.Notify(kindVPC, name, fmt.Sprintf("state unreadable in database: %v", err)) continue } if st != state.Running { @@ -50,7 +50,7 @@ func checkVPC(name string, n notify.Notifier) { } if !netns.Exist(name) { - n.Notify(kindVPC, name, "netns absent (/var/run/netns/"+name+")") + n.Notify(kindVPC, name, "netns missing (/var/run/netns/"+name+")") return } @@ -61,11 +61,11 @@ func checkVPC(name string, n notify.Notifier) { if err := netns.Call(name, func() error { for _, iface := range []string{nsVeth, vpcBridge} { if p := linkProblem(iface); p != "" { - n.Notify(kindVPC, name, p+" (dans le netns)") + n.Notify(kindVPC, name, p+" (in netns)") } } return nil }); err != nil { - n.Notify(kindVPC, name, fmt.Sprintf("entrée dans le netns impossible: %v", err)) + n.Notify(kindVPC, name, fmt.Sprintf("cannot enter netns: %v", err)) } } diff --git a/internal/watchdog/check_vpc_test.go b/internal/watchdog/check_vpc_test.go index 1b37248..3ae8a07 100644 --- a/internal/watchdog/check_vpc_test.go +++ b/internal/watchdog/check_vpc_test.go @@ -122,7 +122,7 @@ func TestCheckVPCs_EtatCorrompu(t *testing.T) { if len(got) != 1 { t.Fatalf("attendu 1 notification pour l'état corrompu, obtenu %d", len(got)) } - if !strings.Contains(got[0].problem, "état illisible") { + if !strings.Contains(got[0].problem, "state unreadable") { t.Errorf("problem = %q, devrait mentionner un état illisible", got[0].problem) } if len(r.forName("vp-suivant")) == 0 { @@ -143,7 +143,7 @@ func TestCheckVPCs_NomIndeductibleNePaniquePas(t *testing.T) { if len(got) != 1 { t.Fatalf("attendu 1 notification, obtenu %d : %v", len(got), got) } - if !strings.Contains(got[0].problem, "indéductibles") { + if !strings.Contains(got[0].problem, "cannot be derived") { t.Errorf("problem = %q, devrait porter sur les interfaces indéductibles", got[0].problem) } } diff --git a/internal/watchdog/notify/stderr.go b/internal/watchdog/notify/stderr.go index 5436b2c..cac8174 100644 --- a/internal/watchdog/notify/stderr.go +++ b/internal/watchdog/notify/stderr.go @@ -16,7 +16,7 @@ func NewStderr(l *slog.Logger) *StderrNotifier { } func (n *StderrNotifier) Notify(kind, name, problem string) { - n.logger.Error("watchdog: incohérence détectée", + n.logger.Error("watchdog: inconsistency detected", "kind", kind, "name", name, "problem", problem, diff --git a/internal/watchdog/resources.go b/internal/watchdog/resources.go index d5e3fd4..37219a5 100644 --- a/internal/watchdog/resources.go +++ b/internal/watchdog/resources.go @@ -52,7 +52,7 @@ func checkUnit(kind, name, unit string, u unitChecker, n notify.Notifier) { } st, err := u.Status(unit) if err != nil { - n.Notify(kind, name, fmt.Sprintf("unit %s illisible: %v", unit, err)) + n.Notify(kind, name, fmt.Sprintf("unit %s unreadable: %v", unit, err)) return } if st.ActiveState != "active" { @@ -64,7 +64,7 @@ func linkProblem(iface string) string { up, err := netif.LinkIsUp(iface) switch { case err != nil: - return fmt.Sprintf("interface %s introuvable: %v", iface, err) + return fmt.Sprintf("interface %s not found: %v", iface, err) case !up: return fmt.Sprintf("interface %s down", iface) } diff --git a/internal/watchdog/watchdog.go b/internal/watchdog/watchdog.go index 0dbb85b..3ab1d52 100644 --- a/internal/watchdog/watchdog.go +++ b/internal/watchdog/watchdog.go @@ -28,7 +28,7 @@ func New(db *badger.DB, cfg *configuration.Config, n notify.Notifier, logger *sl logger = slog.Default() } if interval <= 0 { - logger.Warn("watchdog: intervalle invalide, valeur par défaut appliquée", + logger.Warn("watchdog: invalid interval, default applied", "interval", interval, "default", defaultInterval) interval = defaultInterval } @@ -45,12 +45,12 @@ func (w *Watchdog) Run(ctx context.Context) { ticker := time.NewTicker(w.interval) defer ticker.Stop() - w.logger.Info("watchdog: démarrage", "interval", w.interval) + w.logger.Info("watchdog: starting", "interval", w.interval) for { select { case <-ctx.Done(): - w.logger.Info("watchdog: arrêt") + w.logger.Info("watchdog: stopping") return case <-ticker.C: w.tick() @@ -63,13 +63,13 @@ func (w *Watchdog) tick() { defer closeUnits() if err := CheckVPCs(w.db, w.notifier); err != nil { - w.logger.Error("watchdog: vérification des vpc", "err", err) + w.logger.Error("watchdog: vpc check failed", "err", err) } if err := CheckSubnets(w.db, u, w.notifier); err != nil { - w.logger.Error("watchdog: vérification des subnets", "err", err) + w.logger.Error("watchdog: subnet check failed", "err", err) } if err := CheckVMs(w.db, w.cfg, u, w.notifier); err != nil { - w.logger.Error("watchdog: vérification des vm", "err", err) + w.logger.Error("watchdog: vm check failed", "err", err) } } @@ -77,13 +77,13 @@ func (w *Watchdog) units() (unitChecker, func()) { m, err := systemd.New() if err != nil { if !w.dbusDown { - w.logger.Warn("watchdog: connexion systemd impossible, vérification des units désactivée", "err", err) + w.logger.Warn("watchdog: systemd unreachable, unit checks disabled", "err", err) w.dbusDown = true } return nil, func() {} } if w.dbusDown { - w.logger.Info("watchdog: connexion systemd rétablie") + w.logger.Info("watchdog: systemd connection restored") w.dbusDown = false } return m, m.Close diff --git a/internal/watchdog/watchdog_test.go b/internal/watchdog/watchdog_test.go index c8cea47..019ddb5 100644 --- a/internal/watchdog/watchdog_test.go +++ b/internal/watchdog/watchdog_test.go @@ -61,7 +61,7 @@ func TestNew_IntervalleInvalideUtiliseLeDefaut(t *testing.T) { } } - if !strings.Contains(buf.String(), "intervalle invalide") { + if !strings.Contains(buf.String(), "invalid interval") { t.Error("un intervalle invalide devrait être signalé dans les logs") } } @@ -143,11 +143,11 @@ func TestRun_ErreurDeBaseLogueeEtBoucleContinue(t *testing.T) { for _, line := range logLines(t, buf) { msg, _ := line["msg"].(string) switch { - case strings.Contains(msg, "vérification des vpc"): + case strings.Contains(msg, "vpc check failed"): vpc = true - case strings.Contains(msg, "vérification des subnets"): + case strings.Contains(msg, "subnet check failed"): subnet = true - case strings.Contains(msg, "vérification des vm"): + case strings.Contains(msg, "vm check failed"): vm = true } } @@ -168,10 +168,10 @@ func TestRun_LogueDemarrageEtArret(t *testing.T) { w.Run(ctx) out := buf.String() - if !strings.Contains(out, "watchdog: démarrage") { + if !strings.Contains(out, "watchdog: starting") { t.Error("le démarrage devrait être logué") } - if !strings.Contains(out, "watchdog: arrêt") { + if !strings.Contains(out, "watchdog: stopping") { t.Error("l'arrêt devrait être logué") } } @@ -190,7 +190,7 @@ func TestUnits_ConnexionSystemdIndisponibleSignaleeUneSeuleFois(t *testing.T) { var warnings int for _, line := range logLines(t, buf) { - if msg, _ := line["msg"].(string); strings.Contains(msg, "connexion systemd impossible") { + if msg, _ := line["msg"].(string); strings.Contains(msg, "systemd unreachable") { warnings++ } }