/
logs.go
289 lines (245 loc) · 11.9 KB
/
logs.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
/*
* Copyright (c) 2021 - present Kurtosis Technologies Inc.
* All Rights Reserved.
*/
package logs
import (
"context"
"fmt"
"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"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/highlevel/enclave_id_arg"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/highlevel/engine_consuming_kurtosis_command"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/highlevel/service_identifier_arg"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/args"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/flags"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts"
"github.com/kurtosis-tech/kurtosis/cli/cli/out"
"github.com/kurtosis-tech/kurtosis/cli/cli/user_support_constants"
"github.com/kurtosis-tech/kurtosis/container-engine-lib/lib/backend_interface"
metrics_client "github.com/kurtosis-tech/metrics-library/golang/lib/client"
"github.com/kurtosis-tech/stacktrace"
"github.com/sirupsen/logrus"
"os"
"os/signal"
"strconv"
)
const (
enclaveIdentifierArgKey = "enclave"
isEnclaveIdArgOptional = false
isEnclaveIdArgGreedy = false
serviceIdentifierArgKey = "service"
isServiceIdentifierArgOptional = false
isServiceIdentifierArgGreedy = false
shouldFollowLogsFlagKey = "follow"
matchTextFilterFlagKey = "match"
matchRegexFilterFlagKey = "regex-match"
invertMatchFilterFlagKey = "invert-match"
defaultMatchTextOrRegexFilterFlagValue = ""
kurtosisBackendCtxKey = "kurtosis-backend"
engineClientCtxKey = "engine-client"
interruptChanBufferSize = 5
commonInstructionInMatchFlags = "Important: " + matchTextFilterFlagKey + " and " + matchRegexFilterFlagKey + " flags cannot be used at the same time. You should either use one or the other."
)
var doNotFilterLogLines *kurtosis_context.LogLineFilter = nil
var defaultShouldFollowLogs = strconv.FormatBool(false)
var defaultInvertMatchFilterFlagValue = strconv.FormatBool(false)
var ServiceLogsCmd = &engine_consuming_kurtosis_command.EngineConsumingKurtosisCommand{
CommandStr: command_str_consts.ServiceLogsCmdStr,
ShortDescription: "Get service logs",
LongDescription: "Show logs for a service inside an enclave",
KurtosisBackendContextKey: kurtosisBackendCtxKey,
EngineClientContextKey: engineClientCtxKey,
Flags: []*flags.FlagConfig{
{
Key: shouldFollowLogsFlagKey,
Usage: "Continues to follow the logs until stopped",
Shorthand: "f",
Type: flags.FlagType_Bool,
Default: defaultShouldFollowLogs,
},
{
Key: matchTextFilterFlagKey,
Usage: fmt.Sprintf(
"Filter the log lines returning only those containing this match. %s",
commonInstructionInMatchFlags,
),
Default: defaultMatchTextOrRegexFilterFlagValue,
},
{
Key: matchRegexFilterFlagKey,
Usage: fmt.Sprintf(
"Filter the log lines returning only those containing this regex expression match (re2 syntax regex may be used, more here: %s). This filter will always work but will have degraded performance for tokens. %s",
user_support_constants.GoogleRe2SyntaxDocumentation,
commonInstructionInMatchFlags,
),
Default: defaultMatchTextOrRegexFilterFlagValue,
},
{
Key: invertMatchFilterFlagKey,
Usage: fmt.Sprintf(
"Inverts the filter condition specified by either '%s' or '%s'. Log lines NOT containing %s/%s will be returned",
matchTextFilterFlagKey,
matchRegexFilterFlagKey,
matchTextFilterFlagKey,
matchRegexFilterFlagKey,
),
Shorthand: "v",
Type: flags.FlagType_Bool,
Default: defaultInvertMatchFilterFlagValue,
},
},
Args: []*args.ArgConfig{
//TODO disabling enclaveID validation and serviceUUID validation for allowing consuming logs from removed or stopped enclaves
//TODO we should enable them when #879 is ready: https://github.com/kurtosis-tech/kurtosis/issues/879
enclave_id_arg.NewEnclaveIdentifierArg(
enclaveIdentifierArgKey,
engineClientCtxKey,
isEnclaveIdArgOptional,
isEnclaveIdArgGreedy,
),
service_identifier_arg.NewServiceIdentifierArg(
serviceIdentifierArgKey,
isServiceIdentifierArgGreedy,
isServiceIdentifierArgOptional,
),
},
RunFunc: run,
}
func run(
ctx context.Context,
kurtosisBackend backend_interface.KurtosisBackend,
_ kurtosis_engine_rpc_api_bindings.EngineServiceClient,
_ metrics_client.MetricsClient,
flags *flags.ParsedFlags,
args *args.ParsedArgs,
) error {
enclaveIdentifier, err := args.GetNonGreedyArg(enclaveIdentifierArgKey)
if err != nil {
return stacktrace.Propagate(err, "An error occurred getting the enclave identifier using arg key '%v'", enclaveIdentifierArgKey)
}
serviceIdentifier, err := args.GetNonGreedyArg(serviceIdentifierArgKey)
if err != nil {
return stacktrace.Propagate(err, "An error occurred getting the service identifier using arg key '%v'", serviceIdentifierArgKey)
}
shouldFollowLogs, err := flags.GetBool(shouldFollowLogsFlagKey)
if err != nil {
return stacktrace.Propagate(err, "An error occurred getting the should-follow-logs flag using key '%v'", shouldFollowLogsFlagKey)
}
matchTextStr, err := flags.GetString(matchTextFilterFlagKey)
if err != nil {
return stacktrace.Propagate(err, "An error occurred getting the match flag using key '%v'", matchTextFilterFlagKey)
}
matchRegexStr, err := flags.GetString(matchRegexFilterFlagKey)
if err != nil {
return stacktrace.Propagate(err, "An error occurred getting the regex-match flag using key '%v'", matchRegexFilterFlagKey)
}
invertMatch, err := flags.GetBool(invertMatchFilterFlagKey)
if err != nil {
return stacktrace.Propagate(err, "An error occurred getting the invert match flag using key '%v'", invertMatchFilterFlagKey)
}
kurtosisCtx, err := kurtosis_context.NewKurtosisContextFromLocalEngine()
if err != nil {
return stacktrace.Propagate(err, "An error occurred connecting to the local Kurtosis engine")
}
serviceUuid := getEnclaveAndServiceUuidForIdentifiers(kurtosisCtx, ctx, enclaveIdentifier, serviceIdentifier)
userServiceUuids := map[services.ServiceUUID]bool{
serviceUuid: true,
}
logLineFilter, err := getLogLineFilterFromFilterFlagValues(matchTextStr, matchRegexStr, invertMatch)
if err != nil {
return stacktrace.Propagate(err, "An error occurred getting the log line filter using these filter flag values '%s=%s', '%s=%s', '%s=%v'", matchTextFilterFlagKey, matchTextStr, matchRegexFilterFlagKey, matchRegexStr, invertMatchFilterFlagKey, invertMatch)
}
serviceLogsStreamContentChan, cancelStreamUserServiceLogsFunc, err := kurtosisCtx.GetServiceLogs(ctx, enclaveIdentifier, userServiceUuids, shouldFollowLogs, logLineFilter)
if err != nil {
return stacktrace.Propagate(err, "An error occurred getting user service logs from user services with UUIDs '%+v' in enclave '%v' and with follow logs value '%v'", userServiceUuids, enclaveIdentifier, shouldFollowLogs)
}
defer cancelStreamUserServiceLogsFunc()
// This channel will receive a signal when the user presses an interrupt
interruptChan := make(chan os.Signal, interruptChanBufferSize)
signal.Notify(interruptChan, os.Interrupt)
for {
select {
case serviceLogsStreamContent, isChanOpen := <-serviceLogsStreamContentChan:
if !isChanOpen {
return nil
}
notFoundServiceUuids := serviceLogsStreamContent.GetNotFoundServiceUuids()
for notFoundServiceUuid := range notFoundServiceUuids {
logrus.Warnf("The Kurtosis centralized logs system does not contains any logs for the requested service UUID '%v'. This means that a service with that UUID either doesn't exist, or hasn't sent any logs. You can wait for further responses, or cancel the stream with Ctrl + C.", notFoundServiceUuid)
}
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())
}
case <-interruptChan:
logrus.Debugf("Received signal interruption in service logs Kurtosis CLI command")
return nil
}
}
}
// ====================================================================================================
//
// Private Helper Functions
//
// ====================================================================================================
func getLogLineFilterFromFilterFlagValues(matchTextStr string, matchRegexStr string, invertMatch bool) (*kurtosis_context.LogLineFilter, error) {
if matchTextStr == defaultMatchTextOrRegexFilterFlagValue && matchRegexStr == defaultMatchTextOrRegexFilterFlagValue {
return doNotFilterLogLines, nil
}
if matchTextStr != defaultMatchTextOrRegexFilterFlagValue && matchRegexStr != defaultMatchTextOrRegexFilterFlagValue {
return nil, stacktrace.NewError("Both filter match have being sent '%s', '%s' and it is not allowed, it's allowed to send only one of them", matchTextFilterFlagKey, matchRegexFilterFlagKey)
}
if matchTextStr != defaultMatchTextOrRegexFilterFlagValue && !invertMatch {
return kurtosis_context.NewDoesContainTextLogLineFilter(matchTextStr), nil
}
if matchTextStr != defaultMatchTextOrRegexFilterFlagValue && invertMatch {
return kurtosis_context.NewDoesNotContainTextLogLineFilter(matchTextStr), nil
}
if matchRegexStr != defaultMatchTextOrRegexFilterFlagValue && !invertMatch {
return kurtosis_context.NewDoesContainMatchRegexLogLineFilter(matchRegexStr), nil
}
if matchRegexStr != defaultMatchTextOrRegexFilterFlagValue && invertMatch {
return kurtosis_context.NewDoesNotContainMatchRegexLogLineFilter(matchRegexStr), nil
}
return nil, stacktrace.NewError(
"Something goes wrong during the log line filter definition using these filter flag values '%s=%s', '%s=%s' and '%s=%v', then it was not able to define it, this should never happens; this is a bug in Kurtosis",
matchTextFilterFlagKey, matchTextStr, matchRegexFilterFlagKey, matchRegexStr, invertMatchFilterFlagKey, invertMatch,
)
}
// This function works makes the best effort to get the most accurate enclave uuid and service uuid for the passed values
// defaults to assuming the passed value are uuids
// this function will be a lot cleaner after the object ids are stored in a database
// https://github.com/kurtosis-tech/kurtosis/issues/343
func getEnclaveAndServiceUuidForIdentifiers(kurtosisCtx *kurtosis_context.KurtosisContext, ctx context.Context, enclaveIdentifier string, serviceIdentifier string) services.ServiceUUID {
serviceUuid := services.ServiceUUID(serviceIdentifier)
// we need to get the enclave context to get the historical service identifiers for the enclave
// we also need it to get the `ServiceContext`
enclaveCtx, err := kurtosisCtx.GetEnclaveContext(ctx, enclaveIdentifier)
if err != nil {
// we couldn't get the enclaveCtx we go with service id passed as the serviceUuid
// for the enclave either we got it from the historical values or we return what was passed to the function
return serviceUuid
}
// we see if we can get historical identifiers
serviceIdentifiers, err := enclaveCtx.GetExistingAndHistoricalServiceIdentifiers(ctx)
if err == nil {
// if we can find a match for the service in the historical values, we return that
serviceUuidFromHistoricalServices, err := serviceIdentifiers.GetServiceUuidForIdentifier(serviceIdentifier)
if err == nil {
return serviceUuidFromHistoricalServices
}
}
// otherwise we see if we can find the service in the currently running services
serviceCtx, err := enclaveCtx.GetServiceContext(serviceIdentifier)
if err == nil {
return serviceCtx.GetServiceUUID()
}
// we return the best matches so far for enclave and service uuids
return serviceUuid
}