Compare commits

..

1 Commits

Author SHA1 Message Date
p4u1 10e8bf0c7e feat: abra app logs shows task errors
continuous-integration/drone/pr Build is passing Details
2023-12-13 09:24:27 +01:00
2 changed files with 49 additions and 35 deletions

View File

@ -4,7 +4,9 @@ import (
"context"
"io"
"os"
"slices"
"sync"
"time"
"coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete"
@ -20,17 +22,6 @@ import (
"github.com/urfave/cli"
)
var logOpts = types.ContainerLogsOptions{
ShowStderr: true,
ShowStdout: true,
Since: "",
Until: "",
Timestamps: true,
Follow: true,
Tail: "20",
Details: false,
}
var appLogsCommand = cli.Command{
Name: "logs",
Aliases: []string{"l"},
@ -65,8 +56,6 @@ var appLogsCommand = cli.Command{
logrus.Fatalf("%s is not deployed?", app.Name)
}
logOpts.Since = internal.SinceLogs
serviceName := c.Args().Get(1)
serviceNames := []string{}
if serviceName != "" {
@ -81,7 +70,9 @@ var appLogsCommand = cli.Command{
},
}
// tailLogs lists logs for the given app with optional service names to be filtered on
// 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 {
@ -99,24 +90,36 @@ func tailLogs(cl *dockerClient.Client, app config.App, serviceNames []string) er
filters.Add("name", service.Spec.Name)
tasks, err := cl.TaskList(context.Background(), types.TaskListOptions{Filters: f})
if err != nil {
logrus.Fatal(err)
return err
}
if len(tasks) > 0 {
lastTask := tasks[len(tasks)-1].Status
// 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("Service %s: State %s: %s", service.Spec.Name, task.Status.State, task.Status.Err)
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) {
if internal.StdErrOnly {
logOpts.ShowStdout = false
}
logs, err := cl.ServiceLogs(context.Background(), serviceID, logOpts)
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)
}
@ -129,6 +132,7 @@ func tailLogs(cl *dockerClient.Client, app config.App, serviceNames []string) er
}(service.ID)
}
// Wait for all log streams to be closed.
wg.Wait()
return nil

View File

@ -77,8 +77,11 @@ func StackName(appName string) string {
return stackName
}
// Filters retrieves exact app filters for querying the container runtime. Due
// to upstream issues, filtering works different depending on what you're
// Filters retrieves app filters for querying the container runtime. By default
// it filters on all services in the app. It is also possible to pass an
// otional list of service names, which get filtered instead.
//
// Due to upstream issues, filtering works different depending on what you're
// querying. So, for example, secrets don't work with regex! The caller needs
// to implement their own validation that the right secrets are matched. In
// order to handle these cases, we provide the `appendServiceNames` /
@ -87,11 +90,20 @@ func (a App) Filters(appendServiceNames, exactMatch bool, services ...string) (f
filters := filters.NewArgs()
if len(services) > 0 {
for _, serviceName := range services {
filters.Add("name", fmtFilter(appendServiceNames, exactMatch, a.StackName(), serviceName))
filters.Add("name", ServiceFilter(a.StackName(), serviceName, exactMatch))
}
return filters, nil
}
if appendServiceNames {
f := fmt.Sprintf("%s", a.StackName())
if exactMatch {
f = fmt.Sprintf("^%s", f)
}
filters.Add("name", f)
return filters, nil
}
composeFiles, err := GetComposeFiles(a.Recipe, a.Env)
if err != nil {
return filters, err
@ -104,23 +116,21 @@ func (a App) Filters(appendServiceNames, exactMatch bool, services ...string) (f
}
for _, service := range compose.Services {
filters.Add("name", fmtFilter(appendServiceNames, exactMatch, a.StackName(), service.Name))
f := ServiceFilter(a.StackName(), service.Name, exactMatch)
filters.Add("name", f)
}
return filters, nil
}
func fmtFilter(appendMode bool, exact bool, stack string, service string) string {
if appendMode {
if exact {
return fmt.Sprintf("^%s_%s", stack, service)
}
return fmt.Sprintf("%s_%s", stack, service)
}
// ServiceFilter creates a filter string for filtering a service in the docker
// container runtime. When exact match is true, it uses regex to match the
// string exactly.
func ServiceFilter(stack, service string, exact bool) string {
if exact {
return fmt.Sprintf("^%s", stack)
return fmt.Sprintf("^%s_%s", stack, service)
}
return fmt.Sprintf("%s", stack)
return fmt.Sprintf("%s_%s", stack, service)
}
// ByServer sort a slice of Apps