MuranoCLI 3.0.8 → 3.1.0.beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/MrMurano/Logs.rb +13 -5
- data/lib/MrMurano/ProjectFile.rb +5 -4
- data/lib/MrMurano/SyncUpDown.rb +1 -47
- data/lib/MrMurano/commands/globals.rb +1 -0
- data/lib/MrMurano/commands/logs.rb +601 -206
- data/lib/MrMurano/commands/solution_picker.rb +11 -11
- data/lib/MrMurano/makePretty.rb +55 -28
- data/lib/MrMurano/version.rb +1 -1
- data/spec/fixtures/websocket/README.rst +110 -0
- metadata +5 -4
@@ -15,248 +15,643 @@ require 'MrMurano/Logs'
|
|
15
15
|
require 'MrMurano/ReCommander'
|
16
16
|
require 'MrMurano/Solution'
|
17
17
|
|
18
|
-
#
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
18
|
+
# Because Ruby 2.0 does not support quoted keys, e.g., { '$eq': 'value' }.
|
19
|
+
# rubocop:disable Style/HashSyntax
|
20
|
+
|
21
|
+
class LogsCmd
|
22
|
+
include MrMurano::Verbose
|
23
|
+
|
24
|
+
LOG_EMITTER_TYPES = %i[
|
25
|
+
script
|
26
|
+
call
|
27
|
+
event
|
28
|
+
config
|
29
|
+
service
|
30
|
+
].freeze
|
31
|
+
|
32
|
+
LOG_SEVERITIES = %i[
|
33
|
+
emergency
|
34
|
+
alert
|
35
|
+
critical
|
36
|
+
error
|
37
|
+
warning
|
38
|
+
notice
|
39
|
+
informational
|
40
|
+
debug
|
41
|
+
].freeze
|
42
|
+
|
43
|
+
# (lb): Ideally, we'd use +/- and not +/:, but rb-commander (or is it
|
44
|
+
# OptionParser?) double-parses things that look like switches. E.g.,
|
45
|
+
# `murano logs --types -call` would set options.types to ["-call"]
|
46
|
+
# but would also set options.config to "all". Just one more reason
|
47
|
+
# I do not think rb-commander should call itself a "complete solution".
|
48
|
+
# (Note also we cannot use '!' instead of '-', because Bash.)
|
49
|
+
# Another option would be to use the "no-" option, e.g., "--[no-]types",
|
50
|
+
# but then what do you do with the sindle character '-T' option?
|
51
|
+
EXCLUDE_INDICATOR = ':'
|
52
|
+
INCLUDE_INDICATOR = '+'
|
53
|
+
|
54
|
+
def initialize
|
55
|
+
@filter_severity = []
|
56
|
+
@filter_types = []
|
57
|
+
@filter_events = []
|
58
|
+
@filter_endpoints = []
|
29
59
|
end
|
30
|
-
end
|
31
60
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
61
|
+
def command_logs(cmd)
|
62
|
+
cmd_add_logs_meta(cmd)
|
63
|
+
# Add global solution flag: --type [application|product].
|
64
|
+
cmd_add_solntype_pickers(cmd, exclude_all: true)
|
65
|
+
cmd_add_logs_options(cmd)
|
66
|
+
cmd_add_format_options(cmd)
|
67
|
+
cmd_add_filter_options(cmd)
|
68
|
+
cmd.action do |args, options|
|
69
|
+
@options = options
|
70
|
+
cmd.verify_arg_count!(args)
|
71
|
+
logs_action
|
72
|
+
end
|
39
73
|
end
|
40
|
-
end
|
41
74
|
|
42
|
-
def
|
43
|
-
|
44
|
-
Get the logs for a solution
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
Severity
|
49
|
-
================================================================
|
50
|
-
The severity of the log message, as defined by rsyslog standard.
|
51
|
-
|
52
|
-
ID | Name | Description
|
53
|
-
-- | ------------- | -----------------------------------------
|
54
|
-
0 | Emergency | System is unusable
|
55
|
-
1 | Alert | Action must be taken immediately
|
56
|
-
2 | Critical | Critical conditions
|
57
|
-
3 | Error | Error conditions
|
58
|
-
4 | Warning | Warning conditions
|
59
|
-
5 | Notice | Normal but significant condition
|
60
|
-
6 | Informational | Informational messages
|
61
|
-
7 | Debug | Debug-level messages
|
62
|
-
|
63
|
-
Type
|
64
|
-
================================================================
|
65
|
-
The type (emitter system) of the message.
|
66
|
-
|
67
|
-
Name | Description
|
68
|
-
------- | ----------------------------------------------------
|
69
|
-
Script | Pegasus-Engine: on User Lua “print()” function call
|
70
|
-
Call | Dispatcher: On service calls from Lua.
|
71
|
-
Event | Dispatcher: On event trigger from services
|
72
|
-
Config | Pegasus-API: On solution configuration change or
|
73
|
-
| used service deprecation warning.
|
74
|
-
Service | Services generated & transmitted to Dispatcher.
|
75
|
-
|
76
|
-
Message
|
77
|
-
================================================================
|
78
|
-
Message can be up to 64kb containing plain text describing a log
|
79
|
-
of the event
|
80
|
-
|
81
|
-
Service
|
82
|
-
================================================================
|
83
|
-
The service via which the event name is coming or the service of
|
84
|
-
which the function is called.
|
85
|
-
|
86
|
-
Event
|
87
|
-
================================================================
|
88
|
-
Depending on the type:
|
89
|
-
|
90
|
-
Event, Script => Event name
|
91
|
-
Call => operationId
|
92
|
-
|
93
|
-
Tracking ID
|
94
|
-
================================================================
|
95
|
-
End to end Murano processing id.
|
96
|
-
Used to group logs together for one endpoint request.
|
97
|
-
).strip
|
98
|
-
end
|
75
|
+
def cmd_add_logs_meta(cmd)
|
76
|
+
cmd.syntax = %(murano logs [--options])
|
77
|
+
cmd.summary = %(Get the logs for a solution)
|
78
|
+
cmd_add_help(cmd)
|
79
|
+
cmd_add_examples(cmd)
|
80
|
+
end
|
99
81
|
|
100
|
-
def
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
82
|
+
def cmd_add_help(cmd)
|
83
|
+
cmd.description = %(
|
84
|
+
Get the logs for a solution.
|
85
|
+
|
86
|
+
Each log record contains a number of fields, including the following.
|
87
|
+
|
88
|
+
Severity
|
89
|
+
================================================================
|
90
|
+
The severity of the log message, as defined by rsyslog standard.
|
91
|
+
|
92
|
+
ID | Name | Description
|
93
|
+
-- | ------------- | -----------------------------------------
|
94
|
+
0 | Emergency | System is unusable
|
95
|
+
1 | Alert | Action must be taken immediately
|
96
|
+
2 | Critical | Critical conditions
|
97
|
+
3 | Error | Error conditions
|
98
|
+
4 | Warning | Warning conditions
|
99
|
+
5 | Notice | Normal but significant condition
|
100
|
+
6 | Informational | Informational messages
|
101
|
+
7 | Debug | Debug-level messages
|
102
|
+
|
103
|
+
Type
|
104
|
+
================================================================
|
105
|
+
The type (emitter system) of the message.
|
106
|
+
|
107
|
+
Name | Description
|
108
|
+
------- | ----------------------------------------------------
|
109
|
+
Script | Script Engine: When User Lua script calls `print()`
|
110
|
+
Call | Dispatcher: On service calls from Lua
|
111
|
+
Event | Dispatcher: On event trigger from services
|
112
|
+
Config | API: On solution configuration change, or
|
113
|
+
| used service deprecation warning
|
114
|
+
Service | Services generated & transmitted to Dispatcher
|
115
|
+
|
116
|
+
|
117
|
+
Message
|
118
|
+
================================================================
|
119
|
+
Message can be up to 64kb containing plain text describing a log
|
120
|
+
of the event.
|
121
|
+
|
122
|
+
Service
|
123
|
+
================================================================
|
124
|
+
The service via which the event name is coming or the service of
|
125
|
+
which the function is called.
|
126
|
+
|
127
|
+
Event
|
128
|
+
================================================================
|
129
|
+
Depending on the type:
|
130
|
+
|
131
|
+
Event, Script => Event name
|
132
|
+
Call => operationId
|
133
|
+
|
134
|
+
Tracking ID
|
135
|
+
================================================================
|
136
|
+
End to end Murano processing id.
|
137
|
+
Used to group logs together for one endpoint request.
|
138
|
+
).strip
|
139
|
+
end
|
111
140
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
logs_display(sol, options)
|
117
|
-
end
|
141
|
+
# NOTE (landonb): The Service & Script Debug Log RFC mentions 'subject',
|
142
|
+
# but I've never seen it in any debug log reply.
|
143
|
+
# The Subject line can be used for a short summary.
|
144
|
+
# It should be shorter than 80 characters.
|
118
145
|
|
119
|
-
def
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
pretty: true,
|
124
|
-
localtime: true,
|
125
|
-
raw: false,
|
126
|
-
type: :application,
|
127
|
-
)
|
128
|
-
end
|
146
|
+
def cmd_add_examples(cmd)
|
147
|
+
cmd.example %(
|
148
|
+
View the last 100 product log entries
|
149
|
+
).strip, 'murano logs'
|
129
150
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
151
|
+
cmd.example %(
|
152
|
+
Stream the application logs, including the last 100 records
|
153
|
+
).strip, 'murano logs --follow'
|
154
|
+
|
155
|
+
cmd.example %(
|
156
|
+
Stream the logs generated by 'device2' events
|
157
|
+
).strip, 'murano logs --follow --event device2'
|
158
|
+
|
159
|
+
cmd.example %(
|
160
|
+
Stream the logs generated by the types, 'event' and 'script'
|
161
|
+
).strip, 'murano logs --follow --types event,script'
|
162
|
+
|
163
|
+
cmd.example %(
|
164
|
+
Stream the logs generated by the types, 'call' and 'config'
|
165
|
+
).strip, 'murano logs --follow --types call -T config'
|
166
|
+
|
167
|
+
cmd.example %(
|
168
|
+
Exclude the logs generated by the 'script' type
|
169
|
+
).strip, 'murano logs --follow -T :script'
|
170
|
+
|
171
|
+
cmd.example %(
|
172
|
+
Show last 100 logs with any severity level expect DEBUG
|
173
|
+
).strip, 'murano logs --severity 0-6'
|
174
|
+
|
175
|
+
cmd.example %(
|
176
|
+
Stream only the logs with the DEBUG severity level
|
177
|
+
).strip, 'murano logs --follow -V --severity deB'
|
178
|
+
|
179
|
+
cmd.example %(
|
180
|
+
Stream logs with the severity levels ALERT, CRITICAL, WARNING, and DEBUG
|
181
|
+
).strip, 'murano logs --follow -V -l 1-2,WARN,7'
|
138
182
|
end
|
139
|
-
end
|
140
183
|
|
141
|
-
def
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
184
|
+
def cmd_add_logs_options(cmd)
|
185
|
+
cmd.option '-f', '--[no-]follow', %(Follow logs from server)
|
186
|
+
cmd.option '-r', '--retry', %(Always retry the connection)
|
187
|
+
cmd.option(
|
188
|
+
'-i', '--[no-]insensitive',
|
189
|
+
%(Use case-insensitive matching (default: true))
|
190
|
+
)
|
191
|
+
cmd.option '-N', '--limit LIMIT', Integer, %(Retrieve this many existing logs at start of command (only works with --no-follow))
|
146
192
|
end
|
147
|
-
end
|
148
193
|
|
149
|
-
def
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
194
|
+
def cmd_add_format_options(cmd)
|
195
|
+
cmd_add_format_options_localtime(cmd)
|
196
|
+
cmd_add_format_options_pretty(cmd)
|
197
|
+
cmd_add_format_options_raw(cmd)
|
198
|
+
cmd_add_format_options_message_only(cmd)
|
199
|
+
cmd_add_format_options_one_line(cmd)
|
200
|
+
cmd_add_format_options_align_columns(cmd)
|
201
|
+
cmd_add_format_options_indent_body(cmd)
|
202
|
+
cmd_add_format_options_separators(cmd)
|
203
|
+
cmd_add_format_options_include_tracking(cmd)
|
204
|
+
cmd_add_format_options_sprintf(cmd)
|
205
|
+
end
|
206
|
+
|
207
|
+
def cmd_add_format_options_localtime(cmd)
|
208
|
+
cmd.option '--[no-]localtime', %(Adjust Timestamps to be in local time)
|
209
|
+
end
|
210
|
+
|
211
|
+
def cmd_add_format_options_pretty(cmd)
|
212
|
+
cmd.option '--[no-]pretty', %(Reformat JSON blobs in logs)
|
213
|
+
end
|
214
|
+
|
215
|
+
def cmd_add_format_options_raw(cmd)
|
216
|
+
cmd.option '--raw', %(Do not format the log data)
|
217
|
+
end
|
218
|
+
|
219
|
+
def cmd_add_format_options_message_only(cmd)
|
220
|
+
cmd.option '-o', '--message-only', %(Show only headers and the 'print' message)
|
221
|
+
end
|
222
|
+
|
223
|
+
def cmd_add_format_options_one_line(cmd)
|
224
|
+
cmd.option '--one-line', %(Squeeze each log entry onto one line (wrapping as necessary))
|
225
|
+
end
|
226
|
+
|
227
|
+
def cmd_add_format_options_align_columns(cmd)
|
228
|
+
cmd.option '--[no-]align', %(Align columns in formatted output)
|
229
|
+
end
|
230
|
+
|
231
|
+
def cmd_add_format_options_indent_body(cmd)
|
232
|
+
cmd.option '--[no-]indent', %(Indent body content in formatted output)
|
233
|
+
end
|
234
|
+
|
235
|
+
def cmd_add_format_options_separators(cmd)
|
236
|
+
cmd.option '--[no-]separators', %(Indent body content in formatted output)
|
237
|
+
end
|
238
|
+
|
239
|
+
def cmd_add_format_options_include_tracking(cmd)
|
240
|
+
cmd.option '--[no-]tracking', %(Include tracking ID)
|
241
|
+
end
|
242
|
+
|
243
|
+
def cmd_add_format_options_sprintf(cmd)
|
244
|
+
cmd.option '--sprintf FORMAT', %(Specify timestamp format (default: '%Y-%m-%d %H:%M:%S'))
|
245
|
+
end
|
246
|
+
|
247
|
+
def cmd_add_filter_options(cmd)
|
248
|
+
# Common log fields.
|
249
|
+
cmd_add_filter_option_severity(cmd)
|
250
|
+
cmd_add_filter_option_type(cmd)
|
251
|
+
cmd_add_filter_option_message(cmd)
|
252
|
+
cmd_add_filter_option_service(cmd)
|
253
|
+
cmd_add_filter_option_event(cmd)
|
254
|
+
# Skipping: timestamp filter
|
255
|
+
# Skipping: tracking_id filter
|
256
|
+
# Type-specific fields in data.
|
257
|
+
cmd_add_filter_option_endpoint(cmd)
|
258
|
+
# Skipping: module filter
|
259
|
+
# Skipping: elapsed time filter (i.e., could do { elapsed: { $gt: 10 } })
|
260
|
+
end
|
261
|
+
|
262
|
+
def cmd_add_filter_option_severity(cmd)
|
263
|
+
cmd.option(
|
264
|
+
'-l', '--severity [NAME|LEVEL|RANGE[,NAME|LEVEL|RANGE...]]', Array,
|
265
|
+
%(
|
266
|
+
Only show log entries of this severity.
|
267
|
+
May be specified by name, value, or range, e.g., WARN, 3, 1-4.
|
268
|
+
#{LOG_SEVERITIES.map.with_index { |s, i| "#{s}(#{i})" }.join(' ')}
|
269
|
+
).strip
|
270
|
+
) do |value|
|
271
|
+
@filter_severity.push value
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def cmd_add_filter_option_type(cmd)
|
276
|
+
emitter_type_help = %(
|
277
|
+
Filter log entries by type (emitter system) of message.
|
278
|
+
EMITTERS is 1 or more comma-separated types:
|
279
|
+
#{LOG_EMITTER_TYPES.map(&:to_s)}
|
280
|
+
Use a "#{INCLUDE_INDICATOR}" or "#{EXCLUDE_INDICATOR}" prefix to include or exclude types, respectively.
|
281
|
+
).strip
|
282
|
+
cmd.option('-T EMITTERS', '--types EMITTERS', Array, emitter_type_help) do |values|
|
283
|
+
# This seems a little roundabout, but rb-commander only keeps last value.
|
284
|
+
@filter_types.push values
|
285
|
+
values.map do |val|
|
286
|
+
val.sub(/^[#{INCLUDE_INDICATOR}#{EXCLUDE_INDICATOR}]/, '')
|
157
287
|
end
|
158
288
|
end
|
159
|
-
|
160
|
-
|
289
|
+
end
|
290
|
+
|
291
|
+
def cmd_add_filter_option_message(cmd)
|
292
|
+
cmd.option '-m', '--message GLOB', %(
|
293
|
+
Filter log entries by the message contents
|
294
|
+
).strip
|
295
|
+
end
|
296
|
+
|
297
|
+
def cmd_add_filter_option_service(cmd)
|
298
|
+
cmd.option '-s', '--service GLOB', %(
|
299
|
+
Filter log entries by the originating service
|
300
|
+
).strip
|
301
|
+
end
|
302
|
+
|
303
|
+
def cmd_add_filter_option_event(cmd)
|
304
|
+
cmd.option(
|
305
|
+
'-E', '--event GLOB', Array,
|
306
|
+
%(Filter log entries by the event)
|
307
|
+
) do |value|
|
308
|
+
@filter_events.push value
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
def cmd_add_filter_option_endpoint(cmd)
|
313
|
+
cmd.option(
|
314
|
+
'-e', '--endpoint ENDPOINT',
|
315
|
+
%(Filter log entries by the endpoint (ENDPOINT is VERB:PATH))
|
316
|
+
) do |value|
|
317
|
+
@filter_endpoints.push value
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def logs_action
|
322
|
+
cmd_default_logs_options
|
323
|
+
cmd_verify_logs_options!
|
324
|
+
cmd_defaults_solntype_pickers(@options, :application)
|
325
|
+
@query = assemble_query
|
326
|
+
verbose %(query: #{@query})
|
327
|
+
sol = cmd_get_sol!
|
328
|
+
logs_display(sol)
|
329
|
+
end
|
330
|
+
|
331
|
+
def cmd_default_logs_options
|
332
|
+
@options.default(
|
333
|
+
type: :application,
|
334
|
+
follow: false,
|
335
|
+
retry: false,
|
336
|
+
insensitive: true,
|
337
|
+
limit: nil,
|
338
|
+
localtime: true,
|
339
|
+
pretty: true,
|
340
|
+
raw: false,
|
341
|
+
message_only: false,
|
342
|
+
one_line: false,
|
343
|
+
tracking: false,
|
344
|
+
sprintf: '%Y-%m-%d %H:%M:%S',
|
345
|
+
align: false,
|
346
|
+
indent: false,
|
347
|
+
separators: false,
|
348
|
+
severity: nil,
|
349
|
+
types: [],
|
350
|
+
message: nil,
|
351
|
+
service: nil,
|
352
|
+
event: nil,
|
353
|
+
endpoint: nil,
|
354
|
+
)
|
355
|
+
end
|
356
|
+
|
357
|
+
def cmd_verify_logs_options!
|
358
|
+
n_formatting = 0
|
359
|
+
n_formatting += 1 if @options.raw
|
360
|
+
n_formatting += 1 if @options.message_only
|
361
|
+
n_formatting += 1 if @options.one_line
|
362
|
+
# Global options should really be checked elsewhere. Oh, well.
|
363
|
+
n_formatting += 1 if @options.json
|
364
|
+
n_formatting += 1 if @options.yaml
|
365
|
+
n_formatting += 1 if @options.pp
|
366
|
+
return unless n_formatting > 1
|
367
|
+
format_options = '--raw, --message-only, --one-line, --json, --yaml, or --pp'
|
368
|
+
warn "Try using just one of #{format_options}, but not two or more."
|
161
369
|
exit 1
|
162
370
|
end
|
163
|
-
end
|
164
371
|
|
165
|
-
def
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
372
|
+
def cmd_get_sol!
|
373
|
+
if @options.type == :application
|
374
|
+
MrMurano::Application.new
|
375
|
+
elsif @options.type == :product
|
376
|
+
MrMurano::Product.new
|
377
|
+
else
|
378
|
+
error "Unknown --type specified: #{@options.type}"
|
379
|
+
exit 1
|
380
|
+
end
|
381
|
+
end
|
382
|
+
|
383
|
+
def assemble_query
|
384
|
+
query_parts = {}
|
385
|
+
assemble_query_severity(query_parts)
|
386
|
+
assemble_query_types_array(query_parts)
|
387
|
+
assemble_query_message(query_parts)
|
388
|
+
assemble_query_service(query_parts)
|
389
|
+
assemble_query_event(query_parts)
|
390
|
+
assemble_query_endpoint(query_parts)
|
391
|
+
# Assemble and return actual query string.
|
392
|
+
assemble_query_string(query_parts)
|
170
393
|
end
|
171
|
-
end
|
172
394
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
395
|
+
def assemble_query_severity(query_parts)
|
396
|
+
filter_severity = @filter_severity.flatten
|
397
|
+
return if filter_severity.empty?
|
398
|
+
indices = []
|
399
|
+
filter_severity.each do |sev|
|
400
|
+
index = sev if sev =~ /^[0-9]$/
|
401
|
+
index = LOG_SEVERITIES.find_index { |s| s.to_s =~ /^#{sev.downcase}/ } unless index
|
402
|
+
if index
|
403
|
+
indices.push index.to_i
|
404
|
+
else
|
405
|
+
parts = /^([0-9])-([0-9])$/.match(sev)
|
406
|
+
if !parts.nil?
|
407
|
+
start = parts[1].to_i
|
408
|
+
finis = parts[2].to_i
|
409
|
+
if start < finis
|
410
|
+
more_indices = (start..finis).to_a
|
187
411
|
else
|
188
|
-
|
189
|
-
js = JSON.parse(
|
190
|
-
m,
|
191
|
-
allow_nan: true,
|
192
|
-
symbolize_names: true,
|
193
|
-
create_additions: false,
|
194
|
-
)
|
195
|
-
puts MrMurano::Pretties.MakePrettyLogsV1(js, options)
|
196
|
-
rescue StandardError
|
197
|
-
sol.error '=== JSON parse error, showing raw instead ==='
|
198
|
-
puts m
|
199
|
-
end
|
412
|
+
more_indices = (finis..start).to_a
|
200
413
|
end
|
201
|
-
|
414
|
+
indices += more_indices
|
415
|
+
else
|
416
|
+
warning "Invalid severity: #{sev}"
|
417
|
+
exit 1
|
418
|
+
end
|
419
|
+
end
|
420
|
+
end
|
421
|
+
query_parts['severity'] = { '$in': indices }
|
422
|
+
end
|
423
|
+
|
424
|
+
def assemble_query_types_array(query_parts)
|
425
|
+
assemble_in_or_nin_query(query_parts, 'type', @filter_types.flatten) do |type|
|
426
|
+
index = LOG_EMITTER_TYPES.find_index { |s| s.to_s =~ /^#{type.downcase}/ }
|
427
|
+
if index
|
428
|
+
LOG_EMITTER_TYPES[index].to_s
|
429
|
+
else
|
430
|
+
warning "Invalid emitter type: #{type}"
|
431
|
+
exit 1
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
|
436
|
+
def assemble_query_message(query_parts)
|
437
|
+
assemble_string_search_one(query_parts, 'message', @options.message, regex: true)
|
438
|
+
end
|
439
|
+
|
440
|
+
def assemble_query_service(query_parts)
|
441
|
+
assemble_string_search_one(query_parts, 'service', @options.service)
|
442
|
+
end
|
443
|
+
|
444
|
+
def assemble_query_event(query_parts)
|
445
|
+
assemble_string_search_many(query_parts, 'event', @filter_events)
|
446
|
+
end
|
447
|
+
|
448
|
+
def assemble_query_endpoint(query_parts)
|
449
|
+
terms = @filter_endpoints.map { |endp| format_endpoint(endp) }
|
450
|
+
# FIXME: (lb): MUR-5446: Still unresolved: How to query endpoints.
|
451
|
+
# The RFC says to use 'endpoint', but I've also been told to use
|
452
|
+
# 'data.section.' Neither work for me.
|
453
|
+
assemble_string_search_many(query_parts, 'data.section', terms)
|
454
|
+
end
|
455
|
+
|
456
|
+
def format_endpoint(endp)
|
457
|
+
# E.g., ?query={"data.section":"get_/set"}
|
458
|
+
# The format the user is most likely to use is with a colon, but
|
459
|
+
# we can let the user be a little sloppy, too, e.g., "get /set".
|
460
|
+
parts = endp.split(' ', 2)
|
461
|
+
parts = endp.split(':', 2) unless parts.length > 1
|
462
|
+
parts.join('_').downcase
|
463
|
+
end
|
464
|
+
|
465
|
+
def assemble_string_search_one(query_parts, field, value, regex: false)
|
466
|
+
return if value.to_s.empty?
|
467
|
+
if !regex
|
468
|
+
# Note that some options support strict equality, e.g.,
|
469
|
+
# { 'key': { '$eq': 'value' } }
|
470
|
+
# but post-processed keys like 'data.section' do not.
|
471
|
+
# So we just do normal equality, e.g.,
|
472
|
+
# { 'key': 'value' }
|
473
|
+
query_parts[field] = value
|
474
|
+
else
|
475
|
+
query_parts[field] = { :'$regex' => value }
|
476
|
+
query_parts[field][:'$options'] = 'i' if @options.insensitive
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
def assemble_string_search_many(query_parts, field, arr_of_arrs)
|
481
|
+
terms = arr_of_arrs.flatten
|
482
|
+
return if terms.empty?
|
483
|
+
if terms.length == 1
|
484
|
+
assemble_string_search_one(query_parts, field, terms[0])
|
485
|
+
else
|
486
|
+
assemble_in_or_nin_query(query_parts, field, terms)
|
487
|
+
end
|
488
|
+
end
|
489
|
+
|
490
|
+
def assemble_in_or_nin_query(query_parts, field, terms, &block)
|
491
|
+
return if terms.empty?
|
492
|
+
exclude = term_indicates_exclude?(terms[0])
|
493
|
+
resolved_terms = []
|
494
|
+
terms.each do |term|
|
495
|
+
process_query_term(term, resolved_terms, exclude, field, terms, &block)
|
496
|
+
end
|
497
|
+
return if resolved_terms.empty?
|
498
|
+
if !exclude
|
499
|
+
operator = '$in'
|
500
|
+
else
|
501
|
+
operator = '$nin'
|
502
|
+
end
|
503
|
+
query_parts[field] = { "#{operator}": resolved_terms }
|
504
|
+
end
|
505
|
+
|
506
|
+
def term_indicates_exclude?(term)
|
507
|
+
if term.start_with? EXCLUDE_INDICATOR
|
508
|
+
true
|
509
|
+
else
|
510
|
+
false
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
def process_query_term(term, resolved_terms, exclude, field, terms)
|
515
|
+
verify_term_plus_minux_prefix!(term, exclude, field, terms)
|
516
|
+
term = term.sub(/^[#{INCLUDE_INDICATOR}#{EXCLUDE_INDICATOR}]/, '')
|
517
|
+
term = yield term if block_given?
|
518
|
+
resolved_terms.push term
|
519
|
+
end
|
520
|
+
|
521
|
+
def verify_term_plus_minux_prefix!(term, exclude, field, terms)
|
522
|
+
return unless term =~ /^[#{INCLUDE_INDICATOR}#{EXCLUDE_INDICATOR}]/
|
523
|
+
return unless (
|
524
|
+
(!exclude && term.start_with?(EXCLUDE_INDICATOR)) ||
|
525
|
+
(exclude && term.start_with?(INCLUDE_INDICATOR))
|
526
|
+
)
|
527
|
+
warning(
|
528
|
+
%(You cannot mix + and ! for "#{field}": #{terms.join(',')})
|
529
|
+
)
|
530
|
+
exit 1
|
531
|
+
end
|
532
|
+
|
533
|
+
def assemble_query_string(query_parts)
|
534
|
+
if query_parts.empty?
|
535
|
+
''
|
536
|
+
else
|
537
|
+
# (lb): I tried escaping parts of the query but it broke things.
|
538
|
+
# So I'm assuming we don't need CGI.escape or URI.encode_www_form.
|
539
|
+
query_parts.to_json
|
540
|
+
end
|
541
|
+
end
|
542
|
+
|
543
|
+
def logs_display(sol)
|
544
|
+
if !@options.follow
|
545
|
+
logs_once(sol)
|
546
|
+
else
|
547
|
+
logs_follow(sol)
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
def logs_once(sol)
|
552
|
+
ret = sol.get("/logs#{query_string}")
|
553
|
+
if ret.is_a?(Hash) && ret.key?(:items)
|
554
|
+
ret[:items].reverse.each do |line|
|
555
|
+
if pretty_printing
|
556
|
+
print_pretty(line)
|
557
|
+
else
|
558
|
+
print_raw(line)
|
202
559
|
end
|
560
|
+
end
|
561
|
+
else
|
562
|
+
sol.error "Could not get logs: #{ret}"
|
563
|
+
exit 1
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
567
|
+
def query_string
|
568
|
+
# NOTE (lb): Unsure whether we need to encode parts of the query,
|
569
|
+
# possibly with CGI.escape. Note that http.get calls
|
570
|
+
# URI.encode_www_form(query), which the server will accept,
|
571
|
+
# but it will not produce any results.
|
572
|
+
params = []
|
573
|
+
params += ["query=#{@query}"] unless @query.empty?
|
574
|
+
params += ["limit=#{@options.limit}"] unless @options.limit.nil?
|
575
|
+
querys = params.join('&')
|
576
|
+
querys = "?#{querys}" unless querys.empty?
|
577
|
+
querys
|
578
|
+
end
|
203
579
|
|
204
|
-
|
205
|
-
|
206
|
-
|
580
|
+
# LATER/2017-12-14 (landonb): Show logs from all associated solutions.
|
581
|
+
# We'll have to wire all the WebSockets from within the EM.run block.
|
582
|
+
def logs_follow(sol)
|
583
|
+
formatter = fetch_formatter
|
584
|
+
keep_running = true
|
585
|
+
while keep_running
|
586
|
+
keep_running = @options.retry
|
587
|
+
logs = MrMurano::Logs::Follow.new(@query, @options.limit)
|
588
|
+
logs.run_event_loop(sol) do |line|
|
589
|
+
log_entry = parse_logs_line(line)
|
590
|
+
if log_entry[:statusCode] == 400
|
591
|
+
warning "Query error: #{log_entry}"
|
592
|
+
else
|
593
|
+
formatter.call(log_entry) unless log_entry.nil?
|
207
594
|
end
|
208
595
|
end
|
209
596
|
end
|
210
597
|
end
|
211
|
-
# rubocop:disable Lint/HandleExceptions: Do not suppress exceptions.
|
212
|
-
rescue Interrupt => _
|
213
|
-
end
|
214
598
|
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
599
|
+
def parse_logs_line(line)
|
600
|
+
log_entry = JSON.parse(line)
|
601
|
+
elevate_hash(log_entry)
|
602
|
+
rescue StandardError => err
|
603
|
+
warning "Not JSON: #{err} / #{line}"
|
604
|
+
nil
|
605
|
+
end
|
606
|
+
|
607
|
+
def fetch_formatter
|
608
|
+
if pretty_printing
|
609
|
+
method(:print_pretty)
|
610
|
+
else
|
611
|
+
method(:print_raw)
|
226
612
|
end
|
227
613
|
end
|
228
|
-
end
|
229
614
|
|
230
|
-
def
|
231
|
-
|
232
|
-
|
233
|
-
rescue StandardError => err
|
234
|
-
MrMurano::Verbose.warning "Not JSON: #{err} / #{line}"
|
235
|
-
nil
|
236
|
-
end
|
615
|
+
def pretty_printing
|
616
|
+
!@options.raw && ($cfg['tool.outformat'] == 'best')
|
617
|
+
end
|
237
618
|
|
238
|
-
def
|
239
|
-
|
240
|
-
method(:print_raw)
|
241
|
-
else
|
242
|
-
method(:print_pretty)
|
619
|
+
def print_raw(line)
|
620
|
+
outf line
|
243
621
|
end
|
244
|
-
end
|
245
622
|
|
246
|
-
def
|
247
|
-
|
623
|
+
def print_pretty(line)
|
624
|
+
determine_format(line) if @log_format_is_v2.nil?
|
625
|
+
if @log_format_is_v2
|
626
|
+
puts MrMurano::Pretties.MakePrettyLogsV2(line, @options)
|
627
|
+
else
|
628
|
+
puts MrMurano::Pretties.MakePrettyLogsV1(line, @options)
|
629
|
+
end
|
630
|
+
rescue StandardError => err
|
631
|
+
error "Failed to parse log: #{err} / #{line}"
|
632
|
+
raise
|
633
|
+
end
|
634
|
+
|
635
|
+
def determine_format(line)
|
636
|
+
# FIXME/2018-01-05 (landonb): On bizapi-dev, I see new-format logs,
|
637
|
+
# but on bizapi-staging, I see old school format. So deal with it.
|
638
|
+
# Logs V1 have 4 entries: type, timestamp, subject, data
|
639
|
+
# Logs V2 have lots more entries, including type, timestamp and data.
|
640
|
+
# We could use presence of subject to distinguish; or we could check
|
641
|
+
# timestamp, which is seconds in V1 but msecs in V2; or we could check
|
642
|
+
# data, which is string in V1, and Hash in V2.
|
643
|
+
@log_format_is_v2 = !(line[:data].is_a? String)
|
644
|
+
end
|
248
645
|
end
|
249
646
|
|
250
|
-
def
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
647
|
+
def wire_cmd_logs
|
648
|
+
logs_cmd = LogsCmd.new
|
649
|
+
command(:logs) { |cmd| logs_cmd.command_logs(cmd) }
|
650
|
+
alias_command 'logs application', 'logs', '--type', 'application'
|
651
|
+
alias_command 'logs product', 'logs', '--type', 'product'
|
652
|
+
alias_command 'application logs', 'logs', '--type', 'application'
|
653
|
+
alias_command 'product logs', 'logs', '--type', 'product'
|
255
654
|
end
|
256
655
|
|
257
|
-
|
258
|
-
alias_command 'logs application', 'logs', '--type', 'application'
|
259
|
-
alias_command 'logs product', 'logs', '--type', 'product'
|
260
|
-
alias_command 'application logs', 'logs', '--type', 'application'
|
261
|
-
alias_command 'product logs', 'logs', '--type', 'product'
|
656
|
+
wire_cmd_logs
|
262
657
|
|