diff --git a/cli/cli/command_framework/highlevel/service_identifier_arg/service_identifier_arg.go b/cli/cli/command_framework/highlevel/service_identifier_arg/service_identifier_arg.go index 85c7274a8b..8adbad19ca 100644 --- a/cli/cli/command_framework/highlevel/service_identifier_arg/service_identifier_arg.go +++ b/cli/cli/command_framework/highlevel/service_identifier_arg/service_identifier_arg.go @@ -49,7 +49,7 @@ func NewHistoricalServiceIdentifierArgWithValidationDisabled( return &args.ArgConfig{ Key: serviceIdentifierArgKey, IsOptional: isOptional, - DefaultValue: "", + DefaultValue: []string{}, IsGreedy: isGreedy, ArgCompletionProvider: args.NewManualCompletionsProvider(getCompletionsForExistingAndHistoricalServices(enclaveIdentifierArgKey)), ValidationFunc: noValidationFunc, diff --git a/cli/cli/command_framework/lowlevel/args/consts_for_test.go b/cli/cli/command_framework/lowlevel/args/consts_for_test.go index 2b9f5d6062..0a87f1f5a1 100644 --- a/cli/cli/command_framework/lowlevel/args/consts_for_test.go +++ b/cli/cli/command_framework/lowlevel/args/consts_for_test.go @@ -24,6 +24,7 @@ var validArgsConfig = []*ArgConfig{ IsGreedy: true, }, } + var validTokens = []string{ arg1Value, arg2Value, diff --git a/cli/cli/commands/service/logs/logs.go b/cli/cli/commands/service/logs/logs.go index 1f12ec1377..f123f45996 100644 --- a/cli/cli/commands/service/logs/logs.go +++ b/cli/cli/commands/service/logs/logs.go @@ -8,6 +8,7 @@ package logs import ( "context" "fmt" + "github.com/fatih/color" "github.com/kurtosis-tech/kurtosis/api/golang/core/lib/services" "github.com/kurtosis-tech/kurtosis/api/golang/engine/kurtosis_engine_rpc_api_bindings" "github.com/kurtosis-tech/kurtosis/api/golang/engine/lib/kurtosis_context" @@ -34,10 +35,12 @@ const ( isEnclaveIdArgGreedy = false serviceIdentifierArgKey = "service" - isServiceIdentifierArgOptional = false - isServiceIdentifierArgGreedy = false + isServiceIdentifierArgOptional = true // don't need to pass this in if they use the return all services flag + isServiceIdentifierArgGreedy = true shouldFollowLogsFlagKey = "follow" + returnAllServiceLogs = "all-services" + allServicesWildcard = "*" returnNumLogsFlagKey = "num" returnAllLogsFlagKey = "all" matchTextFilterFlagKey = "match" @@ -55,12 +58,25 @@ const ( commonInstructionInMatchFlags = "Important: " + matchTextFilterFlagKey + " and " + matchRegexFilterFlagKey + " flags cannot be used at the same time. You should either use one or the other." ) +type ColorPrinter func(a ...interface{}) string + +var colorList = []ColorPrinter{ + color.New(color.FgBlue).SprintFunc(), + color.New(color.FgCyan).SprintFunc(), + color.New(color.FgGreen).SprintFunc(), + color.New(color.FgMagenta).SprintFunc(), + color.New(color.FgYellow).SprintFunc(), + color.New(color.FgHiRed).SprintFunc(), + color.New(color.FgHiBlue).SprintFunc(), + color.New(color.FgHiWhite).SprintFunc(), +} + var doNotFilterLogLines *kurtosis_context.LogLineFilter = nil var defaultShouldFollowLogs = strconv.FormatBool(false) var defaultInvertMatchFilterFlagValue = strconv.FormatBool(false) - var defaultShouldReturnAllLogs = strconv.FormatBool(false) +var defaultShouldReturnAllServiceLog = strconv.FormatBool(false) var defaultNumLogLinesFlagValue = strconv.Itoa(defaultNumLogLines) var ServiceLogsCmd = &engine_consuming_kurtosis_command.EngineConsumingKurtosisCommand{ @@ -121,6 +137,13 @@ var ServiceLogsCmd = &engine_consuming_kurtosis_command.EngineConsumingKurtosisC Type: flags.FlagType_Bool, Default: defaultInvertMatchFilterFlagValue, }, + { + Key: returnAllServiceLogs, + Usage: "Returns service log streams for all logs in an enclave", + Shorthand: "x", + Type: flags.FlagType_Bool, + Default: defaultShouldReturnAllServiceLog, + }, }, Args: []*args.ArgConfig{ enclave_id_arg.NewHistoricalEnclaveIdentifiersArgWithValidationDisabled( @@ -131,8 +154,8 @@ var ServiceLogsCmd = &engine_consuming_kurtosis_command.EngineConsumingKurtosisC service_identifier_arg.NewHistoricalServiceIdentifierArgWithValidationDisabled( serviceIdentifierArgKey, enclaveIdentifierArgKey, - isServiceIdentifierArgGreedy, isServiceIdentifierArgOptional, + isServiceIdentifierArgGreedy, ), }, RunFunc: run, @@ -151,10 +174,20 @@ func run( return stacktrace.Propagate(err, "An error occurred getting the enclave identifier using arg key '%v'", enclaveIdentifierArgKey) } - serviceIdentifier, err := args.GetNonGreedyArg(serviceIdentifierArgKey) + shouldReturnAllServiceLogs, err := flags.GetBool(returnAllServiceLogs) + if err != nil { + return stacktrace.Propagate(err, "An error occurred getting the 'all-services' flag using key '%v'", returnAllServiceLogs) + } + + var serviceIdentifiers []string + serviceIdentifiers, err = args.GetGreedyArg(serviceIdentifierArgKey) if err != nil { return stacktrace.Propagate(err, "An error occurred getting the service identifier using arg key '%v'", serviceIdentifierArgKey) } + // if no service identifiers were passed or just the wildcard was passed, default to returning all + if len(serviceIdentifiers) == 0 || (len(serviceIdentifiers) == 1 && serviceIdentifiers[0] == allServicesWildcard) { // + shouldReturnAllServiceLogs = true + } shouldFollowLogs, err := flags.GetBool(shouldFollowLogsFlagKey) if err != nil { @@ -191,10 +224,27 @@ func run( return stacktrace.Propagate(err, "An error occurred connecting to the local Kurtosis engine") } - serviceUuid := getEnclaveAndServiceUuidForIdentifiers(kurtosisCtx, ctx, enclaveIdentifier, serviceIdentifier) + if shouldReturnAllServiceLogs { + enclaveCtx, err := kurtosisCtx.GetEnclaveContext(ctx, enclaveIdentifier) + if err != nil { + return stacktrace.Propagate(err, "An error occurred retrieving enclave context for '%v'", enclaveIdentifier) + } - userServiceUuids := map[services.ServiceUUID]bool{ - serviceUuid: true, + allServiceIdentifiers, err := enclaveCtx.GetExistingAndHistoricalServiceIdentifiers(ctx) + if err != nil { + return stacktrace.Propagate(err, "An error occurred retrieving service identifiers for enclave '%v'", enclaveIdentifier) + } + serviceIdentifiers = allServiceIdentifiers.GetOrderedListOfNames() + } + + userServiceUuids := map[services.ServiceUUID]bool{} + serviceUuids := map[services.ServiceUUID]string{} + serviceColorPrinterMap := map[string]ColorPrinter{} + for idx, serviceIdentifier := range serviceIdentifiers { + serviceUuid := getEnclaveAndServiceUuidForIdentifiers(kurtosisCtx, ctx, enclaveIdentifier, serviceIdentifier) + serviceUuids[serviceUuid] = serviceIdentifier + serviceColorPrinterMap[serviceIdentifier] = colorList[idx%len(colorList)] + userServiceUuids[serviceUuid] = true } logLineFilter, err := getLogLineFilterFromFilterFlagValues(matchTextStr, matchRegexStr, invertMatch) @@ -226,14 +276,16 @@ func run( } userServiceLogsByUuid := serviceLogsStreamContent.GetServiceLogsByServiceUuids() - - userServiceLogs, found := userServiceLogsByUuid[serviceUuid] - if !found { - return stacktrace.NewError("Expected to find logs for user service with UUID '%v' on user service logs map '%+v' but was not found; this should never happen, and is a bug in Kurtosis", serviceUuid, userServiceLogsByUuid) - } - - for _, serviceLog := range userServiceLogs { - out.PrintOutLn(serviceLog.GetContent()) + for serviceUuid, serviceIdentifier := range serviceUuids { + userServiceLogs, found := userServiceLogsByUuid[serviceUuid] + if !found { + return stacktrace.NewError("Expected to find logs for user service with UUID '%v' on user service logs map '%+v' but was not found; this should never happen, and is a bug in Kurtosis", serviceUuid, userServiceLogsByUuid) + } + + for _, serviceLog := range userServiceLogs { + colorPrinter := serviceColorPrinterMap[serviceIdentifier] + out.PrintOutLn(fmt.Sprintf("%v %v", colorPrinter("[%v]", serviceIdentifier), serviceLog.GetContent())) + } } case <-interruptChan: logrus.Debugf("Received signal interruption in service logs Kurtosis CLI command") diff --git a/docs/docs/cli-reference/service-logs.md b/docs/docs/cli-reference/service-logs.md index cc819d6f55..5147e0e888 100644 --- a/docs/docs/cli-reference/service-logs.md +++ b/docs/docs/cli-reference/service-logs.md @@ -4,13 +4,13 @@ sidebar_label: service logs slug: /service-logs --- -To print the logs for a service, run: +To print the logs for services in an enclave, run: ```bash -kurtosis service logs $THE_ENCLAVE_IDENTIFIER $THE_SERVICE_IDENTIFIER +kurtosis service logs $THE_ENCLAVE_IDENTIFIER $THE_SERVICE_IDENTIFIER1 $THE_SERVICE_IDENTIFIER2 $THE_SERVICE_IDENTIFIER3 ``` -where `$THE_ENCLAVE_IDENTIFIER` and the `$THE_SERVICE_IDENTIFIER` are [resource identifiers](../advanced-concepts/resource-identifier.md) for the enclave and service, respectively. The service identifier (name or UUID) is printed upon inspecting an enclave. +where `$THE_ENCLAVE_IDENTIFIER` and the `$THE_SERVICE_IDENTIFIER` are [resource identifiers](../advanced-concepts/resource-identifier.md) for the enclave and services, respectively. The service identifier (name or UUID) is printed upon inspecting an enclave. ::: :::note Number of log lines @@ -23,7 +23,8 @@ Kurtosis will keep logs for up to 4 weeks before removing them to prevent logs f The following optional arguments can be used: 1. `-a`, `--all` can be used to retrieve all logs. 1. `-n`, `--num=uint32` can be used to retrieve X last log lines. (eg. `-n 10` will retrieve last 10 log lines, similar to `tail -n 10`) -1. `-f`, `-follow` can be added to continue following the logs, similar to `tail -f`. +1. `-f`, `--follow` can be added to continue following the logs, similar to `tail -f`. +1. `-x`, `--all-services` can be used to retrieve logs for all services in an enclave. Another option is to pass in the escaped wildcard operator like so `kurtosis service logs enclave-name '*'` 1. `--match=text` can be used for filtering the log lines containing the text. 1. `--regex-match="regex"` can be used for filtering the log lines containing the regex. This filter will also work for text but will have degraded performance. 1. `-v`, `--invert-match` can be used to invert the filter condition specified by either `--match` or `--regex-match`. Log lines NOT containing the match will be returned. diff --git a/engine/server/engine/server/engine_connect_server_service.go b/engine/server/engine/server/engine_connect_server_service.go index 6dc9804e6b..3fc908e916 100644 --- a/engine/server/engine/server/engine_connect_server_service.go +++ b/engine/server/engine/server/engine_connect_server_service.go @@ -280,7 +280,6 @@ func (service *EngineConnectServerService) Clean(ctx context.Context, connectArg } func (service *EngineConnectServerService) GetServiceLogs(ctx context.Context, connectArgs *connect.Request[kurtosis_engine_rpc_api_bindings.GetServiceLogsArgs], stream *connect.ServerStream[kurtosis_engine_rpc_api_bindings.GetServiceLogsResponse]) error { - args := connectArgs.Msg enclaveIdentifier := args.GetEnclaveIdentifier() enclaveUuid, err := service.enclaveManager.GetEnclaveUuidForEnclaveIdentifier(context.Background(), enclaveIdentifier)