lib/timber/log_devices/http.rb in timber-2.6.2 vs lib/timber/log_devices/http.rb in timber-3.0.0
- old
+ new
@@ -15,13 +15,15 @@
# in a thread as not to block application execution and efficiently deliver logs for
# multi-threaded environments.
#
# See {#initialize} for options and more details.
class HTTP
- TIMBER_STAGING_URL = "https://logs-staging.timber.io/frames".freeze
- TIMBER_PRODUCTION_URL = "https://logs.timber.io/frames".freeze
- TIMBER_URL = ENV['TIMBER_STAGING'] ? TIMBER_STAGING_URL : TIMBER_PRODUCTION_URL
+ TIMBER_STAGING_HOST = "logs-staging.timber.io".freeze
+ TIMBER_PRODUCTION_HOST = "logs.timber.io".freeze
+ TIMBER_HOST = ENV['TIMBER_STAGING'] ? TIMBER_STAGING_HOST : TIMBER_PRODUCTION_HOST
+ TIMBER_PORT = 443
+ TIMBER_SCHEME = "https".freeze
CONTENT_TYPE = "application/msgpack".freeze
USER_AGENT = "Timber Ruby/#{Timber::VERSION} (HTTP)".freeze
# Instantiates a new HTTP log device that can be passed to {Timber::Logger#initialize}.
#
@@ -56,22 +58,34 @@
# queue object that queues Net::HTTP requests for delivery. By deafult this is a
# `FlushableDroppingSizedQueue` of size `25`. Meaning once the queue fills up to 25
# requests new requests will be dropped. If you'd prefer to apply back pressure,
# ensuring you do not lose log data, pass a standard {SizedQueue}. See examples for
# an example.
- # @option attributes [Symbol] :timber_url The Timber URL to delivery the log lines. The
- # default is set via {TIMBER_URL}.
+ # @option attributes [Symbol] :timber_host The Timber host to delivery the log lines to.
+ # The default is set via {TIMBER_HOST}.
#
# @example Basic usage
# Timber::Logger.new(Timber::LogDevices::HTTP.new("my_timber_api_key"))
#
# @example Apply back pressure instead of dropping messages
# http_log_device = Timber::LogDevices::HTTP.new("my_timber_api_key", request_queue: SizedQueue.new(25))
# Timber::Logger.new(http_log_device)
- def initialize(api_key, options = {})
+ def initialize(api_key, *args)
+ options = {}
+
+ # Timber 3.0 introduced a second argument `source_id` which is required for the new
+ # Timber API keys. For backwards compability we still support the old source specific
+ # API keys that do not require an explicit source ID.
+ if args.last.is_a?(Hash)
+ options = args.pop
+ end
+
@api_key = api_key || raise(ArgumentError.new("The api_key parameter cannot be blank"))
- @timber_url = URI.parse(options[:timber_url] || ENV['TIMBER_URL'] || TIMBER_URL)
+ @source_id = args.first
+ @timber_host = options[:timber_host] || ENV['TIMBER_HOST'] || TIMBER_HOST
+ @timber_port = options[:timber_port] || ENV['TIMBER_PORT'] || TIMBER_PORT
+ @timber_scheme = options[:timber_scheme] || ENV['TIMBER_SCHEME'] || TIMBER_SCHEME
@batch_size = options[:batch_size] || 1_000
@flush_continuously = options[:flush_continuously] != false
@flush_interval = options[:flush_interval] || 2 # 2 seconds
@requests_per_conn = options[:requests_per_conn] || 2_500
@msg_queue = FlushableDroppingSizedQueue.new(@batch_size)
@@ -119,10 +133,62 @@
# Kill the request queue thread. Flushing ensures that no requests are pending.
@request_outlet_thread.kill if @request_outlet_thread
end
+ def deliver_one(msg)
+ http = build_http
+
+ begin
+ resp = http.start do |conn|
+ req = build_request([msg])
+ @requests_in_flight += 1
+ conn.request(req)
+ end
+ return resp
+ rescue => e
+ Timber::Config.instance.debug { "error: #{e.message}" }
+ return e
+ ensure
+ http.finish if http.started?
+ @requests_in_flight -= 1
+ end
+ end
+
+ def verify_delivery!
+ 5.times do |i|
+ sleep(2)
+
+ if @last_resp.nil?
+ print "."
+ elsif @last_resp.code == "202"
+ puts "Log delivery successful! View your logs at https://app.timber.io"
+ else
+ raise <<-MESSAGE
+
+Log delivery failed!
+
+Status: #{@last_resp.code}
+Body: #{@last_resp.body}
+
+You can enable internal Timber debug logging with the following:
+
+Timber::Config.instance.debug_logger = ::Logger.new(STDOUT)
+MESSAGE
+ end
+ end
+
+ raise <<-MESSAGE
+
+Log delivery failed! No request was made.
+
+You can enable internal debug logging with the following:
+
+Timber::Config.instance.debug_logger = ::Logger.new(STDOUT)
+MESSAGE
+ end
+
private
# This is a convenience method to ensure the flush thread are
# started. This is called lazily from {#write} so that we
# only start the threads as needed, but it also ensures
# threads are started after process forking.
@@ -137,19 +203,16 @@
end
end
end
# Builds an HTTP request based on the current messages queued.
- def build_request
- msgs = @msg_queue.flush
- return if msgs.empty?
-
- req = Net::HTTP::Post.new(@timber_url.path)
+ def build_request(msgs)
+ path = @source_id.nil? ? "/frames" : "/sources/#{@source_id}/frames"
+ req = Net::HTTP::Post.new(path)
req['Authorization'] = authorization_payload
req['Content-Type'] = CONTENT_TYPE
req['User-Agent'] = USER_AGENT
-
req.body = msgs.to_msgpack
req
end
# Flushes the message buffer asynchronously. The reason we provide this
@@ -157,11 +220,14 @@
# Timber API. The application limit is multiples of the buffer limit,
# hence the `@request_queue`, allowing us to buffer beyond the Timber API
# imposed limit.
def flush_async
@last_async_flush = Time.now
- req = build_request
+ msgs = @msg_queue.flush
+ return if msgs.empty?
+
+ req = build_request(msgs)
if !req.nil?
Timber::Config.instance.debug { "New request placed on queue" }
request_attempt = RequestAttempt.new(req)
@request_queue.enq(request_attempt)
end
@@ -212,13 +278,13 @@
@last_async_flush.nil? || (Time.now.to_f - @last_async_flush.to_f).abs >= @flush_interval
end
# Builds an `Net::HTTP` object to deliver requests over.
def build_http
- http = Net::HTTP.new(@timber_url.host, @timber_url.port)
+ http = Net::HTTP.new(@timber_host, @timber_port)
http.set_debug_output(Config.instance.debug_logger) if Config.instance.debug_logger
- if @timber_url.scheme == 'https'
+ if @timber_scheme == 'https'
http.use_ssl = true
# Verification on Windows fails despite having a valid certificate.
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
end
http.read_timeout = 30
@@ -285,19 +351,32 @@
ensure
@requests_in_flight -= 1
end
num_reqs += 1
- Timber::Config.instance.debug { "Request successful: #{resp.code}" }
+
+ @last_resp = resp
+
+ Timber::Config.instance.debug do
+ if resp.code == "202"
+ "Logs successfully sent! View your logs at https://app.timber.io"
+ else
+ "Log delivery failed! status: #{resp.code}, body: #{resp.body}"
+ end
+ end
end
end
true
end
# Builds the `Authorization` header value for HTTP delivery to the Timber API.
def authorization_payload
- @authorization_payload ||= "Basic #{Base64.urlsafe_encode64(@api_key).chomp}"
+ @authorization_payload ||= if @source_id.nil?
+ "Basic #{Base64.urlsafe_encode64(@api_key).chomp}"
+ else
+ "Bearer #{@api_key}"
+ end
end
end
end
-end
\ No newline at end of file
+end