All checks were successful
continuous-integration/drone/push Build is passing
The log command now checks for the ready state in the task list. If it is not ready. It shows the task logs. This might look like this: ``` ERRO[0000] Service abra-test-recipe_default_app: State rejected: No such image: ngaaaax:1.21.0 ERRO[0000] Service abra-test-recipe_default_app: State preparing: ERRO[0000] Service abra-test-recipe_default_app: State rejected: No such image: ngaaaax:1.21.0 ERRO[0000] Service abra-test-recipe_default_app: State rejected: No such image: ngaaaax:1.21.0 ERRO[0000] Service abra-test-recipe_default_app: State rejected: No such image: ngaaaax:1.21.0 ``` Closes coop-cloud/organising#518 Reviewed-on: coop-cloud/abra#395 Reviewed-by: decentral1se <decentral1se@noreply.git.coopcloud.tech> Co-authored-by: p4u1 <p4u1_f4u1@riseup.net> Co-committed-by: p4u1 <p4u1_f4u1@riseup.net>
140 lines
3.5 KiB
Go
140 lines
3.5 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"os"
|
|
"slices"
|
|
"sync"
|
|
"time"
|
|
|
|
"coopcloud.tech/abra/cli/internal"
|
|
"coopcloud.tech/abra/pkg/autocomplete"
|
|
"coopcloud.tech/abra/pkg/client"
|
|
"coopcloud.tech/abra/pkg/config"
|
|
"coopcloud.tech/abra/pkg/recipe"
|
|
"coopcloud.tech/abra/pkg/upstream/stack"
|
|
"github.com/docker/docker/api/types"
|
|
"github.com/docker/docker/api/types/filters"
|
|
"github.com/docker/docker/api/types/swarm"
|
|
dockerClient "github.com/docker/docker/client"
|
|
"github.com/sirupsen/logrus"
|
|
"github.com/urfave/cli"
|
|
)
|
|
|
|
var appLogsCommand = cli.Command{
|
|
Name: "logs",
|
|
Aliases: []string{"l"},
|
|
ArgsUsage: "<domain> [<service>]",
|
|
Usage: "Tail app logs",
|
|
Flags: []cli.Flag{
|
|
internal.StdErrOnlyFlag,
|
|
internal.SinceLogsFlag,
|
|
internal.DebugFlag,
|
|
},
|
|
Before: internal.SubCommandBefore,
|
|
BashComplete: autocomplete.AppNameComplete,
|
|
Action: func(c *cli.Context) error {
|
|
app := internal.ValidateApp(c)
|
|
stackName := app.StackName()
|
|
|
|
if err := recipe.EnsureExists(app.Recipe); err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
cl, err := client.New(app.Server)
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
isDeployed, _, err := stack.IsDeployed(context.Background(), cl, stackName)
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
if !isDeployed {
|
|
logrus.Fatalf("%s is not deployed?", app.Name)
|
|
}
|
|
|
|
serviceName := c.Args().Get(1)
|
|
serviceNames := []string{}
|
|
if serviceName != "" {
|
|
serviceNames = []string{serviceName}
|
|
}
|
|
err = tailLogs(cl, app, serviceNames)
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
|
|
return nil
|
|
},
|
|
}
|
|
|
|
// tailLogs prints logs for the given app with optional service names to be
|
|
// filtered on. It also checks if the latest task is not runnning and then
|
|
// prints the past tasks.
|
|
func tailLogs(cl *dockerClient.Client, app config.App, serviceNames []string) error {
|
|
f, err := app.Filters(true, false, serviceNames...)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: f})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
for _, service := range services {
|
|
filters := filters.NewArgs()
|
|
filters.Add("name", service.Spec.Name)
|
|
tasks, err := cl.TaskList(context.Background(), types.TaskListOptions{Filters: f})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(tasks) > 0 {
|
|
// Need to sort the tasks by the CreatedAt field in the inverse order.
|
|
// Otherwise they are in the reversed order and not sorted properly.
|
|
slices.SortFunc[[]swarm.Task](tasks, func(t1, t2 swarm.Task) int {
|
|
return int(t2.Meta.CreatedAt.Unix() - t1.Meta.CreatedAt.Unix())
|
|
})
|
|
lastTask := tasks[0].Status
|
|
if lastTask.State != swarm.TaskStateRunning {
|
|
for _, task := range tasks {
|
|
logrus.Errorf("[%s] %s State %s: %s", service.Spec.Name, task.Meta.CreatedAt.Format(time.RFC3339), task.Status.State, task.Status.Err)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collect the logs in a go routine, so the logs from all services are
|
|
// collected in parallel.
|
|
wg.Add(1)
|
|
go func(serviceID string) {
|
|
logs, err := cl.ServiceLogs(context.Background(), serviceID, types.ContainerLogsOptions{
|
|
ShowStderr: true,
|
|
ShowStdout: !internal.StdErrOnly,
|
|
Since: internal.SinceLogs,
|
|
Until: "",
|
|
Timestamps: true,
|
|
Follow: true,
|
|
Tail: "20",
|
|
Details: false,
|
|
})
|
|
if err != nil {
|
|
logrus.Fatal(err)
|
|
}
|
|
defer logs.Close()
|
|
|
|
_, err = io.Copy(os.Stdout, logs)
|
|
if err != nil && err != io.EOF {
|
|
logrus.Fatal(err)
|
|
}
|
|
}(service.ID)
|
|
}
|
|
|
|
// Wait for all log streams to be closed.
|
|
wg.Wait()
|
|
|
|
return nil
|
|
}
|