parallel_report_portal 2.0.0
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 +7 -0
- data/.drone.yml +21 -0
- data/.gitignore +18 -0
- data/.rspec +1 -0
- data/Appraisals +15 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +148 -0
- data/LICENSE.txt +22 -0
- data/ParallelReportPortal.drawio +1 -0
- data/README.md +76 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/gemfiles/.bundle/config +2 -0
- data/gemfiles/cucumber_3.2.gemfile +7 -0
- data/gemfiles/cucumber_4.1.gemfile +7 -0
- data/gemfiles/cucumber_5.2.gemfile +7 -0
- data/gemfiles/cucumber_6.0.gemfile +7 -0
- data/lib/parallel_report_portal.rb +41 -0
- data/lib/parallel_report_portal/clock.rb +13 -0
- data/lib/parallel_report_portal/configuration.rb +138 -0
- data/lib/parallel_report_portal/cucumber/formatter.rb +73 -0
- data/lib/parallel_report_portal/cucumber/report.rb +226 -0
- data/lib/parallel_report_portal/file_utils.rb +62 -0
- data/lib/parallel_report_portal/http.rb +228 -0
- data/lib/parallel_report_portal/version.rb +3 -0
- data/parallel_report_portal.gemspec +45 -0
- metadata +241 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module ParallelReportPortal
|
|
2
|
+
# This module is responsilbe for the timekeeping for the tests.
|
|
3
|
+
module Clock
|
|
4
|
+
# Get the current time.
|
|
5
|
+
#
|
|
6
|
+
# This is based on the Unix time stamp and is in milliseconds.
|
|
7
|
+
#
|
|
8
|
+
# @return [Integer] the number of milliseconds since the Unix epoc.
|
|
9
|
+
def clock
|
|
10
|
+
(Time.now.to_f * 1000).to_i
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
module ParallelReportPortal
|
|
2
|
+
# The Configuration class holds the connection properties to communicate with
|
|
3
|
+
# Report Portal and to identify the user and project for reporting.
|
|
4
|
+
#
|
|
5
|
+
# It attempts to load a configuration file called +report_portal.yml+ first in a
|
|
6
|
+
# local directory called +config+ and if that's not found in the current directory.
|
|
7
|
+
# (Report Portal actually tells you to create a files called +REPORT_PORTAL.YML+ in
|
|
8
|
+
# uppercase -- for this reason the initializer is case insensitive with regards to
|
|
9
|
+
# the file name)
|
|
10
|
+
#
|
|
11
|
+
# It will then try an apply the following environment variables, if present (these
|
|
12
|
+
# can be specified in either lowercase for backwards compatibility with the official
|
|
13
|
+
# gem or in uppercase for reasons of sanity)
|
|
14
|
+
#
|
|
15
|
+
# == Environment variables
|
|
16
|
+
#
|
|
17
|
+
# RP_UUID:: The UUID of the user associated with this launch
|
|
18
|
+
# RP_ENDPOINT:: the URL of the Report Portal API endpoint
|
|
19
|
+
# RP_PROJECT:: the Report Portal project name -- this must already exist within Report Port and this user must be a member of the project
|
|
20
|
+
# RP_LAUNCH:: The name of this launch
|
|
21
|
+
# RP_DESCRIPTION:: A textual string describing this launch
|
|
22
|
+
# RP_TAGS:: A set of tags to pass to Report Portal for this launch. If these are set via an environment variable, provide a comma-separated string of tags
|
|
23
|
+
# RP_ATTRIBUTES:: A set of attribute tags to pass to Report Portal for this launch. If these are set via an environment variable, provide a comma-separated string of attributes
|
|
24
|
+
class Configuration
|
|
25
|
+
ATTRIBUTES = [:uuid, :endpoint, :project, :launch, :debug, :description, :tags, :attributes]
|
|
26
|
+
|
|
27
|
+
# @return [String] the Report Portal user UUID
|
|
28
|
+
attr_accessor :uuid
|
|
29
|
+
# @return [String] the Report Portal URI - this should include the scheme
|
|
30
|
+
# e.g. +https://reportportal.local/api/v1+
|
|
31
|
+
attr_accessor :endpoint
|
|
32
|
+
# @return [String] the Report Portal project name.
|
|
33
|
+
# This must exist and match the project name within
|
|
34
|
+
# Report Portal.
|
|
35
|
+
attr_accessor :project
|
|
36
|
+
# @return [String] the launch name for this test run.
|
|
37
|
+
attr_accessor :launch
|
|
38
|
+
# @return [Array<String>] an array of tags to attach to this launch.
|
|
39
|
+
attr_reader :tags
|
|
40
|
+
# @return [Boolean] true if this is a debug run (this launch will appear
|
|
41
|
+
# on the debug tab in Report Portal).
|
|
42
|
+
attr_reader :debug
|
|
43
|
+
# @return [String] a textual description of this launch.
|
|
44
|
+
attr_accessor :description
|
|
45
|
+
# @return [Array<String>] an array of attributes to attach to this launch
|
|
46
|
+
# (Report Portal 5)
|
|
47
|
+
attr_reader :attributes
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# Create an instance of Configuration.
|
|
51
|
+
#
|
|
52
|
+
# The initializer will first attempt to load a configuration files called
|
|
53
|
+
# +report_portal.yml+ (case insensitive) in the both the +config+ and current
|
|
54
|
+
# working directory (the former takes precidence). It will then apply
|
|
55
|
+
# any of the environment variable values.
|
|
56
|
+
def initialize
|
|
57
|
+
load_configuration_file
|
|
58
|
+
ATTRIBUTES.each do |attr|
|
|
59
|
+
env_value = get_env("rp_#{attr.to_s}")
|
|
60
|
+
send(:"#{attr}=", env_value) if env_value
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Sets the tags for the launch. If an array is provided, the array is used,
|
|
65
|
+
# if a string is provided, the string is broken into components by splitting
|
|
66
|
+
# on a comma.
|
|
67
|
+
#
|
|
68
|
+
# e.g.
|
|
69
|
+
# configuration.tags="one,two, three"
|
|
70
|
+
# #=> ["one", "two", "three"]
|
|
71
|
+
#
|
|
72
|
+
# param [String | Array<String>] taglist a list of tags to set
|
|
73
|
+
def tags=(taglist)
|
|
74
|
+
if taglist.is_a?(String)
|
|
75
|
+
@tags = taglist.split(',').map(&:strip)
|
|
76
|
+
elsif taglist.is_a?(Array)
|
|
77
|
+
@tags = taglist
|
|
78
|
+
else
|
|
79
|
+
@tags = []
|
|
80
|
+
end
|
|
81
|
+
tags
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# Enables the debug flag which is sent to Report Portal. If this flag is set
|
|
86
|
+
# Report Portal will include this launch in its 'debug' tab.
|
|
87
|
+
#
|
|
88
|
+
# param [Boolean | String] value if the value is a Boolean, it will take that value
|
|
89
|
+
# if it is a String, it will set values of 'true' to +true+, else all values will be false.
|
|
90
|
+
def debug=(value)
|
|
91
|
+
@debug = if [true, false].include?(value)
|
|
92
|
+
value
|
|
93
|
+
else
|
|
94
|
+
value.to_s.downcase.strip == 'true'
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Sets the attributes for the launch. If an array is provided, the array is used,
|
|
99
|
+
# if a string is provided, the string is broken into components by splitting
|
|
100
|
+
# on a comma.
|
|
101
|
+
#
|
|
102
|
+
# e.g.
|
|
103
|
+
# configuration.tags="one,two, three"
|
|
104
|
+
# #=> ["one", "two", "three"]
|
|
105
|
+
#
|
|
106
|
+
# param [String | Array<String>] taglist a list of tags to set
|
|
107
|
+
def attributes=(attrlist)
|
|
108
|
+
if attrlist.is_a?(String)
|
|
109
|
+
@attributes = attrlist.split(',').map(&:strip)
|
|
110
|
+
elsif attrlist.is_a?(Array)
|
|
111
|
+
@attributes = attrlist
|
|
112
|
+
else
|
|
113
|
+
@attributes = []
|
|
114
|
+
end
|
|
115
|
+
attributes
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
private
|
|
119
|
+
|
|
120
|
+
def get_env(name)
|
|
121
|
+
ENV[name.upcase] || ENV[name.downcase]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def load_configuration_file
|
|
125
|
+
files = Dir['./config/*'] + Dir['./*']
|
|
126
|
+
files
|
|
127
|
+
.filter { |fn| fn.downcase.end_with?('/report_portal.yml') }
|
|
128
|
+
.first
|
|
129
|
+
.then { |fn| fn ? File.read(fn) : '' }
|
|
130
|
+
.then { |ys| YAML.safe_load(ys, symbolize_names: true) }
|
|
131
|
+
.then do |yaml|
|
|
132
|
+
ATTRIBUTES.each do |attr|
|
|
133
|
+
send(:"#{attr}=", yaml[attr]) if yaml&.fetch(attr, nil)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
require_relative 'report'
|
|
2
|
+
|
|
3
|
+
module ParallelReportPortal
|
|
4
|
+
module Cucumber
|
|
5
|
+
# Formatter supporting the Cucumber formatter API.
|
|
6
|
+
# This is the class which does the heavy-lifting by
|
|
7
|
+
# integrating with cucumber.
|
|
8
|
+
class Formatter
|
|
9
|
+
|
|
10
|
+
CucumberMessagesVersion=[4,0,0]
|
|
11
|
+
|
|
12
|
+
# Create a new formatter instance
|
|
13
|
+
#
|
|
14
|
+
# @param [Cucumber::Configuration] cucumber_config the cucumber configuration environment
|
|
15
|
+
def initialize(cucumber_config)
|
|
16
|
+
@ast_lookup = if (::Cucumber::VERSION.split('.').map(&:to_i) <=> CucumberMessagesVersion) > 0
|
|
17
|
+
require 'cucumber/formatter/ast_lookup'
|
|
18
|
+
::Cucumber::Formatter::AstLookup.new(cucumber_config)
|
|
19
|
+
else
|
|
20
|
+
nil
|
|
21
|
+
end
|
|
22
|
+
start_background_thread.priority = Thread.main.priority + 1
|
|
23
|
+
register_event_handlers(cucumber_config)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def report
|
|
29
|
+
@report ||= Report.new(@ast_lookup)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def register_event_handlers(config)
|
|
33
|
+
[:test_case_started,
|
|
34
|
+
:test_case_finished,
|
|
35
|
+
:test_step_started,
|
|
36
|
+
:test_step_finished].each do |event_name|
|
|
37
|
+
config.on_event(event_name) do |event|
|
|
38
|
+
background_queue << -> { report.public_send(event_name, event, ParallelReportPortal.clock) }
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
config.on_event :test_run_started, &method(:handle_test_run_started )
|
|
42
|
+
config.on_event :test_run_finished, &method(:handle_test_run_finished)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def handle_test_run_started(event)
|
|
46
|
+
background_queue << proc { report.launch_started(ParallelReportPortal.clock) }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def background_queue
|
|
50
|
+
@background_queue ||= Queue.new
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def start_background_thread
|
|
54
|
+
@background_thread ||= Thread.new do
|
|
55
|
+
loop do
|
|
56
|
+
code = background_queue.shift
|
|
57
|
+
code.call
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def handle_test_run_finished(event)
|
|
63
|
+
background_queue << proc do
|
|
64
|
+
report.feature_finished(ParallelReportPortal.clock)
|
|
65
|
+
report.launch_finished(ParallelReportPortal.clock)
|
|
66
|
+
end
|
|
67
|
+
sleep 0.01 while !background_queue.empty? || background_queue.num_waiting == 0
|
|
68
|
+
@background_thread.kill
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
require 'faraday'
|
|
2
|
+
require 'tree'
|
|
3
|
+
|
|
4
|
+
module ParallelReportPortal
|
|
5
|
+
module Cucumber
|
|
6
|
+
# Report object. This handles the management of the state heirarchy and
|
|
7
|
+
# the issuing of the requests to the HTTP module.
|
|
8
|
+
class Report
|
|
9
|
+
|
|
10
|
+
attr_reader :launch_id
|
|
11
|
+
|
|
12
|
+
Feature = Struct.new(:feature, :id)
|
|
13
|
+
|
|
14
|
+
LOG_LEVELS = {
|
|
15
|
+
error: 'ERROR',
|
|
16
|
+
warn: 'WARN',
|
|
17
|
+
info: 'INFO',
|
|
18
|
+
debug: 'DEBUG',
|
|
19
|
+
trace: 'TRACE',
|
|
20
|
+
fatal: 'FATAL',
|
|
21
|
+
unknown: 'UNKNOWN'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# Create a new instance of the report
|
|
26
|
+
def initialize(ast_lookup = nil)
|
|
27
|
+
@feature = nil
|
|
28
|
+
@tree = Tree::TreeNode.new( 'root' )
|
|
29
|
+
@ast_lookup = ast_lookup
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Issued to start a launch. It is possilbe that this method could be called
|
|
33
|
+
# from multiple processes for the same launch if this is being run with
|
|
34
|
+
# parallel tests enabled. A temporary launch file will be created (using
|
|
35
|
+
# exclusive locking). The first time this method is called it will write the
|
|
36
|
+
# launch id to the launch file, subsequent calls by other processes will read
|
|
37
|
+
# this launch id and use that.
|
|
38
|
+
#
|
|
39
|
+
# @param start_time [Integer] the millis from the epoch
|
|
40
|
+
# @return [String] the UUID of this launch
|
|
41
|
+
def launch_started(start_time)
|
|
42
|
+
ParallelReportPortal.file_open_exlock_and_block(ParallelReportPortal.launch_id_file, 'a+' ) do |file|
|
|
43
|
+
if file.size == 0
|
|
44
|
+
@launch_id = ParallelReportPortal.req_launch_started(start_time)
|
|
45
|
+
file.write(@launch_id)
|
|
46
|
+
file.flush
|
|
47
|
+
else
|
|
48
|
+
@launch_id = file.readline
|
|
49
|
+
end
|
|
50
|
+
@launch_id
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Called to finish a launch. Any open children items will be closed in the process.
|
|
55
|
+
#
|
|
56
|
+
# @param clock [Integer] the millis from the epoch
|
|
57
|
+
def launch_finished(clock)
|
|
58
|
+
@tree.postordered_each do |node|
|
|
59
|
+
ParallelReportPortal.req_feature_finished(node.content, clock) unless node.is_root?
|
|
60
|
+
end
|
|
61
|
+
ParallelReportPortal.req_launch_finished(launch_id, clock)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Called to indicate that a feature has started.
|
|
65
|
+
#
|
|
66
|
+
# @param
|
|
67
|
+
def feature_started(feature, clock)
|
|
68
|
+
parent_id = hierarchy(feature, clock)
|
|
69
|
+
feature = feature.feature if using_cucumber_messages?
|
|
70
|
+
ParallelReportPortal.req_feature_started(launch_id, parent_id, feature, clock)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def feature_finished(clock)
|
|
74
|
+
if @feature
|
|
75
|
+
resp = ParallelReportPortal.req_feature_finished(@feature.id, clock)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def test_case_started(event, clock)
|
|
80
|
+
test_case = lookup_test_case(event.test_case)
|
|
81
|
+
feature = lookup_feature(event.test_case)
|
|
82
|
+
feature = current_feature(feature, clock)
|
|
83
|
+
@test_case_id = ParallelReportPortal.req_test_case_started(launch_id, feature.id, test_case, clock)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_case_finished(event, clock)
|
|
87
|
+
result = event.result
|
|
88
|
+
status = result.to_sym
|
|
89
|
+
failure_message = nil
|
|
90
|
+
if [:undefined, :pending].include?(status)
|
|
91
|
+
status = :failed
|
|
92
|
+
failure_message = result.message
|
|
93
|
+
end
|
|
94
|
+
resp = ParallelReportPortal.req_test_case_finished(@test_case_id, status, clock)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def test_step_started(event, clock)
|
|
98
|
+
test_step = event.test_step
|
|
99
|
+
if !hook?(test_step)
|
|
100
|
+
step_source = lookup_step_source(test_step)
|
|
101
|
+
detail = "#{step_source.keyword} #{step_source.text}"
|
|
102
|
+
if (using_cucumber_messages? ? test_step : step_source).multiline_arg.doc_string?
|
|
103
|
+
detail << %(\n"""\n#{(using_cucumber_messages? ? test_step : step_source).multiline_arg.content}\n""")
|
|
104
|
+
elsif (using_cucumber_messages? ? test_step : step_source).multiline_arg.data_table?
|
|
105
|
+
detail << (using_cucumber_messages? ? test_step : step_source).multiline_arg.raw.reduce("\n") {|acc, row| acc << "| #{row.join(' | ')} |\n"}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
ParallelReportPortal.req_log(@test_case_id, detail, status_to_level(:trace), clock)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def test_step_finished(event, clock)
|
|
113
|
+
test_step = event.test_step
|
|
114
|
+
result = event.result
|
|
115
|
+
status = result.to_sym
|
|
116
|
+
if !hook?(test_step)
|
|
117
|
+
step_source = lookup_step_source(test_step)
|
|
118
|
+
detail = "#{step_source.text}"
|
|
119
|
+
|
|
120
|
+
if [:failed, :pending, :undefined].include?(status)
|
|
121
|
+
level = :error
|
|
122
|
+
detail = if [:failed, :pending].include?(status)
|
|
123
|
+
ex = result.exception
|
|
124
|
+
sprintf("%s: %s\n %s", ex.class.name, ex.message, ex.backtrace.join("\n "))
|
|
125
|
+
else
|
|
126
|
+
sprintf("Undefined step: %s:\n%s", test_step.text, test_step.source.last.backtrace_line)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
ParallelReportPortal.req_log(@test_case_id, detail, level, clock)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def using_cucumber_messages?
|
|
137
|
+
@ast_lookup != nil
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def hierarchy(feature, clock)
|
|
141
|
+
node = nil
|
|
142
|
+
path_components = if using_cucumber_messages?
|
|
143
|
+
feature.uri.split(File::SEPARATOR)
|
|
144
|
+
else
|
|
145
|
+
feature.location.file.split(File::SEPARATOR)
|
|
146
|
+
end
|
|
147
|
+
ParallelReportPortal.file_open_exlock_and_block(ParallelReportPortal.hierarchy_file, 'a+b' ) do |file|
|
|
148
|
+
@tree = Marshal.load(File.read(file)) if file.size > 0
|
|
149
|
+
node = @tree.root
|
|
150
|
+
path_components[0..-2].each do |component|
|
|
151
|
+
next_node = node[component]
|
|
152
|
+
unless next_node
|
|
153
|
+
id = ParallelReportPortal.req_hierarchy(launch_id, "Folder: #{component}", node.content, 'SUITE', [], nil, clock )
|
|
154
|
+
next_node = Tree::TreeNode.new(component, id)
|
|
155
|
+
node << next_node
|
|
156
|
+
node = next_node
|
|
157
|
+
else
|
|
158
|
+
node = next_node
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
file.truncate(0)
|
|
162
|
+
file.write(Marshal.dump(@tree))
|
|
163
|
+
file.flush
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
node.content
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def lookup_feature(test_case)
|
|
170
|
+
if using_cucumber_messages?
|
|
171
|
+
@ast_lookup.gherkin_document(test_case.location.file)
|
|
172
|
+
else
|
|
173
|
+
test_case.feature
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def lookup_test_case(test_case)
|
|
178
|
+
if using_cucumber_messages?
|
|
179
|
+
@ast_lookup.gherkin_document(test_case.location.file).feature
|
|
180
|
+
else
|
|
181
|
+
test_case
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def lookup_step_source(step)
|
|
186
|
+
if using_cucumber_messages?
|
|
187
|
+
@ast_lookup.step_source(step).step
|
|
188
|
+
else
|
|
189
|
+
step.source.last
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def current_feature(feature, clock)
|
|
194
|
+
if @feature&.feature == feature
|
|
195
|
+
@feature
|
|
196
|
+
else
|
|
197
|
+
feature_finished(clock)
|
|
198
|
+
@feature = Feature.new(feature, feature_started(feature, clock))
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def hook?(test_step)
|
|
203
|
+
if using_cucumber_messages?
|
|
204
|
+
test_step.hook?
|
|
205
|
+
else
|
|
206
|
+
! test_step.source.last.respond_to?(:keyword)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def status_to_level(status)
|
|
211
|
+
case status
|
|
212
|
+
when :passed
|
|
213
|
+
LOG_LEVELS[:info]
|
|
214
|
+
when :failed, :undefined, :pending, :error
|
|
215
|
+
LOG_LEVELS[:error]
|
|
216
|
+
when :skipped
|
|
217
|
+
LOG_LEVELS[:warn]
|
|
218
|
+
else
|
|
219
|
+
LOG_LEVELS.fetch(status, LOG_LEVELS[:info])
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|