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
This commit is contained in:
Jeffrey Paul 2025-12-29 15:48:23 +07:00
parent 3f9d83c436
commit daaf00893c
3 changed files with 36 additions and 7 deletions

View File

@ -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. // HandleAppLogs returns the container logs handler.
func (h *Handlers) HandleAppLogs() http.HandlerFunc { func (h *Handlers) HandleAppLogs() http.HandlerFunc {
return func(writer http.ResponseWriter, request *http.Request) { return func(writer http.ResponseWriter, request *http.Request) {
@ -343,16 +346,37 @@ func (h *Handlers) HandleAppLogs() http.HandlerFunc {
return return
} }
// Container logs fetching not yet implemented writer.Header().Set("Content-Type", "text/plain; charset=utf-8")
writer.Header().Set("Content-Type", "text/plain")
if !application.ContainerID.Valid { if !application.ContainerID.Valid {
_, _ = writer.Write([]byte("No container running")) _, _ = writer.Write([]byte("No container running\n"))
return 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))
} }
} }

View File

@ -9,6 +9,7 @@ import (
"go.uber.org/fx" "go.uber.org/fx"
"git.eeqj.de/sneak/upaas/internal/database" "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/globals"
"git.eeqj.de/sneak/upaas/internal/healthcheck" "git.eeqj.de/sneak/upaas/internal/healthcheck"
"git.eeqj.de/sneak/upaas/internal/logger" "git.eeqj.de/sneak/upaas/internal/logger"
@ -30,6 +31,7 @@ type Params struct {
App *app.Service App *app.Service
Deploy *deploy.Service Deploy *deploy.Service
Webhook *webhook.Service Webhook *webhook.Service
Docker *docker.Client
} }
// Handlers provides HTTP request handlers. // Handlers provides HTTP request handlers.
@ -42,6 +44,7 @@ type Handlers struct {
appService *app.Service appService *app.Service
deploy *deploy.Service deploy *deploy.Service
webhook *webhook.Service webhook *webhook.Service
docker *docker.Client
} }
// New creates a new Handlers instance. // New creates a new Handlers instance.
@ -55,6 +58,7 @@ func New(_ fx.Lifecycle, params Params) (*Handlers, error) {
appService: params.App, appService: params.App,
deploy: params.Deploy, deploy: params.Deploy,
webhook: params.Webhook, webhook: params.Webhook,
docker: params.Docker,
}, nil }, nil
} }

View File

@ -86,7 +86,7 @@ func createAppServices(
logInstance *logger.Logger, logInstance *logger.Logger,
dbInstance *database.Database, dbInstance *database.Database,
cfg *config.Config, cfg *config.Config,
) (*auth.Service, *app.Service, *deploy.Service, *webhook.Service) { ) (*auth.Service, *app.Service, *deploy.Service, *webhook.Service, *docker.Client) {
t.Helper() t.Helper()
authSvc, authErr := auth.New(fx.Lifecycle(nil), auth.ServiceParams{ authSvc, authErr := auth.New(fx.Lifecycle(nil), auth.ServiceParams{
@ -128,7 +128,7 @@ func createAppServices(
}) })
require.NoError(t, webhookErr) require.NoError(t, webhookErr)
return authSvc, appSvc, deploySvc, webhookSvc return authSvc, appSvc, deploySvc, webhookSvc, dockerClient
} }
func setupTestHandlers(t *testing.T) *testContext { func setupTestHandlers(t *testing.T) *testContext {
@ -138,7 +138,7 @@ func setupTestHandlers(t *testing.T) *testContext {
globalInstance, logInstance, dbInstance, hcInstance := createCoreServices(t, cfg) globalInstance, logInstance, dbInstance, hcInstance := createCoreServices(t, cfg)
authSvc, appSvc, deploySvc, webhookSvc := createAppServices( authSvc, appSvc, deploySvc, webhookSvc, dockerClient := createAppServices(
t, t,
logInstance, logInstance,
dbInstance, dbInstance,
@ -156,6 +156,7 @@ func setupTestHandlers(t *testing.T) *testContext {
App: appSvc, App: appSvc,
Deploy: deploySvc, Deploy: deploySvc,
Webhook: webhookSvc, Webhook: webhookSvc,
Docker: dockerClient,
}, },
) )
require.NoError(t, handlerErr) require.NoError(t, handlerErr)