hoodoo 1.0.5 → 1.1.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.
Files changed (27) hide show
  1. checksums.yaml +8 -8
  2. data/lib/hoodoo/client/endpoint/endpoints/amqp.rb +43 -25
  3. data/lib/hoodoo/discovery.rb +1 -1
  4. data/lib/hoodoo/services/discovery/discoverers/by_flux.rb +130 -0
  5. data/lib/hoodoo/services/discovery/results/for_amqp.rb +15 -21
  6. data/lib/hoodoo/services/discovery/results/for_local.rb +17 -0
  7. data/lib/hoodoo/services/middleware/amqp_log_message.rb +92 -154
  8. data/lib/hoodoo/services/middleware/amqp_log_writer.rb +25 -50
  9. data/lib/hoodoo/services/middleware/middleware.rb +75 -25
  10. data/lib/hoodoo/services/services/service.rb +2 -2
  11. data/lib/hoodoo/version.rb +1 -1
  12. data/spec/services/discovery/discoverers/by_flux_spec.rb +134 -0
  13. data/spec/services/discovery/results/for_amqp_spec.rb +4 -7
  14. data/spec/services/discovery/results/for_local_spec.rb +4 -0
  15. data/spec/services/middleware/amqp_log_message_spec.rb +32 -34
  16. data/spec/services/middleware/amqp_log_writer_spec.rb +2 -5
  17. data/spec/services/middleware/middleware_exotic_communication_spec.rb +147 -143
  18. data/spec/services/middleware/middleware_logging_spec.rb +10 -10
  19. data/spec/services/middleware/middleware_multi_remote_spec.rb +1 -1
  20. data/spec/services/middleware/middleware_public_spec.rb +73 -18
  21. data/spec/services/middleware/middleware_spec.rb +4 -4
  22. data/spec/services/services/application_spec.rb +3 -3
  23. data/spec/spec_helper.rb +3 -8
  24. metadata +19 -7
  25. data/lib/hoodoo/services/discovery/discoverers/by_consul.rb +0 -66
  26. data/spec/alchemy/alchemy-amq.rb +0 -33
  27. data/spec/services/discovery/discoverers/by_consul_spec.rb +0 -29
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- YzEwZWNjMTA5OTJhODlhN2VkZGQxMDQ0NzNhOGVjZjUxMTM2ZmM1Nw==
4
+ MTFiNjAzNDk4MDdmOTFmNWRkZTg1MmY3ODExMmY3ZGU4MTU3YjkzNg==
5
5
  data.tar.gz: !binary |-
6
- OTA1MzJjZTZlYzFhY2FiNWUxZWM3MDBiN2QwYzEwNDYyNDM2Y2ZjOA==
6
+ MjMwNDRjODEzZDU5YmRmM2E0YmRlNTlmYTVkYzA3NmIxMDA3ZjE1Yg==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- ZDQ4YmVkYmQ3YWEwMWY1NGU1ZDZlYTA2NjAxNzM2NTFiYzk5NjI1NTNmZWE0
10
- NGYwYjhlMmIxZmY0YTViZjk4Zjg4ODgzODRjNGQ5MDhmNjEwZjVmZDJhM2Yx
11
- ODQzZjliM2NhYTE1NTQ2ZjdjNzk1YTZmNmE3NTM2MGQ2NWFhNjE=
9
+ ZjEzZDYxZDJjZGI3MWU0NDFmYTYwNzQ2ZmYwZGY4MmQ2Zjc0NWJhZmRiMjli
10
+ ZjQ0MTA1MzkzYTFkN2RhZTA1YWFkNjc1MDI4MDgwNzYyZDk5YmFhZTdmZDE1
11
+ ZTJlYjRjZDc5N2IzYmNiNTc0M2Q0NzMzNDhlODk3OTEzZjc0YTU=
12
12
  data.tar.gz: !binary |-
13
- ODhiYjgzMTFmYTZhOGFhZGM4NDM2OWU3N2MwNjE3OGVhMTk0MGFkNGFhZTg5
14
- Y2E5Mzg0YjI3OTBkNzcyNjJhZGI5MjYyN2M5YTYxOWYyYjVjMTgxMGRiM2Ix
15
- MGQ4YTBiNWY4MzMzNTI3NWJkODI3ZTE0YWJmYjlkYjI3YWQ4MTg=
13
+ MzMwNGY1MGZlOTFiZjE3YmI0MDgxYjcwYTUxYTliNTQxNDFjODMwNThkMGUx
14
+ ZDk5NzE0ZDcwMWY1YzZhMTIxMzgyY2ZiMWJiMTFiZjk4ZmNlMTE0N2JmZThj
15
+ ZDIxZWM4MzQ0ODc0ODdmNWU5OWEzYmU3ODlkMTE4OTI1ZTNiYWM=
@@ -39,7 +39,7 @@ module Hoodoo
39
39
  # to keep Rack happy.
40
40
 
41
41
  endpoint_uri = URI.parse( 'http://localhost:80' )
42
- endpoint_uri.path = @discovery_result.equivalent_path
42
+ endpoint_uri.path = @discovery_result.routing_path
43
43
 
44
44
  @description = Hoodoo::Client::Endpoint::HTTPBased::DescriptionOfRequest.new
45
45
  @description.discovery_result = @discovery_result
@@ -136,40 +136,58 @@ module Hoodoo
136
136
  action = description_of_request.action
137
137
  data = get_data_for_request( description_of_request )
138
138
 
139
- # Host and port are just there to keep Rack happy at the
139
+ # Host and port are only provided to keep Rack happy at the
140
140
  # receiving end of the over-AMQP synthesised HTTP request.
141
141
 
142
- alchemy_options = {
143
- :host => description_of_request.endpoint_uri.host,
144
- :port => description_of_request.endpoint_uri.port,
145
- :query => data.query_hash,
146
- :body => data.body_string,
147
- :headers => data.header_hash
142
+ http_method =
143
+ {
144
+ :create => 'POST',
145
+ :update => 'PATCH',
146
+ :delete => 'DELETE'
147
+ }[ action ] || 'GET'
148
+
149
+ http_message =
150
+ {
151
+ 'scheme' => 'http',
152
+ 'verb' => http_method,
153
+
154
+ 'host' => description_of_request.endpoint_uri.host,
155
+ 'port' => description_of_request.endpoint_uri.port,
156
+ 'path' => data.full_uri.path,
157
+ 'query' => data.query_hash,
158
+
159
+ 'headers' => data.header_hash,
160
+ 'body' => data.body_string
148
161
  }
149
162
 
150
163
  unless self.session_id().nil? # Session comes from Endpoint superclass
151
- alchemy_options[ :session_id ] = self.session_id()
164
+ http_message[ 'session_id' ] = self.session_id()
152
165
  end
153
166
 
154
- http_method = {
155
- :create => 'POST',
156
- :update => 'PATCH',
157
- :delete => 'DELETE'
158
- }[ action ] || 'GET'
167
+ amqp_response = self.alchemy().send_message_to_resource( http_message )
168
+
169
+ description_of_response = DescriptionOfResponse.new
170
+ description_of_response.action = action
171
+ description_of_response.http_headers = {}
159
172
 
160
- description_of_response = DescriptionOfResponse.new
161
- description_of_response.action = action
173
+ if amqp_response == AlchemyFlux::TimeoutError
174
+ description_of_response.http_status_code = 408
175
+ description_of_response.raw_body_data = '408 Timeout'
162
176
 
163
- amqp_response = self.alchemy().http_request(
164
- description_of_request.discovery_result.queue_name,
165
- http_method,
166
- data.full_uri.path,
167
- alchemy_options
168
- )
177
+ elsif amqp_response == AlchemyFlux::MessageNotDeliveredError
178
+ description_of_response.http_status_code = 404
179
+ description_of_response.raw_body_data = '404 Not Found'
169
180
 
170
- description_of_response.http_status_code = amqp_response.status_code.to_i
171
- description_of_response.http_headers = amqp_response.headers || {}
172
- description_of_response.raw_body_data = amqp_response.body
181
+ elsif amqp_response.is_a?( Hash )
182
+ description_of_response.http_status_code = amqp_response[ 'status_code' ].to_i
183
+ description_of_response.http_headers = amqp_response[ 'headers' ] || {}
184
+ description_of_response.raw_body_data = amqp_response[ 'body' ]
185
+
186
+ else
187
+ description_of_response.http_status_code = 500
188
+ description_of_response.raw_body_data = '500 Internal Server Error'
189
+
190
+ end
173
191
 
174
192
  return get_data_for_response( description_of_response )
175
193
  end
@@ -15,6 +15,6 @@ require 'hoodoo/services/discovery/results/for_local'
15
15
  require 'hoodoo/services/discovery/results/for_remote'
16
16
 
17
17
  require 'hoodoo/services/discovery/discoverers/by_convention'
18
- require 'hoodoo/services/discovery/discoverers/by_consul'
18
+ require 'hoodoo/services/discovery/discoverers/by_flux'
19
19
  require 'hoodoo/services/discovery/discoverers/by_drb/drb_server'
20
20
  require 'hoodoo/services/discovery/discoverers/by_drb/by_drb'
@@ -0,0 +1,130 @@
1
+ ########################################################################
2
+ # File:: by_flux.rb
3
+ # (C):: Loyalty New Zealand 2015
4
+ #
5
+ # Purpose:: Discover resource endpoint locations via Alchemy Flux.
6
+ # ----------------------------------------------------------------------
7
+ # 03-Mar-2015 (ADH): Created.
8
+ # 21-Jan-2016 (ADH): Reimplemented for Alchemy Flux.
9
+ ########################################################################
10
+
11
+ module Hoodoo
12
+ module Services
13
+ class Discovery # Just used as a namespace here
14
+
15
+ # Discover resource endpoint locations via Alchemy Flux.
16
+ #
17
+ # For Flux, it's less about discovery as it is about convention
18
+ # and announcing. We have to set some system variables when the
19
+ # application starts up, _before_ the Rack `run` call gets as
20
+ # far as the Alchemy Flux server's implementation - in practice
21
+ # this means announcement needs to happen from the Hoodoo
22
+ # middleware's constructor, synchronously. The environment
23
+ # variables tell Flux about this local service's URI-located
24
+ # endpoints and derive a consistent, replicable 'service name'
25
+ # from the resources which the service implements.
26
+ #
27
+ # Once all that is set up, the local Alchemy instance knows how
28
+ # to listen for relevant messages for 'this' service on the queue
29
+ # and Hoodoo in 'this' service knowns which resources are local,
30
+ # or which are remote; and it knows that Flux is able in turn to
31
+ # use URI-based to-resource communications for inter-resource
32
+ # calls without any further explicit discovery within Hoodoo
33
+ # beyond simply saying "here's the AMQP Flux endpoint class".
34
+ #
35
+ class ByFlux < Hoodoo::Services::Discovery
36
+
37
+ protected
38
+
39
+ # Announce the location of an instance to Alchemy Flux.
40
+ #
41
+ # Call via Hoodoo::Services::Discovery::Base#announce.
42
+ #
43
+ # +resource+:: Passed to #discover_remote.
44
+ # +version+:: Passed to #discover_remote.
45
+ # +options+:: See below.
46
+ #
47
+ # The Options hash informs the announcer of the intended endpoint
48
+ # base URI for the resource and also, where available, provides a
49
+ # head-up on the full range of resource _names_ that will be
50
+ # present in this single service application (see
51
+ # Hoodoo::Services::Service::comprised_of). Keys MUST be Symbols.
52
+ # Associated required values are:
53
+ #
54
+ # +services+:: Array of Hoodoo::Services::Discovery::ForLocal
55
+ # instances describing available resources in this
56
+ # local service.
57
+ #
58
+ def announce_remote( resource, version, options = {} )
59
+
60
+ alchemy_resource_paths = ENV[ 'ALCHEMY_RESOURCE_PATHS' ]
61
+ alchemy_service_name = ENV[ 'ALCHEMY_SERVICE_NAME' ]
62
+
63
+ # Under Flux, we "announce" via a local environment variable when
64
+ # this service awakens which tells Flux what to listen for on the
65
+ # AMQP queue.
66
+ #
67
+ # Since inbound HTTP calls into the architecture are based on URIs
68
+ # and paths, there needs to be a mapping at that point to queue
69
+ # endpoints. Historically Hoodoo adopted an (in hindsight, unwise)
70
+ # approach of "/v<version>/<pluralised_resource>" c.f. Rails,
71
+ # rather than just "/<version>/<resource>" - e.g. there was
72
+ # "/v1/members" instead of "/1/Member". This means things like the
73
+ # "ByConvention" discoverer have to use pluralisation rules and
74
+ # exceptions. It's messy.
75
+ #
76
+ # To clean things up, the work on Alchemy Flux sets up *two* paths
77
+ # in Hoodoo - the old one for backwards compatibility, and a new
78
+ # one of the above simpler form. Now it's easy to go from version
79
+ # and resource name to path or back internally with no mappings.
80
+ #
81
+ if ( alchemy_resource_paths.nil? ||
82
+ alchemy_resource_paths.strip.empty? )
83
+
84
+ services = options[ :services ] || []
85
+ paths = []
86
+
87
+ services.each do | service |
88
+ custom_path = service.base_path
89
+ de_facto_path = service.de_facto_base_path
90
+
91
+ paths << custom_path << de_facto_path
92
+ end
93
+
94
+ ENV[ 'ALCHEMY_RESOURCE_PATHS' ] = paths.join( ',' )
95
+ end
96
+
97
+ if ( alchemy_service_name.nil? ||
98
+ alchemy_service_name.strip.empty? )
99
+ ENV[ 'ALCHEMY_SERVICE_NAME' ] = Hoodoo::Services::Middleware::service_name()
100
+ end
101
+
102
+ return discover_remote( resource, version )
103
+ end
104
+
105
+ # Discover the location of an instance.
106
+ #
107
+ # Returns a Hoodoo::Services::Discovery::ForAMQP instance if
108
+ # the endpoint is found, else +nil+.
109
+ #
110
+ # Call via Hoodoo::Services::Discovery::Base#announce.
111
+ #
112
+ # +resource+:: Passed to #discover_remote.
113
+ # +version+:: Passed to #discover_remote.
114
+ #
115
+ def discover_remote( resource, version )
116
+ de_facto_path = Hoodoo::Services::Middleware::de_facto_path_for(
117
+ resource,
118
+ version
119
+ )
120
+
121
+ return Hoodoo::Services::Discovery::ForAMQP.new(
122
+ resource: resource,
123
+ version: version
124
+ )
125
+ end
126
+
127
+ end
128
+ end
129
+ end
130
+ end
@@ -25,32 +25,26 @@ module Hoodoo
25
25
  #
26
26
  attr_accessor :version
27
27
 
28
- # Queue name for the target service implementation, as a
29
- # String (e.g. "service.account").
28
+ # Path at which the resource is expected to be found on the queue
29
+ # (routing via Topic Exchange and Alchemy Flux's translations of
30
+ # paths to keys).
30
31
  #
31
- attr_accessor :queue_name
32
-
33
- # URL path equivalent that should be mapped to the queue in
34
- # #queue_name, as a String (e.g. "/v2/accounts").
35
- #
36
- attr_accessor :equivalent_path
32
+ attr_reader :routing_path
37
33
 
38
34
  # Create an instance with named parameters as follows:
39
35
  #
40
- # +resource+:: See #resource.
41
- # +version+:: See #version.
42
- # +queue_name+:: See #queue_name.
43
- # +equivalent_pat+:: See #equivalent_pat.
36
+ # +resource+:: See #resource.
37
+ # +version+:: See #version.
44
38
  #
45
- def initialize( resource:,
46
- version:,
47
- queue_name:,
48
- equivalent_path: )
49
-
50
- self.resource = resource.to_sym
51
- self.version = version.to_i
52
- self.queue_name = queue_name
53
- self.equivalent_path = equivalent_path
39
+ def initialize( resource:, version: )
40
+
41
+ @resource = resource.to_sym
42
+ @version = version.to_i
43
+ @routing_path = Hoodoo::Services::Middleware.de_facto_path_for(
44
+ resource,
45
+ version
46
+ )
47
+
54
48
  end
55
49
  end
56
50
  end
@@ -46,6 +46,17 @@ module Hoodoo
46
46
  #
47
47
  attr_accessor :routing_regexp
48
48
 
49
+ # The de facto routing equivalent of #base_path. This is not
50
+ # a custom path based on the interface's declared endpoint; it
51
+ # is derived directlyf rom resource or version - for example,
52
+ # "/1/Product" or "/2/Member". String.
53
+ #
54
+ attr_accessor :de_facto_base_path
55
+
56
+ # As #routing_regexp, but matches #de_facto_base_path.
57
+ #
58
+ attr_accessor :de_facto_routing_regexp
59
+
49
60
  # The Hoodoo::Services::Interface subclass _class_ describing the
50
61
  # resource interface.
51
62
  #
@@ -62,6 +73,8 @@ module Hoodoo
62
73
  # +version+:: See #version.
63
74
  # +base_path+:: See #base_path.
64
75
  # +routing_regexp+:: See #routing_regexp.
76
+ # +de_facto_base_path+:: See #de_facto_base_path.
77
+ # +de_facto_routing_regexp+:: See #de_facto_routing_regexp.
65
78
  # +interface_class+:: See #interface_class.
66
79
  # +implementation_instance+:: See #implementation_instance.
67
80
  #
@@ -69,6 +82,8 @@ module Hoodoo
69
82
  version:,
70
83
  base_path:,
71
84
  routing_regexp:,
85
+ de_facto_base_path:,
86
+ de_facto_routing_regexp:,
72
87
  interface_class:,
73
88
  implementation_instance: )
74
89
 
@@ -76,6 +91,8 @@ module Hoodoo
76
91
  self.version = version.to_i
77
92
  self.base_path = base_path
78
93
  self.routing_regexp = routing_regexp
94
+ self.de_facto_base_path = de_facto_base_path
95
+ self.de_facto_routing_regexp = de_facto_routing_regexp
79
96
  self.interface_class = interface_class
80
97
  self.implementation_instance = implementation_instance
81
98
  end
@@ -3,8 +3,9 @@
3
3
  # (C):: Loyalty New Zealand 2014
4
4
  #
5
5
  # Purpose:: Structured logging onto an AMQP-based queue, via the
6
- # AlchemyAMQ gem. Optional; class is defined only if the
7
- # supporting AlchemyAMQ gem's classes are defined.
6
+ # Alchemy Flux gem. This class just exists to rationalise
7
+ # any parameters inbound in order to generate a clean
8
+ # representation for Flux.
8
9
  #
9
10
  # The middleware uses this to put log and error messages
10
11
  # on the queue. Interested services use this to read such
@@ -13,173 +14,110 @@
13
14
  # 20-Nov-2014 (ADH): Created.
14
15
  ########################################################################
15
16
 
16
- # TODO: See spec/alchemy/alchemy-amq.rb. Remove this once 'real' Alchemy
17
- # is opened.
18
- #
19
- $LOAD_PATH.unshift File.join( File.dirname( __FILE__ ), 'alchemy' )
20
-
21
17
  module Hoodoo; module Services
22
18
  class Middleware
23
19
 
24
- begin
25
- require 'alchemy-amq' # Optional
20
+ # A representation of a log message placed on an AMQP-based queue.
21
+ # The primary expected communications interface is Alchemy Flux at
22
+ # the time of writing.
23
+ #
24
+ class AMQPLogMessage
26
25
 
27
- # For AlchemyAMQ gem users, the AMQPLogMessage class provides an
28
- # AlchemyAMQ::Message subclass used for sending structured log data to
29
- # the queue. Hoodoo::Logger uses this.
26
+ # The Time +strftime+ formatter used for string conversions in this
27
+ # class.
30
28
  #
31
- # See the AlchemyAMQ gem for more details.
29
+ TIME_FORMATTER = '%Y-%m-%d %H:%M:%S.%12N %Z'
30
+
31
+ # A UUID to assign to this log message. See Hoodoo::UUID::generate.
32
32
  #
33
- class AMQPLogMessage < ::AlchemyAMQ::Message
34
-
35
- # The named "type" of this message, to be registered with AlchemyAMQ.
36
- #
37
- TYPE = 'hoodoo_service_middleware_amqp_log_message'
38
-
39
- # The Time +strftime+ formatter used for string conversions in this
40
- # class.
41
- #
42
- TIME_FORMATTER = '%Y-%m-%d %H:%M:%S.%12N %Z'
43
-
44
- # This line of code registers wth AlchemyAMQ, but also makes RDoc
45
- # screw up. RDoc decides that we have a new module,
46
- # Hoodoo::Services::Middleware::AMQPLogMessage::AlchemyAMQ. Very
47
- # strange...
48
- #
49
- ::AlchemyAMQ::Message.register_type( TYPE, self )
50
-
51
- # ...so do _this_ purely so that we can get 100% real documentation
52
- # coverage without it being clouded by RDoc's hiccups.
53
-
54
- # This documentation exists purely to work around an RDoc hiccup where
55
- # it thinks such a module exists.
56
- #
57
- # See file "services/middleware/amqp_log_message.rb" for details.
58
- #
59
- module AlchemyAMQ
60
- end
33
+ attr_accessor :id
61
34
 
62
- # A UUID to assign to this log message. See Hoodoo::UUID::generate.
63
- #
64
- attr_accessor :id
65
-
66
- # Logging level. See Hoodoo::Logger.
67
- #
68
- attr_accessor :level
69
-
70
- # Logging component. See Hoodoo::Logger.
71
- #
72
- attr_accessor :component
73
-
74
- # Component log code. See Hoodoo::Logger.
75
- #
76
- attr_accessor :code
77
-
78
- # The time at which this log message is being reported to the Logger
79
- # instance. This is a formatted *String* to high accuracy. See also
80
- # #reported_at=.
81
- #
82
- attr_reader :reported_at
83
-
84
- # Set the time read back by #reported_at using a Time instance. This
85
- # is formatted internally as a String via TIME_FORMATTER and reported
86
- # as such in subsequent calls to #reported_at.
87
- #
88
- # Conversion from Time to String is done here, rather than by the
89
- # caller setting this instance's variables, so that we can internally
90
- # enforce the accuracy required for this field.
91
- #
92
- # +time+:: The Time instance to set (and process into a string
93
- # internally via TIME_FORMATTER), *or* a String instance
94
- # already so formatted, *or* +nil+ to clear the value.
95
- #
96
- def reported_at=( time )
97
- if time.is_a?( String )
98
- @reported_at = time
99
- elsif time.is_a?( Time )
100
- @reported_at = time.strftime( TIME_FORMATTER )
101
- else
102
- @reported_at = nil
103
- end
104
- end
35
+ # Logging level. See Hoodoo::Logger.
36
+ #
37
+ attr_accessor :level
105
38
 
106
- # Log payload. See Hoodoo::Logger.
107
- #
108
- attr_accessor :data
109
-
110
- # Optional calling Caller ID, via session data inside the payload - see
111
- # Hoodoo::Logger.
112
- #
113
- attr_accessor :caller_id
114
-
115
- # Optional interaction UUID, via session data inside the payload - see
116
- # Hoodoo::Logger.
117
- #
118
- attr_accessor :interaction_id
119
-
120
- # Optional hash of identity properties from the session data inside the
121
- # payload - see Hoodoo::Logger.
122
- #
123
- attr_accessor :identity
124
-
125
- # Create an instance with options keyed on the attributes defined for
126
- # the class.
127
- #
128
- def initialize(options = {})
129
- update( options ) # Should be called before 'super'
130
- super( options )
131
-
132
- @type = AMQPLogMessage::TYPE
133
- end
39
+ # Logging component. See Hoodoo::Logger.
40
+ #
41
+ attr_accessor :component
134
42
 
135
- # Seralize this instance. See the AlchemyAMQ gem and
136
- # AlchemyAMQ::Message#serialize.
137
- #
138
- def serialize
139
- @content = {
140
- :id => @id,
141
- :level => @level,
142
- :component => @component,
143
- :code => @code,
144
- :reported_at => @reported_at,
145
-
146
- :data => @data,
147
-
148
- :interaction_id => @interaction_id,
149
- :caller_id => @caller_id,
150
- :identity => @identity
151
- }
152
-
153
- super
154
- end
43
+ # Component log code. See Hoodoo::Logger.
44
+ #
45
+ attr_accessor :code
155
46
 
156
- # Unpack a serialized representation into this instance. See the
157
- # AlchemyAMQ gem and AlchemyAMQ::Message#deserialize.
158
- #
159
- def deserialize
160
- super
161
- update( @content )
47
+ # The time at which this log message is being reported to the Logger
48
+ # instance. This is a formatted *String* to high accuracy. See also
49
+ # #reported_at=.
50
+ #
51
+ attr_reader :reported_at
52
+
53
+ # Set the time read back by #reported_at using a Time instance. This
54
+ # is formatted internally as a String via TIME_FORMATTER and reported
55
+ # as such in subsequent calls to #reported_at.
56
+ #
57
+ # Conversion from Time to String is done here, rather than by the
58
+ # caller setting this instance's variables, so that we can internally
59
+ # enforce the accuracy required for this field.
60
+ #
61
+ # +time+:: The Time instance to set (and process into a string
62
+ # internally via TIME_FORMATTER), *or* a String instance
63
+ # already so formatted, *or* +nil+ to clear the value.
64
+ #
65
+ def reported_at=( time )
66
+ if time.is_a?( String )
67
+ @reported_at = time
68
+ elsif time.is_a?( Time )
69
+ @reported_at = time.strftime( TIME_FORMATTER )
70
+ else
71
+ @reported_at = nil
162
72
  end
73
+ end
74
+
75
+ # Log payload. See Hoodoo::Logger.
76
+ #
77
+ attr_accessor :data
163
78
 
164
- # Set public attribute values according to an options hash keyed on
165
- # the attributes defined for the class.
166
- #
167
- def update( options )
168
- self.id = options[ :id ]
169
- self.level = options[ :level ]
170
- self.component = options[ :component ]
171
- self.code = options[ :code ]
172
- self.reported_at = options[ :reported_at ]
173
-
174
- self.data = options[ :data ]
175
-
176
- self.interaction_id = options[ :interaction_id ]
177
- self.caller_id = options[ :caller_id ]
178
- self.identity = options[ :identity ]
79
+ # Optional calling Caller ID, via session data inside the payload - see
80
+ # Hoodoo::Logger.
81
+ #
82
+ attr_accessor :caller_id
83
+
84
+ # Optional interaction UUID, via session data inside the payload - see
85
+ # Hoodoo::Logger.
86
+ #
87
+ attr_accessor :interaction_id
88
+
89
+ # Optional hash of identity properties from the session data inside the
90
+ # payload - see Hoodoo::Logger.
91
+ #
92
+ attr_accessor :identity
93
+
94
+ # Create an instance with options keyed on the attributes defined for
95
+ # the class. Option keys may be Strings or Symbols but must only match
96
+ # defined attribute names.
97
+ #
98
+ def initialize( options = {} )
99
+ options.each do | name, value |
100
+ send( "#{ name }=", value )
179
101
  end
180
102
  end
181
103
 
182
- rescue LoadError # Optional file 'alchemy-amq' is absent
104
+ # Retrieve a simple Hash representation of this instance.
105
+ #
106
+ def to_h
107
+ {
108
+ 'id' => @id,
109
+ 'level' => @level,
110
+ 'component' => @component,
111
+ 'code' => @code,
112
+ 'reported_at' => @reported_at,
113
+
114
+ 'data' => @data,
115
+
116
+ 'interaction_id' => @interaction_id,
117
+ 'caller_id' => @caller_id,
118
+ 'identity' => Hoodoo::Utilities.stringify( @identity )
119
+ }
120
+ end
183
121
  end
184
122
 
185
123
  end