mirror of
https://github.com/psviderski/uncloud.git
synced 2026-08-26 19:13:34 +00:00
feat: support filtering service logs by machine (-m/--machine)
This commit is contained in:
@@ -25,6 +25,7 @@ type logsOptions struct {
|
|||||||
since string
|
since string
|
||||||
until string
|
until string
|
||||||
utc bool
|
utc bool
|
||||||
|
machines []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewLogsCommand() *cobra.Command {
|
func NewLogsCommand() *cobra.Command {
|
||||||
@@ -44,6 +45,8 @@ func NewLogsCommand() *cobra.Command {
|
|||||||
|
|
||||||
cmd.Flags().BoolVarP(&options.follow, "follow", "f", false,
|
cmd.Flags().BoolVarP(&options.follow, "follow", "f", false,
|
||||||
"Continually stream new logs.")
|
"Continually stream new logs.")
|
||||||
|
cmd.Flags().StringSliceVarP(&options.machines, "machine", "m", nil,
|
||||||
|
"Filter logs by machine name or ID. Can be specified multiple times or as a comma-separated list.")
|
||||||
cmd.Flags().StringVarP(&options.tail, "tail", "n", "100",
|
cmd.Flags().StringVarP(&options.tail, "tail", "n", "100",
|
||||||
"Show the most recent logs and limit the number of lines shown per replica. Use 'all' to show all logs.")
|
"Show the most recent logs and limit the number of lines shown per replica. Use 'all' to show all logs.")
|
||||||
cmd.Flags().StringVar(&options.since, "since", "",
|
cmd.Flags().StringVar(&options.since, "since", "",
|
||||||
@@ -86,13 +89,13 @@ func streamLogs(ctx context.Context, uncli *cli.CLI, serviceNames []string, opts
|
|||||||
Tail: tail,
|
Tail: tail,
|
||||||
Since: opts.since,
|
Since: opts.since,
|
||||||
Until: opts.until,
|
Until: opts.until,
|
||||||
|
Machines: cli.ExpandCommaSeparatedValues(opts.machines),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Collect log streams from all services.
|
// Collect log streams from all services.
|
||||||
machineIDsSet := mapset.NewSet[string]()
|
machineIDsSet := mapset.NewSet[string]()
|
||||||
svcStreams := make([]<-chan api.ServiceLogEntry, 0, len(serviceNames))
|
svcStreams := make([]<-chan api.ServiceLogEntry, 0, len(serviceNames))
|
||||||
for _, serviceName := range serviceNames {
|
for _, serviceName := range serviceNames {
|
||||||
// TODO: set Heartbeats in the opts.
|
|
||||||
svc, ch, err := c.ServiceLogs(ctx, serviceName, logsOpts)
|
svc, ch, err := c.ServiceLogs(ctx, serviceName, logsOpts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("stream logs for service '%s': %w", serviceName, err)
|
return fmt.Errorf("stream logs for service '%s': %w", serviceName, err)
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ type ServiceLogsOptions struct {
|
|||||||
Tail int
|
Tail int
|
||||||
Since string
|
Since string
|
||||||
Until string
|
Until string
|
||||||
|
// Machines filters logs to only include containers running on the specified machines (names or IDs).
|
||||||
|
// If empty, logs from all machines are included.
|
||||||
|
Machines []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServiceLogEntry represents a single log entry from a service container.
|
// ServiceLogEntry represents a single log entry from a service container.
|
||||||
|
|||||||
@@ -149,18 +149,6 @@ func (cli *Client) PushImage(ctx context.Context, image string, opts PushImageOp
|
|||||||
return fmt.Errorf("list machines: %w", err)
|
return fmt.Errorf("list machines: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if all specified machines were found.
|
|
||||||
if len(machineMembers) != len(opts.Machines) {
|
|
||||||
var notFound []string
|
|
||||||
for _, nameOrID := range opts.Machines {
|
|
||||||
if machineMembers.FindByNameOrID(nameOrID) == nil {
|
|
||||||
notFound = append(notFound, nameOrID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("machines not found: %s", strings.Join(notFound, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, mm := range machineMembers {
|
for _, mm := range machineMembers {
|
||||||
machines = append(machines, mm.Machine)
|
machines = append(machines, mm.Machine)
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-3
@@ -2,6 +2,7 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
@@ -27,16 +28,23 @@ func (cli *Client) ServiceLogs(
|
|||||||
return svc, nil, fmt.Errorf("no containers found for service: %s", serviceNameOrID)
|
return svc, nil, fmt.Errorf("no containers found for service: %s", serviceNameOrID)
|
||||||
}
|
}
|
||||||
|
|
||||||
machines, err := cli.ListMachines(ctx, nil)
|
machines, err := cli.ListMachines(ctx, &api.MachineFilter{
|
||||||
|
NamesOrIDs: opts.Machines,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return svc, nil, fmt.Errorf("list machines: %w", err)
|
return svc, nil, fmt.Errorf("list machines: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctrStreams := make([]<-chan api.ServiceLogEntry, 0, len(svc.Containers))
|
ctrStreams := make([]<-chan api.ServiceLogEntry, 0, len(svc.Containers))
|
||||||
for _, ctr := range svc.Containers {
|
for _, ctr := range svc.Containers {
|
||||||
// Try to get machine name for ServiceLogEntry metadata and friendlier error message.
|
// Skip containers not running on the specified machines.
|
||||||
machineName := ctr.MachineID
|
|
||||||
m := machines.FindByNameOrID(ctr.MachineID)
|
m := machines.FindByNameOrID(ctr.MachineID)
|
||||||
|
if len(opts.Machines) > 0 && m == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Machine name for ServiceLogEntry metadata and friendlier error message.
|
||||||
|
machineName := ctr.MachineID
|
||||||
if m != nil {
|
if m != nil {
|
||||||
machineName = m.Machine.Name
|
machineName = m.Machine.Name
|
||||||
}
|
}
|
||||||
@@ -59,6 +67,10 @@ func (cli *Client) ServiceLogs(
|
|||||||
ctrStreams = append(ctrStreams, enrichedStream)
|
ctrStreams = append(ctrStreams, enrichedStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(ctrStreams) == 0 {
|
||||||
|
return svc, nil, errors.New("no service containers found on the specified machine(s)")
|
||||||
|
}
|
||||||
|
|
||||||
// Use the log merger to combine streams from all containers in chronological order.
|
// Use the log merger to combine streams from all containers in chronological order.
|
||||||
merger := NewLogMerger(ctrStreams, DefaultLogMergerOptions)
|
merger := NewLogMerger(ctrStreams, DefaultLogMergerOptions)
|
||||||
mergedStream := merger.Stream()
|
mergedStream := merger.Stream()
|
||||||
|
|||||||
+30
-27
@@ -2,7 +2,8 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"slices"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
"github.com/psviderski/uncloud/internal/machine/api/pb"
|
||||||
"github.com/psviderski/uncloud/pkg/api"
|
"github.com/psviderski/uncloud/pkg/api"
|
||||||
@@ -32,42 +33,44 @@ func (cli *Client) ListMachines(ctx context.Context, filter *api.MachineFilter)
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
machines := api.MachineMembersList(resp.Machines)
|
||||||
|
|
||||||
machines := resp.Machines
|
if filter == nil {
|
||||||
|
return machines, nil
|
||||||
|
}
|
||||||
|
|
||||||
if filter != nil {
|
// Apply the filter.
|
||||||
var matchedMachines api.MachineMembersList
|
if len(filter.NamesOrIDs) > 0 {
|
||||||
|
var matched api.MachineMembersList
|
||||||
|
var notFound []string
|
||||||
|
|
||||||
|
for _, nameOrID := range filter.NamesOrIDs {
|
||||||
|
if m := machines.FindByNameOrID(nameOrID); m != nil {
|
||||||
|
matched = append(matched, m)
|
||||||
|
} else {
|
||||||
|
notFound = append(notFound, nameOrID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
machines = matched
|
||||||
|
|
||||||
|
if len(notFound) > 0 {
|
||||||
|
return nil, fmt.Errorf("machines not found: %s", strings.Join(notFound, ", "))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if filter.Available {
|
||||||
|
var available api.MachineMembersList
|
||||||
for _, m := range machines {
|
for _, m := range machines {
|
||||||
if MachineMatchesFilter(m, filter) {
|
if m.State != pb.MachineMember_DOWN {
|
||||||
matchedMachines = append(matchedMachines, m)
|
available = append(available, m)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
machines = matchedMachines
|
machines = available
|
||||||
}
|
}
|
||||||
|
|
||||||
return machines, nil
|
return machines, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func MachineMatchesFilter(machine *pb.MachineMember, filter *api.MachineFilter) bool {
|
|
||||||
if filter == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if filter.Available && machine.State == pb.MachineMember_DOWN {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(filter.NamesOrIDs) > 0 {
|
|
||||||
if !slices.ContainsFunc(filter.NamesOrIDs, func(nameOrID string) bool {
|
|
||||||
return machine.Machine.Id == nameOrID || machine.Machine.Name == nameOrID
|
|
||||||
}) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateMachine updates machine configuration in the cluster.
|
// UpdateMachine updates machine configuration in the cluster.
|
||||||
func (cli *Client) UpdateMachine(ctx context.Context, req *pb.UpdateMachineRequest) (*pb.MachineInfo, error) {
|
func (cli *Client) UpdateMachine(ctx context.Context, req *pb.UpdateMachineRequest) (*pb.MachineInfo, error) {
|
||||||
resp, err := cli.ClusterClient.UpdateMachine(ctx, req)
|
resp, err := cli.ClusterClient.UpdateMachine(ctx, req)
|
||||||
|
|||||||
Reference in New Issue
Block a user