# Structured Log Structured Log Class StructuredLog offers structured (as opposed to flat) logging. Nested sections (blocks) in Ruby code become nested XML elements in the log. This sectioning allows you to group actions in your program, and that grouping carries over into the log. Optionally, each section may include: And of course the class offers many ways to log data. ## About the Examples A working example is worth a thousand words (maybe). Each of the following sections features an example Ruby program, followed by its output log. ## Sections ### Nested Sections Nesting Use nested sections to give structure to your log. ```sections.rb```: ```ruby require 'structured_log' StructuredLog.open('sections.xml') do |log| # Any code can be here. log.section('Outer') do # Any code can be here. log.section('Mid') do # Any code can be here. log.section('Inner') do # Any code can be here. end # Any code can be here. end # Any code can be here. end # Any code can be here. end ``` ```sections.xml```: ```xml
``` ### Text Text Add text to a section element by passing a string argument. ```text.rb```: ```ruby require 'structured_log' text = 'This section has text.' StructuredLog.open('text.xml') do |log| log.section('with_text', text) do # Any code can be here. end end ``` ```text.xml```: ```xml
This section has text.
``` ### Attributes Attributes Add attributes to a section element by passing a hash argument. ```attributes.rb```: ```ruby require 'structured_log' attributes = {:a => 0, :b => 1} StructuredLog.open('attributes.xml') do |log| log.section('with_attributes', attributes) do log.comment('This section has attributes a and b.') end end ``` ```attributes.xml```: ```xml
This section has attributes a and b.
``` ### Timestamps and Durations Time Add a timestamp or duration to a section element by passing a special symbol argument. ```time.rb```: ```ruby require 'structured_log' StructuredLog.open('time.xml') do |log| log.section('Section with timestamp', :timestamp) do log.comment('This section has a timestamp.') end log.section('Section with duration', :duration) do log.comment('This section has a duration.') sleep 1 end log.section('Section with both', :duration, :timestamp) do log.comment('This section has both.') sleep 1 end end ``` ```time.xml```: ```xml
This section has a timestamp.
This section has a duration.
This section has both.
``` ### Rescued Section Rescue Add rescuing to a section element by passing a special symbol argument. For the rescued exception, the class, message, and backtrace are logged. ```rescue.rb```: ```ruby require 'structured_log' StructuredLog.open('rescue.xml') do |log| log.section('Section with rescue', :rescue) do log.comment('This section will terminate because of the failure.') fail 'This exception will be rescued and logged.' log.comment('This comment will not be reached.') end log.section('Another section') do log.comment('This comment will be reached and logged, because of rescue above.') end end ``` ```rescue.xml```: ```xml
This section will terminate because of the failure. This exception will be rescued and logged. ' C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/structured_log-0.1.0/lib/structured_log.rb:58:in `block in section' C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/structured_log-0.1.0/lib/structured_log.rb:116:in `put_element' C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/structured_log-0.1.0/lib/structured_log.rb:57:in `section' C:/Users/Burdette/Documents/GitHub/structured_log/readme_files/scripts/rescue.rb:4:in `block in
' C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/structured_log-0.1.0/lib/structured_log.rb:38:in `open' C:/Users/Burdette/Documents/GitHub/structured_log/readme_files/scripts/rescue.rb:3:in `
' ]]>
This comment will be reached and logged, because of rescue above.
``` ### Potpourri Potpourri Pass any mixture of arguments to method section. The section name must be first; after that, anything goes. ```potpourri.rb```: ```ruby require 'structured_log' attributes = {:a => 0, :b => 1} more_attributes = {:c => 2, :d => 3} text = 'This section has a potpourri.' float = 3.14159 boolean = false fixnum = 1066 time = Time.new exception = RuntimeError.new('Oops!') StructuredLog.open('potpourri.xml') do |log| log.section('All together now', 'Order does not matter except in aggregating text and attributes.') do args = [attributes, :rescue, text, float, :duration, more_attributes, boolean, :timestamp, fixnum, time, exception] log.section('Potpourri', *args) do end log.section('Reverse potpourri', *args.reverse) do end end end ``` ```potpourri.xml```: ```xml
Order does not matter except in aggregating text and attributes.
This section has a potpourri.3.14159false10662018-06-09 11:38:11 -0500#<RuntimeError: Oops!>
#<RuntimeError: Oops!>2018-06-09 11:38:11 -05001066false3.14159This section has a potpourri.
``` ## Data ### Hash-LIke Objects Hash Use method put_each_pair, or its alias put_hash, to log an object that respond_to?(:each_pair). ```hash.rb```: ```ruby require 'structured_log' hash = { :a => 'z', :aa => 'zz', :aaa => 'zzz', :aaaa => 'zzzz', } StructuredLog.open('hash.xml') do |log| log.put_hash('my_hash', hash) end ``` ```hash.xml```: ```xml z aa => zz aaa => zzz aaaa => zzzz ]]> ``` ### Array-Like Objects Array Use method put_each_with_index, or its aliases put_array and put_set, to log an object that respond_to?(:each_with_index). ```array.rb```: ```ruby require 'structured_log' array = %w/foo bar baz bat/ StructuredLog.open('array.xml') do |log| log.put_array('my_array', array) end ``` ```array.xml```: ```xml ``` ### Other Objects Data Use method put_data to log any object. ```data.rb```: ```ruby require 'structured_log' data = { :float => 3.14, :fixnum => 1066, :false => false, :time => Time.new, :exception => RuntimeError.new('Oops!'), :nil => nil, } StructuredLog.open('data.xml') do |log| data.each_pair do |type, datum| name = "my_#{type}" log.put_data(name, datum) end end ``` ```data.xml```: ```xml ]]> ``` ### Formatted Text Use method put_cdata to log a string (possibly multi-line) as CDATA. ```cdata.rb```: ```ruby require 'structured_log' text = < ```cdata.xml```: ```xml ``` ### Comment Comment Use method comment to log a comment. ```comment.rb```: ```ruby require 'structured_log' StructuredLog.open('comment.xml') do |log| log.comment('My comment can be any text.') end ``` ```comment.xml```: ```xml My comment can be any text. ``` ## Custom Logging Custom At the heart of class StructuredLog is method put_element. It logs an element, possibly with children, attributes, and text. Several methods call it, and you can too. Basically, it's just like method section, except that you choose the element name (instead of the fixed name section). Otherwise, it handles a block and all the same arguments as section. ### Section Create a custom section by calling method put_element with a block. The custom section will have children if you call logging methods within the block. ```custom_section.rb```: ```ruby require 'structured_log' StructuredLog.open('custom_section.xml') do |log| log.section('With blocks') do log.put_element('section_with_children') do log.put_element('child', :rank => 'Older') log.put_element('child', :rank => 'Younger') end log.put_element('section_with_duration', :duration, 'Block contains timed code to be timed.') do sleep 1 end log.put_element('section_with_rescue', :rescue, 'Block contains code to be rescued if necessary.') do end end end ``` ```custom_section.xml```: ```xml
Block contains timed code to be timed. Block contains code to be rescued if necessary.
``` ### Entry Create a custom entry by calling method put_element without a block. The custom entry will not have children. ```custom_entry.rb```: ```ruby require 'structured_log' StructuredLog.open('custom_entry.xml') do |log| log.section('Without blocks') do log.put_element('element_with_text', 'No child elements, just this text.') log.put_element('element_with_attributes', {:a => 0, :b => 1}) log.put_element('element_with_timestamp', :timestamp) log.put_element('element_with_data', 3.14159) end end ``` ```custom_entry.xml```: ```xml
No child elements, just this text. 3.14159
``` ## Uncaught Exception Exception Finally, what about an uncaught exception, one not rescued by :rescue? When an exception is raised in a section that does not have :rescue, the logger rescues and logs it anyway, just as if there were an invisible "outermost section" with :rescue (which, in fact, there is). Just as for a rescued exception, the log includes the exception's class, message, and backtrace. ```exception.rb```: ```ruby require 'structured_log' StructuredLog.open('exception.xml') do |log| fail('Oops!') end ``` ```exception.xml```: ```xml Oops! ' C:/Ruby22-x64/lib/ruby/gems/2.2.0/gems/structured_log-0.1.0/lib/structured_log.rb:38:in `open' C:/Users/Burdette/Documents/GitHub/structured_log/readme_files/scripts/exception.rb:3:in `
' ]]> ```