Pattern for finding places for underscores when changing a camel-cased string to a snake-cased one.
The Observability::Sender
used to deliver events
Create a new Observer
that will send events via the specified sender
.
# File lib/observability/observer.rb, line 23
def initialize( sender_type=nil )
@sender = self.configured_sender( sender_type )
@event_stack = Concurrent::ThreadLocalVar.new( &Array.method(:new) )
@context_stack = Concurrent::ThreadLocalVar.new( &Array.method(:new) )
end
Add an object
and/or a Hash of fields
to the current event.
# File lib/observability/observer.rb, line 102
def add( object=nil, **fields )
self.log.debug "Adding %p" % [ object || fields ]
event = @event_stack.value.last or return
if object
object_fields = self.fields_from_object( object )
fields = fields.merge( object_fields )
end
event.merge( fields )
end
Add the specified fields
to the current event and any that are created before the current event is finished.
# File lib/observability/observer.rb, line 117
def add_context( object=nil, **fields )
self.log.debug "Adding context from %p" % [ object || fields ]
current_context = @context_stack.value.last or return
if object
object_fields = self.fields_from_object( object )
fields = fields.merge( object_fields )
end
current_context.merge!( fields )
end
Create a new event with the specified type
and make it the current one.
# File lib/observability/observer.rb, line 52
def event( type, **options, &block )
type = self.type_from_object( type )
fields = self.fields_from_options( options )
event = Observability::Event.new( type, **fields )
@event_stack.value.push( event )
new_context = @context_stack.value.last&.dup || {}
@context_stack.value.push( new_context )
return self.finish_after_block( event.object_id, &block ) if block
return event.object_id
end
Finish the and send the current event, comparing it against event_marker
:TODO: Instead of raising, dequeue events up to the given marker if it exists in the queue?
# File lib/observability/observer.rb, line 72
def finish( event_marker=nil )
raise "Event mismatch" if
event_marker && @event_stack.value.last.object_id != event_marker
event = @event_stack.value.pop
context = @context_stack.value.pop
self.log.debug "Adding context %p (%d left) to finishing event." %
[ context, @context_stack.value.length ]
event.merge( context )
self.log.debug "Finishing event: %p" % [ event ]
self.sender.enqueue( event )
return event
end
Call the given block
, then when it returns, finish the event that corresponds to the given marker
.
# File lib/observability/observer.rb, line 91
def finish_after_block( event_marker=nil, &block )
block.call( self )
rescue Exception => err
self.add( err )
raise
ensure
self.finish( event_marker )
end
Returns true
if the Observer
has events that are under construction.
# File lib/observability/observer.rb, line 137
def has_pending_events?
return self.pending_event_count.nonzero? ? true : false
end
Return the depth of the stack of pending events.
# File lib/observability/observer.rb, line 131
def pending_event_count
return @event_stack.value.length
end
Start recording events and sending them.
# File lib/observability/observer.rb, line 40
def start
self.sender.start
end
Stop recording and sending events.
# File lib/observability/observer.rb, line 46
def stop
self.sender.stop
end
Create an instance of the given sender_type
, or the type specified in the configuration if sender_type
is nil.
# File lib/observability/observer.rb, line 289
def configured_sender( sender_type )
return Observability::Sender.create( sender_type ) if sender_type
return Observability::Sender.configured_type
end
Return a Hash of fields to add to the current event derived from the given exception
object.
# File lib/observability/observer.rb, line 272
def fields_from_exception( exception )
trace_frames = exception.backtrace_locations.map do |loc|
{ label: loc.label, path: loc.absolute_path, lineno: loc.lineno }
end
return {
error: {
type: exception.class.name,
message: exception.message,
backtrace: trace_frames
}
}
end
Return a Hash of fields to add to the current event derived from the given object
.
# File lib/observability/observer.rb, line 256
def fields_from_object( object )
case object
when ::Exception
return self.fields_from_exception( object )
else
if object.respond_to?( :to_h )
return object.to_h
else
raise "don't know how to derive fields from a %p" % [ object.class ]
end
end
end
Extract fields specified by the specified options
and return them all merged into one Hash. :TODO: Handle options like :model, :timed, etc.
# File lib/observability/observer.rb, line 234
def fields_from_options( options )
fields = {}
options.each do |key, value|
self.log.debug "Applying option %p: %p" % [ key, value ]
case key
when :add
fields.merge!( value )
when :timed
duration_callback = lambda {|ev| Concurrent.monotonic_time - ev.start }
fields.merge!( duration: duration_callback )
else
raise "unknown event option %p" % [ key ]
end
end
return fields
end
Derive an event type from the specified Method and a detail
.
# File lib/observability/observer.rb, line 207
def type_from_context( context, *details )
prefix = self.type_from_object( context )
suffix = details.map {|detail| detail.to_s }
return ([prefix] + suffix).join( '.' )
end
Derive an event type from the specified Proc, which should be a block passed to an observed method.
# File lib/observability/observer.rb, line 217
def type_from_context_proc( object )
bind = object.binding
recv = bind.receiver
methname = bind.eval( "__method__" )
meth = recv.method( methname )
return self.type_from_object( meth )
end
Derive an event type from the specified Method or UnboundMethod object
.
# File lib/observability/observer.rb, line 198
def type_from_method( object )
name = object.original_name
mod = object.owner
return [ self.type_from_module(mod), name.to_s ].join( '.' )
end
Derive an event type from the specified Class or Module object
.
# File lib/observability/observer.rb, line 182
def type_from_module( object )
if ( name = object.name )
name = name.split( '::' ).collect do |part|
part.gsub( SNAKE_CASE_SEPARATOR ) do |m|
"%s_%s" % [ m[0], m[1] ]
end
end.join( '.' )
return name.downcase
else
return "anonymous_%s_%d" % [ object.class.name.downcase, object.object_id ]
end
end
Derive an event type from the specified object
and an optional block
that provides additional context.
# File lib/observability/observer.rb, line 153
def type_from_object( object, block=nil )
case object
when Module
self.log.debug "Deriving a type from module %p" % [ object ]
return self.type_from_module( object )
when Method, UnboundMethod
self.log.debug "Deriving a type from method %p" % [ object ]
return self.type_from_method( object )
when Array
self.log.debug "Deriving a type from context %p" % [ object ]
return self.type_from_context( *object )
when Proc
self.log.debug "Deriving a type from context proc %p" % [ object ]
return self.type_from_context_proc( object )
when String
self.log.debug "Using string %p as type" % [ object ]
return object
else
raise "don't know how to derive an event type from a %p" % [ object.class ]
end
end