structured_log 0.9.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/.gitignore +9 -0
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +27 -0
- data/LICENSE +21 -0
- data/LICENSE.txt +21 -0
- data/README.md +620 -0
- data/Rakefile +44 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/images/array.jpg +0 -0
- data/images/attributes.png +0 -0
- data/images/comment.png +0 -0
- data/images/custom.png +0 -0
- data/images/data.png +0 -0
- data/images/exception.png +0 -0
- data/images/hash.png +0 -0
- data/images/nesting.jpg +0 -0
- data/images/potpourri.png +0 -0
- data/images/rescue.jpg +0 -0
- data/images/structured.png +0 -0
- data/images/text.jpg +0 -0
- data/images/time.png +0 -0
- data/lib/structured_log.rb +322 -0
- data/lib/structured_log/version.rb +3 -0
- data/readme_files/README.template.md +167 -0
- data/readme_files/logs/array.xml +10 -0
- data/readme_files/logs/attributes.xml +7 -0
- data/readme_files/logs/cdata.xml +12 -0
- data/readme_files/logs/comment.xml +5 -0
- data/readme_files/logs/custom_entry.xml +12 -0
- data/readme_files/logs/custom_section.xml +14 -0
- data/readme_files/logs/data.xml +20 -0
- data/readme_files/logs/exception.xml +14 -0
- data/readme_files/logs/hash.xml +10 -0
- data/readme_files/logs/potpourri.xml +13 -0
- data/readme_files/logs/potpourri_other.xml +20 -0
- data/readme_files/logs/potpourri_usual.xml +8 -0
- data/readme_files/logs/rescue.xml +28 -0
- data/readme_files/logs/sections.xml +7 -0
- data/readme_files/logs/text.xml +5 -0
- data/readme_files/logs/time.xml +17 -0
- data/readme_files/scripts/array.rb +6 -0
- data/readme_files/scripts/attributes.rb +8 -0
- data/readme_files/scripts/cdata.rb +17 -0
- data/readme_files/scripts/comment.rb +5 -0
- data/readme_files/scripts/custom_entry.rb +10 -0
- data/readme_files/scripts/custom_section.rb +15 -0
- data/readme_files/scripts/data.rb +16 -0
- data/readme_files/scripts/exception.rb +5 -0
- data/readme_files/scripts/hash.rb +11 -0
- data/readme_files/scripts/potpourri.rb +20 -0
- data/readme_files/scripts/potpourri_other.rb +17 -0
- data/readme_files/scripts/potpourri_usual.rb +12 -0
- data/readme_files/scripts/rescue.rb +12 -0
- data/readme_files/scripts/sections.rb +17 -0
- data/readme_files/scripts/text.rb +8 -0
- data/readme_files/scripts/time.rb +15 -0
- data/structured_log.gemspec +38 -0
- metadata +176 -0
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'markdown_helper'
|
4
|
+
|
5
|
+
Rake::TestTask.new(:test) do |t|
|
6
|
+
t.libs << 'test'
|
7
|
+
t.libs << 'lib'
|
8
|
+
t.test_files = FileList['test/**/*_test.rb']
|
9
|
+
end
|
10
|
+
|
11
|
+
namespace :build do
|
12
|
+
|
13
|
+
desc 'Build README.md file from README.template.md'
|
14
|
+
task :readme do
|
15
|
+
readme_dir_path = File.join(
|
16
|
+
File.dirname(__FILE__),
|
17
|
+
'readme_files',
|
18
|
+
)
|
19
|
+
source_dir_path = File.join(
|
20
|
+
readme_dir_path,
|
21
|
+
'scripts',
|
22
|
+
)
|
23
|
+
target_dir_path = File.join(
|
24
|
+
readme_dir_path,
|
25
|
+
'logs',
|
26
|
+
)
|
27
|
+
source_file_paths = Dir.glob("#{source_dir_path}/*.rb")
|
28
|
+
# Run the scripts in the logs directory,
|
29
|
+
# because (e.g.) in array.rb, the :file_path
|
30
|
+
# to the output log file is the simple 'array.xml',
|
31
|
+
# which keeps the file more readable.
|
32
|
+
chdir(target_dir_path) do
|
33
|
+
source_file_paths.each do |source_file_path|
|
34
|
+
command = "ruby #{source_file_path}"
|
35
|
+
system(command)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
markdown_helper = MarkdownHelper.new
|
39
|
+
markdown_helper.include('readme_files/README.template.md', 'README.md')
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
|
44
|
+
task :default => :test
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'structured_log'
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require 'pry'
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require 'irb'
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
data/images/array.jpg
ADDED
Binary file
|
Binary file
|
data/images/comment.png
ADDED
Binary file
|
data/images/custom.png
ADDED
Binary file
|
data/images/data.png
ADDED
Binary file
|
Binary file
|
data/images/hash.png
ADDED
Binary file
|
data/images/nesting.jpg
ADDED
Binary file
|
Binary file
|
data/images/rescue.jpg
ADDED
Binary file
|
Binary file
|
data/images/text.jpg
ADDED
Binary file
|
data/images/time.png
ADDED
Binary file
|
@@ -0,0 +1,322 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
|
3
|
+
class StructuredLog
|
4
|
+
|
5
|
+
attr_accessor \
|
6
|
+
:file,
|
7
|
+
:file_path,
|
8
|
+
:backtrace_filter,
|
9
|
+
:root_name,
|
10
|
+
:xml_indentation
|
11
|
+
|
12
|
+
include REXML
|
13
|
+
|
14
|
+
DEFAULT_FILE_NAME = 'log.xml'
|
15
|
+
DEFAULT_DIR_PATH = '.'
|
16
|
+
DEFAULT_XML_ROOT_TAG_NAME = 'log'
|
17
|
+
DEFAULT_XML_INDENTATION = 2
|
18
|
+
|
19
|
+
# Message for no block error.
|
20
|
+
NO_BLOCK_GIVEN_MSG = 'No block given'
|
21
|
+
# Message for calling-new error.
|
22
|
+
NO_NEW_MSG = format('Please use %s.open, not %s.new.', self.class.name, self.class.name)
|
23
|
+
|
24
|
+
# Callers should call this method, not method +new+.
|
25
|
+
# +file_path+ is the path to the output log file.
|
26
|
+
# Options can include:
|
27
|
+
# - :root_name => _root-xml-tag-name_.
|
28
|
+
# - :xml_indentation => Integer: indentation for nesting XML sub-elements.
|
29
|
+
def self.open(file_path = File.join(DEFAULT_DIR_PATH, DEFAULT_FILE_NAME), options=Hash.new)
|
30
|
+
raise NO_BLOCK_GIVEN_MSG unless (block_given?)
|
31
|
+
default_options = Hash[
|
32
|
+
:root_name => DEFAULT_XML_ROOT_TAG_NAME,
|
33
|
+
:xml_indentation => DEFAULT_XML_INDENTATION
|
34
|
+
]
|
35
|
+
options = default_options.merge(options)
|
36
|
+
log = self.new(file_path, options, im_ok_youre_not_ok = true)
|
37
|
+
begin
|
38
|
+
yield log
|
39
|
+
rescue => x
|
40
|
+
log.put_element('uncaught_exception', :timestamp, :class => x.class) do
|
41
|
+
log.put_element('message', x.message)
|
42
|
+
log.put_element('backtrace') do
|
43
|
+
backtrace = log.send(:filter_backtrace, x.backtrace)
|
44
|
+
log.send(:put_cdata, backtrace)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
log.send(:dispose)
|
49
|
+
log.file_path
|
50
|
+
end
|
51
|
+
|
52
|
+
# Start a new section, within the current section.
|
53
|
+
# Sections may be nested.
|
54
|
+
# - +name+: name for the section.
|
55
|
+
# - *+args+: passed to method +put_element+.
|
56
|
+
def section(name, *args)
|
57
|
+
put_element('section', {:name => name}, *args) do
|
58
|
+
yield
|
59
|
+
end
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def comment(text, *args)
|
64
|
+
put_element('comment', text, *args)
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
# Log an XML element.
|
69
|
+
# - +element_name+: Element name for logged element.
|
70
|
+
# - *+args+: Anything; processed left to right; for each _arg_:
|
71
|
+
# - +Hash+: becomes element attributes.
|
72
|
+
# - +String+: appended to PCDATA.
|
73
|
+
# - +:timestamp+: causes a timestamp to be added to the element.
|
74
|
+
# - +:duration+: causes block's execution duration to be added to the element.
|
75
|
+
# - +:rescue+: causes any exception raised during block execution to be rescued and logged.
|
76
|
+
# - else: _arg_.inspect is appended to PCDATA.
|
77
|
+
def put_element(element_name = 'element', *args)
|
78
|
+
attributes = {}
|
79
|
+
pcdata = ''
|
80
|
+
start_time = nil
|
81
|
+
duration_to_be_included = false
|
82
|
+
block_to_be_rescued = false
|
83
|
+
args.each do |arg|
|
84
|
+
case
|
85
|
+
when arg.kind_of?(Hash)
|
86
|
+
attributes.merge!(arg)
|
87
|
+
when arg.kind_of?(String)
|
88
|
+
pcdata += arg
|
89
|
+
when arg == :timestamp
|
90
|
+
attributes[:timestamp] = StructuredLog.timestamp
|
91
|
+
when arg == :duration
|
92
|
+
duration_to_be_included = true
|
93
|
+
when arg == :rescue
|
94
|
+
block_to_be_rescued = true
|
95
|
+
else
|
96
|
+
pcdata = pcdata + arg.inspect
|
97
|
+
end
|
98
|
+
end
|
99
|
+
log_puts("BEGIN\t#{element_name}")
|
100
|
+
put_attributes(attributes)
|
101
|
+
unless pcdata.empty?
|
102
|
+
# Guard against using a terminator that's a substring of pcdata.
|
103
|
+
s = 'EOT'
|
104
|
+
terminator = s
|
105
|
+
while pcdata.match(terminator) do
|
106
|
+
terminator += s
|
107
|
+
end
|
108
|
+
log_puts("PCDATA\t<<#{terminator}")
|
109
|
+
log_puts(pcdata)
|
110
|
+
log_puts(terminator)
|
111
|
+
end
|
112
|
+
start_time = Time.new if duration_to_be_included
|
113
|
+
if block_given?
|
114
|
+
if block_to_be_rescued
|
115
|
+
begin
|
116
|
+
yield
|
117
|
+
rescue Exception => x
|
118
|
+
put_element('rescued_exception', :timestamp, :class => x.class) do
|
119
|
+
put_element('message', x.message)
|
120
|
+
put_element('backtrace') do
|
121
|
+
put_cdata(filter_backtrace(x.backtrace))
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
else
|
126
|
+
yield
|
127
|
+
end
|
128
|
+
end
|
129
|
+
if start_time
|
130
|
+
end_time = Time.now
|
131
|
+
duration_f = end_time.to_f - start_time.to_f
|
132
|
+
duration_s = format('%.3f', duration_f)
|
133
|
+
put_attributes({:duration_seconds => duration_s})
|
134
|
+
end
|
135
|
+
log_puts("END\t#{element_name}")
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
|
139
|
+
def put_each_with_index(name, obj)
|
140
|
+
lines = ['']
|
141
|
+
obj.each_with_index do |item, i|
|
142
|
+
lines.push(format('%6d %s', i, item.to_s))
|
143
|
+
end
|
144
|
+
lines.push('')
|
145
|
+
lines.push('')
|
146
|
+
put_element('each_with_index', :name => name, :class => obj.class) do
|
147
|
+
put_cdata(lines.join("\n"))
|
148
|
+
end
|
149
|
+
nil
|
150
|
+
end
|
151
|
+
alias put_array put_each_with_index
|
152
|
+
alias put_set put_each_with_index
|
153
|
+
|
154
|
+
def put_each_pair(name, obj)
|
155
|
+
lines = ['']
|
156
|
+
obj.each_pair do |key, value|
|
157
|
+
lines.push(format('%s => %s', key, value))
|
158
|
+
end
|
159
|
+
lines.push('')
|
160
|
+
lines.push('')
|
161
|
+
put_element('each_pair', :name => name, :class => obj.class) do
|
162
|
+
put_cdata(lines.join("\n"))
|
163
|
+
end
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
alias put_hash put_each_pair
|
167
|
+
|
168
|
+
def put_data(name, obj)
|
169
|
+
put_element('data', :name => name, :class => obj.class) do
|
170
|
+
put_cdata(obj.inspect)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def put_cdata(text)
|
175
|
+
# Guard against using a terminator that's a substring of the cdata.
|
176
|
+
s = 'EOT'
|
177
|
+
terminator = s
|
178
|
+
while text.match(terminator) do
|
179
|
+
terminator += s
|
180
|
+
end
|
181
|
+
log_puts("CDATA\t<<#{terminator}")
|
182
|
+
log_puts(text)
|
183
|
+
log_puts(terminator)
|
184
|
+
nil
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def initialize(file_path = File.join(DEFAULT_DIR_PATH, DEFAULT_FILE_NAME), options = Hash.new, im_ok_youre_not_ok = false)
|
190
|
+
unless im_ok_youre_not_ok
|
191
|
+
# Caller should call StructuredLog.open, not StructuredLog.new.
|
192
|
+
raise RuntimeError.new(NO_NEW_MSG)
|
193
|
+
end
|
194
|
+
self.file_path = file_path
|
195
|
+
self.root_name = options[:root_name]
|
196
|
+
self.xml_indentation = options[:xml_indentation]
|
197
|
+
self.backtrace_filter = options[:backtrace_filter] || /structured_log|ruby/
|
198
|
+
self.file = File.open(self.file_path, 'w')
|
199
|
+
log_puts("REMARK\tThis text log is the precursor for an XML log.")
|
200
|
+
log_puts("REMARK\tIf the logged process completes, this text will be converted to XML.")
|
201
|
+
log_puts("BEGIN\t#{self.root_name}")
|
202
|
+
nil
|
203
|
+
end
|
204
|
+
|
205
|
+
def dispose
|
206
|
+
|
207
|
+
# Close the text log.
|
208
|
+
log_puts("END\t#{self.root_name}")
|
209
|
+
self.file.close
|
210
|
+
|
211
|
+
# Create the xml log.
|
212
|
+
document = REXML::Document.new
|
213
|
+
File.open(self.file_path, 'r') do |file|
|
214
|
+
element = document
|
215
|
+
stack = Array.new
|
216
|
+
data_a = nil
|
217
|
+
terminator = nil
|
218
|
+
file.each_line do |line|
|
219
|
+
line.chomp!
|
220
|
+
line_type, text = line.split("\t", 2)
|
221
|
+
case line_type
|
222
|
+
when 'REMARK'
|
223
|
+
next
|
224
|
+
when 'BEGIN'
|
225
|
+
element_name = text
|
226
|
+
element = element.add_element(element_name)
|
227
|
+
stack.push(element)
|
228
|
+
when 'END'
|
229
|
+
stack.pop
|
230
|
+
element = stack.last
|
231
|
+
when 'ATTRIBUTE'
|
232
|
+
attr_name, attr_value = text.split("\t", 2)
|
233
|
+
element.add_attribute(attr_name, attr_value)
|
234
|
+
when 'CDATA'
|
235
|
+
stack.push(:cdata)
|
236
|
+
data_a = Array.new
|
237
|
+
terminator = text.split('<<', 2).last
|
238
|
+
when 'PCDATA'
|
239
|
+
stack.push(:pcdata)
|
240
|
+
data_a = Array.new
|
241
|
+
terminator = text.split('<<', 2).last
|
242
|
+
when terminator
|
243
|
+
data_s = data_a.join("\n")
|
244
|
+
data_a = nil
|
245
|
+
terminator = nil
|
246
|
+
data_type = stack.last
|
247
|
+
case data_type
|
248
|
+
when :cdata
|
249
|
+
cdata = CData.new(data_s)
|
250
|
+
element.add(cdata)
|
251
|
+
when :pcdata
|
252
|
+
element.add_text(data_s)
|
253
|
+
else
|
254
|
+
# Don't want to raise an exception and spoil the run
|
255
|
+
end
|
256
|
+
stack.pop
|
257
|
+
else
|
258
|
+
data_a.push(line) if (terminator)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
document << XMLDecl.default
|
262
|
+
end
|
263
|
+
|
264
|
+
File.open(self.file_path, 'w') do |file|
|
265
|
+
document.write(file, self.xml_indentation)
|
266
|
+
file.puts('')
|
267
|
+
end
|
268
|
+
nil
|
269
|
+
end
|
270
|
+
|
271
|
+
def put_attributes(attributes)
|
272
|
+
attributes.each_pair do |name, value|
|
273
|
+
value = case
|
274
|
+
when value.is_a?(String)
|
275
|
+
value
|
276
|
+
when value.is_a?(Symbol)
|
277
|
+
value.to_s
|
278
|
+
else
|
279
|
+
value.inspect
|
280
|
+
end
|
281
|
+
log_puts("ATTRIBUTE\t#{name}\t#{value}")
|
282
|
+
end
|
283
|
+
nil
|
284
|
+
end
|
285
|
+
|
286
|
+
def log_puts(text)
|
287
|
+
self.file.puts(text)
|
288
|
+
self.file.flush
|
289
|
+
nil
|
290
|
+
end
|
291
|
+
|
292
|
+
# Filters lines, to make the backtrace more readable.
|
293
|
+
def filter_backtrace(lines)
|
294
|
+
filtered = []
|
295
|
+
lines.each do |line|
|
296
|
+
unless line.match(self.backtrace_filter)
|
297
|
+
filtered.push(line)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
filtered = lines if filtered.empty?
|
301
|
+
filtered.push('')
|
302
|
+
filtered.push('')
|
303
|
+
filtered.unshift('')
|
304
|
+
filtered.join("\n")
|
305
|
+
end
|
306
|
+
|
307
|
+
# Return a timestamp string.
|
308
|
+
# The important property of this string
|
309
|
+
# is that it can be incorporated into a legal directory path
|
310
|
+
# (i.e., has no colons, etc.).
|
311
|
+
def self.timestamp
|
312
|
+
now = Time.now
|
313
|
+
ts = now.strftime('%Y-%m-%d-%a-%H.%M.%S')
|
314
|
+
usec_s = (now.usec / 1000).to_s
|
315
|
+
while usec_s.length < 3 do
|
316
|
+
usec_s = '0' + usec_s
|
317
|
+
end
|
318
|
+
# noinspection RubyUnusedLocalVariable
|
319
|
+
ts += ".#{usec_s}"
|
320
|
+
end
|
321
|
+
|
322
|
+
end
|
@@ -0,0 +1,167 @@
|
|
1
|
+
# Structured Log
|
2
|
+
|
3
|
+
<img src="images/structured.png" height="70" alt="Structured Log">
|
4
|
+
|
5
|
+
Class <code>StructuredLog</code> offers structured (as opposed to flat) logging. Nested sections (blocks) in Ruby code become nested XML elements in the log.
|
6
|
+
|
7
|
+
This sectioning allows you to group actions in your program, and that grouping carries over into the log.
|
8
|
+
|
9
|
+
Optionally, each section may include:
|
10
|
+
<ul>
|
11
|
+
<li>A timestamp.
|
12
|
+
<li>A duration.
|
13
|
+
<li>The ability to rescue and log an exception.
|
14
|
+
</ul>
|
15
|
+
|
16
|
+
And of course the class offers many ways to log data.
|
17
|
+
|
18
|
+
## About the Examples
|
19
|
+
|
20
|
+
A working example is worth a thousand words (maybe).
|
21
|
+
|
22
|
+
Each of the following sections features an example Ruby program, followed by its output log.
|
23
|
+
|
24
|
+
|
25
|
+
## Sections
|
26
|
+
|
27
|
+
### Nested Sections
|
28
|
+
<img src="images/nesting.jpg" height="70" alt="Nesting">
|
29
|
+
|
30
|
+
Use nested sections to give structure to your log.
|
31
|
+
|
32
|
+
@[ruby](scripts/sections.rb)
|
33
|
+
|
34
|
+
@[xml](logs/sections.xml)
|
35
|
+
|
36
|
+
### Text
|
37
|
+
<img src="images/text.jpg" height="70" alt="Text">
|
38
|
+
|
39
|
+
Add text to a <code>section</code> element by passing a string argument.
|
40
|
+
|
41
|
+
@[ruby](scripts/text.rb)
|
42
|
+
|
43
|
+
@[xml](logs/text.xml)
|
44
|
+
|
45
|
+
### Attributes
|
46
|
+
<img src="images/attributes.png" height="70" alt="Attributes">
|
47
|
+
|
48
|
+
Add attributes to a <code>section</code> element by passing a hash argument.
|
49
|
+
|
50
|
+
@[ruby](scripts/attributes.rb)
|
51
|
+
|
52
|
+
@[xml](logs/attributes.xml)
|
53
|
+
|
54
|
+
### Timestamps and Durations
|
55
|
+
<img src="images/time.png" height="70" alt="Time">
|
56
|
+
|
57
|
+
Add a timestamp or duration to a <code>section</code> element by passing a special symbol argument.
|
58
|
+
|
59
|
+
@[ruby](scripts/time.rb)
|
60
|
+
|
61
|
+
@[xml](logs/time.xml)
|
62
|
+
|
63
|
+
### Rescued Section
|
64
|
+
<img src="images/rescue.jpg" height="70" alt="Rescue">
|
65
|
+
|
66
|
+
Add rescuing to a <code>section</code> element by passing a special symbol argument.
|
67
|
+
|
68
|
+
For the rescued exception, the class, message, and backtrace are logged.
|
69
|
+
|
70
|
+
@[ruby](scripts/rescue.rb)
|
71
|
+
|
72
|
+
@[xml](logs/rescue.xml)
|
73
|
+
|
74
|
+
### Potpourri
|
75
|
+
<img src="images/potpourri.png" height="70" alt="Potpourri">
|
76
|
+
|
77
|
+
Pass any mixture of arguments to method <code>section</code>.
|
78
|
+
|
79
|
+
The section name must be first; after that, anything goes.
|
80
|
+
|
81
|
+
@[ruby](scripts/potpourri.rb)
|
82
|
+
|
83
|
+
@[xml](logs/potpourri.xml)
|
84
|
+
|
85
|
+
## Data
|
86
|
+
|
87
|
+
### Hash-LIke Objects
|
88
|
+
<img src="images/hash.png" height="30" alt="Hash">
|
89
|
+
|
90
|
+
Use method <code>put_each_pair</code>, or its alias <code>put_hash</code>, to log an object that <code>respond_to?(:each_pair)</code>.
|
91
|
+
|
92
|
+
@[ruby](scripts/hash.rb)
|
93
|
+
|
94
|
+
@[xml](logs/hash.xml)
|
95
|
+
|
96
|
+
### Array-Like Objects
|
97
|
+
<img src="images/array.jpg" height="30" alt="Array">
|
98
|
+
|
99
|
+
Use method <code>put_each_with_index</code>, or its aliases <code>put_array</code> and <code>put_set</code>, to log an object that <code>respond_to?(:each_with_index)</code>.
|
100
|
+
|
101
|
+
@[ruby](scripts/array.rb)
|
102
|
+
|
103
|
+
@[xml](logs/array.xml)
|
104
|
+
|
105
|
+
### Other Objects
|
106
|
+
<img src="images/data.png" height="70" alt="Data">
|
107
|
+
|
108
|
+
Use method <code>put_data</code> to log any object.
|
109
|
+
|
110
|
+
@[ruby](scripts/data.rb)
|
111
|
+
|
112
|
+
@[xml](logs/data.xml)
|
113
|
+
|
114
|
+
### Formatted Text
|
115
|
+
|
116
|
+
Use method <code>put_cdata</code> to log a string (possibly multi-line) as CDATA.
|
117
|
+
|
118
|
+
@[ruby](scripts/cdata.rb)
|
119
|
+
|
120
|
+
@[xml](logs/cdata.xml)
|
121
|
+
|
122
|
+
### Comment
|
123
|
+
<img src="images/comment.png" height="70" alt="Comment">
|
124
|
+
|
125
|
+
Use method <code>comment</code> to log a comment.
|
126
|
+
|
127
|
+
@[ruby](scripts/comment.rb)
|
128
|
+
|
129
|
+
@[xml](logs/comment.xml)
|
130
|
+
|
131
|
+
## Custom Logging
|
132
|
+
<img src="images/custom.png" width="70" alt="Custom">
|
133
|
+
|
134
|
+
At the heart of class <code>StructuredLog</code> is method <code>put_element</code>. It logs an element, possibly with children, attributes, and text. Several methods call it, and you can too.
|
135
|
+
|
136
|
+
Basically, it's just like method <code>section</code>, except that you choose the element name (instead of the fixed name <code>section</code>).
|
137
|
+
|
138
|
+
Otherwise, it handles a block and all the same arguments as <code>section</code>.
|
139
|
+
|
140
|
+
### Section
|
141
|
+
|
142
|
+
Create a custom section by calling method <code>put_element</code> with a block. The custom section will have children if you call logging methods within the block.
|
143
|
+
|
144
|
+
@[ruby](scripts/custom_section.rb)
|
145
|
+
|
146
|
+
@[xml](logs/custom_section.xml)
|
147
|
+
|
148
|
+
### Entry
|
149
|
+
|
150
|
+
Create a custom entry by calling method <code>put_element</code> without a block. The custom entry will not have children.
|
151
|
+
|
152
|
+
@[ruby](scripts/custom_entry.rb)
|
153
|
+
|
154
|
+
@[xml](logs/custom_entry.xml)
|
155
|
+
|
156
|
+
## Uncaught Exception
|
157
|
+
<img src="images/exception.png" width="70" alt="Exception">
|
158
|
+
|
159
|
+
Finally, what about an uncaught exception, one not rescued by <code>:rescue</code>?
|
160
|
+
|
161
|
+
When an exception is raised in a section that does not have <code>:rescue</code>, the logger rescues and logs it anyway, just as if there were an invisible "outermost section" with <code>:rescue</code> (which, in fact, there is).
|
162
|
+
|
163
|
+
Just as for a rescued exception, the log includes the exception's class, message, and backtrace.
|
164
|
+
|
165
|
+
@[ruby](scripts/exception.rb)
|
166
|
+
|
167
|
+
@[xml](logs/exception.xml)
|