mirror of
https://github.com/netbirdio/netbird.git
synced 2026-04-18 08:16:39 +00:00
[client] Add systemd netbird logs to debug bundle (#3917)
This commit is contained in:
@@ -2,6 +2,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"runtime"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/kardianos/service"
|
"github.com/kardianos/service"
|
||||||
@@ -27,12 +28,19 @@ func newProgram(ctx context.Context, cancel context.CancelFunc) *program {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func newSVCConfig() *service.Config {
|
func newSVCConfig() *service.Config {
|
||||||
return &service.Config{
|
config := &service.Config{
|
||||||
Name: serviceName,
|
Name: serviceName,
|
||||||
DisplayName: "Netbird",
|
DisplayName: "Netbird",
|
||||||
Description: "Netbird mesh network client",
|
Description: "Netbird mesh network client",
|
||||||
Option: make(service.KeyValue),
|
Option: make(service.KeyValue),
|
||||||
|
EnvVars: make(map[string]string),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "linux" {
|
||||||
|
config.EnvVars["SYSTEMD_UNIT"] = serviceName
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSVC(prg *program, conf *service.Config) (service.Service, error) {
|
func newSVC(prg *program, conf *service.Config) (service.Service, error) {
|
||||||
|
|||||||
@@ -274,10 +274,15 @@ func (g *BundleGenerator) createArchive() error {
|
|||||||
log.Errorf("Failed to add wg show output: %v", err)
|
log.Errorf("Failed to add wg show output: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if g.logFile != "console" {
|
if g.logFile != "console" && g.logFile != "" {
|
||||||
if err := g.addLogfile(); err != nil {
|
if err := g.addLogfile(); err != nil {
|
||||||
return fmt.Errorf("add log file: %w", err)
|
log.Errorf("Failed to add log file to debug bundle: %v", err)
|
||||||
|
if err := g.trySystemdLogFallback(); err != nil {
|
||||||
|
log.Errorf("Failed to add systemd logs as fallback: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} else if err := g.trySystemdLogFallback(); err != nil {
|
||||||
|
log.Errorf("Failed to add systemd logs: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -4,17 +4,104 @@ package debug
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/google/nftables"
|
"github.com/google/nftables"
|
||||||
"github.com/google/nftables/expr"
|
"github.com/google/nftables/expr"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
maxLogEntries = 100000
|
||||||
|
maxLogAge = 7 * 24 * time.Hour // Last 7 days
|
||||||
|
)
|
||||||
|
|
||||||
|
// trySystemdLogFallback attempts to get logs from systemd journal as fallback
|
||||||
|
func (g *BundleGenerator) trySystemdLogFallback() error {
|
||||||
|
log.Debug("Attempting to collect systemd journal logs")
|
||||||
|
|
||||||
|
serviceName := getServiceName()
|
||||||
|
journalLogs, err := getSystemdLogs(serviceName)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("get systemd logs for %s: %w", serviceName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.Contains(journalLogs, "No recent log entries found") {
|
||||||
|
log.Debug("No recent log entries found in systemd journal")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if g.anonymize {
|
||||||
|
journalLogs = g.anonymizer.AnonymizeString(journalLogs)
|
||||||
|
}
|
||||||
|
|
||||||
|
logReader := strings.NewReader(journalLogs)
|
||||||
|
fileName := fmt.Sprintf("systemd-%s.log", serviceName)
|
||||||
|
if err := g.addFileToZip(logReader, fileName); err != nil {
|
||||||
|
return fmt.Errorf("add systemd logs to bundle: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Added systemd journal logs for %s to debug bundle", serviceName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getServiceName gets the service name from environment or defaults to netbird
|
||||||
|
func getServiceName() string {
|
||||||
|
if unitName := os.Getenv("SYSTEMD_UNIT"); unitName != "" {
|
||||||
|
log.Debugf("Detected SYSTEMD_UNIT environment variable: %s", unitName)
|
||||||
|
return unitName
|
||||||
|
}
|
||||||
|
|
||||||
|
return "netbird"
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSystemdLogs retrieves logs from systemd journal for a specific service using journalctl
|
||||||
|
func getSystemdLogs(serviceName string) (string, error) {
|
||||||
|
args := []string{
|
||||||
|
"-u", fmt.Sprintf("%s.service", serviceName),
|
||||||
|
"--since", fmt.Sprintf("-%s", maxLogAge.String()),
|
||||||
|
"--lines", fmt.Sprintf("%d", maxLogEntries),
|
||||||
|
"--no-pager",
|
||||||
|
"--output", "short-iso",
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, "journalctl", args...)
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
cmd.Stdout = &stdout
|
||||||
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
|
||||||
|
return "", fmt.Errorf("journalctl command timed out after 30 seconds")
|
||||||
|
}
|
||||||
|
if strings.Contains(err.Error(), "executable file not found") {
|
||||||
|
return "", fmt.Errorf("journalctl command not found: %w", err)
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("execute journalctl: %w (stderr: %s)", err, stderr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
logs := stdout.String()
|
||||||
|
if strings.TrimSpace(logs) == "" {
|
||||||
|
return "No recent log entries found in systemd journal", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
header := fmt.Sprintf("=== Systemd Journal Logs for %s.service (last %d entries, max %s) ===\n",
|
||||||
|
serviceName, maxLogEntries, maxLogAge.String())
|
||||||
|
|
||||||
|
return header + logs, nil
|
||||||
|
}
|
||||||
|
|
||||||
// addFirewallRules collects and adds firewall rules to the archive
|
// addFirewallRules collects and adds firewall rules to the archive
|
||||||
func (g *BundleGenerator) addFirewallRules() error {
|
func (g *BundleGenerator) addFirewallRules() error {
|
||||||
log.Info("Collecting firewall rules")
|
log.Info("Collecting firewall rules")
|
||||||
@@ -481,7 +568,7 @@ func formatExpr(exp expr.Any) string {
|
|||||||
case *expr.Fib:
|
case *expr.Fib:
|
||||||
return formatFib(e)
|
return formatFib(e)
|
||||||
case *expr.Target:
|
case *expr.Target:
|
||||||
return fmt.Sprintf("jump %s", e.Name) // Properly format jump targets
|
return fmt.Sprintf("jump %s", e.Name)
|
||||||
case *expr.Immediate:
|
case *expr.Immediate:
|
||||||
if e.Register == 1 {
|
if e.Register == 1 {
|
||||||
return formatImmediateData(e.Data)
|
return formatImmediateData(e.Data)
|
||||||
|
|||||||
@@ -6,3 +6,9 @@ package debug
|
|||||||
func (g *BundleGenerator) addFirewallRules() error {
|
func (g *BundleGenerator) addFirewallRules() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (g *BundleGenerator) trySystemdLogFallback() error {
|
||||||
|
// Systemd is only available on Linux
|
||||||
|
// TODO: Add BSD support
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user