loggability 0.15.1 → 0.16.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4dd5d6f5605a1a639a0143107b4079272e35bb7ffba9e2b76b150c1fab57e11f
4
- data.tar.gz: aef9016eb5f208d0d168d8e4aac0b03047851b582f8afcb437b37bb9f9b5ad4d
3
+ metadata.gz: '08efe977f97d6ff78421b1ff180716378aa56afb83b0aac80cf36550f4509e00'
4
+ data.tar.gz: 1af9769dad5f2eca223b413f1ead75dcbe2e322dc96f4d9598d3f275d3fa661a
5
5
  SHA512:
6
- metadata.gz: e14c6919279635835bd126a7c878ef69149811640cd4c632022e1677c18abfb3ebadc67fe8a12e9d35e35a94296e2c3ca32614c0c998ed9089b8e9b72d25ec56
7
- data.tar.gz: d2481edb5e1a26dcc7f2a78420831d127b9951dd36e02e898bbd7fded829895083071627d9d78616fbad692937eb05edbdd39456efa0034aae4647cce597c484
6
+ metadata.gz: 9fd3be386367fab5fbf7b1afba29e234f452160af4514098880483d6d4c377bc201c4558aad2e1e19e8ddbd2c24afbef34cf25fe6047a7fbaa2a08c742141de5
7
+ data.tar.gz: e96d9bd362d3709c3035938e03fa303c635846c1722a270b2d9286c0736c46d103f00d05f5391ad96b3dbcbcc5c3f5262378e62a38ac704ccaddc53dc9644089
checksums.yaml.gz.sig CHANGED
Binary file
data.tar.gz.sig CHANGED
Binary file
data/History.rdoc CHANGED
@@ -1,6 +1,15 @@
1
1
  = Release History for loggability
2
2
 
3
3
  ---
4
+ == v0.16.0 [2020-02-24] Michael Granger <ged@faeriemud.org>
5
+
6
+ Improvements:
7
+
8
+ - Expose a "log device" API and add the ability to write to multiple devices
9
+ - Add a device for logging to an HTTP service
10
+
11
+ Thanks to Mahmood Khan <mkhan1484@gmail.com> for the patch.
12
+
4
13
 
5
14
  == v0.15.1 [2020-01-09] Michael Granger <ged@faeriemud.org>
6
15
 
data/Manifest.txt CHANGED
@@ -1,9 +1,5 @@
1
1
  .simplecov
2
- ChangeLog
3
2
  History.rdoc
4
- Manifest.txt
5
- README.md
6
- Rakefile
7
3
  lib/loggability.rb
8
4
  lib/loggability/constants.rb
9
5
  lib/loggability/formatter.rb
@@ -14,8 +10,16 @@ lib/loggability/formatter/structured.rb
14
10
  lib/loggability/logclient.rb
15
11
  lib/loggability/logger.rb
16
12
  lib/loggability/loghost.rb
13
+ lib/loggability/log_device.rb
14
+ lib/loggability/log_device/appending.rb
15
+ lib/loggability/log_device/datadog.rb
16
+ lib/loggability/log_device/file.rb
17
+ lib/loggability/log_device/http.rb
17
18
  lib/loggability/override.rb
18
19
  lib/loggability/spechelpers.rb
20
+ Manifest.txt
21
+ Rakefile
22
+ README.md
19
23
  spec/helpers.rb
20
24
  spec/loggability/formatter/color_spec.rb
21
25
  spec/loggability/formatter/default_spec.rb
@@ -24,6 +28,10 @@ spec/loggability/formatter/structured_spec.rb
24
28
  spec/loggability/formatter_spec.rb
25
29
  spec/loggability/logger_spec.rb
26
30
  spec/loggability/loghost_spec.rb
31
+ spec/loggability/log_device/appending_spec.rb
32
+ spec/loggability/log_device/datadog_spec.rb
33
+ spec/loggability/log_device/file_spec.rb
34
+ spec/loggability/log_device/http_spec.rb
27
35
  spec/loggability/override_spec.rb
28
36
  spec/loggability/spechelpers_spec.rb
29
37
  spec/loggability_spec.rb
data/lib/loggability.rb CHANGED
@@ -9,7 +9,7 @@ require 'date'
9
9
  module Loggability
10
10
 
11
11
  # Package version constant
12
- VERSION = '0.15.1'
12
+ VERSION = '0.16.0'
13
13
 
14
14
  # The key for the global logger (Loggability's own logger)
15
15
  GLOBAL_KEY = :__global__
@@ -29,7 +29,7 @@ module Loggability
29
29
  (?<severity>(?i:debug|info|warn|error|fatal))
30
30
  (?:
31
31
  \s+
32
- (?<target>(?:[\w\-/:\.]|\\[ ])+)
32
+ (?<target>(?:[\w\-/:\.\[\]]|\\[ ])+)
33
33
  )?
34
34
  (?: \s+\(
35
35
  (?<format>\w+)
@@ -38,10 +38,16 @@ module Loggability
38
38
  $
39
39
  }x
40
40
 
41
- require 'loggability/constants'
42
- include Loggability::Constants
43
41
 
44
- require 'loggability/logger'
42
+ # Automatically load subordinate classes/modules
43
+ autoload :Constants, 'loggability/constants'
44
+ autoload :LogDevice, 'loggability/log_device'
45
+ autoload :Logger, 'loggability/logger'
46
+ autoload :LogHost, 'loggability/loghost'
47
+ autoload :LogClient, 'loggability/logclient'
48
+ autoload :Override, 'loggability/override'
49
+
50
+ include Loggability::Constants
45
51
 
46
52
 
47
53
  ##
@@ -55,10 +61,6 @@ module Loggability
55
61
  @config = CONFIG_DEFAULTS.dup.freeze
56
62
 
57
63
 
58
- # Automatically log the log host and log client mixins when they're referenced
59
- autoload :LogHost, 'loggability/loghost'
60
- autoload :LogClient, 'loggability/logclient'
61
- autoload :Override, 'loggability/override'
62
64
 
63
65
 
64
66
  ### Cast the given +device+ to a Loggability::Logger, if possible, and return it. If
@@ -321,6 +323,7 @@ module Loggability
321
323
  target = case target
322
324
  when 'STDOUT' then $stdout
323
325
  when 'STDERR' then $stderr
326
+ when /:/ then Loggability::LogDevice.parse_device_spec( target )
324
327
  else
325
328
  target
326
329
  end
@@ -0,0 +1,86 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # encoding: utf-8
4
+
5
+ require 'loggability' unless defined?( Loggability )
6
+
7
+
8
+ # An abstract base class for logging devices. A device manages the actual writing of messages
9
+ # to whatever destination logs are supposed to be shipped to, along with any buffering,
10
+ # encoding, or serialization that needs to be done.
11
+ #
12
+ # Log devices are loadable by name via the ::create method if they are declared in a
13
+ # directory named `loggability/log_device/` in the gem path.
14
+ #
15
+ # Concrete log devices are required to implement two methods: #write and #close.
16
+ #
17
+ # [write]
18
+ # Takes one argument, which is the message that needs to be written.
19
+ #
20
+ # [close]
21
+ # Close any open filehandles or connections established by the device.
22
+ class Loggability::LogDevice
23
+
24
+
25
+ # Regexp used to split up logging devices in config lines
26
+ DEVICE_TARGET_REGEX = /^([\s*a-z]\w*)(?:\[(.*)\])?/
27
+
28
+
29
+ ### Parses out the target class name and its arguments from the +target_spec+
30
+ ### then requires the subclass and instantiates it by passing the arguments.
31
+ ### The +target_spec+ comes from a config file in the format of:
32
+ ###
33
+ ### logging:
34
+ ### datadog[data_dog_api_key]
35
+ ###
36
+ ### In the above example:
37
+ ### * "datadog" is the log device to send logs to
38
+ ### * "data_dog_api_key" is the argument that will be passed onto the datadog
39
+ ### log device's constructor
40
+ def self::parse_device_spec( target_spec )
41
+ targets = target_spec.split( ':' ).compact
42
+ return targets.map do |t|
43
+ target_subclass = t[ DEVICE_TARGET_REGEX, 1 ]&.strip().to_sym
44
+ target_subclass_args = t[ DEVICE_TARGET_REGEX, 2 ]
45
+
46
+ self.create( target_subclass, target_subclass_args )
47
+ end
48
+ end
49
+
50
+
51
+ ### Requires the subclass and instantiates it with the passed-in arguments and
52
+ ### then returns an instance of it.
53
+ def self::create( target, *target_args )
54
+ modname = target.to_s.capitalize
55
+
56
+ self.load_device_type( target ) unless self.const_defined?( modname, false )
57
+ subclass = self.const_get( modname, false )
58
+
59
+ return subclass.new( *target_args )
60
+ rescue NameError => err
61
+ raise LoadError, "failed to load %s LogDevice: %s" % [ target, err.message ]
62
+ end
63
+
64
+
65
+ ### Attempt to load a LogDevice of the given +type+.
66
+ def self::load_device_type( type )
67
+ require_path = "loggability/log_device/%s" % [ type.to_s.downcase ]
68
+ require( require_path )
69
+ end
70
+
71
+
72
+ ### Write a +message+ to the device. This needs to be overridden by concrete
73
+ ### subclasses; calling this implementation will raise an NotImplementedError.
74
+ def write( message )
75
+ raise NotImplementedError, "%s is not implemented by %s" % [ __callee__, self.class.name ]
76
+ end
77
+
78
+
79
+ ### Close the device. This needs to be overridden by concrete subclasses;
80
+ ### calling this implementation will raise an NotImplementedError.
81
+ def close
82
+ raise NotImplementedError, "%s is not implemented by %s" % [ __callee__, self.class.name ]
83
+ end
84
+
85
+ end # class Loggability::LogDevice
86
+
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # encoding: utf-8
4
+
5
+ require 'loggability/logger' unless defined?( Loggability::Logger )
6
+
7
+ # A log device that appends to the object it's constructed with instead of writing
8
+ # to a file descriptor or a file.
9
+ class Loggability::LogDevice::Appending < Loggability::LogDevice
10
+
11
+ ### Create a new +Appending+ log device that will append content to +array+.
12
+ def initialize( target )
13
+ @target = target || []
14
+ end
15
+
16
+
17
+ ######
18
+ public
19
+ ######
20
+
21
+ # The target of the log device
22
+ attr_reader :target
23
+
24
+
25
+ ### Append the specified +message+ to the target.
26
+ def write( message )
27
+ @target << message
28
+ end
29
+
30
+
31
+ ### No-op -- this is here just so Logger doesn't complain
32
+ def close; end
33
+
34
+ end # class Loggability::LogDevice::Appending
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # encoding: utf-8
4
+
5
+ require 'uri'
6
+ require 'socket'
7
+ require 'net/https'
8
+ require 'json'
9
+ require 'concurrent'
10
+ require 'loggability/logger' unless defined?( Loggability::Logger )
11
+
12
+ require 'loggability/log_device/http'
13
+
14
+
15
+ # A log device that sends logs to Datadog's HTTP endpoint
16
+ # for receiving logs
17
+ class Loggability::LogDevice::Datadog < Loggability::LogDevice::Http
18
+
19
+ ### Datadog's HTTP endpoint URL for sending logs to
20
+ DEFAULT_ENDPOINT = URI( "https://http-intake.logs.datadoghq.com/v1/input" )
21
+
22
+ ### The max number of messages that can be sent to datadog in a single payload
23
+ MAX_BATCH_SIZE = 480
24
+
25
+ ### The max size in bytes for a single message.
26
+ ### Limiting the message size to 200kB to leave room for other info such as
27
+ ### tags, metadata, etc.
28
+ ### DataDog's max size for a single log entry is 256kB
29
+ MAX_MESSAGE_BYTESIZE = 204_800
30
+
31
+ ### The max size in bytes of all messages in the batch.
32
+ ### Limiting the total messages size to 4MB to leave room for other info such as
33
+ ### tags, metadata, etc.
34
+ ### Datadog's max size for the entire payload is 5MB
35
+ MAX_BATCH_BYTESIZE = 4_194_304
36
+
37
+ # Override the default HTTP device options for sending logs to DD
38
+ DEFAULT_OPTIONS = {
39
+ max_batch_size: MAX_BATCH_SIZE,
40
+ max_message_bytesize: MAX_MESSAGE_BYTESIZE,
41
+ max_batch_bytesize: MAX_BATCH_BYTESIZE,
42
+ }
43
+
44
+
45
+ ### Create a new Datadog
46
+ def initialize( api_key, endpoint=DEFAULT_ENDPOINT, options={} )
47
+ if endpoint.is_a?( Hash )
48
+ options = endpoint
49
+ endpoint = DEFAULT_ENDPOINT
50
+ end
51
+
52
+ super( endpoint, options )
53
+
54
+ @api_key = api_key
55
+ @hostname = Socket.gethostname
56
+ end
57
+
58
+
59
+ ######
60
+ public
61
+ ######
62
+
63
+ ##
64
+ # The name of the current host
65
+ attr_reader :hostname
66
+
67
+ ##
68
+ # The configured Datadog API key
69
+ attr_reader :api_key
70
+
71
+
72
+ ### Format an individual log +message+ for Datadog.
73
+ def format_log_message( message )
74
+ return {
75
+ hostname: self.hostname,
76
+ message: message
77
+ }.to_json
78
+ end
79
+
80
+
81
+ ### Overridden to add the configured API key to the headers of each request.
82
+ def make_batch_request
83
+ request = super
84
+
85
+ request[ 'DD-API-KEY' ] = self.api_key
86
+
87
+ return request
88
+ end
89
+
90
+ end # class Loggability::LogDevice::Datadog
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # encoding: utf-8
4
+
5
+ require 'logger'
6
+ require 'loggability/logger' unless defined?( Loggability::Logger )
7
+
8
+ # A log device that delegates to the ruby's default Logger's file device and writes
9
+ # to a file descriptor or a file.
10
+ class Loggability::LogDevice::File < Loggability::LogDevice
11
+
12
+ ### Create a new +File+ device that will write to the file using the built-in ruby's +File+ log device
13
+ def initialize( target )
14
+ @target = ::Logger::LogDevice.new( target )
15
+ end
16
+
17
+
18
+ ######
19
+ public
20
+ ######
21
+
22
+ # The target of the log device
23
+ attr_reader :target
24
+
25
+
26
+ ### Append the specified +message+ to the target.
27
+ def write( message )
28
+ self.target.write( message )
29
+ end
30
+
31
+
32
+ ### close the file
33
+ def close
34
+ self.target.close
35
+ end
36
+
37
+ end # class Loggability::LogDevice::File
@@ -0,0 +1,288 @@
1
+ #!/usr/bin/env ruby
2
+ # vim: set nosta noet ts=4 sw=4:
3
+ # encoding: utf-8
4
+
5
+ require 'socket'
6
+ require 'uri'
7
+ require 'net/https'
8
+ require 'json'
9
+ require 'concurrent'
10
+ require 'loggability/logger' unless defined?( Loggability::Logger )
11
+
12
+ require 'loggability/log_device'
13
+
14
+ # This is the a generalized class that allows its subclasses to send log
15
+ # messages to HTTP endpoints asynchronously on a separate thread.
16
+ class Loggability::LogDevice::Http < Loggability::LogDevice
17
+
18
+ # The default HTTP endpoint URL to send logs to
19
+ DEFAULT_ENDPOINT = "http://localhost:12775/v1/logs"
20
+
21
+ # The default maximum number of messages that can be sent to the server in a single payload
22
+ DEFAULT_MAX_BATCH_SIZE = 100
23
+
24
+ # The default max size in bytes for a single message.
25
+ DEFAULT_MAX_MESSAGE_BYTESIZE = 2 * 16
26
+
27
+ # The default number of seconds between batches
28
+ DEFAULT_BATCH_INTERVAL = 60
29
+
30
+ # The default number of seconds to wait for data to be written before timing out
31
+ DEFAULT_WRITE_TIMEOUT = 15
32
+
33
+ # The default Executor class to use for asynchronous tasks
34
+ DEFAULT_EXECUTOR_CLASS = Concurrent::SingleThreadExecutor
35
+
36
+ # The default options for new instances
37
+ DEFAULT_OPTIONS = {
38
+ execution_interval: DEFAULT_BATCH_INTERVAL,
39
+ write_timeout: DEFAULT_WRITE_TIMEOUT,
40
+ max_batch_size: DEFAULT_MAX_BATCH_SIZE,
41
+ max_message_bytesize: DEFAULT_MAX_MESSAGE_BYTESIZE,
42
+ executor_class: DEFAULT_EXECUTOR_CLASS,
43
+ }
44
+
45
+
46
+ ### Initialize the HTTP log device to send to the specified +endpoint+ with the
47
+ ### given +options+. Valid options are:
48
+ ###
49
+ ### [:batch_interval]
50
+ ### Maximum number of seconds between batches
51
+ ### [:write_timeout]
52
+ ### How many seconds to wait for data to be written while sending a batch
53
+ ### [:max_batch_size]
54
+ ### The maximum number of messages that can be in a single batch
55
+ ### [:max_batch_bytesize]
56
+ ### The maximum number of bytes that can be in the payload of a single batch
57
+ ### [:max_message_bytesize]
58
+ ### The maximum number of bytes that can be in a single message
59
+ ### [:executor_class]
60
+ ### The Concurrent executor class to use for asynchronous tasks.
61
+ def initialize( endpoint=DEFAULT_ENDPOINT, opts={} )
62
+ if endpoint.is_a?( Hash )
63
+ opts = endpoint
64
+ endpoint = DEFAULT_ENDPOINT
65
+ end
66
+
67
+ opts = DEFAULT_OPTIONS.merge( opts )
68
+
69
+ @endpoint = URI( endpoint ).freeze
70
+ @logs_queue = Queue.new
71
+
72
+ @batch_interval = opts[:batch_interval] || DEFAULT_BATCH_INTERVAL
73
+ @write_timeout = opts[:write_timeout] || DEFAULT_WRITE_TIMEOUT
74
+ @max_batch_size = opts[:max_batch_size] || DEFAULT_MAX_BATCH_SIZE
75
+ @max_message_bytesize = opts[:max_message_bytesize] || DEFAULT_MAX_MESSAGE_BYTESIZE
76
+ @executor_class = opts[:executor_class] || DEFAULT_EXECUTOR_CLASS
77
+
78
+ @max_batch_bytesize = opts[:max_batch_bytesize] || @max_batch_size * @max_message_bytesize
79
+ @last_send_time = Concurrent.monotonic_time
80
+ end
81
+
82
+
83
+ ######
84
+ public
85
+ ######
86
+
87
+ ##
88
+ # The single thread pool executor
89
+ attr_reader :executor
90
+
91
+ ##
92
+ # The URI of the endpoint to send messages to
93
+ attr_reader :endpoint
94
+
95
+ ##
96
+ # The Queue that contains any log messages which have not yet been sent to the
97
+ # logging service.
98
+ attr_reader :logs_queue
99
+
100
+ ##
101
+ # The monotonic clock time when the last batch of logs were sent
102
+ attr_accessor :last_send_time
103
+
104
+ ##
105
+ # Number of seconds after the task completes before the task is performed again.
106
+ attr_reader :batch_interval
107
+
108
+ ##
109
+ # How many seconds to wait for data to be written while sending a batch
110
+ attr_reader :write_timeout
111
+
112
+ ##
113
+ # The maximum number of messages to post at one time
114
+ attr_reader :max_batch_size
115
+
116
+ ##
117
+ # The maximum number of bytes of a single message to include in a batch
118
+ attr_reader :max_message_bytesize
119
+
120
+ ##
121
+ # The maximum number of bytes that will be included in a single POST
122
+ attr_reader :max_batch_bytesize
123
+
124
+ ##
125
+ # The Concurrent executor class to use for asynchronous tasks
126
+ attr_reader :executor_class
127
+
128
+ ##
129
+ # The timer task thread
130
+ attr_reader :timer_task
131
+
132
+
133
+ ### LogDevice API -- write a message to the HTTP device.
134
+ def write( message )
135
+ self.start unless self.running?
136
+ self.logs_queue.enq( message )
137
+ self.send_logs
138
+ end
139
+
140
+
141
+ ### LogDevice API -- stop the batch thread and close the http connection
142
+ def close
143
+ self.stop
144
+ self.http_client.finish
145
+ rescue IOError
146
+ # ignore it since http session has not yet started.
147
+ end
148
+
149
+
150
+ ### Starts a thread pool with a single thread.
151
+ def start
152
+ self.start_executor
153
+ self.start_timer_task
154
+ end
155
+
156
+
157
+ ### Returns +true+ if the device has started sending messages to the logging endpoint.
158
+ def running?
159
+ return self.executor&.running?
160
+ end
161
+
162
+
163
+ ### Shutdown the executor, which is a pool of single thread
164
+ ### waits 3 seconds for shutdown to complete
165
+ def stop
166
+ return unless self.running?
167
+
168
+ self.timer_task.shutdown if self.timer_task&.running?
169
+ self.executor.shutdown
170
+
171
+ unless self.executor.wait_for_termination( 3 )
172
+ self.executor.halt
173
+ self.executor.wait_for_termination( 3 )
174
+ end
175
+ end
176
+
177
+
178
+ ### Start the background thread that sends messages.
179
+ def start_executor
180
+ @executor = self.executor_class.new
181
+ @executor.auto_terminate = true unless @executor.serialized?
182
+ end
183
+
184
+
185
+ ### Create a timer task that calls that sends logs at regular interval
186
+ def start_timer_task
187
+ @timer_task = Concurrent::TimerTask.execute( execution_interval: self.batch_interval ) do
188
+ self.send_logs
189
+ end
190
+ end
191
+
192
+
193
+ ### Sends a batch of log messages to the logging service. This executes inside
194
+ ### the sending thread.
195
+ def send_logs
196
+ self.executor.post do
197
+ if self.batch_ready?
198
+ # p "Batch ready; sending."
199
+ request = self.make_batch_request
200
+ request.body = self.get_next_log_payload
201
+
202
+ # p "Sending request", request
203
+
204
+ self.http_client.request( request ) do |res|
205
+ p( res ) if $DEBUG
206
+ end
207
+
208
+ self.last_send_time = Concurrent.monotonic_time
209
+ else
210
+ # p "Batch not ready yet."
211
+ end
212
+ end
213
+ end
214
+
215
+
216
+ ### Returns +true+ if a batch of logs is ready to be sent.
217
+ def batch_ready?
218
+ seconds_since_last_send = Concurrent.monotonic_time - self.last_send_time
219
+
220
+ return self.logs_queue.size >= self.max_batch_size ||
221
+ seconds_since_last_send >= self.batch_interval
222
+ end
223
+ alias_method :has_batch_ready?, :batch_ready?
224
+
225
+
226
+ ### Returns a new HTTP request (a subclass of Net::HTTPRequest) suitable for
227
+ ### sending the next batch of logs to the service. Defaults to a POST of JSON data. This
228
+ ### executes inside the sending thread.
229
+ def make_batch_request
230
+ request = Net::HTTP::Post.new( self.endpoint.path )
231
+ request[ 'Content-Type' ] = 'application/json'
232
+
233
+ return request
234
+ end
235
+
236
+
237
+ ### Dequeue pending log messages to send to the service and return them as a
238
+ ### suitably-encoded String. The default is a JSON Array. This executes inside
239
+ ### the sending thread.
240
+ def get_next_log_payload
241
+ buf = []
242
+ count = 0
243
+ bytes = 0
244
+
245
+ # Be conservative so as not to overflow
246
+ max_size = self.max_batch_bytesize - self.max_message_bytesize - 2 # for the outer Array
247
+
248
+ while count < self.max_batch_size && bytes < max_size && !self.logs_queue.empty?
249
+ formatted_message = self.format_log_message( self.logs_queue.deq )
250
+
251
+ count += 1
252
+ bytes += formatted_message.bytesize + 3 # comma and delimiters
253
+
254
+ buf << formatted_message
255
+ end
256
+
257
+ return '[' + buf.join(',') + ']'
258
+ end
259
+
260
+
261
+ ### Returns the given +message+ in whatever format individual log messages are
262
+ ### expected to be in by the service. The default just returns the stringified
263
+ ### +message+. This executes inside the sending thread.
264
+ def format_log_message( message )
265
+ return message.to_s[ 0 ... self.max_message_bytesize ].dump
266
+ end
267
+
268
+
269
+ ### sets up a configured http object ready to instantiate connections
270
+ def http_client
271
+ return @http_client ||= begin
272
+ uri = URI.parse( self.endpoint )
273
+
274
+ http = Net::HTTP.new( uri.host, uri.port )
275
+ http.write_timeout = self.write_timeout
276
+
277
+ if uri.scheme == 'https'
278
+ http.use_ssl = true
279
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
280
+ end
281
+
282
+ http
283
+ end
284
+ end
285
+
286
+
287
+ end # class Loggability::LogDevice::Http
288
+