From daaf00893c06be84224a2555aeb200277dbc0113 Mon Sep 17 00:00:00 2001 From: sneak Date: Mon, 29 Dec 2025 15:48:23 +0700 Subject: [PATCH] Implement container logs handler - Add Docker client to handlers for container operations - Implement HandleAppLogs() to fetch and return container logs - Support ?tail=N query parameter (default 500 lines) - Handle missing container gracefully --- internal/handlers/app.go | 32 ++++++++++++++++++++++++++---- internal/handlers/handlers.go | 4 ++++ internal/handlers/handlers_test.go | 7 ++++--- 3 files changed, 36 insertions(+), 7 deletions(-) diff --git a/internal/handlers/app.go b/internal/handlers/app.go index 120a29d..55cd8ed 100644 --- a/internal/handlers/app.go +++ b/internal/handlers/app.go @@ -331,6 +331,9 @@ func (h *Handlers) HandleAppDeployments() http.HandlerFunc { } } +// defaultLogTail is the default number of log lines to fetch. +const defaultLogTail = "500" + // HandleAppLogs returns the container logs handler. func (h *Handlers) HandleAppLogs() http.HandlerFunc { return func(writer http.ResponseWriter, request *http.Request) { @@ -343,16 +346,37 @@ func (h *Handlers) HandleAppLogs() http.HandlerFunc { return } - // Container logs fetching not yet implemented - writer.Header().Set("Content-Type", "text/plain") + writer.Header().Set("Content-Type", "text/plain; charset=utf-8") if !application.ContainerID.Valid { - _, _ = writer.Write([]byte("No container running")) + _, _ = writer.Write([]byte("No container running\n")) return } - _, _ = writer.Write([]byte("Container logs not implemented yet")) + tail := request.URL.Query().Get("tail") + if tail == "" { + tail = defaultLogTail + } + + logs, logsErr := h.docker.ContainerLogs( + request.Context(), + application.ContainerID.String, + tail, + ) + if logsErr != nil { + h.log.Error("failed to get container logs", + "error", logsErr, + "app", application.Name, + "container", application.ContainerID.String, + ) + + _, _ = writer.Write([]byte("Failed to fetch container logs\n")) + + return + } + + _, _ = writer.Write([]byte(logs)) } } diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go index 2d8c3ec..f3c1989 100644 --- a/internal/handlers/handlers.go +++ b/internal/handlers/handlers.go @@ -9,6 +9,7 @@ import ( "go.uber.org/fx" "git.eeqj.de/sneak/upaas/internal/database" + "git.eeqj.de/sneak/upaas/internal/docker" "git.eeqj.de/sneak/upaas/internal/globals" "git.eeqj.de/sneak/upaas/internal/healthcheck" "git.eeqj.de/sneak/upaas/internal/logger" @@ -30,6 +31,7 @@ type Params struct { App *app.Service Deploy *deploy.Service Webhook *webhook.Service + Docker *docker.Client } // Handlers provides HTTP request handlers. @@ -42,6 +44,7 @@ type Handlers struct { appService *app.Service deploy *deploy.Service webhook *webhook.Service + docker *docker.Client } // New creates a new Handlers instance. @@ -55,6 +58,7 @@ func New(_ fx.Lifecycle, params Params) (*Handlers, error) { appService: params.App, deploy: params.Deploy, webhook: params.Webhook, + docker: params.Docker, }, nil } diff --git a/internal/handlers/handlers_test.go b/internal/handlers/handlers_test.go index 5bf5d61..77e207c 100644 --- a/internal/handlers/handlers_test.go +++ b/internal/handlers/handlers_test.go @@ -86,7 +86,7 @@ func createAppServices( logInstance *logger.Logger, dbInstance *database.Database, cfg *config.Config, -) (*auth.Service, *app.Service, *deploy.Service, *webhook.Service) { +) (*auth.Service, *app.Service, *deploy.Service, *webhook.Service, *docker.Client) { t.Helper() authSvc, authErr := auth.New(fx.Lifecycle(nil), auth.ServiceParams{ @@ -128,7 +128,7 @@ func createAppServices( }) require.NoError(t, webhookErr) - return authSvc, appSvc, deploySvc, webhookSvc + return authSvc, appSvc, deploySvc, webhookSvc, dockerClient } func setupTestHandlers(t *testing.T) *testContext { @@ -138,7 +138,7 @@ func setupTestHandlers(t *testing.T) *testContext { globalInstance, logInstance, dbInstance, hcInstance := createCoreServices(t, cfg) - authSvc, appSvc, deploySvc, webhookSvc := createAppServices( + authSvc, appSvc, deploySvc, webhookSvc, dockerClient := createAppServices( t, logInstance, dbInstance, @@ -156,6 +156,7 @@ func setupTestHandlers(t *testing.T) *testContext { App: appSvc, Deploy: deploySvc, Webhook: webhookSvc, + Docker: dockerClient, }, ) require.NoError(t, handlerErr)