From 077eeb79170d3286cae8e817306a927f35b46ddf Mon Sep 17 00:00:00 2001 From: Amir Raminfar Date: Wed, 14 Aug 2024 10:04:18 -0700 Subject: [PATCH] chore: uses zerolog instead of logrus (#3203) --- .github/workflows/test.yml | 2 +- go.mod | 4 +- go.sum | 19 ++-- internal/agent/client.go | 13 ++- internal/agent/client_test.go | 3 +- internal/agent/server.go | 13 +-- internal/analytics/http_beacon.go | 6 +- internal/auth/proxy.go | 4 +- internal/auth/users.go | 11 +- internal/cache/expire.go | 8 +- internal/docker/client.go | 24 ++--- internal/docker/container_store.go | 25 +++-- internal/docker/event_generator.go | 9 +- internal/docker/host.go | 6 +- internal/docker/level_guesser.go | 4 +- internal/docker/stats_collector.go | 20 ++-- internal/healthcheck/http.go | 4 +- internal/healthcheck/rpc.go | 6 +- internal/profile/disk.go | 8 +- internal/support/cli/analytics.go | 6 +- internal/support/cli/certs.go | 7 +- internal/support/cli/clients.go | 21 ++-- internal/support/cli/logger.go | 20 ++-- internal/support/cli/valid_env.go | 4 +- internal/support/docker/multi_host_service.go | 6 +- .../docker/retriable_client_manager.go | 19 ++-- .../support/docker/swarm_client_manager.go | 33 +++--- internal/web/actions.go | 12 +-- internal/web/auth.go | 6 +- internal/web/events.go | 27 +++-- internal/web/healthcheck.go | 8 +- internal/web/index.go | 23 ++-- internal/web/logs.go | 37 +++---- internal/web/profile.go | 8 +- internal/web/releases.go | 7 +- internal/web/routes.go | 6 +- main.go | 102 +++++++++--------- package.json | 2 +- 38 files changed, 278 insertions(+), 265 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7487fc63..54497127 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,7 +54,7 @@ jobs: - name: Install Go uses: actions/setup-go@v5 with: - go-version: 1.22.x + go-version: 1.23.x check-latest: true - name: Checkout code uses: actions/checkout@v4 diff --git a/go.mod b/go.mod index 24c224f1..b1bccf9e 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,6 @@ require ( github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/sergi/go-diff v1.3.1 // indirect - github.com/sirupsen/logrus v1.9.3 github.com/spf13/afero v1.11.0 github.com/stretchr/objx v0.5.2 // indirect github.com/stretchr/testify v1.9.0 @@ -30,6 +29,7 @@ require ( github.com/go-chi/jwtauth/v5 v5.3.1 github.com/goccy/go-json v0.10.3 github.com/puzpuzpuz/xsync/v3 v3.4.0 + github.com/rs/zerolog v1.33.0 github.com/samber/lo v1.47.0 github.com/wk8/go-ordered-map/v2 v2.1.8 github.com/yuin/goldmark v1.7.4 @@ -59,6 +59,8 @@ require ( github.com/lestrrat-go/jwx/v2 v2.1.1 // indirect github.com/lestrrat-go/option v1.0.1 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index b079f4fe..865e02b8 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,7 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -28,8 +29,6 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnN github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= -github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY= -github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v27.1.2+incompatible h1:AhGzR1xaQIy53qCkxARaFluI00WPGtXn0AJuoQsVYTY= github.com/docker/docker v27.1.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= @@ -51,6 +50,7 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -85,6 +85,11 @@ github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0V github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= github.com/moby/term v0.0.0-20201216013528-df9cb8a40635 h1:rzf0wL0CHVc8CEsgyygG0Mn9CNCCPZqOPaz8RiiHYQk= @@ -102,8 +107,9 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4= github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA= -github.com/samber/lo v1.46.0 h1:w8G+oaCPgz1PoCJztqymCFaKwXt+5cCXn51uPxExFfQ= -github.com/samber/lo v1.46.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc= github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU= github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= @@ -121,7 +127,6 @@ github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -183,10 +188,12 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/internal/agent/client.go b/internal/agent/client.go index 1251ed78..56deccbf 100644 --- a/internal/agent/client.go +++ b/internal/agent/client.go @@ -13,7 +13,7 @@ import ( "github.com/amir20/dozzle/internal/agent/pb" "github.com/amir20/dozzle/internal/docker" "github.com/amir20/dozzle/internal/utils" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" orderedmap "github.com/wk8/go-ordered-map/v2" "google.golang.org/grpc" "google.golang.org/grpc/codes" @@ -32,7 +32,7 @@ func NewClient(endpoint string, certificates tls.Certificate, opts ...grpc.DialO caCertPool := x509.NewCertPool() c, err := x509.ParseCertificate(certificates.Certificate[0]) if err != nil { - log.Fatalf("failed to parse certificate: %v", err) + return nil, fmt.Errorf("failed to parse certificate: %w", err) } caCertPool.AddCert(c) tlsConfig := &tls.Config{ @@ -126,7 +126,8 @@ func sendLogs(stream pb.AgentService_StreamLogsClient, events chan<- *docker.Log m, err := resp.Event.Message.UnmarshalNew() if err != nil { - log.Fatalf("cannot unpack message %v", err) + log.Error().Err(err).Msg("agent client: failed to unmarshal message") + continue } var message any @@ -136,8 +137,10 @@ func sendLogs(stream pb.AgentService_StreamLogsClient, events chan<- *docker.Log case *pb.ComplexMessage: message = jsonBytesToOrderedMap(m.Data) + default: - log.Fatalf("agent client: unknown type %T", m) + log.Error().Type("message", m).Msg("agent client: unknown message type") + continue } events <- &docker.LogEvent{ @@ -175,7 +178,7 @@ func (c *Client) StreamRawBytes(ctx context.Context, containerID string, since t if err == io.EOF || err == context.Canceled { return } else { - log.Warnf("error while streaming raw bytes %v", err) + log.Error().Err(err).Msg("agent client: failed to receive raw bytes") return } } diff --git a/internal/agent/client_test.go b/internal/agent/client_test.go index bf5c947f..3a16f79d 100644 --- a/internal/agent/client_test.go +++ b/internal/agent/client_test.go @@ -127,7 +127,8 @@ func init() { Stats: utils.NewRingBuffer[docker.ContainerStat](300), }, nil) - server := NewServer(client, certs, "test") + server, _ := NewServer(client, certs, "test") + go server.Serve(lis) } diff --git a/internal/agent/server.go b/internal/agent/server.go index 2180b211..b6c6dd6f 100644 --- a/internal/agent/server.go +++ b/internal/agent/server.go @@ -6,13 +6,14 @@ import ( "crypto/tls" "crypto/x509" "encoding/json" + "fmt" + "time" "github.com/amir20/dozzle/internal/agent/pb" "github.com/amir20/dozzle/internal/docker" + "github.com/rs/zerolog/log" orderedmap "github.com/wk8/go-ordered-map/v2" - - log "github.com/sirupsen/logrus" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" @@ -302,11 +303,11 @@ func (s *server) ContainerAction(ctx context.Context, in *pb.ContainerActionRequ return &pb.ContainerActionResponse{}, nil } -func NewServer(client docker.Client, certificates tls.Certificate, dozzleVersion string) *grpc.Server { +func NewServer(client docker.Client, certificates tls.Certificate, dozzleVersion string) (*grpc.Server, error) { caCertPool := x509.NewCertPool() c, err := x509.ParseCertificate(certificates.Certificate[0]) if err != nil { - log.Fatalf("failed to parse certificate: %v", err) + return nil, fmt.Errorf("failed to parse certificate: %w", err) } caCertPool.AddCert(c) @@ -323,7 +324,7 @@ func NewServer(client docker.Client, certificates tls.Certificate, dozzleVersion grpcServer := grpc.NewServer(grpc.Creds(creds)) pb.RegisterAgentServiceServer(grpcServer, newServer(client, dozzleVersion)) - return grpcServer + return grpcServer, nil } func logEventToPb(event *docker.LogEvent) *pb.LogEvent { @@ -344,7 +345,7 @@ func logEventToPb(event *docker.LogEvent) *pb.LogEvent { }) default: - log.Fatalf("agent server: unknown type %T", event.Message) + log.Error().Type("message", event.Message).Msg("agent server: unknown message type") } return &pb.LogEvent{ diff --git a/internal/analytics/http_beacon.go b/internal/analytics/http_beacon.go index 09574b4b..4bcf600e 100644 --- a/internal/analytics/http_beacon.go +++ b/internal/analytics/http_beacon.go @@ -7,11 +7,11 @@ import ( "net/http" "net/http/httputil" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) func SendBeacon(e BeaconEvent) error { - log.Tracef("sending beacon: %+v", e) + log.Trace().Interface("event", e).Msg("sending beacon") jsonValue, err := json.Marshal(e) if err != nil { return err @@ -33,7 +33,7 @@ func SendBeacon(e BeaconEvent) error { if err != nil { return err } - log.Debugf("%v", string(dump)) + log.Debug().Str("response", string(dump)).Msg("google analytics returned non-2xx status code") return fmt.Errorf("google analytics returned non-2xx status code: %v", response.Status) } diff --git a/internal/auth/proxy.go b/internal/auth/proxy.go index e6875dfe..336180ac 100644 --- a/internal/auth/proxy.go +++ b/internal/auth/proxy.go @@ -7,7 +7,7 @@ import ( "net/http" "strings" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) type contextKey string @@ -49,6 +49,6 @@ func (p *proxyAuthContext) AuthMiddleware(next http.Handler) http.Handler { } func (p *proxyAuthContext) CreateToken(username, password string) (string, error) { - log.Fatalf("CreateToken not implemented for proxy auth") + log.Fatal().Msg("CreateToken not implemented in proxy auth") return "", nil } diff --git a/internal/auth/users.go b/internal/auth/users.go index d8706dab..e8437e2e 100644 --- a/internal/auth/users.go +++ b/internal/auth/users.go @@ -12,7 +12,7 @@ import ( "time" "github.com/go-chi/jwtauth/v5" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" "gopkg.in/yaml.v3" ) @@ -90,11 +90,11 @@ func decodeUsersFromFile(path string) (UserDatabase, error) { for username, user := range users.Users { user.Username = username if user.Password == "" { - log.Fatalf("User %s has no password", username) + log.Fatal().Msgf("User %s has an empty password", username) } if len(user.Password) != 64 { - log.Fatalf("User %s has an invalid password hash", username) + log.Fatal().Str("password", user.Password).Msgf("User %s has an invalid password hash", username) } if user.Name == "" { @@ -115,7 +115,7 @@ func (u *UserDatabase) readFileIfChanged() error { } if info.ModTime().After(u.LastRead) { - log.Infof("Found changes to %s. Updating users...", u.Path) + log.Info().Msg("Reloading user database") users, err := decodeUsersFromFile(u.Path) if err != nil { return err @@ -129,7 +129,8 @@ func (u *UserDatabase) readFileIfChanged() error { func (u *UserDatabase) Find(username string) *User { if err := u.readFileIfChanged(); err != nil { - log.Errorf("Error reading users file: %s", err) + log.Error().Err(err).Msg("Failed to read user database") + return nil } user, ok := u.Users[username] if !ok { diff --git a/internal/cache/expire.go b/internal/cache/expire.go index 451061d7..31d7140a 100644 --- a/internal/cache/expire.go +++ b/internal/cache/expire.go @@ -4,7 +4,7 @@ import ( "sync" "time" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) type Cache[T any] struct { @@ -36,11 +36,7 @@ func (c *Cache[T]) GetWithHit() (T, error, bool) { } c.Timestamp = time.Now() } - if hit { - log.Debugf("Cache hit for %T", c.Data) - } else { - log.Debugf("Cache miss for %T", c.Data) - } + log.Debug().Bool("hit", hit).Type("data", c.Data).Msg("Cache hit") return c.Data, nil, hit } diff --git a/internal/docker/client.go b/internal/docker/client.go index 0e7a0f85..0cd1e63e 100644 --- a/internal/docker/client.go +++ b/internal/docker/client.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "io" - "path/filepath" "sort" "strconv" "strings" @@ -21,7 +20,7 @@ import ( "github.com/docker/docker/api/types/system" "github.com/docker/docker/client" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) type StdType int @@ -83,7 +82,7 @@ type httpClient struct { func NewClient(cli DockerCLI, filters filters.Args, host Host) Client { info, err := cli.Info(context.Background()) if err != nil { - log.Errorf("unable to get docker info: %v", err) + log.Error().Err(err).Msg("Failed to get docker info") } host.NCPU = info.NCPU @@ -107,7 +106,7 @@ func NewLocalClient(f map[string][]string, hostname string) (Client, error) { } } - log.Debugf("filterArgs = %v", filterArgs) + log.Debug().Interface("filterArgs", filterArgs).Msg("Creating local client") cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) @@ -149,10 +148,10 @@ func NewRemoteClient(f map[string][]string, host Host) (Client, error) { } } - log.Debugf("filterArgs = %v", filterArgs) + log.Debug().Interface("filterArgs", filterArgs).Msg("Creating remote client") if host.URL.Scheme != "tcp" { - log.Fatal("Only tcp scheme is supported") + return nil, fmt.Errorf("invalid scheme: %s", host.URL.Scheme) } opts := []client.Opt{ @@ -160,10 +159,10 @@ func NewRemoteClient(f map[string][]string, host Host) (Client, error) { } if host.ValidCerts { - log.Debugf("Using TLS client config with certs at: %s", filepath.Dir(host.CertPath)) + log.Debug().Str("caCertPath", host.CACertPath).Str("certPath", host.CertPath).Str("keyPath", host.KeyPath).Msg("Using TLS for remote client") opts = append(opts, client.WithTLSClientConfig(host.CACertPath, host.CertPath, host.KeyPath)) } else { - log.Debugf("No valid certs found, using plain TCP") + log.Debug().Msg("Not using TLS for remote client") } opts = append(opts, client.WithAPIVersionNegotiation()) @@ -181,7 +180,7 @@ func NewRemoteClient(f map[string][]string, host Host) (Client, error) { // Finds a container by id, skipping the filters func (d *httpClient) FindContainer(id string) (Container, error) { - log.Debugf("finding container with id: %s", id) + log.Debug().Str("id", id).Msg("Finding container") if json, err := d.cli.ContainerInspect(context.Background(), id); err == nil { return newContainerFromJSON(json, d.host.ID), nil } else { @@ -204,7 +203,7 @@ func (d *httpClient) ContainerActions(action ContainerAction, containerID string } func (d *httpClient) ListContainers() ([]Container, error) { - log.Debugf("listing containers with filters: %v", d.filters) + log.Debug().Interface("filter", d.filters).Str("host", d.host.Name).Msg("Listing containers") containerListOptions := container.ListOptions{ Filters: d.filters, All: true, @@ -277,7 +276,7 @@ func (d *httpClient) ContainerStats(ctx context.Context, id string, stats chan<- } func (d *httpClient) ContainerLogs(ctx context.Context, id string, since time.Time, stdType StdType) (io.ReadCloser, error) { - log.WithField("id", id).WithField("since", since).WithField("stdType", stdType).Debug("streaming logs for container") + log.Debug().Str("id", id).Time("since", since).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Streaming logs for container") sinceQuery := since.Add(-50 * time.Millisecond).Format(time.RFC3339Nano) options := container.LogsOptions{ @@ -320,6 +319,7 @@ func (d *httpClient) ContainerEvents(ctx context.Context, messages chan<- Contai } func (d *httpClient) ContainerLogsBetweenDates(ctx context.Context, id string, from time.Time, to time.Time, stdType StdType) (io.ReadCloser, error) { + log.Debug().Str("id", id).Time("from", from).Time("to", to).Stringer("stdType", stdType).Str("host", d.host.Name).Msg("Fetching logs between dates for container") options := container.LogsOptions{ ShowStdout: stdType&STDOUT != 0, ShowStderr: stdType&STDERR != 0, @@ -328,8 +328,6 @@ func (d *httpClient) ContainerLogsBetweenDates(ctx context.Context, id string, f Until: to.Format(time.RFC3339Nano), } - log.Debugf("fetching logs from Docker with option: %+v", options) - reader, err := d.cli.ContainerLogs(ctx, id, options) if err != nil { return nil, err diff --git a/internal/docker/container_store.go b/internal/docker/container_store.go index 1af0d2ac..63529de7 100644 --- a/internal/docker/container_store.go +++ b/internal/docker/container_store.go @@ -7,8 +7,8 @@ import ( "sync/atomic" "github.com/puzpuzpuz/xsync/v3" + "github.com/rs/zerolog/log" "github.com/samber/lo" - log "github.com/sirupsen/logrus" "golang.org/x/sync/semaphore" ) @@ -51,10 +51,10 @@ var ( func (s *ContainerStore) checkConnectivity() error { if s.connected.CompareAndSwap(false, true) { go func() { - log.Debugf("subscribing to docker events from container store %s", s.client.Host()) + log.Debug().Str("host", s.client.Host().Name).Msg("docker store subscribing docker events") err := s.client.ContainerEvents(s.ctx, s.events) if !errors.Is(err, context.Canceled) { - log.Errorf("docker store unexpectedly disconnected from docker events from %s with %v", s.client.Host(), err) + log.Error().Err(err).Str("host", s.client.Host().Name).Msg("docker store unexpectedly disconnected from docker events") } s.connected.Store(false) }() @@ -76,7 +76,7 @@ func (s *ContainerStore) checkConnectivity() error { for i, c := range running { if err := sem.Acquire(s.ctx, 1); err != nil { - log.Errorf("failed to acquire semaphore: %v", err) + log.Error().Err(err).Msg("failed to acquire semaphore") break } go func(c Container, i int) { @@ -88,10 +88,10 @@ func (s *ContainerStore) checkConnectivity() error { } if err := sem.Acquire(s.ctx, maxFetchParallelism); err != nil { - log.Errorf("failed to acquire semaphore: %v", err) + log.Error().Err(err).Msg("failed to acquire semaphore") } - log.Debugf("finished initializing container store with %d containers", len(containers)) + log.Debug().Int("containers", len(containers)).Msg("finished initializing container store") } } @@ -120,7 +120,7 @@ func (s *ContainerStore) FindContainer(id string) (Container, error) { if ok { return *container, nil } else { - log.Warnf("container %s not found in store", id) + log.Warn().Str("id", id).Msg("container not found") return Container{}, ErrContainerNotFound } } @@ -132,7 +132,6 @@ func (s *ContainerStore) Client() Client { func (s *ContainerStore) SubscribeEvents(ctx context.Context, events chan<- ContainerEvent) { go func() { if s.statsCollector.Start(s.ctx) { - log.Debug("clearing container stats as stats collector has been stopped") s.containers.Range(func(_ string, c *Container) bool { c.Stats.Clear() return true @@ -171,7 +170,7 @@ func (s *ContainerStore) init() { for { select { case event := <-s.events: - log.Tracef("received event: %+v", event) + log.Trace().Str("event", event.Name).Str("id", event.ActorID).Msg("received container event") switch event.Name { case "start": if container, err := s.client.FindContainer(event.ActorID); err == nil { @@ -183,7 +182,7 @@ func (s *ContainerStore) init() { }) if valid { - log.Debugf("container %s started", container.ID) + log.Debug().Str("id", container.ID).Msg("container started") s.containers.Store(container.ID, &container) s.newContainerSubscribers.Range(func(c context.Context, containers chan<- Container) bool { select { @@ -195,13 +194,13 @@ func (s *ContainerStore) init() { } } case "destroy": - log.Debugf("container %s destroyed", event.ActorID) + log.Debug().Str("id", event.ActorID).Msg("container destroyed") s.containers.Delete(event.ActorID) case "die": s.containers.Compute(event.ActorID, func(c *Container, loaded bool) (*Container, bool) { if loaded { - log.Debugf("container %s died", c.ID) + log.Debug().Str("id", c.ID).Msg("container died") c.State = "exited" return c, false } else { @@ -216,7 +215,7 @@ func (s *ContainerStore) init() { s.containers.Compute(event.ActorID, func(c *Container, loaded bool) (*Container, bool) { if loaded { - log.Debugf("health status for container %s is %s", c.ID, healthy) + log.Debug().Str("id", c.ID).Str("health", healthy).Msg("container health status changed") c.Health = healthy return c, false } else { diff --git a/internal/docker/event_generator.go b/internal/docker/event_generator.go index 8c543ada..d2a0fd51 100644 --- a/internal/docker/event_generator.go +++ b/internal/docker/event_generator.go @@ -17,7 +17,7 @@ import ( orderedmap "github.com/wk8/go-ordered-map/v2" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) type EventGenerator struct { @@ -100,7 +100,6 @@ func (g *EventGenerator) consumeReader() { if readerError != nil { if readerError != ErrBadHeader { - log.Tracef("reader error: %v", readerError) g.Errors <- readerError close(g.buffer) break @@ -141,7 +140,7 @@ func readEvent(reader *bufio.Reader, tty bool) (string, StdType, error) { return "", streamType, err } if n != 8 { - log.Warnf("unable to read header: %v", header) + log.Warn().Bytes("header", header).Msg("short read") message, _ := reader.ReadString('\n') return message, streamType, ErrBadHeader } @@ -152,7 +151,7 @@ func readEvent(reader *bufio.Reader, tty bool) (string, StdType, error) { case 2: streamType = STDERR default: - log.Warnf("unknown stream type: %v", header[0]) + log.Warn().Bytes("header", header).Msg("unknown stream type") } count := binary.BigEndian.Uint32(header[4:]) @@ -183,7 +182,7 @@ func createEvent(message string, streamType StdType) *LogEvent { var jsonErr *json.UnmarshalTypeError if errors.As(err, &jsonErr) { if jsonErr.Value == "string" { - log.Warnf("unable to parse json logs - error was \"%v\" while trying unmarshal \"%v\"", err.Error(), message) + log.Warn().Err(err).Str("value", jsonErr.Value).Msg("failed to unmarshal json") } } } else { diff --git a/internal/docker/host.go b/internal/docker/host.go index 8fe0153a..60e4ae4e 100644 --- a/internal/docker/host.go +++ b/internal/docker/host.go @@ -7,7 +7,7 @@ import ( "path/filepath" "strings" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) type Host struct { @@ -49,14 +49,14 @@ func ParseConnection(connection string) (Host, error) { basePath, err := filepath.Abs("./certs") if err != nil { - log.Fatalf("error converting certs path to absolute: %s", err) + return Host{}, err } host := remoteUrl.Hostname() if _, err := os.Stat(filepath.Join(basePath, host)); !os.IsNotExist(err) { basePath = filepath.Join(basePath, host) } else { - log.Debugf("Remote host certificate path does not exist %s, falling back to default: %s", filepath.Join(basePath, host), basePath) + log.Debug().Msgf("Remote host certificate path does not exist %s, falling back to default: %s", filepath.Join(basePath, host), basePath) } cacertPath := filepath.Join(basePath, "ca.pem") diff --git a/internal/docker/level_guesser.go b/internal/docker/level_guesser.go index 3bd2ed09..de98d1b4 100644 --- a/internal/docker/level_guesser.go +++ b/internal/docker/level_guesser.go @@ -4,7 +4,7 @@ import ( "regexp" "strings" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" orderedmap "github.com/wk8/go-ordered-map/v2" ) @@ -74,7 +74,7 @@ func guessLogLevel(logEvent *LogEvent) string { } default: - log.Debugf("unknown type to guess level: %T", value) + log.Debug().Type("type", value).Msg("unknown logEvent type") } return "" diff --git a/internal/docker/stats_collector.go b/internal/docker/stats_collector.go index 9516097c..bfc6762b 100644 --- a/internal/docker/stats_collector.go +++ b/internal/docker/stats_collector.go @@ -9,7 +9,7 @@ import ( "time" "github.com/puzpuzpuz/xsync/v3" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) type StatsCollector struct { @@ -48,7 +48,7 @@ func (c *StatsCollector) forceStop() { if c.stopper != nil { c.stopper() c.stopper = nil - log.Debug("stopping container stats collector") + log.Debug().Str("host", c.client.Host().ID).Msg("stopped container stats collector") } } @@ -56,7 +56,6 @@ func (c *StatsCollector) Stop() { c.mu.Lock() defer c.mu.Unlock() if c.totalStarted.Add(-1) == 0 { - log.Tracef("scheduled to stop container stats collector %s", c.client.Host()) c.timer = time.AfterFunc(timeToStop, func() { c.forceStop() }) @@ -66,7 +65,6 @@ func (c *StatsCollector) Stop() { func (c *StatsCollector) reset() { c.mu.Lock() defer c.mu.Unlock() - log.Tracef("resetting timer for container stats collector %s", c.client.Host()) if c.timer != nil { c.timer.Stop() } @@ -76,11 +74,11 @@ func (c *StatsCollector) reset() { func streamStats(parent context.Context, sc *StatsCollector, id string) { ctx, cancel := context.WithCancel(parent) sc.cancelers.Store(id, cancel) - log.Debugf("starting to stream stats for: %s", id) + log.Debug().Str("container", id).Str("host", sc.client.Host().Name).Msg("starting to stream stats") if err := sc.client.ContainerStats(ctx, id, sc.stream); err != nil { - log.Debugf("stopping to stream stats for: %s", id) + log.Debug().Str("container", id).Str("host", sc.client.Host().Name).Err(err).Msg("stopping to stream stats") if !errors.Is(err, context.Canceled) && !errors.Is(err, io.EOF) { - log.Errorf("unexpected error when streaming container stats: %v", err) + log.Error().Str("container", id).Str("host", sc.client.Host().Name).Err(err).Msg("unexpected error while streaming stats") } } } @@ -106,16 +104,16 @@ func (sc *StatsCollector) Start(parentCtx context.Context) bool { } } } else { - log.Errorf("error while listing containers: %v", err) + log.Error().Str("host", sc.client.Host().Name).Err(err).Msg("failed to list containers") } events := make(chan ContainerEvent) go func() { - log.Debugf("subscribing to docker events from stats collector %s", sc.client.Host()) + log.Debug().Str("host", sc.client.Host().Name).Msg("starting to listen to docker events") err := sc.client.ContainerEvents(context.Background(), events) if !errors.Is(err, context.Canceled) { - log.Errorf("stats collector unexpectedly disconnected from docker events from %s with %v", sc.client.Host(), err) + log.Error().Str("host", sc.client.Host().Name).Err(err).Msg("unexpected error while listening to docker events") } sc.forceStop() }() @@ -137,7 +135,7 @@ func (sc *StatsCollector) Start(parentCtx context.Context) bool { for { select { case <-ctx.Done(): - log.Info("stopped collecting container stats") + log.Info().Str("host", sc.client.Host().Name).Msg("stopped container stats collector") return true case stat := <-sc.stream: sc.subscribers.Range(func(c context.Context, stats chan<- ContainerStat) bool { diff --git a/internal/healthcheck/http.go b/internal/healthcheck/http.go index c4bc492f..024c6140 100644 --- a/internal/healthcheck/http.go +++ b/internal/healthcheck/http.go @@ -5,7 +5,7 @@ import ( "net/http" "strings" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) func HttpRequest(addr string, base string) error { @@ -23,7 +23,7 @@ func HttpRequest(addr string, base string) error { url = "http://" + url } - log.Info("Checking health of " + url) + log.Info().Str("url", url).Msg("performing healthcheck") resp, err := http.Get(url) if err != nil { diff --git a/internal/healthcheck/rpc.go b/internal/healthcheck/rpc.go index e1426462..17c495e4 100644 --- a/internal/healthcheck/rpc.go +++ b/internal/healthcheck/rpc.go @@ -4,15 +4,15 @@ import ( "crypto/tls" "github.com/amir20/dozzle/internal/agent" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) func RPCRequest(addr string, certs tls.Certificate) error { client, err := agent.NewClient(addr, certs) if err != nil { - log.Fatalf("Failed to create agent client: %v", err) + log.Fatal().Err(err).Msg("Failed to create agent client") } containers, err := client.ListContainers() - log.Tracef("Found %d containers.", len(containers)) + log.Trace().Int("containers", len(containers)).Msg("Healtcheck RPC request completed") return err } diff --git a/internal/profile/disk.go b/internal/profile/disk.go index adfb00f5..3cbeb2e2 100644 --- a/internal/profile/disk.go +++ b/internal/profile/disk.go @@ -10,7 +10,7 @@ import ( "path/filepath" "github.com/amir20/dozzle/internal/auth" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) const ( @@ -51,12 +51,12 @@ var mux = &sync.Mutex{} func init() { path, err := filepath.Abs("./data") if err != nil { - log.Fatalf("Unable to get absolute path for data directory: %s", err) + log.Fatal().Err(err).Msg("Unable to get absolute path") return } if _, err := os.Stat(path); os.IsNotExist(err) { if err := os.Mkdir(path, 0755); err != nil { - log.Fatalf("Unable to create data directory: %s", err) + log.Fatal().Err(err).Msg("Unable to create data directory") return } } @@ -103,7 +103,7 @@ func Save(user auth.User, profile Profile) error { return err } - log.Debugf("Saved settings for user %s", user.Username) + log.Debug().Str("path", filePath).Msg("Profile saved") return f.Sync() } diff --git a/internal/support/cli/analytics.go b/internal/support/cli/analytics.go index 0d0906e0..f12f4366 100644 --- a/internal/support/cli/analytics.go +++ b/internal/support/cli/analytics.go @@ -3,7 +3,7 @@ package cli import ( "github.com/amir20/dozzle/internal/analytics" "github.com/amir20/dozzle/internal/docker" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) func StartEvent(args Args, mode string, client docker.Client, subCommand string) { @@ -33,8 +33,8 @@ func StartEvent(args Args, mode string, client docker.Client, subCommand string) event.ServerID = "n/a" } - log.Tracef("sending beacon event: %+v", event) + log.Trace().Interface("event", event).Msg("Sending analytics event") if err := analytics.SendBeacon(event); err != nil { - log.Debug(err) + log.Debug().Err(err).Msg("Failed to send analytics event") } } diff --git a/internal/support/cli/certs.go b/internal/support/cli/certs.go index 3c8f8ada..507f0126 100644 --- a/internal/support/cli/certs.go +++ b/internal/support/cli/certs.go @@ -5,17 +5,16 @@ import ( "embed" "os" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) func ReadCertificates(certs embed.FS) (tls.Certificate, error) { if pair, err := tls.LoadX509KeyPair("dozzle_cert.pem", "dozzle_key.pem"); err == nil { - log.Infof("Found dozzle certificate and key at ./dozzle_cert.pem and ./dozzle_key.pem") + log.Info().Msg("Loaded custom dozzle certificate and key") return pair, nil } else { if !os.IsNotExist(err) { - log.Errorf("Failed to load dozzle certificate and key: %v", err) - log.Warnf("Falling back to shared certificate and key") + log.Fatal().Err(err).Msg("Failed to load custom dozzle certificate and key. Stopping...") } } diff --git a/internal/support/cli/clients.go b/internal/support/cli/clients.go index 3b3add8d..55cdb1c0 100644 --- a/internal/support/cli/clients.go +++ b/internal/support/cli/clients.go @@ -5,31 +5,30 @@ import ( "github.com/amir20/dozzle/internal/docker" docker_support "github.com/amir20/dozzle/internal/support/docker" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) func CreateMultiHostService(embeddedCerts embed.FS, args Args) (docker.Client, *docker_support.MultiHostService) { var clients []docker_support.ClientService if len(args.RemoteHost) > 0 { - log.Warnf(`Remote host flag is deprecated and will be removed in future versions. Agents will replace remote hosts as a safer and performant option. See https://github.com/amir20/dozzle/issues/3066 for discussion.`) + log.Warn().Msg(`Remote host flag is deprecated and will be removed in future versions. Agents will replace remote hosts as a safer and performant option. See https://github.com/amir20/dozzle/issues/3066 for discussion.`) } for _, remoteHost := range args.RemoteHost { host, err := docker.ParseConnection(remoteHost) if err != nil { - log.Fatalf("Could not parse remote host %s: %s", remoteHost, err) + log.Fatal().Err(err).Interface("host", remoteHost).Msg("Could not parse remote host") } - log.Debugf("creating remote client for %s with %+v", host.Name, host) - log.Infof("Creating client for %s with %s", host.Name, host.URL.String()) + + log.Info().Interface("host", host).Msg("Adding remote host") if client, err := docker.NewRemoteClient(args.Filter, host); err == nil { if _, err := client.ListContainers(); err == nil { - log.Debugf("connected to local Docker Engine") clients = append(clients, docker_support.NewDockerClientService(client)) } else { - log.Warnf("Could not connect to remote host %s: %s", host.ID, err) + log.Warn().Err(err).Interface("host", host).Msg("Could not connect to remote host") } } else { - log.Warnf("Could not create client for %s: %s", host.ID, err) + log.Warn().Err(err).Interface("host", host).Msg("Could not create remote client") } } @@ -37,16 +36,16 @@ func CreateMultiHostService(embeddedCerts embed.FS, args Args) (docker.Client, * if err == nil { _, err := localClient.ListContainers() if err != nil { - log.Debugf("could not connect to local Docker Engine: %s", err) + log.Debug().Err(err).Msg("Could not connect to local Docker Engine") } else { - log.Debugf("connected to local Docker Engine") + log.Debug().Msg("Adding local Docker Engine") clients = append(clients, docker_support.NewDockerClientService(localClient)) } } certs, err := ReadCertificates(embeddedCerts) if err != nil { - log.Fatalf("Could not read certificates: %v", err) + log.Fatal().Err(err).Msg("Could not read certificates") } clientManager := docker_support.NewRetriableClientManager(args.RemoteAgent, certs, clients...) diff --git a/internal/support/cli/logger.go b/internal/support/cli/logger.go index 98ccaa00..728e0642 100644 --- a/internal/support/cli/logger.go +++ b/internal/support/cli/logger.go @@ -1,17 +1,25 @@ package cli import ( - log "github.com/sirupsen/logrus" + "os" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" ) func ConfigureLogger(level string) { - if l, err := log.ParseLevel(level); err == nil { - log.SetLevel(l) + if level, err := zerolog.ParseLevel(level); err == nil { + zerolog.SetGlobalLevel(level) } else { panic(err) } - log.SetFormatter(&log.TextFormatter{ - DisableLevelTruncation: true, - }) + _, dev := os.LookupEnv("DEV") + + if dev { + writer := zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) { + w.FieldsOrder = []string{"id", "from", "to", "since"} + }) + log.Logger = log.Output(writer) + } } diff --git a/internal/support/cli/valid_env.go b/internal/support/cli/valid_env.go index 11dd6b88..845ab954 100644 --- a/internal/support/cli/valid_env.go +++ b/internal/support/cli/valid_env.go @@ -5,7 +5,7 @@ import ( "reflect" "strings" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) func ValidateEnvVars(types ...interface{}) { @@ -26,7 +26,7 @@ func ValidateEnvVars(types ...interface{}) { for _, env := range os.Environ() { actual := strings.Split(env, "=")[0] if strings.HasPrefix(actual, "DOZZLE_") && !expectedEnvs[actual] { - log.Warnf("Unexpected environment variable %s", actual) + log.Warn().Str("env", actual).Msg("Unexpected environment variable") } } } diff --git a/internal/support/docker/multi_host_service.go b/internal/support/docker/multi_host_service.go index 5663d7c3..170f7438 100644 --- a/internal/support/docker/multi_host_service.go +++ b/internal/support/docker/multi_host_service.go @@ -5,7 +5,7 @@ import ( "fmt" "github.com/amir20/dozzle/internal/docker" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) type ContainerFilter = func(*docker.Container) bool @@ -36,8 +36,6 @@ func NewMultiHostService(manager ClientManager) *MultiHostService { manager: manager, } - log.Debugf("created multi host service manager %s", manager) - return m } @@ -75,7 +73,7 @@ func (m *MultiHostService) ListAllContainers() ([]docker.Container, []error) { list, err := client.ListContainers() if err != nil { host, _ := client.Host() - log.Debugf("error listing containers for host %s: %v", host.ID, err) + log.Debug().Err(err).Str("host", host.Name).Msg("error listing containers") host.Available = false errors = append(errors, &HostUnavailableError{Host: host, Err: err}) continue diff --git a/internal/support/docker/retriable_client_manager.go b/internal/support/docker/retriable_client_manager.go index bf2bacaa..a07f31ba 100644 --- a/internal/support/docker/retriable_client_manager.go +++ b/internal/support/docker/retriable_client_manager.go @@ -11,7 +11,7 @@ import ( "github.com/puzpuzpuz/xsync/v3" lop "github.com/samber/lo/parallel" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) type RetriableClientManager struct { @@ -23,18 +23,16 @@ type RetriableClientManager struct { } func NewRetriableClientManager(agents []string, certs tls.Certificate, clients ...ClientService) *RetriableClientManager { - log.Debugf("creating retriable client manager with %d clients and %d agents", len(clients), len(agents)) - clientMap := make(map[string]ClientService) for _, client := range clients { host, err := client.Host() if err != nil { - log.Warnf("error fetching host info for client %s: %v", host.ID, err) + log.Warn().Err(err).Str("host", host.Name).Msg("error fetching host info for client") continue } if _, ok := clientMap[host.ID]; ok { - log.Warnf("duplicate client found for host %s", host.ID) + log.Warn().Str("host", host.Name).Msg("duplicate client found") } else { clientMap[host.ID] = client } @@ -44,20 +42,20 @@ func NewRetriableClientManager(agents []string, certs tls.Certificate, clients . for _, endpoint := range agents { agent, err := agent.NewClient(endpoint, certs) if err != nil { - log.Warnf("error creating agent client for %s: %v", endpoint, err) + log.Warn().Err(err).Str("endpoint", endpoint).Msg("error creating agent client") failed = append(failed, endpoint) continue } host, err := agent.Host() if err != nil { - log.Warnf("error fetching host info for agent %s: %v", endpoint, err) + log.Warn().Err(err).Str("endpoint", endpoint).Msg("error fetching host info for agent") failed = append(failed, endpoint) continue } if _, ok := clientMap[host.ID]; ok { - log.Warnf("duplicate client found for host %s", host.ID) + log.Warn().Str("host", host.Name).Str("id", host.ID).Msg("duplicate host with same ID found") } else { clientMap[host.ID] = NewAgentService(agent) } @@ -88,7 +86,7 @@ func (m *RetriableClientManager) RetryAndList() ([]ClientService, []error) { for _, endpoint := range m.failedAgents { agent, err := agent.NewClient(endpoint, m.certs) if err != nil { - log.Warnf("error creating agent client for %s: %v", endpoint, err) + log.Warn().Err(err).Str("endpoint", endpoint).Msg("error creating agent client") errors = append(errors, err) newFailed = append(newFailed, endpoint) continue @@ -96,7 +94,7 @@ func (m *RetriableClientManager) RetryAndList() ([]ClientService, []error) { host, err := agent.Host() if err != nil { - log.Warnf("error fetching host info for agent %s: %v", endpoint, err) + log.Warn().Err(err).Str("endpoint", endpoint).Msg("error fetching host info for agent") errors = append(errors, err) newFailed = append(newFailed, endpoint) continue @@ -153,7 +151,6 @@ func (m *RetriableClientManager) Hosts() []docker.Host { hosts := lop.Map(clients, func(client ClientService, _ int) docker.Host { host, err := client.Host() - log.Debugf("host: %v, err: %v", host, err) if err != nil { host.Available = false } else { diff --git a/internal/support/docker/swarm_client_manager.go b/internal/support/docker/swarm_client_manager.go index 080b19e7..4b3327b7 100644 --- a/internal/support/docker/swarm_client_manager.go +++ b/internal/support/docker/swarm_client_manager.go @@ -14,7 +14,7 @@ import ( "github.com/samber/lo" lop "github.com/samber/lo/parallel" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) type SwarmClientManager struct { @@ -51,17 +51,17 @@ func NewSwarmClientManager(localClient docker.Client, certs tls.Certificate) *Sw id, ok := os.LookupEnv("HOSTNAME") if !ok { - log.Fatal("HOSTNAME environment variable not set when looking for swarm service name") + log.Fatal().Msg("HOSTNAME environment variable not set when looking for swarm service name") } container, err := localClient.FindContainer(id) if err != nil { - log.Fatalf("error finding container %s: %v", id, err) + log.Fatal().Err(err).Msg("error finding own container when looking for swarm service name") } serviceName := container.Labels["com.docker.swarm.service.name"] - log.Debugf("found swarm internal service name: %s", serviceName) + log.Debug().Str("service", serviceName).Msg("found swarm service name") return &SwarmClientManager{ localClient: localClient, @@ -85,12 +85,11 @@ func (m *SwarmClientManager) Subscribe(ctx context.Context, channel chan<- docke func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) { m.mu.Lock() - log.Debugf("looking up swarm services: tasks.%s", m.name) ips, err := net.LookupIP(fmt.Sprintf("tasks.%s", m.name)) errors := make([]error, 0) if err != nil { - log.Fatalf("error looking up swarm services: %v", err) + log.Fatal().Err(err).Msg("error looking up swarm service tasks") errors = append(errors, err) m.mu.Unlock() return m.List(), errors @@ -102,47 +101,51 @@ func (m *SwarmClientManager) RetryAndList() ([]ClientService, []error) { return host.Endpoint }) - log.Debugf("tasks.dozzle = %v, localIP = %v, clients.endpoints = %v", ips, m.localIPs, lo.Keys(endpoints)) + ipStrings := lo.Map(ips, func(ip net.IP, _ int) string { + return ip.String() + }) + + log.Debug().Strs(fmt.Sprintf("tasks.%s", m.name), ipStrings).Strs("localIPs", m.localIPs).Strs("clients.endpoints", lo.Keys(endpoints)).Msg("found swarm service tasks") for _, ip := range ips { if lo.Contains(m.localIPs, ip.String()) { - log.Debugf("skipping local ip %s", ip.String()) + log.Debug().Stringer("ip", ip).Msg("skipping local IP") continue } if _, ok := endpoints[ip.String()+":7007"]; ok { - log.Debugf("skipping existing client for %s", ip.String()) + log.Debug().Stringer("ip", ip).Msg("skipping existing client") continue } agent, err := agent.NewClient(ip.String()+":7007", m.certs) if err != nil { - log.Warnf("error creating client for %s: %v", ip, err) + log.Warn().Err(err).Stringer("ip", ip).Msg("error creating agent client") errors = append(errors, err) continue } host, err := agent.Host() if err != nil { - log.Warnf("error getting host data for agent %s: %v", ip, err) + log.Warn().Err(err).Stringer("ip", ip).Msg("error getting host from agent client") errors = append(errors, err) if err := agent.Close(); err != nil { - log.Warnf("error closing local client: %v", err) + log.Warn().Err(err).Stringer("ip", ip).Msg("error closing agent client") } continue } if host.ID == m.localClient.Host().ID { - log.Debugf("skipping local client with ID %s", host.ID) + log.Debug().Stringer("ip", ip).Msg("skipping local client") if err := agent.Close(); err != nil { - log.Warnf("error closing local client: %v", err) + log.Warn().Err(err).Stringer("ip", ip).Msg("error closing agent client") } continue } client := NewAgentService(agent) m.clients[host.ID] = client - log.Infof("added client for %s", host.ID) + log.Info().Stringer("ip", ip).Str("id", host.ID).Str("name", host.Name).Msg("added new swarm agent") m.subscribers.Range(func(ctx context.Context, channel chan<- docker.Host) bool { host.Available = true diff --git a/internal/web/actions.go b/internal/web/actions.go index 2c039693..e2fc08d6 100644 --- a/internal/web/actions.go +++ b/internal/web/actions.go @@ -5,35 +5,33 @@ import ( "github.com/amir20/dozzle/internal/docker" "github.com/go-chi/chi/v5" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) func (h *handler) containerActions(w http.ResponseWriter, r *http.Request) { action := chi.URLParam(r, "action") id := chi.URLParam(r, "id") - log.Debugf("container action: %s, container id: %s", action, id) - containerService, err := h.multiHostService.FindContainer(hostKey(r), id) if err != nil { - log.Errorf("error while trying to find container: %v", err) + log.Error().Err(err).Msg("error while trying to find container") http.Error(w, err.Error(), http.StatusNotFound) return } parsedAction, err := docker.ParseContainerAction(action) if err != nil { - log.Errorf("error while trying to parse action: %s", action) + log.Error().Err(err).Msg("error while trying to parse action") http.Error(w, err.Error(), http.StatusBadRequest) return } if err := containerService.Action(parsedAction); err != nil { - log.Errorf("error while trying to perform action: %s", action) + log.Error().Err(err).Msg("error while trying to perform container action") http.Error(w, err.Error(), http.StatusInternalServerError) return } - log.Infof("container action performed: %s; container id: %s", action, id) + log.Info().Str("action", action).Str("container", containerService.Container.Name).Msg("container action performed") http.Error(w, "", http.StatusNoContent) } diff --git a/internal/web/auth.go b/internal/web/auth.go index acc4d357..fbe94296 100644 --- a/internal/web/auth.go +++ b/internal/web/auth.go @@ -4,7 +4,7 @@ import ( "net/http" "time" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) func (h *handler) createToken(w http.ResponseWriter, r *http.Request) { @@ -19,11 +19,11 @@ func (h *handler) createToken(w http.ResponseWriter, r *http.Request) { Path: "/", SameSite: http.SameSiteLaxMode, }) - log.Infof("Token created for user %s", user) + log.Info().Str("user", user).Msg("Token created") w.WriteHeader(http.StatusOK) w.Write([]byte(http.StatusText(http.StatusOK))) } else { - log.Errorf("Error while creating token: %v", err) + log.Error().Err(err).Msg("Failed to create token") http.Error(w, err.Error(), http.StatusUnauthorized) } } diff --git a/internal/web/events.go b/internal/web/events.go index c47533e7..133ce23e 100644 --- a/internal/web/events.go +++ b/internal/web/events.go @@ -9,8 +9,7 @@ import ( "github.com/amir20/dozzle/internal/analytics" "github.com/amir20/dozzle/internal/docker" docker_support "github.com/amir20/dozzle/internal/support/docker" - - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) { @@ -37,17 +36,18 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) { allContainers, errors := h.multiHostService.ListAllContainers() for _, err := range errors { - log.Warnf("error listing containers: %v", err) + log.Warn().Err(err).Msg("error listing containers") if hostNotAvailableError, ok := err.(*docker_support.HostUnavailableError); ok { bytes, _ := json.Marshal(hostNotAvailableError.Host) if _, err := fmt.Fprintf(w, "event: update-host\ndata: %s\n\n", string(bytes)); err != nil { - log.Errorf("error writing event to event stream: %v", err) + log.Error().Err(err).Msg("error writing event to event stream") } } } if err := sendContainersJSON(allContainers, w); err != nil { - log.Errorf("error writing containers to event stream: %v", err) + log.Error().Err(err).Msg("error writing containers to event stream") + return } f.Flush() @@ -59,13 +59,13 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) { case host := <-availableHosts: bytes, _ := json.Marshal(host) if _, err := fmt.Fprintf(w, "event: update-host\ndata: %s\n\n", string(bytes)); err != nil { - log.Errorf("error writing event to event stream: %v", err) + log.Error().Err(err).Msg("error writing event to event stream") } f.Flush() case stat := <-stats: bytes, _ := json.Marshal(stat) if _, err := fmt.Fprintf(w, "event: container-stat\ndata: %s\n\n", string(bytes)); err != nil { - log.Errorf("error writing stat to event stream: %v", err) + log.Error().Err(err).Msg("error writing event to event stream") return } f.Flush() @@ -76,10 +76,10 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) { switch event.Name { case "start", "die", "destroy": if event.Name == "start" { - log.Debugf("found new container with id: %v", event.ActorID) + log.Debug().Str("container", event.ActorID).Msg("container started") if containers, err := h.multiHostService.ListContainersForHost(event.Host); err == nil { if err := sendContainersJSON(containers, w); err != nil { - log.Errorf("error encoding containers to stream: %v", err) + log.Error().Err(err).Msg("error writing containers to event stream") return } } @@ -87,14 +87,14 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) { bytes, _ := json.Marshal(event) if _, err := fmt.Fprintf(w, "event: container-event\ndata: %s\n\n", string(bytes)); err != nil { - log.Errorf("error writing event to event stream: %v", err) + log.Error().Err(err).Msg("error writing event to event stream") return } f.Flush() case "health_status: healthy", "health_status: unhealthy": - log.Debugf("triggering docker health event: %v", event.Name) + log.Debug().Str("container", event.ActorID).Str("health", event.Name).Msg("container health status") healthy := "unhealthy" if event.Name == "health_status: healthy" { healthy = "healthy" @@ -105,13 +105,12 @@ func (h *handler) streamEvents(w http.ResponseWriter, r *http.Request) { } bytes, _ := json.Marshal(payload) if _, err := fmt.Fprintf(w, "event: container-health\ndata: %s\n\n", string(bytes)); err != nil { - log.Errorf("error writing event to event stream: %v", err) + log.Error().Err(err).Msg("error writing event to event stream") return } f.Flush() } case <-ctx.Done(): - log.Debugf("context done, closing event stream") return } } @@ -140,7 +139,7 @@ func sendBeaconEvent(h *handler, r *http.Request, runningContainers int) { } if err := analytics.SendBeacon(b); err != nil { - log.Debugf("error sending beacon: %v", err) + log.Debug().Err(err).Msg("error sending beacon") } } diff --git a/internal/web/healthcheck.go b/internal/web/healthcheck.go index 9949a818..c9d48897 100644 --- a/internal/web/healthcheck.go +++ b/internal/web/healthcheck.go @@ -3,17 +3,17 @@ package web import ( "net/http" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) func (h *handler) healthcheck(w http.ResponseWriter, r *http.Request) { - log.Trace("Executing healthcheck request") + log.Trace().Msg("Healthcheck request received") _, errors := h.multiHostService.ListAllContainers() if len(errors) > 0 { - log.Error(errors) + log.Error().Err(errors[0]).Msg("Error listing containers") http.Error(w, "Error listing containers", http.StatusInternalServerError) } else { - http.Error(w, "OK", http.StatusOK) + w.WriteHeader(http.StatusOK) } } diff --git a/internal/web/index.go b/internal/web/index.go index d1a1da45..7a9976bf 100644 --- a/internal/web/index.go +++ b/internal/web/index.go @@ -16,7 +16,7 @@ import ( "github.com/amir20/dozzle/internal/auth" "github.com/amir20/dozzle/internal/profile" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) func (h *handler) index(w http.ResponseWriter, req *http.Request) { @@ -70,15 +70,15 @@ func (h *handler) executeTemplate(w http.ResponseWriter, req *http.Request) { } config["user"] = user } else if h.config.Authorization.Provider == FORWARD_PROXY { - log.Error("Unable to find remote user. Please check your proxy configuration. Expecting headers Remote-Email, Remote-User, Remote-Name.") - log.Debugf("Dumping all headers for url /%s", req.URL.String()) + log.Error().Msg("Unable to find remote user. Please check your proxy configuration. Expecting headers Remote-Email, Remote-User, Remote-Name.") + log.Debug().Str("url", req.URL.String()).Msg("Dumping all headers for request") for k, v := range req.Header { - log.Debugf("%s: %s", k, v) + log.Debug().Strs(k, v).Send() } http.Error(w, "Unauthorized user", http.StatusUnauthorized) return } else if h.config.Authorization.Provider == SIMPLE && req.URL.Path != "login" { - log.Debugf("Redirecting to login page for url /%s", req.URL.String()) + log.Debug().Str("url", req.URL.String()).Msg("Redirecting to login page") http.Redirect(w, req, path.Clean(h.config.Base+"/login")+"?redirectUrl=/"+req.URL.String(), http.StatusTemporaryRedirect) return } @@ -91,11 +91,11 @@ func (h *handler) executeTemplate(w http.ResponseWriter, req *http.Request) { } file, err := h.content.Open("index.html") if err != nil { - log.Panic(err) + log.Fatal().Err(err).Msg("Could not open index.html") } bytes, err := io.ReadAll(file) if err != nil { - log.Panic(err) + log.Fatal().Err(err).Msg("Could not read index.html") } tmpl, err := template.New("index.html").Funcs(template.FuncMap{ "marshal": func(v interface{}) template.JS { @@ -109,13 +109,12 @@ func (h *handler) executeTemplate(w http.ResponseWriter, req *http.Request) { }, }).Parse(string(bytes)) if err != nil { - log.Panic(err) + log.Fatal().Err(err).Msg("Could not parse index.html") } err = tmpl.Execute(w, data) if err != nil { - log.Panic(err) - http.Error(w, err.Error(), http.StatusInternalServerError) + log.Fatal().Err(err).Msg("Could not execute index.html") } } @@ -130,12 +129,12 @@ func (h *handler) readManifest() map[string]interface{} { } bytes, err := io.ReadAll(file) if err != nil { - log.Fatalf("Could not read .vite/manifest.json: %v", err) + log.Fatal().Err(err).Msg("Could not read .vite/manifest.json") } var manifest map[string]interface{} err = json.Unmarshal(bytes, &manifest) if err != nil { - log.Fatalf("Could not parse .vite/manifest.json: %v", err) + log.Fatal().Err(err).Msg("Could not unmarshal .vite/manifest.json") } return manifest } diff --git a/internal/web/logs.go b/internal/web/logs.go index 49f309f7..a7934dce 100644 --- a/internal/web/logs.go +++ b/internal/web/logs.go @@ -21,7 +21,7 @@ import ( "github.com/dustin/go-humanize" "github.com/go-chi/chi/v5" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) func (h *handler) downloadLogs(w http.ResponseWriter, r *http.Request) { @@ -105,7 +105,9 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request) events, err := containerService.LogsBetweenDates(r.Context(), from, to, stdTypes) if err != nil { - log.Errorf("error while streaming logs %v", err.Error()) + log.Error().Err(err).Msg("error fetching logs") + http.Error(w, err.Error(), http.StatusInternalServerError) + return } buffer := utils.NewRingBuffer[*docker.LogEvent](500) @@ -117,7 +119,8 @@ func (h *handler) fetchLogsBetweenDates(w http.ResponseWriter, r *http.Request) encoder := json.NewEncoder(w) for _, event := range buffer.Data() { if err := encoder.Encode(event); err != nil { - log.Errorf("json encoding error while streaming %v", err.Error()) + log.Error().Err(err).Msg("error encoding log event") + return } } } @@ -202,22 +205,22 @@ func streamLogsForContainers(w http.ResponseWriter, r *http.Request, multiHostCl defer ticker.Stop() existingContainers, errs := multiHostClient.ListAllContainersFiltered(filter) if len(errs) > 0 { - log.Warnf("error while listing containers %v", errs) + log.Warn().Err(errs[0]).Msg("error while listing containers") } streamLogs := func(container docker.Container) { containerService, err := multiHostClient.FindContainer(container.Host, container.ID) if err != nil { - log.Errorf("error while finding container %v", err.Error()) + log.Error().Err(err).Msg("error while finding container") return } err = containerService.StreamLogs(r.Context(), container.StartedAt, stdTypes, logs) if err != nil { if errors.Is(err, io.EOF) { - log.WithError(err).Debugf("stream closed for container %v", container.Name) + log.Debug().Str("container", container.ID).Msg("streaming ended") events <- &docker.ContainerEvent{ActorID: container.ID, Name: "container-stopped", Host: container.Host} } else if !errors.Is(err, context.Canceled) { - log.Errorf("unknown error while streaming %v", err.Error()) + log.Error().Err(err).Str("container", container.ID).Msg("unknown error while streaming logs") } } } @@ -234,7 +237,7 @@ loop: select { case event := <-logs: if buf, err := json.Marshal(event); err != nil { - log.Errorf("json encoding error while streaming %v", err.Error()) + log.Error().Err(err).Msg("error encoding log event") } else { fmt.Fprintf(w, "data: %s\n", buf) } @@ -251,28 +254,26 @@ loop: go streamLogs(container) case event := <-events: - log.Debugf("received container event %v", event) + log.Debug().Str("event", event.Name).Str("container", event.ActorID).Msg("received event") if buf, err := json.Marshal(event); err != nil { - log.Errorf("json encoding error while streaming %v", err.Error()) + log.Error().Err(err).Msg("error encoding container event") } else { fmt.Fprintf(w, "event: container-event\ndata: %s\n\n", buf) f.Flush() } case <-r.Context().Done(): - log.Debugf("context cancelled") break loop } } - if log.IsLevelEnabled(log.DebugLevel) { + if e := log.Debug(); e.Enabled() { var m runtime.MemStats runtime.ReadMemStats(&m) - log.WithFields(log.Fields{ - "allocated": humanize.Bytes(m.Alloc), - "totalAllocated": humanize.Bytes(m.TotalAlloc), - "system": humanize.Bytes(m.Sys), - "routines": runtime.NumGoroutine(), - }).Debug("runtime mem stats") + e.Str("allocated", humanize.Bytes(m.Alloc)). + Str("totalAllocated", humanize.Bytes(m.TotalAlloc)). + Str("system", humanize.Bytes(m.Sys)). + Int("routines", runtime.NumGoroutine()). + Msg("runtime mem stats") } } diff --git a/internal/web/profile.go b/internal/web/profile.go index 94113d48..a1921910 100644 --- a/internal/web/profile.go +++ b/internal/web/profile.go @@ -6,7 +6,7 @@ import ( "github.com/amir20/dozzle/internal/auth" "github.com/amir20/dozzle/internal/profile" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) func (h *handler) updateProfile(w http.ResponseWriter, r *http.Request) { @@ -18,7 +18,7 @@ func (h *handler) updateProfile(w http.ResponseWriter, r *http.Request) { if err := profile.UpdateFromReader(*user, r.Body); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) - log.Errorf("Unable to save user settings: %s", err) + log.Error().Err(err).Msg("Failed to update profile") return } @@ -39,7 +39,7 @@ func (h *handler) avatar(w http.ResponseWriter, r *http.Request) { return } - log.Debugf("Fetching avatar from %s", url) + log.Trace().Str("url", url).Msg("Fetching avatar") response, err := http.Get(url) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -49,7 +49,7 @@ func (h *handler) avatar(w http.ResponseWriter, r *http.Request) { defer response.Body.Close() if response.StatusCode != http.StatusOK { - log.Errorf("Received status code %d from %s", response.StatusCode, url) + log.Error().Str("url", url).Int("status", response.StatusCode).Msg("Failed to fetch avatar") return } diff --git a/internal/web/releases.go b/internal/web/releases.go index f9253b57..927e4e16 100644 --- a/internal/web/releases.go +++ b/internal/web/releases.go @@ -7,7 +7,7 @@ import ( "github.com/amir20/dozzle/internal/cache" "github.com/amir20/dozzle/internal/releases" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) var cachedReleases *cache.Cache[[]releases.Release] @@ -22,7 +22,7 @@ func (h *handler) releases(w http.ResponseWriter, r *http.Request) { if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) - log.Debugf("error reading releases: %v", err) + log.Debug().Err(err).Msg("error fetching releases") return } @@ -32,6 +32,7 @@ func (h *handler) releases(w http.ResponseWriter, r *http.Request) { } if err := json.NewEncoder(w).Encode(releases); err != nil { - log.Errorf("json encoding error while streaming %v", err.Error()) + log.Error().Err(err).Msg("error encoding releases") + http.Error(w, err.Error(), http.StatusInternalServerError) } } diff --git a/internal/web/routes.go b/internal/web/routes.go index c40a39b3..493c1c85 100644 --- a/internal/web/routes.go +++ b/internal/web/routes.go @@ -10,7 +10,7 @@ import ( docker_support "github.com/amir20/dozzle/internal/support/docker" "github.com/go-chi/chi/v5" - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) type AuthProvider string @@ -73,7 +73,7 @@ func createRouter(h *handler) *chi.Mux { } if h.config.Authorization.Provider != NONE && h.config.Authorization.Authorizer == nil { - log.Panic("Authorization provider is set but no authorizer is provided") + log.Fatal().Msg("Authorization provider is set but no authorizer is provided") } r.Route(base, func(r chi.Router) { @@ -134,7 +134,7 @@ func hostKey(r *http.Request) string { host := chi.URLParam(r, "host") if host == "" { - log.Fatalf("No host found for url %v", r.URL) + log.Fatal().Str("url", r.URL.String()).Msg("Host parameter not found in the URL path") } return host diff --git a/main.go b/main.go index 90383c48..c091a4ea 100644 --- a/main.go +++ b/main.go @@ -21,8 +21,7 @@ import ( "github.com/amir20/dozzle/internal/support/cli" docker_support "github.com/amir20/dozzle/internal/support/docker" "github.com/amir20/dozzle/internal/web" - - log "github.com/sirupsen/logrus" + "github.com/rs/zerolog/log" ) //go:embed all:dist @@ -40,44 +39,48 @@ func main() { case *cli.AgentCmd: client, err := docker.NewLocalClient(args.Filter, args.Hostname) if err != nil { - log.Fatalf("Could not create docker client: %v", err) + log.Fatal().Err(err).Msg("Could not create docker client") } certs, err := cli.ReadCertificates(certs) if err != nil { - log.Fatalf("Could not read certificates: %v", err) + log.Fatal().Err(err).Msg("Could not read certificates") } listener, err := net.Listen("tcp", args.Agent.Addr) if err != nil { - log.Fatalf("failed to listen: %v", err) + log.Fatal().Err(err).Msg("failed to listen") } tempFile, err := os.CreateTemp("./", "agent-*.addr") if err != nil { - log.Fatalf("failed to create temp file: %v", err) + log.Fatal().Err(err).Msg("failed to create temp file") } io.WriteString(tempFile, listener.Addr().String()) go cli.StartEvent(args, "", client, "agent") - server := agent.NewServer(client, certs, args.Version()) + server, err := agent.NewServer(client, certs, args.Version()) + if err != nil { + log.Fatal().Err(err).Msg("failed to create agent server") + } ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() go func() { - log.Infof("Dozzle agent version %s", args.Version()) - log.Infof("Agent listening on %s", listener.Addr().String()) + log.Info().Msgf("Dozzle agent version %s", args.Version()) + log.Info().Msgf("Agent listening on %s", listener.Addr().String()) + if err := server.Serve(listener); err != nil { - log.Fatalf("failed to serve: %v", err) + log.Error().Err(err).Msg("failed to serve") } }() <-ctx.Done() stop() - log.Info("Shutting down agent") + log.Info().Msg("Shutting down agent") server.Stop() - log.Debugf("deleting %s", tempFile.Name()) + log.Debug().Str("file", tempFile.Name()).Msg("Removing temp file") os.Remove(tempFile.Name()) case *cli.HealthcheckCmd: files, err := os.ReadDir(".") if err != nil { - log.Fatalf("Failed to read directory: %v", err) + log.Fatal().Err(err).Msg("Failed to read directory") } agentAddress := "" @@ -85,7 +88,7 @@ func main() { if match, _ := filepath.Match("agent-*.addr", file.Name()); match { data, err := os.ReadFile(file.Name()) if err != nil { - log.Fatalf("Failed to read file: %v", err) + log.Fatal().Err(err).Msg("Failed to read file") } agentAddress = string(data) break @@ -93,22 +96,22 @@ func main() { } if agentAddress == "" { if err := healthcheck.HttpRequest(args.Addr, args.Base); err != nil { - log.Fatalf("Failed to make request: %v", err) + log.Fatal().Err(err).Msg("Failed to make request") } } else { certs, err := cli.ReadCertificates(certs) if err != nil { - log.Fatalf("Could not read certificates: %v", err) + log.Fatal().Err(err).Msg("Could not read certificates") } if err := healthcheck.RPCRequest(agentAddress, certs); err != nil { - log.Fatalf("Failed to make request: %v", err) + log.Fatal().Err(err).Msg("Failed to make request") } } case *cli.GenerateCmd: cli.StartEvent(args, "", nil, "generate") if args.Generate.Username == "" || args.Generate.Password == "" { - log.Fatal("Username and password are required") + log.Fatal().Msg("Username and password are required") } buffer := auth.GenerateUsers(auth.User{ @@ -119,7 +122,7 @@ func main() { }, true) if _, err := os.Stdout.Write(buffer.Bytes()); err != nil { - log.Fatalf("Failed to write to stdout: %v", err) + log.Fatal().Err(err).Msg("Failed to write to stdout") } } @@ -127,55 +130,58 @@ func main() { } if args.AuthProvider != "none" && args.AuthProvider != "forward-proxy" && args.AuthProvider != "simple" { - log.Fatalf("Invalid auth provider %s", args.AuthProvider) + log.Fatal().Str("provider", args.AuthProvider).Msg("Invalid auth provider") } - log.Infof("Dozzle version %s", args.Version()) + log.Info().Msgf("Dozzle version %s", args.Version()) var multiHostService *docker_support.MultiHostService if args.Mode == "server" { var localClient docker.Client localClient, multiHostService = cli.CreateMultiHostService(certs, args) if multiHostService.TotalClients() == 0 { - log.Fatal("Could not connect to any Docker Engines") + log.Fatal().Msg("Could not connect to any Docker Engine") } else { - log.Infof("Connected to %d Docker Engine(s)", multiHostService.TotalClients()) + log.Info().Int("clients", multiHostService.TotalClients()).Msg("Connected to Docker") } go cli.StartEvent(args, "server", localClient, "") } else if args.Mode == "swarm" { localClient, err := docker.NewLocalClient(args.Filter, args.Hostname) if err != nil { - log.Fatalf("Could not connect to local Docker Engine: %s", err) + log.Fatal().Err(err).Msg("Could not create docker client") } certs, err := cli.ReadCertificates(certs) if err != nil { - log.Fatalf("Could not read certificates: %v", err) + log.Fatal().Err(err).Msg("Could not read certificates") } manager := docker_support.NewSwarmClientManager(localClient, certs) multiHostService = docker_support.NewMultiHostService(manager) - log.Infof("Starting in Swarm mode") + log.Info().Msg("Starting in swarm mode") listener, err := net.Listen("tcp", ":7007") if err != nil { - log.Fatalf("failed to listen: %v", err) + log.Fatal().Err(err).Msg("failed to listen") + } + server, err := agent.NewServer(localClient, certs, args.Version()) + if err != nil { + log.Fatal().Err(err).Msg("failed to create agent") } - server := agent.NewServer(localClient, certs, args.Version()) go cli.StartEvent(args, "swarm", localClient, "") go func() { - log.Infof("Agent listening on %s", listener.Addr().String()) + log.Info().Msgf("Dozzle agent version %s", args.Version()) if err := server.Serve(listener); err != nil { - log.Fatalf("failed to serve: %v", err) + log.Error().Err(err).Msg("failed to serve") } }() } else { - log.Fatalf("Invalid mode %s", args.Mode) + log.Fatal().Str("mode", args.Mode).Msg("Invalid mode") } srv := createServer(args, multiHostService) go func() { - log.Infof("Accepting connections on %s", srv.Addr) + log.Info().Msgf("Accepting connections on %s", args.Addr) if err := srv.ListenAndServe(); err != http.ErrServerClosed { - log.Fatal(err) + log.Fatal().Err(err).Msg("failed to listen") } }() ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) @@ -183,13 +189,13 @@ func main() { <-ctx.Done() stop() - log.Info("shutting down gracefully, press Ctrl+C again to force") + log.Info().Msg("shutting down gracefully, press Ctrl+C again to force") ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() if err := srv.Shutdown(ctx); err != nil { - log.Fatal(err) + log.Error().Err(err).Msg("failed to shut down") } - log.Debug("shutdown complete") + log.Debug().Msg("shut down complete") } func createServer(args cli.Args, multiHostService *docker_support.MultiHostService) *http.Server { @@ -198,29 +204,29 @@ func createServer(args cli.Args, multiHostService *docker_support.MultiHostServi var provider web.AuthProvider = web.NONE var authorizer web.Authorizer if args.AuthProvider == "forward-proxy" { - log.Debug("Using forward proxy authentication") + log.Debug().Msg("Using forward proxy authentication") provider = web.FORWARD_PROXY authorizer = auth.NewForwardProxyAuth(args.AuthHeaderUser, args.AuthHeaderEmail, args.AuthHeaderName) } else if args.AuthProvider == "simple" { - log.Debug("Using simple authentication") + log.Debug().Msg("Using simple authentication") provider = web.SIMPLE path, err := filepath.Abs("./data/users.yml") if err != nil { - log.Fatalf("Could not find absolute path to users.yml file: %s", err) + log.Fatal().Err(err).Msg("Could not get absolute path") } if _, err := os.Stat(path); os.IsNotExist(err) { - log.Fatalf("Could not find users.yml file at %s", path) + log.Fatal().Msg("users.yml file does not exist") } - log.Debugf("Reading users from %s", path) + log.Debug().Str("path", path).Msg("Reading users.yml file") db, err := auth.ReadUsersFromFile(path) if err != nil { - log.Fatalf("Could not read users.yml file at %s: %s", path, err) + log.Fatal().Err(err).Msg("Could not read users.yml file") } - log.Debugf("Read %d users", len(db.Users)) + log.Debug().Int("users", len(db.Users)).Msg("Loaded users") authorizer = auth.NewSimpleAuth(db) } @@ -240,25 +246,25 @@ func createServer(args cli.Args, multiHostService *docker_support.MultiHostServi assets, err := fs.Sub(content, "dist") if err != nil { - log.Fatalf("Could not open embedded dist folder: %v", err) + log.Fatal().Err(err).Msg("Could not get sub filesystem") } if _, ok := os.LookupEnv("LIVE_FS"); ok { if dev { - log.Info("Using live filesystem at ./public") + log.Info().Msg("Using live filesystem at ./public") assets = os.DirFS("./public") } else { - log.Info("Using live filesystem at ./dist") + log.Info().Msg("Using live filesystem at ./dist") assets = os.DirFS("./dist") } } if !dev { if _, err := assets.Open(".vite/manifest.json"); err != nil { - log.Fatal(".vite/manifest.json not found") + log.Fatal().Msg("manifest.json not found") } if _, err := assets.Open("index.html"); err != nil { - log.Fatal("index.html not found") + log.Fatal().Msg("index.html not found") } } diff --git a/package.json b/package.json index 1f157432..73b3d20b 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "license": "ISC", "author": "Amir Raminfar ", "scripts": { - "agent:dev": "DOZZLE_AGENT_ADDR=localhost:7007 reflex -c .reflex.agent", + "agent:dev": "DOZZLE_AGENT_ADDR=localhost:7007 DEV=true reflex -c .reflex.agent", "watch:frontend": "vite --open http://localhost:3100/", "watch:backend": "LIVE_FS=true DEV=true DOZZLE_ADDR=localhost:3100 reflex -c .reflex.server", "dev": "concurrently --kill-others \"npm:watch:*\"",