logtail-ruby 0.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.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +33 -0
- data/.gitignore +24 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +10 -0
- data/LICENSE.md +15 -0
- data/README.md +4 -0
- data/Rakefile +72 -0
- data/lib/logtail.rb +36 -0
- data/lib/logtail/config.rb +154 -0
- data/lib/logtail/config/integrations.rb +17 -0
- data/lib/logtail/context.rb +9 -0
- data/lib/logtail/contexts.rb +12 -0
- data/lib/logtail/contexts/http.rb +31 -0
- data/lib/logtail/contexts/release.rb +52 -0
- data/lib/logtail/contexts/runtime.rb +23 -0
- data/lib/logtail/contexts/session.rb +24 -0
- data/lib/logtail/contexts/system.rb +29 -0
- data/lib/logtail/contexts/user.rb +28 -0
- data/lib/logtail/current_context.rb +168 -0
- data/lib/logtail/event.rb +36 -0
- data/lib/logtail/events.rb +10 -0
- data/lib/logtail/events/controller_call.rb +44 -0
- data/lib/logtail/events/error.rb +40 -0
- data/lib/logtail/events/exception.rb +10 -0
- data/lib/logtail/events/sql_query.rb +26 -0
- data/lib/logtail/events/template_render.rb +25 -0
- data/lib/logtail/integration.rb +40 -0
- data/lib/logtail/integrator.rb +50 -0
- data/lib/logtail/log_devices.rb +8 -0
- data/lib/logtail/log_devices/http.rb +368 -0
- data/lib/logtail/log_devices/http/flushable_dropping_sized_queue.rb +52 -0
- data/lib/logtail/log_devices/http/request_attempt.rb +20 -0
- data/lib/logtail/log_entry.rb +110 -0
- data/lib/logtail/logger.rb +270 -0
- data/lib/logtail/logtail.rb +36 -0
- data/lib/logtail/timer.rb +21 -0
- data/lib/logtail/util.rb +7 -0
- data/lib/logtail/util/non_nil_hash_builder.rb +40 -0
- data/lib/logtail/version.rb +3 -0
- data/logtail-ruby.gemspec +43 -0
- data/spec/README.md +13 -0
- data/spec/logtail/current_context_spec.rb +113 -0
- data/spec/logtail/events/controller_call_spec.rb +12 -0
- data/spec/logtail/events/error_spec.rb +15 -0
- data/spec/logtail/log_devices/http_spec.rb +185 -0
- data/spec/logtail/log_entry_spec.rb +22 -0
- data/spec/logtail/logger_spec.rb +227 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/logtail.rb +5 -0
- data/spec/support/socket_hostname.rb +12 -0
- data/spec/support/timecop.rb +3 -0
- data/spec/support/webmock.rb +3 -0
- metadata +238 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'logtail/util'
|
2
|
+
require 'logtail/event'
|
3
|
+
|
4
|
+
module Logtail
|
5
|
+
module Events
|
6
|
+
# @private
|
7
|
+
class SQLQuery < Logtail::Event
|
8
|
+
attr_reader :sql, :duration_ms, :message
|
9
|
+
|
10
|
+
def initialize(attributes)
|
11
|
+
@sql = attributes[:sql]
|
12
|
+
@duration_ms = attributes[:duration_ms]
|
13
|
+
@message = attributes[:message]
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_hash
|
17
|
+
{
|
18
|
+
sql_query_executed: Util::NonNilHashBuilder.build do |h|
|
19
|
+
h.add(:sql, sql)
|
20
|
+
h.add(:duration_ms, duration_ms)
|
21
|
+
end
|
22
|
+
}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require "logtail/util"
|
2
|
+
require "logtail/event"
|
3
|
+
module Logtail
|
4
|
+
module Events
|
5
|
+
# @private
|
6
|
+
class TemplateRender < Logtail::Event
|
7
|
+
attr_reader :message, :name, :duration_ms
|
8
|
+
|
9
|
+
def initialize(attributes)
|
10
|
+
@name = attributes[:name]
|
11
|
+
@duration_ms = attributes[:duration_ms]
|
12
|
+
@message = attributes[:message]
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_hash
|
16
|
+
{
|
17
|
+
template_rendered: Util::NonNilHashBuilder.build do |h|
|
18
|
+
h.add(:name, name)
|
19
|
+
h.add(:duration_ms, duration_ms)
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Logtail
|
2
|
+
# An integration represent an integration for an entire library. For example, `Rack`.
|
3
|
+
# While the Logtail `Rack` integration is comprised of multiple middlewares, the
|
4
|
+
# `Logtail::Integrations::Rack` module is an entire integration that extends this module.
|
5
|
+
module Integration
|
6
|
+
# Easily sisable entire library integrations. This is like removing the code from
|
7
|
+
# Logtail. It will not touch this library and the library will function as it would
|
8
|
+
# without Logtail.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# Logtail::Integrations::ActiveRecord.enabled = false
|
12
|
+
def enabled=(value)
|
13
|
+
@enabled = value
|
14
|
+
end
|
15
|
+
|
16
|
+
# Accessor method for {#enabled=}
|
17
|
+
def enabled?
|
18
|
+
@enabled != false
|
19
|
+
end
|
20
|
+
|
21
|
+
# Silences a library's logs. This ensures that logs are not generated at all
|
22
|
+
# from this library.
|
23
|
+
#
|
24
|
+
# @example
|
25
|
+
# Logtail::Integrations::ActiveRecord.silence = true
|
26
|
+
def silence=(value)
|
27
|
+
@silence = value
|
28
|
+
end
|
29
|
+
|
30
|
+
# Accessor method for {#silence=}
|
31
|
+
def silence?
|
32
|
+
@silence == true
|
33
|
+
end
|
34
|
+
|
35
|
+
# Abstract method that each integration must implement.
|
36
|
+
def integrate!
|
37
|
+
raise NotImplementedError.new
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Logtail
|
2
|
+
# Base class for `Logtail::Integrations::*`. Provides a common interface for all integrators.
|
3
|
+
# An integrator is a single specific integration into a part of a library. See
|
4
|
+
# {Integration} for higher library level integration settings.
|
5
|
+
class Integrator
|
6
|
+
# Raised when an integrators requirements are not met. For example, this will be raised
|
7
|
+
# in the ActiveRecord integration if ActiveRecord is not available as a dependency in
|
8
|
+
# the current application.
|
9
|
+
class RequirementNotMetError < StandardError; end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_writer :enabled
|
13
|
+
|
14
|
+
# Allows you to enable / disable specific integrations.
|
15
|
+
#
|
16
|
+
# @note Disabling specific low level integrations should only be needed for edge cases.
|
17
|
+
# If you want to disable integration with an entire library, we recommend doing so
|
18
|
+
# at a higher level. Ex: `Logtail::Integrations::ActiveRecord.enabled = false`.
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# Logtail::Integrations::ActiveRecord::LogSubscriber.enabled = false
|
22
|
+
def enabled?
|
23
|
+
@enabled != false
|
24
|
+
end
|
25
|
+
|
26
|
+
# Convenience class level method that runs the integrator by instantiating a new
|
27
|
+
# object and calling {#integrate!}. It also takes care to look at the if the integrator
|
28
|
+
# is enabled, skipping it if not.
|
29
|
+
def integrate!(*args)
|
30
|
+
if !enabled?
|
31
|
+
Config.instance.debug_logger.debug("#{name} integration disabled, skipping") if Config.instance.debug_logger
|
32
|
+
return false
|
33
|
+
end
|
34
|
+
|
35
|
+
new(*args).integrate!
|
36
|
+
Config.instance.debug_logger.debug("Integrated #{name}") if Config.instance.debug_logger
|
37
|
+
true
|
38
|
+
# RequirementUnsatisfiedError is the only silent failure we support
|
39
|
+
rescue RequirementNotMetError => e
|
40
|
+
Config.instance.debug_logger.debug("Failed integrating #{name}: #{e.message}") if Config.instance.debug_logger
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Abstract method that each integration must implement.
|
46
|
+
def integrate!
|
47
|
+
raise NotImplementedError.new
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,368 @@
|
|
1
|
+
require "base64"
|
2
|
+
require "msgpack"
|
3
|
+
require "net/https"
|
4
|
+
|
5
|
+
require "logtail/config"
|
6
|
+
require "logtail/log_devices/http/flushable_dropping_sized_queue"
|
7
|
+
require "logtail/log_devices/http/request_attempt"
|
8
|
+
require "logtail/version"
|
9
|
+
|
10
|
+
module Logtail
|
11
|
+
module LogDevices
|
12
|
+
# A highly efficient log device that buffers and delivers log messages over HTTPS to
|
13
|
+
# the Logtail API. It uses batches, keep-alive connections, and msgpack to deliver logs with
|
14
|
+
# high-throughput and little overhead. All log preparation and delivery is done asynchronously
|
15
|
+
# in a thread as not to block application execution and efficiently deliver logs for
|
16
|
+
# multi-threaded environments.
|
17
|
+
#
|
18
|
+
# See {#initialize} for options and more details.
|
19
|
+
class HTTP
|
20
|
+
LOGTAIL_STAGING_HOST = "logs-staging.logtail.com".freeze
|
21
|
+
LOGTAIL_PRODUCTION_HOST = "logs.logtail.com".freeze
|
22
|
+
LOGTAIL_HOST = ENV['LOGTAIL_STAGING'] ? LOGTAIL_STAGING_HOST : LOGTAIL_PRODUCTION_HOST
|
23
|
+
LOGTAIL_PORT = 443
|
24
|
+
LOGTAIL_SCHEME = "https".freeze
|
25
|
+
CONTENT_TYPE = "application/msgpack".freeze
|
26
|
+
USER_AGENT = "Logtail Ruby/#{Logtail::VERSION} (HTTP)".freeze
|
27
|
+
|
28
|
+
# Instantiates a new HTTP log device that can be passed to {Logtail::Logger#initialize}.
|
29
|
+
#
|
30
|
+
# The class maintains a buffer which is flushed in batches to the Logtail API. 2
|
31
|
+
# options control when the flush happens, `:batch_byte_size` and `:flush_interval`.
|
32
|
+
# If either of these are surpassed, the buffer will be flushed.
|
33
|
+
#
|
34
|
+
# By default, the buffer will apply back pressure when the rate of log messages exceeds
|
35
|
+
# the maximum delivery rate. If you don't want to sacrifice app performance in this case
|
36
|
+
# you can drop the log messages instead by passing a {DroppingSizedQueue} via the
|
37
|
+
# `:request_queue` option.
|
38
|
+
#
|
39
|
+
# @param api_key [String] The API key provided to you after you add your application to
|
40
|
+
# [Logtail](https://logtail.com).
|
41
|
+
# @param [Hash] options the options to create a HTTP log device with.
|
42
|
+
# @option attributes [Symbol] :batch_size (1000) Determines the maximum of log lines in
|
43
|
+
# each HTTP payload. If the queue exceeds this limit an HTTP request will be issued. Bigger
|
44
|
+
# payloads mean higher throughput, but also use more memory. Logtail will not accept
|
45
|
+
# payloads larger than 1mb.
|
46
|
+
# @option attributes [Symbol] :flush_continuously (true) This should only be disabled under
|
47
|
+
# special circumstsances (like test suites). Setting this to `false` disables the
|
48
|
+
# continuous flushing of log message. As a result, flushing must be handled externally
|
49
|
+
# via the #flush method.
|
50
|
+
# @option attributes [Symbol] :flush_interval (1) How often the client should
|
51
|
+
# attempt to deliver logs to the Logtail API in fractional seconds. The HTTP client buffers
|
52
|
+
# logs and this options represents how often that will happen, assuming `:batch_byte_size`
|
53
|
+
# is not met.
|
54
|
+
# @option attributes [Symbol] :requests_per_conn (2500) The number of requests to send over a
|
55
|
+
# single persistent connection. After this number is met, the connection will be closed
|
56
|
+
# and a new one will be opened.
|
57
|
+
# @option attributes [Symbol] :request_queue (FlushableDroppingSizedQueue.new(25)) The request
|
58
|
+
# queue object that queues Net::HTTP requests for delivery. By deafult this is a
|
59
|
+
# `FlushableDroppingSizedQueue` of size `25`. Meaning once the queue fills up to 25
|
60
|
+
# requests new requests will be dropped. If you'd prefer to apply back pressure,
|
61
|
+
# ensuring you do not lose log data, pass a standard {SizedQueue}. See examples for
|
62
|
+
# an example.
|
63
|
+
# @option attributes [Symbol] :logtail_host The Logtail host to delivery the log lines to.
|
64
|
+
# The default is set via {LOGTAIL_HOST}.
|
65
|
+
#
|
66
|
+
# @example Basic usage
|
67
|
+
# Logtail::Logger.new(Logtail::LogDevices::HTTP.new("my_logtail_api_key"))
|
68
|
+
#
|
69
|
+
# @example Apply back pressure instead of dropping messages
|
70
|
+
# http_log_device = Logtail::LogDevices::HTTP.new("my_logtail_api_key", request_queue: SizedQueue.new(25))
|
71
|
+
# Logtail::Logger.new(http_log_device)
|
72
|
+
def initialize(api_key, options = {})
|
73
|
+
@api_key = api_key || raise(ArgumentError.new("The api_key parameter cannot be blank"))
|
74
|
+
@logtail_host = options[:logtail_host] || ENV['LOGTAIL_HOST'] || LOGTAIL_HOST
|
75
|
+
@logtail_port = options[:logtail_port] || ENV['LOGTAIL_PORT'] || LOGTAIL_PORT
|
76
|
+
@logtail_scheme = options[:logtail_scheme] || ENV['LOGTAIL_SCHEME'] || LOGTAIL_SCHEME
|
77
|
+
@batch_size = options[:batch_size] || 1_000
|
78
|
+
@flush_continuously = options[:flush_continuously] != false
|
79
|
+
@flush_interval = options[:flush_interval] || 2 # 2 seconds
|
80
|
+
@requests_per_conn = options[:requests_per_conn] || 2_500
|
81
|
+
@msg_queue = FlushableDroppingSizedQueue.new(@batch_size)
|
82
|
+
@request_queue = options[:request_queue] || FlushableDroppingSizedQueue.new(25)
|
83
|
+
@successive_error_count = 0
|
84
|
+
@requests_in_flight = 0
|
85
|
+
end
|
86
|
+
|
87
|
+
# Write a new log line message to the buffer, and flush asynchronously if the
|
88
|
+
# message queue is full. We flush asynchronously because the maximum message batch
|
89
|
+
# size is constricted by the Logtail API. The actual application limit is a multiple
|
90
|
+
# of this. Hence the `@request_queue`.
|
91
|
+
def write(msg)
|
92
|
+
@msg_queue.enq(msg)
|
93
|
+
|
94
|
+
# Lazily start flush threads to ensure threads are alive after forking processes.
|
95
|
+
# If the threads are started during instantiation they will not be copied when
|
96
|
+
# the current process is forked. This is the case with various web servers,
|
97
|
+
# such as phusion passenger.
|
98
|
+
ensure_flush_threads_are_started
|
99
|
+
|
100
|
+
if @msg_queue.full?
|
101
|
+
Logtail::Config.instance.debug { "Flushing HTTP buffer via write" }
|
102
|
+
flush_async
|
103
|
+
end
|
104
|
+
true
|
105
|
+
end
|
106
|
+
|
107
|
+
# Flush all log messages in the buffer synchronously. This method will not return
|
108
|
+
# until delivery of the messages has been successful. If you want to flush
|
109
|
+
# asynchronously see {#flush_async}.
|
110
|
+
def flush
|
111
|
+
flush_async
|
112
|
+
wait_on_request_queue
|
113
|
+
true
|
114
|
+
end
|
115
|
+
|
116
|
+
# Closes the log device, cleans up, and attempts one last delivery.
|
117
|
+
def close
|
118
|
+
# Kill the flush thread immediately since we are about to flush again.
|
119
|
+
@flush_thread.kill if @flush_thread
|
120
|
+
|
121
|
+
# Flush all remaining messages
|
122
|
+
flush
|
123
|
+
|
124
|
+
# Kill the request queue thread. Flushing ensures that no requests are pending.
|
125
|
+
@request_outlet_thread.kill if @request_outlet_thread
|
126
|
+
end
|
127
|
+
|
128
|
+
def deliver_one(msg)
|
129
|
+
http = build_http
|
130
|
+
|
131
|
+
begin
|
132
|
+
resp = http.start do |conn|
|
133
|
+
req = build_request([msg])
|
134
|
+
@requests_in_flight += 1
|
135
|
+
conn.request(req)
|
136
|
+
end
|
137
|
+
return resp
|
138
|
+
rescue => e
|
139
|
+
Logtail::Config.instance.debug { "error: #{e.message}" }
|
140
|
+
return e
|
141
|
+
ensure
|
142
|
+
http.finish if http.started?
|
143
|
+
@requests_in_flight -= 1
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def verify_delivery!
|
148
|
+
5.times do |i|
|
149
|
+
sleep(2)
|
150
|
+
|
151
|
+
if @last_resp.nil?
|
152
|
+
print "."
|
153
|
+
elsif @last_resp.code == "202"
|
154
|
+
puts "Log delivery successful! View your logs at https://logtail.com"
|
155
|
+
else
|
156
|
+
raise <<-MESSAGE
|
157
|
+
|
158
|
+
Log delivery failed!
|
159
|
+
|
160
|
+
Status: #{@last_resp.code}
|
161
|
+
Body: #{@last_resp.body}
|
162
|
+
|
163
|
+
You can enable internal Logtail debug logging with the following:
|
164
|
+
|
165
|
+
Logtail::Config.instance.debug_logger = ::Logger.new(STDOUT)
|
166
|
+
MESSAGE
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
raise <<-MESSAGE
|
171
|
+
|
172
|
+
Log delivery failed! No request was made.
|
173
|
+
|
174
|
+
You can enable internal debug logging with the following:
|
175
|
+
|
176
|
+
Logtail::Config.instance.debug_logger = ::Logger.new(STDOUT)
|
177
|
+
MESSAGE
|
178
|
+
end
|
179
|
+
|
180
|
+
private
|
181
|
+
# This is a convenience method to ensure the flush thread are
|
182
|
+
# started. This is called lazily from {#write} so that we
|
183
|
+
# only start the threads as needed, but it also ensures
|
184
|
+
# threads are started after process forking.
|
185
|
+
def ensure_flush_threads_are_started
|
186
|
+
if @flush_continuously
|
187
|
+
if @request_outlet_thread.nil? || !@request_outlet_thread.alive?
|
188
|
+
@request_outlet_thread = Thread.new { request_outlet }
|
189
|
+
end
|
190
|
+
|
191
|
+
if @flush_thread.nil? || !@flush_thread.alive?
|
192
|
+
@flush_thread = Thread.new { intervaled_flush }
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
# Builds an HTTP request based on the current messages queued.
|
198
|
+
def build_request(msgs)
|
199
|
+
path = '/'
|
200
|
+
req = Net::HTTP::Post.new(path)
|
201
|
+
req['Authorization'] = authorization_payload
|
202
|
+
req['Content-Type'] = CONTENT_TYPE
|
203
|
+
req['User-Agent'] = USER_AGENT
|
204
|
+
req.body = msgs.to_msgpack
|
205
|
+
req
|
206
|
+
end
|
207
|
+
|
208
|
+
# Flushes the message buffer asynchronously. The reason we provide this
|
209
|
+
# method is because the message buffer limit is constricted by the
|
210
|
+
# Logtail API. The application limit is multiples of the buffer limit,
|
211
|
+
# hence the `@request_queue`, allowing us to buffer beyond the Logtail API
|
212
|
+
# imposed limit.
|
213
|
+
def flush_async
|
214
|
+
@last_async_flush = Time.now
|
215
|
+
msgs = @msg_queue.flush
|
216
|
+
return if msgs.empty?
|
217
|
+
|
218
|
+
req = build_request(msgs)
|
219
|
+
if !req.nil?
|
220
|
+
Logtail::Config.instance.debug { "New request placed on queue" }
|
221
|
+
request_attempt = RequestAttempt.new(req)
|
222
|
+
@request_queue.enq(request_attempt)
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
# Waits on the request queue. This is used in {#flush} to ensure
|
227
|
+
# the log data has been delivered before returning.
|
228
|
+
def wait_on_request_queue
|
229
|
+
# Wait 20 seconds
|
230
|
+
40.times do |i|
|
231
|
+
if @request_queue.size == 0 && @requests_in_flight == 0
|
232
|
+
Logtail::Config.instance.debug { "Request queue is empty and no requests are in flight, finish waiting" }
|
233
|
+
return true
|
234
|
+
end
|
235
|
+
Logtail::Config.instance.debug do
|
236
|
+
"Request size #{@request_queue.size}, reqs in-flight #{@requests_in_flight}, " \
|
237
|
+
"continue waiting (iteration #{i + 1})"
|
238
|
+
end
|
239
|
+
sleep 0.5
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
# Flushes the message queue on an interval. You will notice that {#write} also
|
244
|
+
# flushes the buffer if it is full. This method takes note of this via the
|
245
|
+
# `@last_async_flush` variable as to not flush immediately after a write flush.
|
246
|
+
def intervaled_flush
|
247
|
+
# Wait specified time period before starting
|
248
|
+
sleep @flush_interval
|
249
|
+
|
250
|
+
loop do
|
251
|
+
begin
|
252
|
+
if intervaled_flush_ready?
|
253
|
+
Logtail::Config.instance.debug { "Flushing HTTP buffer via the interval" }
|
254
|
+
flush_async
|
255
|
+
end
|
256
|
+
|
257
|
+
sleep(0.5)
|
258
|
+
rescue Exception => e
|
259
|
+
Logtail::Config.instance.debug { "Intervaled HTTP flush failed: #{e.inspect}\n\n#{e.backtrace}" }
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Determines if the loop in {#intervaled_flush} is ready to be flushed again. It
|
265
|
+
# uses the `@last_async_flush` variable to ensure that a flush does not happen
|
266
|
+
# too rapidly ({#write} also triggers a flush).
|
267
|
+
def intervaled_flush_ready?
|
268
|
+
@last_async_flush.nil? || (Time.now.to_f - @last_async_flush.to_f).abs >= @flush_interval
|
269
|
+
end
|
270
|
+
|
271
|
+
# Builds an `Net::HTTP` object to deliver requests over.
|
272
|
+
def build_http
|
273
|
+
http = Net::HTTP.new(@logtail_host, @logtail_port)
|
274
|
+
http.set_debug_output(Config.instance.debug_logger) if Config.instance.debug_logger
|
275
|
+
if @logtail_scheme == 'https'
|
276
|
+
http.use_ssl = true
|
277
|
+
# Verification on Windows fails despite having a valid certificate.
|
278
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
279
|
+
end
|
280
|
+
http.read_timeout = 30
|
281
|
+
http.ssl_timeout = 10
|
282
|
+
http.open_timeout = 10
|
283
|
+
http
|
284
|
+
end
|
285
|
+
|
286
|
+
# Creates a loop that processes the `@request_queue` on an interval.
|
287
|
+
def request_outlet
|
288
|
+
loop do
|
289
|
+
http = build_http
|
290
|
+
|
291
|
+
begin
|
292
|
+
Logtail::Config.instance.debug { "Starting HTTP connection" }
|
293
|
+
|
294
|
+
http.start do |conn|
|
295
|
+
deliver_requests(conn)
|
296
|
+
end
|
297
|
+
rescue => e
|
298
|
+
Logtail::Config.instance.debug { "#request_outlet error: #{e.message}" }
|
299
|
+
ensure
|
300
|
+
Logtail::Config.instance.debug { "Finishing HTTP connection" }
|
301
|
+
http.finish if http.started?
|
302
|
+
end
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
# Creates a loop that delivers requests over an open (kept alive) HTTP connection.
|
307
|
+
# If the connection dies, the request is thrown back onto the queue and
|
308
|
+
# the method returns. It is the responsibility of the caller to implement retries
|
309
|
+
# and establish a new connection.
|
310
|
+
def deliver_requests(conn)
|
311
|
+
num_reqs = 0
|
312
|
+
|
313
|
+
while num_reqs < @requests_per_conn
|
314
|
+
if @request_queue.size > 0
|
315
|
+
Logtail::Config.instance.debug { "Waiting on next request, threads waiting: #{@request_queue.size}" }
|
316
|
+
end
|
317
|
+
|
318
|
+
request_attempt = @request_queue.deq
|
319
|
+
|
320
|
+
if request_attempt.nil?
|
321
|
+
sleep(1)
|
322
|
+
else
|
323
|
+
request_attempt.attempted!
|
324
|
+
@requests_in_flight += 1
|
325
|
+
|
326
|
+
begin
|
327
|
+
resp = conn.request(request_attempt.request)
|
328
|
+
rescue => e
|
329
|
+
Logtail::Config.instance.debug { "#deliver_requests error: #{e.message}" }
|
330
|
+
|
331
|
+
# Throw the request back on the queue for a retry if it has been attempted less
|
332
|
+
# than 3 times
|
333
|
+
if request_attempt.attempts < 3
|
334
|
+
Logtail::Config.instance.debug { "Request is being retried, #{request_attempt.attempts} previous attempts" }
|
335
|
+
@request_queue.enq(request_attempt)
|
336
|
+
else
|
337
|
+
Logtail::Config.instance.debug { "Request is being dropped, #{request_attempt.attempts} previous attempts" }
|
338
|
+
end
|
339
|
+
|
340
|
+
return false
|
341
|
+
ensure
|
342
|
+
@requests_in_flight -= 1
|
343
|
+
end
|
344
|
+
|
345
|
+
num_reqs += 1
|
346
|
+
|
347
|
+
@last_resp = resp
|
348
|
+
|
349
|
+
Logtail::Config.instance.debug do
|
350
|
+
if resp.code == "202"
|
351
|
+
"Logs successfully sent! View your logs at https://logtail.com"
|
352
|
+
else
|
353
|
+
"Log delivery failed! status: #{resp.code}, body: #{resp.body}"
|
354
|
+
end
|
355
|
+
end
|
356
|
+
end
|
357
|
+
end
|
358
|
+
|
359
|
+
true
|
360
|
+
end
|
361
|
+
|
362
|
+
# Builds the `Authorization` header value for HTTP delivery to the Logtail API.
|
363
|
+
def authorization_payload
|
364
|
+
"Bearer #{@api_key}"
|
365
|
+
end
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|