timber 1.0.8 → 1.0.9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9e48b303535350b36a68d4af296da5a9ee1ddfe1
4
- data.tar.gz: accd259a2827a8a466e5cb2aadfc93ac8d350cb9
3
+ metadata.gz: 2388bf1db8a727e78be858eb8ed03fd1dec10f95
4
+ data.tar.gz: 392fe3ea6f24f874849cad8ccc015ce989e67fda
5
5
  SHA512:
6
- metadata.gz: 2f4bf4cf69e07973acc22b59892bcb0b33c76af23de8023fa6fde65807752d7f2d5739ecbed720e0c344bf2c14eda2b994772ec0450fc36c40922d9681e65ea1
7
- data.tar.gz: 5917a43860fe36328e1a5246596eca5124ce4a4ca589f41a7b1a3da529484c5b04fab5760f96e98d74f64e90a75b782b7188f2057aa6d2423705a9f03f151ed4
6
+ metadata.gz: 856d7ebf1f309b905899dd8136a9af9ce60cab4d4c88c6d1b02009cc6b0677b51932366f22d4abdd4fd4f662e90f44021f8a9240f10a7c8a6886eddaf8ee417b
7
+ data.tar.gz: 24384bcea4caa8d460e1b4eab07047e2c6c94a181110d8b16c47eb1f672abdde6f8158e36a3e7bcf121410e1532e63083b810834d3ec7641c113d608ce1b215c
data/README.md CHANGED
@@ -10,8 +10,12 @@
10
10
  [![View docs](https://img.shields.io/badge/docs-viewdocs-blue.svg?style=flat-square "Viewdocs")](http://www.rubydoc.info/github/timberio/timber-ruby)
11
11
 
12
12
 
13
+ **Timber is in beta testing. If interested, please email beta@timber.io**
14
+
15
+
13
16
  1. [What is timber?](#what-is-timber)
14
- 2. [Why timber?](#why-timber)
17
+ 2. [How it works](#how-it-works)
18
+ 3. [Why timber?](#why-timber)
15
19
  4. [Logging Custom Events](#logging-custom-events)
16
20
  5. [The Timber Console / Pricing](#the-timber-console--pricing)
17
21
  6. [Install](#install)
@@ -19,11 +23,22 @@
19
23
 
20
24
  ## What is Timber?
21
25
 
22
- [Timber](http://timber.io) is a different kind of logging platform; it goes beyond traditional
23
- logging by automatically enriching your logs with application level metadata, turning them
24
- into rich, structured events without altering the essence of logging.
26
+ Using your logs shouldn't be a time consuming, frustrating process! If you were like us, it goes
27
+ something like this:
28
+
29
+ > How do I access my logs? Which provider should I use? Why is it so expensive?
30
+ > Why is searching so difficult and time consuming? Should I structure my logs? Which format
31
+ > should I use? Will they still be human readable? What if the structure changes? What about logs
32
+ > from 3rd party libraries? Ahhh!!!
33
+
34
+ Timber solves this by providing a complete, managed, end-to-end logging solution that marries
35
+ a beautiful, *fast*, console with libraries that automatically structure and enrich your logs.
25
36
 
26
- For example, it turns this:
37
+
38
+ ## How it works
39
+
40
+ The Timber ruby library takes care of all of the log structuring madness. For example,
41
+ it turns this Rails log line:
27
42
 
28
43
  ```
29
44
  Completed 200 OK in 117ms (Views: 85.2ms | ActiveRecord: 25.3ms)
@@ -58,8 +73,10 @@ Into this:
58
73
  }
59
74
  ```
60
75
 
61
- It does the same for `http requests`, `sql queries`, `exceptions`, `template renderings`,
62
- and any other event your framework logs.
76
+ Notice we preserve the original log message. When viewing this event in the
77
+ [Timber Console](https://timber.io), you'll see the simple, human readable line with the
78
+ ability to view, and use, the attached structured data! Also, notice how rich the event is.
79
+ Beecause we're inside your application, we can capture data beyond what's in the log line.
63
80
 
64
81
  (for a full list see [`Timber::Events`](lib/timber/events))
65
82
 
@@ -68,13 +85,14 @@ and any other event your framework logs.
68
85
 
69
86
  Glad you asked! :)
70
87
 
71
- 1. It's application aware and enriches your logs with data you can't get otherwise.
72
- 2. It defines a shared schema across all of our libraries. Meaning your log data, across all
73
- applications, is normalized.
74
- 3. It does not alter the original log message, giving you the best of both worlds: human
75
- readable logs *and* rich structured events.
76
- 4. It's completely transparent with absolutely no vendor lock-in or risk of code debt. It
77
- does not introduce a special API, it's just good ol' loggin'.
88
+ 1. Human readable logs *and* rich structured data. You don't have to choose.
89
+ 2. Data beyond what's in the log line itself making your logs exceptionally useful.
90
+ 3. Normalized log data. Timber enforces a strict schema, meaning your log data, across all apps
91
+ and languages will be normalized.
92
+ 4. Absolutely no lock-in or risk of code debt. No fancy API, no proprietary data format locked
93
+ away in our servers, Timber is just good ol' loggin'.
94
+ 5. Fully managed, all the way from the log messages to the console you use. No fragile parsing
95
+ rules or complicated interfaces.
78
96
 
79
97
 
80
98
  ## Logging Custom Events
@@ -89,7 +107,7 @@ Logger.warn "Payment rejected for customer abcd1234, reason: Card expired"
89
107
 
90
108
  # Structured hash
91
109
  Logger.warn message: "Payment rejected", type: :payment_rejected,
92
- data: %{customer_id: "abcd1234", amount: 100, reason: "Card expired"}
110
+ data: {customer_id: "abcd1234", amount: 100, reason: "Card expired"}
93
111
 
94
112
  # Using a Struct
95
113
  PaymentRejectedEvent = Struct.new(:customer_id, :amount, :reason) do
@@ -108,8 +126,8 @@ No mention of Timber anywhere!
108
126
 
109
127
  > What good is structured log data if you can't search and visualize it?
110
128
 
111
- Enter [the Timber Console](https://timber.io). It's a modern, fast, and beautiful console for
112
- searching and visualizing your logs.
129
+ The [Timber Console](https://timber.io) is *fast*, modern, and beautiful console designed
130
+ specifically for this library.
113
131
 
114
132
  A few example queries:
115
133
 
@@ -120,7 +138,7 @@ A few example queries:
120
138
 
121
139
  > This is all gravy, but wouldn't the extra data get expensive?
122
140
 
123
- If you opt use the [Timber Console](https://timber.io), we only charge for
141
+ If you opt to use the [Timber Console](https://timber.io), we only charge for
124
142
  the size of the `message`, `dt`, and `event.custom` attributes. Everything else is
125
143
  stored at no cost to you. [Say wha?!](http://i.giphy.com/l0HlL2vlfpWI0meJi.gif). This ensures
126
144
  pricing remains predictable. We charge per GB sent to us and retained. No user limits,
@@ -131,6 +149,8 @@ For more details checkout out [timber.io](https://timber.io).
131
149
 
132
150
  ## Install
133
151
 
152
+ **Timber is in beta testing. If interested, please email beta@timber.io**
153
+
134
154
  ### 1. Install the gem:
135
155
 
136
156
  ```ruby
@@ -1,130 +1,229 @@
1
- require "timber/log_devices/http/triggered_buffer"
2
-
3
1
  module Timber
4
2
  module LogDevices
5
- # A log device that buffers and delivers log messages over HTTPS to the Timber API in batches.
6
- # The buffer and delivery strategy are very efficient and the log messages will be delivered in
7
- # msgpack format.
3
+ # A log device that buffers and delivers log messages over HTTPS to the Timber API.
4
+ # It uses batches, keep-alive connections, and messagepack to delivery logs with
5
+ # high-throughput and little overhead.
8
6
  #
9
7
  # See {#initialize} for options and more details.
10
8
  class HTTP
11
- API_URI = URI.parse(ENV["TIMBER_INGESTION_URL"] || "https://logs.timber.io/frames")
12
- CONTENT_TYPE = "application/x-timber-msgpack-frame-1".freeze
13
- CONNECTION_HEADER = "keep-alive".freeze
14
- USER_AGENT = "Timber Ruby Gem/#{Timber::VERSION}".freeze
15
- HTTPS = Net::HTTP.new(API_URI.host, API_URI.port).tap do |https|
16
- https.use_ssl = true
17
- https.read_timeout = 30
18
- https.ssl_timeout = 10
19
- # Ruby 1.9.X doesn't have this setting.
20
- if https.respond_to?(:keep_alive_timeout=)
21
- https.keep_alive_timeout = 60
9
+ # @private
10
+ class LogMsgQueue
11
+ MAX_MSG_BYTES = 50_000 # 50kb
12
+
13
+ def initialize(max_bytes)
14
+ @lock = Mutex.new
15
+ @max_bytes = max_bytes
16
+ @array = []
17
+ @bytesize = 0
18
+ end
19
+
20
+ def enqueue(msg)
21
+ if msg.bytesize > MAX_MSG_BYTES
22
+ raise ArgumentError.new("Log message exceeds the #{MAX_MSG_BYTES} bytes limit")
23
+ end
24
+
25
+ @lock.synchronize do
26
+ @array << msg
27
+ @bytesize += msg.bytesize
28
+ end
29
+ end
30
+
31
+ def flush
32
+ @lock.synchronize do
33
+ old = @array
34
+ @array = []
35
+ @bytesize = 0
36
+ return old
37
+ end
38
+ end
39
+
40
+ def full?
41
+ @lock.synchronize do
42
+ @bytesize >= @max_bytes
43
+ end
44
+ end
45
+
46
+ def size
47
+ @array.size
48
+ end
49
+ end
50
+
51
+ # Works like SizedQueue, but drops message instead of blocking. Pass one of these in
52
+ # to {HTTP#intiialize} via the :request_queue option if you'd prefer to drop messages
53
+ # in the event of a buffer overflow instead of applying back pressure.
54
+ class DroppingSizedQueue < SizedQueue
55
+ # Returns true/false depending on whether the queue is full or not
56
+ def push(obj)
57
+ @mutex.synchronize do
58
+ return false unless @que.length < @max
59
+
60
+ @que.push obj
61
+ begin
62
+ t = @waiting.shift
63
+ t.wakeup if t
64
+ rescue ThreadError
65
+ retry
66
+ end
67
+ return true
68
+ end
22
69
  end
23
- https.open_timeout = 10
24
70
  end
71
+
72
+ TIMBER_URL = "https://logs.timber.io/frames".freeze
73
+ CONTENT_TYPE = "application/x-timber-msgpack-frame-1".freeze
74
+ USER_AGENT = "Timber Ruby Gem/#{Timber::VERSION}".freeze
25
75
  DELIVERY_FREQUENCY_SECONDS = 2.freeze
26
- RETRY_LIMIT = 3.freeze
76
+ RETRY_LIMIT = 5.freeze
27
77
  BACKOFF_RATE_SECONDS = 3.freeze
28
78
 
29
79
 
30
80
  # Instantiates a new HTTP log device that can be passed to {Timber::Logger#initialize}.
31
81
  #
82
+ # The class maintains a buffer which is flushed in batches to the Timber API. 2
83
+ # options control when the flush happens, `:batch_byte_size` and `:flush_interval`.
84
+ # If either of these are surpassed, the buffer will be flushed.
85
+ #
86
+ # By default, the buffer will apply back pressure log messages are generated faster than
87
+ # the client can delivery them. But you can drop messages instead by passing a
88
+ # {DroppingSizedQueue} via the `:request_queue` option.
89
+ #
32
90
  # @param api_key [String] The API key provided to you after you add your application to
33
91
  # [Timber](https://timber.io).
34
92
  # @param [Hash] options the options to create a HTTP log device with.
35
- # @option attributes [Symbol] :payload_limit_bytes Determines the maximum size in bytes that
36
- # and HTTP payload can be. Please see {TriggereBuffer#initialize} for the default.
37
- # @option attributes [Symbol] :buffer_limit_bytes Determines the maximum size of the total
38
- # buffer. This should be many times larger than the `:payload_limit_bytes`.
39
- # Please see {TriggereBuffer#initialize} for the default.
40
- # @option attributes [Symbol] :buffer_overflow_handler (nil) When a single message exceeds
41
- # `:payload_limit_bytes` or the entire buffer exceeds `:buffer_limit_bytes`, the Proc
42
- # passed to this option will be called with the msg that would overflow the buffer. See
43
- # the examples on how to use this properly.
44
- # @option attributes [Symbol] :delivery_frequency_seconds (2) How often the client should
45
- # attempt to deliver logs to the Timber API. The HTTP client buffers logs between calls.
93
+ # @option attributes [Symbol] :batch_byte_size Determines the maximum size in bytes for
94
+ # each HTTP payload. If the buffer exceeds this limit a delivery will be attempted.
95
+ # @option attributes [Symbol] :debug Whether to print debug output or not. This is also
96
+ # inferred from ENV['debug']. Output will be sent to `Timber::Config.logger`.
97
+ # @option attributes [Symbol] :flush_interval (2) How often the client should
98
+ # attempt to deliver logs to the Timber API. The HTTP client buffers logs and this
99
+ # options represents how often that will happen, assuming `:batch_byte_size` is not met.
100
+ # @option attributes [Symbol] :requests_per_conn The number of requests to send over a
101
+ # single persistent connection. After this number is met, the connection will be closed
102
+ # and a new one will be opened.
103
+ # @option attributes [Symbol] :request_queue The request queue object that queues Net::HTTP
104
+ # requests for delivery. By deafult this is a `SizedQueue` of size `3`. Meaning once
105
+ # 3 requests are placed on the queue for delivery, back pressure will be applied. IF
106
+ # you'd prefer to drop messages instead, pass a {DroppingSizedQueue}. See examples for
107
+ # an example.
108
+ # @option attributes [Symbol] :timber_url The Timber URL to delivery the log lines. The
109
+ # default is set via {TIMBER_URL}.
46
110
  #
47
111
  # @example Basic usage
48
112
  # Timber::Logger.new(Timber::LogDevices::HTTP.new("my_timber_api_key"))
49
113
  #
50
- # @example Handling buffer overflows
51
- # # Persist overflowed lines to a file
52
- # # Note: You could write these to any permanent storage.
53
- # overflow_log_path = "/path/to/my/overflow_log.log"
54
- # overflow_handler = Proc.new { |log_line_msg| File.write(overflow_log_path, log_line_ms) }
114
+ # @example Dropping messages instead of applying back pressure
55
115
  # http_log_device = Timber::LogDevices::HTTP.new("my_timber_api_key",
56
- # buffer_overflow_handler: overflow_handler)
116
+ # request_queue: Timber::LogDevices::HTTP::DroppingSizedQueue.new(3))
57
117
  # Timber::Logger.new(http_log_device)
58
118
  def initialize(api_key, options = {})
59
119
  @api_key = api_key
60
- @buffer = TriggeredBuffer.new(
61
- payload_limit_bytes: options[:payload_limit_bytes],
62
- limit_bytes: options[:buffer_limit_bytes],
63
- overflow_handler: options[:buffer_overflow_handler]
64
- )
65
- @delivery_interval_thread = Thread.new do
66
- loop do
67
- sleep(options[:delivery_frequency_seconds] || DELIVERY_FREQUENCY_SECONDS)
68
-
69
- @last_messages_overflow_count = 0
70
- messages_overflown_count = @buffer.messages_overflown_count
71
- if messages_overflown_count >= @last_messages_overflow_count
72
- difference = messages_overflown_count - @last_messages_overflow_count
73
- @last_messages_overflow_count = messages_overflown_count
74
- logger.warn("Timber HTTP buffer has overflown #{difference} times")
75
- end
120
+ @debug = options[:debug] || ENV['debug']
121
+ @timber_url = URI.parse(options[:timber_url] || ENV['TIMBER_URL'] || TIMBER_URL)
122
+ @batch_byte_size = options[:batch_byte_size] || 3_000_000 # 3mb
123
+ @flush_interval = options[:flush_interval] || 2 # 2 seconds
124
+ @requests_per_conn = options[:requests_per_conn] || 1_000
125
+ @msg_queue = LogMsgQueue.new(@batch_byte_size)
126
+ @request_queue = options[:request_queue] || SizedQueue.new(3)
127
+ @req_in_flight = 0
76
128
 
77
- buffer_for_delivery = @buffer.reserve
78
- if buffer_for_delivery
79
- deliver(buffer_for_delivery)
80
- end
81
- end
129
+ if options[:threads] != false
130
+ @outlet_thread = Thread.new { outlet }
131
+ @flush_thread = Thread.new { intervaled_flush }
82
132
  end
83
133
  end
84
134
 
85
135
  # Write a new log line message to the buffer, and deliver if the msg exceeds the
86
136
  # payload limit.
87
137
  def write(msg)
88
- buffer_for_delivery = @buffer.write(msg)
89
- if buffer_for_delivery
90
- deliver(buffer_for_delivery)
138
+ @msg_queue.enqueue(msg)
139
+ if @msg_queue.full?
140
+ flush
91
141
  end
92
142
  true
93
143
  end
94
144
 
95
145
  # Closes the log device, cleans up, and attempts one last delivery.
96
146
  def close
97
- @delivery_interval_thread.kill
98
- buffer_for_delivery = @buffer.reserve
99
- if buffer_for_delivery
100
- deliver(buffer_for_delivery)
101
- end
147
+ @flush_thread.kill if @flush_thread
148
+ @outlet_thread.kill if @outlet_thread
149
+ flush
102
150
  end
103
151
 
104
152
  private
105
- def deliver(body)
106
- Thread.new do
107
- RETRY_LIMIT.times do |try_index|
108
- request = Net::HTTP::Post.new(API_URI.request_uri).tap do |req|
109
- req['Authorization'] = authorization_payload
110
- req['Connection'] = CONNECTION_HEADER
111
- req['Content-Type'] = CONTENT_TYPE
112
- req['User-Agent'] = USER_AGENT
113
- req.body = body
153
+ def debug?
154
+ !@debug.nil?
155
+ end
156
+
157
+ def flush
158
+ msgs = @msg_queue.flush
159
+ return if msgs.empty?
160
+
161
+ body = ""
162
+ msgs.each do |msg|
163
+ body << msg
164
+ end
165
+
166
+ req = Net::HTTP::Post.new(@timber_url.path)
167
+ req['Authorization'] = authorization_payload
168
+ req['Content-Type'] = CONTENT_TYPE
169
+ req['User-Agent'] = USER_AGENT
170
+ req.body = body
171
+ @request_queue.enq(req)
172
+ @last_flush = Time.now
173
+ end
174
+
175
+ def intervaled_flush
176
+ # Wait specified time period before starting
177
+ sleep @flush_interval
178
+ loop do
179
+ begin
180
+ if intervaled_flush_ready?
181
+ flush
114
182
  end
183
+ sleep(0.1)
184
+ rescue Exception => e
185
+ logger.error("Timber intervaled flush failed: #{e.inspect}")
186
+ end
187
+ end
188
+ end
189
+
190
+ def intervaled_flush_ready?
191
+ @last_flush.nil? || (Time.now.to_f - @last_flush.to_f).abs >= @flush_interval
192
+ end
193
+
194
+ def outlet
195
+ loop do
196
+ http = Net::HTTP.new(@timber_url.host, @timber_url.port)
197
+ http.set_debug_output(logger) if debug?
198
+ http.use_ssl = true if @timber_url.scheme == 'https'
199
+ http.read_timeout = 30
200
+ http.ssl_timeout = 10
201
+ http.open_timeout = 10
115
202
 
116
- res = HTTPS.request(request)
117
- code = res.code.to_i
118
- if code < 200 || code >= 300
119
- try = try_index + 1
120
- logger.debug("Timber HTTP delivery failed, try #{try} - #{res.code}: #{res.body}")
121
- sleep(try * BACKOFF_RATE_SECONDS)
122
- else
123
- @buffer.remove(body)
124
- logger.debug("Timber HTTP delivery successful - #{code}")
125
- logger.debug("Timber new buffer size - #{@buffer.total_bytesize}")
126
- break # exit the loop
203
+ begin
204
+ http.start do |conn|
205
+ num_reqs = 0
206
+ while num_reqs < @requests_per_conn
207
+ #Blocks waiting for a request.
208
+ req = @request_queue.deq
209
+ @req_in_flight += 1
210
+ resp = nil
211
+ begin
212
+ resp = conn.request(req)
213
+ rescue => e
214
+ logger.error("Timber request error: #{e.message}") if debug?
215
+ next
216
+ ensure
217
+ @req_in_flight -= 1
218
+ end
219
+ num_reqs += 1
220
+ logger.info("Timber request successful: #{resp.code}") if debug?
221
+ end
127
222
  end
223
+ rescue => e
224
+ logger.error("Timber request error: #{e.message}") if debug?
225
+ ensure
226
+ http.finish if http.started?
128
227
  end
129
228
  end
130
229
  end
@@ -1,3 +1,3 @@
1
1
  module Timber
2
- VERSION = "1.0.8"
2
+ VERSION = "1.0.9"
3
3
  end
@@ -2,33 +2,31 @@ require "spec_helper"
2
2
 
3
3
  describe Timber::LogDevices::HTTP do
4
4
  describe "#initialize" do
5
- it "should start a thread for delivery" do
6
- expect_any_instance_of(described_class).to receive(:deliver).at_least(1).times.and_return(true)
7
- http = described_class.new("MYKEY", delivery_frequency_seconds: 0.1)
8
- thread = http.instance_variable_get(:@delivery_interval_thread)
5
+ it "should initialize properly" do
6
+ http = described_class.new("MYKEY", flush_interval: 0.1)
7
+ thread = http.instance_variable_get(:@flush_thread)
8
+ expect(thread).to be_alive
9
+ thread = http.instance_variable_get(:@flush_thread)
9
10
  expect(thread).to be_alive
10
-
11
- http.write("my log message")
12
- sleep 0.3 # too fast!
13
11
  end
14
12
  end
15
13
 
16
14
  describe "#write" do
17
15
  let(:http) { described_class.new("MYKEY") }
18
- let(:buffer) { http.instance_variable_get(:@buffer) }
16
+ let(:msg_queue) { http.instance_variable_get(:@msg_queue) }
19
17
 
20
18
  it "should buffer the messages" do
21
19
  http.write("test log message")
22
- expect(buffer.reserve).to eq("test log message")
20
+ expect(msg_queue.flush).to eq(["test log message"])
23
21
  end
24
22
 
25
- context "with a low payload limit" do
26
- let(:http) { described_class.new("MYKEY", :payload_limit_bytes => 20) }
23
+ context "with a low batch byte size" do
24
+ let(:http) { described_class.new("MYKEY", :batch_byte_size => 20) }
27
25
 
28
26
  it "should attempt a delivery when the payload limit is exceeded" do
29
27
  message = "a" * 19
30
28
  http.write(message)
31
- expect(http).to receive(:deliver).exactly(1).times.with(message)
29
+ expect(http).to receive(:flush).exactly(1).times
32
30
  http.write("my log message")
33
31
  end
34
32
  end
@@ -37,9 +35,12 @@ describe Timber::LogDevices::HTTP do
37
35
  describe "#close" do
38
36
  let(:http) { described_class.new("MYKEY") }
39
37
 
40
- it "should kill the delivery thread the messages" do
38
+ it "should kill the threads" do
41
39
  http.close
42
- thread = http.instance_variable_get(:@delivery_interval_thread)
40
+ thread = http.instance_variable_get(:@flush_thread)
41
+ sleep 0.1 # too fast!
42
+ expect(thread).to_not be_alive
43
+ thread = http.instance_variable_get(:@outlet_thread)
43
44
  sleep 0.1 # too fast!
44
45
  expect(thread).to_not be_alive
45
46
  end
@@ -47,39 +48,57 @@ describe Timber::LogDevices::HTTP do
47
48
  it "should attempt a delivery" do
48
49
  message = "a" * 19
49
50
  http.write(message)
50
- expect(http).to receive(:deliver).exactly(1).times.with(message)
51
+ expect(http).to receive(:flush).exactly(1).times
51
52
  http.close
52
53
  end
53
54
  end
54
55
 
55
- describe "#deliver" do
56
- let(:http) { described_class.new("MYKEY") }
56
+ # Testing a private method because it helps break down our tests
57
+ describe "#flush" do
58
+ it "should add a request to the queue" do
59
+ http = described_class.new("MYKEY", threads: false)
60
+ http.write("This is a log message")
61
+ http.send(:flush)
62
+ request_queue = http.instance_variable_get(:@request_queue)
63
+ request = request_queue.deq
64
+ expect(request).to be_kind_of(Net::HTTP::Post)
65
+ expect(request.body).to eq("This is a log message")
57
66
 
58
- after(:each) { http.close }
67
+ message_queue = http.instance_variable_get(:@msg_queue)
68
+ expect(message_queue.size).to eq(0)
69
+ end
70
+ end
71
+
72
+ # Testing a private method because it helps break down our tests
73
+ describe "#intervaled_flush" do
74
+ it "should start a intervaled flush thread and flush on an interval" do
75
+ http = described_class.new("MYKEY", flush_interval: 0.1)
76
+ expect(http).to receive(:flush).exactly(1).times
77
+ sleep 0.15 # too fast!
78
+ mock = expect(http).to receive(:flush).exactly(1).times
79
+ sleep 0.15 # too fast!
80
+ end
81
+ end
59
82
 
60
- it "should delivery properly and flush the buffer" do
83
+ # Outlet
84
+ describe "#outlet" do
85
+ it "should start a intervaled flush thread and flush on an interval" do
61
86
  stub = stub_request(:post, "https://logs.timber.io/frames").
62
87
  with(
63
- :body => "test log message",
88
+ :body => 'test log message',
64
89
  :headers => {
65
90
  'Authorization' => 'Basic TVlLRVk=',
66
- 'Connection' => 'keep-alive',
67
91
  'Content-Type' => 'application/x-timber-msgpack-frame-1',
68
92
  'User-Agent' => "Timber Ruby Gem/#{Timber::VERSION}"
69
93
  }
70
94
  ).
71
95
  to_return(:status => 200, :body => "", :headers => {})
72
96
 
97
+ http = described_class.new("MYKEY", flush_interval: 0.1)
73
98
  http.write("test log message")
74
- buffer = http.instance_variable_get(:@buffer)
75
- buffers = buffer.instance_variable_get(:@buffers)
76
- expect(buffers.size).to eq(1)
77
- body = buffer.reserve
78
- thread = http.send(:deliver, body)
79
- thread.join
99
+ sleep 0.2
80
100
 
81
101
  expect(stub).to have_been_requested.times(1)
82
- expect(buffers.size).to eq(0)
83
102
  end
84
103
  end
85
104
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timber
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.8
4
+ version: 1.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Timber Technologies, Inc.
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-12-16 00:00:00.000000000 Z
11
+ date: 2016-12-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: msgpack
@@ -62,7 +62,6 @@ files:
62
62
  - lib/timber/frameworks/rails.rb
63
63
  - lib/timber/log_devices.rb
64
64
  - lib/timber/log_devices/http.rb
65
- - lib/timber/log_devices/http/triggered_buffer.rb
66
65
  - lib/timber/log_entry.rb
67
66
  - lib/timber/logger.rb
68
67
  - lib/timber/probe.rb
@@ -95,7 +94,6 @@ files:
95
94
  - spec/support/timecop.rb
96
95
  - spec/support/webmock.rb
97
96
  - spec/timber/events_spec.rb
98
- - spec/timber/log_devices/http/triggered_buffer_spec.rb
99
97
  - spec/timber/log_devices/http_spec.rb
100
98
  - spec/timber/logger_spec.rb
101
99
  - spec/timber/probes/action_controller_log_subscriber_spec.rb
@@ -143,7 +141,6 @@ test_files:
143
141
  - spec/support/timecop.rb
144
142
  - spec/support/webmock.rb
145
143
  - spec/timber/events_spec.rb
146
- - spec/timber/log_devices/http/triggered_buffer_spec.rb
147
144
  - spec/timber/log_devices/http_spec.rb
148
145
  - spec/timber/logger_spec.rb
149
146
  - spec/timber/probes/action_controller_log_subscriber_spec.rb
@@ -1,87 +0,0 @@
1
- require "monitor"
2
-
3
- module Timber
4
- module LogDevices
5
- class HTTP
6
- # Maintains a triggered buffer, where the trigger is {PAYLOAD_LIMIT_BYTES}. Once the buffer
7
- # exceeds this limit it will lock and return that buffer up to that point while still making
8
- # a new buffer available for writes. This ensures that the HTTP client can attempt to deliver
9
- # the buffer contents without blocking execution of the application.
10
- #
11
- # If the overall buffer exceeeds the overall limit (specified by the `:limit_bytes` option),
12
- # then a buffer overflow is triggered. This can be customized using the `:overflow_handler`
13
- # option.
14
- class TriggeredBuffer
15
- DEFAULT_PAYLOAD_LIMIT_BYTES = 5_000_000 # 5mb, the Timber API will not accept messages larger than this
16
- DEFAULT_LIMIT_BYTES = 50_000_000 # 50mb
17
-
18
- attr_reader :messages_overflown_count
19
-
20
- def initialize(options = {})
21
- @buffers = []
22
- @monitor = Monitor.new
23
- @payload_limit_bytes = options[:payload_limit_bytes] || DEFAULT_PAYLOAD_LIMIT_BYTES
24
- @limit_bytes = options[:limit_bytes] || DEFAULT_LIMIT_BYTES
25
- @overflow_handler = options[:overflow_handler]
26
- @messages_overflown_count = 0
27
- end
28
-
29
- def write(msg)
30
- if msg.bytesize > @payload_limit_bytes || (msg.bytesize + total_bytesize) > @limit_bytes
31
- handle_overflow(msg)
32
- return nil
33
- end
34
-
35
- @monitor.synchronize do
36
- buffer = writable_buffer
37
- if @buffers == [] || buffer.nil? || buffer.frozen?
38
- @buffers << msg
39
- nil
40
- elsif (buffer.bytesize + msg.bytesize) > @payload_limit_bytes
41
- @buffers << msg
42
- buffer.freeze
43
- else
44
- buffer << msg
45
- nil
46
- end
47
- end
48
- end
49
-
50
- def reserve
51
- @monitor.synchronize do
52
- buffer = writable_buffer
53
- if buffer
54
- buffer.freeze
55
- end
56
- end
57
- end
58
-
59
- def remove(buffer)
60
- @monitor.synchronize do
61
- @buffers.delete(buffer)
62
- end
63
- end
64
-
65
- def total_bytesize
66
- @buffers.reduce(0) { |acc, buffer| acc + buffer.bytesize }
67
- end
68
-
69
- private
70
- def writable_buffer
71
- @buffers.find { |buffer| !buffer.frozen? }
72
- end
73
-
74
- def handle_overflow(msg)
75
- @messages_overflown_count += 1
76
- if @overflow_handler
77
- @overflow_handler.call(msg)
78
- end
79
- end
80
-
81
- def logger
82
- Config.instance.logger
83
- end
84
- end
85
- end
86
- end
87
- end
@@ -1,58 +0,0 @@
1
- require "spec_helper"
2
-
3
- describe Timber::LogDevices::HTTP::TriggeredBuffer do
4
- describe "#write" do
5
- it "should trigger a buffer overflow for large messages" do
6
- buffer = described_class.new(:payload_limit_bytes => 10)
7
- msg = "a" * 11
8
- expect(buffer).to receive(:handle_overflow).exactly(1).times.with(msg)
9
- buffer.write(msg)
10
- end
11
-
12
- it "should trigger a buffer overflow when exceeding the limit" do
13
- buffer = described_class.new(:limit_bytes => 10)
14
- msg = "a" * 11
15
- expect(buffer).to receive(:handle_overflow).exactly(1).times.with(msg)
16
- buffer.write(msg)
17
- end
18
-
19
- it "should start a new buffer when empty and append when not" do
20
- buffer = described_class.new
21
- result = buffer.write("test")
22
- expect(result).to be_nil
23
- expect(buffer.send(:writable_buffer)).to eq("test")
24
- result = buffer.write("again")
25
- expect(result).to be_nil
26
- expect(buffer.send(:writable_buffer)).to eq("testagain")
27
- end
28
-
29
- it "should return the old buffer when it has exceeded it's limit" do
30
- buffer = described_class.new(:payload_limit_bytes => 10)
31
- msg = "a" * 6
32
- result = buffer.write(msg)
33
- expect(result).to be_nil
34
- result = buffer.write(msg)
35
- expect(result).to eq(msg)
36
- expect(result).to be_frozen
37
- end
38
-
39
- it "should write a new buffer when the latest is frozen" do
40
- buffer = described_class.new
41
- buffer.write("test")
42
- result = buffer.reserve
43
- expect(result).to eq("test")
44
- buffer.write("again")
45
- expect(buffer.send(:writable_buffer)).to eq("again")
46
- end
47
- end
48
-
49
- describe "#reserve" do
50
- it "should reserve the latest buffer and freeze it" do
51
- buffer = described_class.new
52
- buffer.write("test")
53
- result = buffer.reserve
54
- expect(result).to eq("test")
55
- expect(result).to be_frozen
56
- end
57
- end
58
- end