timber 1.0.11 → 1.0.12

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
  SHA1:
3
- metadata.gz: d077443fc6d81d17024ca768f807d246ae550741
4
- data.tar.gz: ad06e2613fa515befaf240c91ed03329f75dd432
3
+ metadata.gz: 66168776f4469f5a0ed9795a376b7f29fefe7a07
4
+ data.tar.gz: 3782864585581e766834c6a4e907b7a91aac4006
5
5
  SHA512:
6
- metadata.gz: 0ee5c69b706a378a785484b71c3553d103d501b2e4ac47be9a71c53a15f9aa230f01afd6c21b7178e49c651a026fe3504687890e403c8e7c537445910f890c5e
7
- data.tar.gz: aeb022f26127fdecde663867d82e1a672a9f211078d0dbd922b1fa4d4bb16f78fbdd4e503e35a9504146a6bcc7cd05a4e6872d2223cf947eef83869fe016b1fb
6
+ metadata.gz: c3439222a545671693a4dcf0a1124627df087e7de306775a2d682c03ca963b598f202959ea0137af793ce91ef5db3634f9d4369c859eb55196a65c0d6f7ba3b5
7
+ data.tar.gz: c8cecdddcc719f6b5f84453ee784e9c5b1a5d2a4b34d6d7c69868c0cf05c8789a3555de2626b6f11d52f88bc900318dc76e77926dce5046dc5f01c47b7cdd457
@@ -8,23 +8,15 @@ module Timber
8
8
  class HTTP
9
9
  # @private
10
10
  class LogMsgQueue
11
- MAX_MSG_BYTES = 50_000 # 50kb
12
-
13
- def initialize(max_bytes)
11
+ def initialize(max_size)
14
12
  @lock = Mutex.new
15
- @max_bytes = max_bytes
13
+ @max_size = max_size
16
14
  @array = []
17
- @bytesize = 0
18
15
  end
19
16
 
20
17
  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
18
  @lock.synchronize do
26
19
  @array << msg
27
- @bytesize += msg.bytesize
28
20
  end
29
21
  end
30
22
 
@@ -32,14 +24,13 @@ module Timber
32
24
  @lock.synchronize do
33
25
  old = @array
34
26
  @array = []
35
- @bytesize = 0
36
27
  return old
37
28
  end
38
29
  end
39
30
 
40
31
  def full?
41
32
  @lock.synchronize do
42
- @bytesize >= @max_bytes
33
+ size >= @max_size
43
34
  end
44
35
  end
45
36
 
@@ -70,7 +61,7 @@ module Timber
70
61
  end
71
62
 
72
63
  TIMBER_URL = "https://logs.timber.io/frames".freeze
73
- CONTENT_TYPE = "application/x-timber-msgpack-frame-1; charset=ascii-8bit".freeze
64
+ CONTENT_TYPE = "application/msgpack".freeze
74
65
  USER_AGENT = "Timber Ruby Gem/#{Timber::VERSION}".freeze
75
66
  DELIVERY_FREQUENCY_SECONDS = 2.freeze
76
67
  RETRY_LIMIT = 5.freeze
@@ -90,17 +81,17 @@ module Timber
90
81
  # @param api_key [String] The API key provided to you after you add your application to
91
82
  # [Timber](https://timber.io).
92
83
  # @param [Hash] options the options to create a HTTP log device with.
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
84
+ # @option attributes [Symbol] :batch_size (500) Determines the maximum of log lines in each HTTP
85
+ # payload. If the queue exceeds this limit a HTTP request will be issued.
86
+ # @option attributes [Symbol] :debug (false) Whether to print debug output or not. This is also
96
87
  # inferred from ENV['debug']. Output will be sent to `Timber::Config.logger`.
97
88
  # @option attributes [Symbol] :flush_interval (2) How often the client should
98
89
  # attempt to deliver logs to the Timber API. The HTTP client buffers logs and this
99
90
  # 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
91
+ # @option attributes [Symbol] :requests_per_conn (1000) The number of requests to send over a
101
92
  # single persistent connection. After this number is met, the connection will be closed
102
93
  # and a new one will be opened.
103
- # @option attributes [Symbol] :request_queue The request queue object that queues Net::HTTP
94
+ # @option attributes [Symbol] :request_queue (SizedQueue.new(3)) The request queue object that queues Net::HTTP
104
95
  # requests for delivery. By deafult this is a `SizedQueue` of size `3`. Meaning once
105
96
  # 3 requests are placed on the queue for delivery, back pressure will be applied. IF
106
97
  # you'd prefer to drop messages instead, pass a {DroppingSizedQueue}. See examples for
@@ -119,12 +110,12 @@ module Timber
119
110
  @api_key = api_key
120
111
  @debug = options[:debug] || ENV['debug']
121
112
  @timber_url = URI.parse(options[:timber_url] || ENV['TIMBER_URL'] || TIMBER_URL)
122
- @batch_byte_size = options[:batch_byte_size] || 3_000_000 # 3mb
113
+ @batch_size = options[:batch_size] || 500
123
114
  @flush_interval = options[:flush_interval] || 2 # 2 seconds
124
115
  @requests_per_conn = options[:requests_per_conn] || 1_000
125
- @msg_queue = LogMsgQueue.new(@batch_byte_size)
116
+ @msg_queue = LogMsgQueue.new(@batch_size)
126
117
  @request_queue = options[:request_queue] || SizedQueue.new(3)
127
- @req_in_flight = 0
118
+ @requests_in_flight = 0
128
119
 
129
120
  if options[:threads] != false
130
121
  @outlet_thread = Thread.new { outlet }
@@ -158,17 +149,12 @@ module Timber
158
149
  msgs = @msg_queue.flush
159
150
  return if msgs.empty?
160
151
 
161
- body = ""
162
- msgs.each do |msg|
163
- body << msg
164
- end
165
-
166
152
  req = Net::HTTP::Post.new(@timber_url.path)
167
153
  req['Accept'] = "application/json"
168
154
  req['Authorization'] = authorization_payload
169
155
  req['Content-Type'] = CONTENT_TYPE
170
156
  req['User-Agent'] = USER_AGENT
171
- req.body = body
157
+ req.body = msgs.to_msgpack
172
158
  @request_queue.enq(req)
173
159
  @last_flush = Time.now
174
160
  end
@@ -205,9 +191,9 @@ module Timber
205
191
  http.start do |conn|
206
192
  num_reqs = 0
207
193
  while num_reqs < @requests_per_conn
208
- #Blocks waiting for a request.
194
+ # Blocks waiting for a request.
209
195
  req = @request_queue.deq
210
- @req_in_flight += 1
196
+ @requests_in_flight += 1
211
197
  resp = nil
212
198
  begin
213
199
  resp = conn.request(req)
@@ -215,7 +201,7 @@ module Timber
215
201
  logger.error("Timber request error: #{e.message}") if debug?
216
202
  next
217
203
  ensure
218
- @req_in_flight -= 1
204
+ @requests_in_flight -= 1
219
205
  end
220
206
  num_reqs += 1
221
207
  logger.info("Timber request successful: #{resp.code}") if debug?
@@ -51,6 +51,10 @@ module Timber
51
51
  as_json(options).to_json
52
52
  end
53
53
 
54
+ def to_msgpack(*args)
55
+ as_json.to_msgpack(*args)
56
+ end
57
+
54
58
  private
55
59
  def formatted_dt
56
60
  @formatted_dt ||= time.iso8601(DT_PRECISION)
@@ -119,31 +119,14 @@ module Timber
119
119
  end
120
120
  end
121
121
 
122
- # Structures your log messages into JSON.
123
- #
124
- # logger = Timber::Logger.new(STDOUT)
125
- # logger.formatter = Timber::JSONFormatter.new
126
- #
127
- # Example message:
128
- #
129
- # {"level":"info","dt":"2016-09-01T07:00:00.000000-05:00","message":"My log message"}
130
- #
131
- class MsgPackFormatter < Formatter
122
+ # Passes through the LogEntry object. This is specifically used for the {Timber::LogDevices::HTTP}
123
+ # class. This allows the IO device to format it however it wants. This is neccessary for
124
+ # MessagePack because it requires a fixed array size before encoding. And since HTTP is
125
+ # sending data in batches, the encoding should happen there.
126
+ class PassThroughFormatter < Formatter
132
127
  def call(severity, time, progname, msg)
133
- # use << for concatenation for performance reasons
134
- hash = build_log_entry(severity, time, progname, msg).as_json
135
- to_msgpack(hash) << "\n"
128
+ build_log_entry(severity, time, progname, msg)
136
129
  end
137
-
138
- private
139
- def to_msgpack(msg)
140
- begin
141
- msg.to_msgpack
142
- rescue NoMethodError
143
- json = JSON.generate(msg)
144
- JSON.parse(json).to_msgpack
145
- end
146
- end
147
130
  end
148
131
 
149
132
  # Creates a new Timber::Logger instances. Accepts the same arguments as `::Logger.new`.
@@ -156,7 +139,7 @@ module Timber
156
139
  def initialize(*args)
157
140
  super(*args)
158
141
  if args.size == 1 and args.first.is_a?(LogDevices::HTTP)
159
- self.formatter = MsgPackFormatter.new
142
+ self.formatter = PassThroughFormatter.new
160
143
  else
161
144
  self.formatter = HybridFormatter.new
162
145
  end
@@ -165,7 +148,7 @@ module Timber
165
148
  def formatter=(value)
166
149
  if @dev.is_a?(Timber::LogDevices::HTTP)
167
150
  raise ArgumentError.new("The formatter cannot be changed when using the " +
168
- "Timber::LogDevices::HTTP log device. The MsgPackFormatter must be used for proper " +
151
+ "Timber::LogDevices::HTTP log device. The PassThroughFormatter must be used for proper " +
169
152
  "delivery.")
170
153
  end
171
154
  super
@@ -1,3 +1,3 @@
1
1
  module Timber
2
- VERSION = "1.0.11"
2
+ VERSION = "1.0.12"
3
3
  end
@@ -20,12 +20,11 @@ describe Timber::LogDevices::HTTP do
20
20
  expect(msg_queue.flush).to eq(["test log message"])
21
21
  end
22
22
 
23
- context "with a low batch byte size" do
24
- let(:http) { described_class.new("MYKEY", :batch_byte_size => 20) }
23
+ context "with a low batch size" do
24
+ let(:http) { described_class.new("MYKEY", :batch_size => 2) }
25
25
 
26
- it "should attempt a delivery when the payload limit is exceeded" do
27
- message = "a" * 19
28
- http.write(message)
26
+ it "should attempt a delivery when the limit is exceeded" do
27
+ http.write("test")
29
28
  expect(http).to receive(:flush).exactly(1).times
30
29
  http.write("my log message")
31
30
  end
@@ -55,18 +54,30 @@ describe Timber::LogDevices::HTTP do
55
54
 
56
55
  # Testing a private method because it helps break down our tests
57
56
  describe "#flush" do
57
+ let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
58
+
58
59
  it "should add a request to the queue" do
59
60
  http = described_class.new("MYKEY", threads: false)
60
- http.write("This is a log message")
61
+ log_entry = Timber::LogEntry.new("INFO", time, nil, "test log message 1", nil, nil)
62
+ http.write(log_entry)
63
+ log_entry = Timber::LogEntry.new("INFO", time, nil, "test log message 2", nil, nil)
64
+ http.write(log_entry)
61
65
  http.send(:flush)
62
66
  request_queue = http.instance_variable_get(:@request_queue)
63
67
  request = request_queue.deq
64
68
  expect(request).to be_kind_of(Net::HTTP::Post)
65
- expect(request.body).to eq("This is a log message")
69
+ expect(request.body).to eq("\x92\x83\xA5level\xA4INFO\xA2dt\xBB2016-09-01T12:00:00.000000Z\xA7message\xB2test log message 1\x83\xA5level\xA4INFO\xA2dt\xBB2016-09-01T12:00:00.000000Z\xA7message\xB2test log message 2".force_encoding("ASCII-8BIT"))
66
70
 
67
71
  message_queue = http.instance_variable_get(:@msg_queue)
68
72
  expect(message_queue.size).to eq(0)
69
73
  end
74
+
75
+ it "should preserve formatting for mshpack payloads" do
76
+ http = described_class.new("MYKEY", threads: false)
77
+ http.write("This is a log message 1".to_msgpack)
78
+ http.write("This is a log message 2".to_msgpack)
79
+ http.send(:flush)
80
+ end
70
81
  end
71
82
 
72
83
  # Testing a private method because it helps break down our tests
@@ -74,30 +85,34 @@ describe Timber::LogDevices::HTTP do
74
85
  it "should start a intervaled flush thread and flush on an interval" do
75
86
  http = described_class.new("MYKEY", flush_interval: 0.1)
76
87
  expect(http).to receive(:flush).exactly(1).times
77
- sleep 0.15 # too fast!
88
+ sleep 0.12 # too fast!
78
89
  mock = expect(http).to receive(:flush).exactly(1).times
79
- sleep 0.15 # too fast!
90
+ sleep 0.12 # too fast!
80
91
  end
81
92
  end
82
93
 
83
94
  # Outlet
84
95
  describe "#outlet" do
96
+ let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
97
+
85
98
  it "should start a intervaled flush thread and flush on an interval" do
86
99
  stub = stub_request(:post, "https://logs.timber.io/frames").
87
100
  with(
88
- :body => "test log message 1test log message 2",
101
+ :body => "\x92\x83\xA5level\xA4INFO\xA2dt\xBB2016-09-01T12:00:00.000000Z\xA7message\xB2test log message 1\x83\xA5level\xA4INFO\xA2dt\xBB2016-09-01T12:00:00.000000Z\xA7message\xB2test log message 2".force_encoding("ASCII-8BIT"),
89
102
  :headers => {
90
103
  'Accept' => 'application/json',
91
104
  'Authorization' => 'Basic TVlLRVk=',
92
- 'Content-Type' => 'application/x-timber-msgpack-frame-1; charset=ascii-8bit',
105
+ 'Content-Type' => 'application/msgpack',
93
106
  'User-Agent' => "Timber Ruby Gem/#{Timber::VERSION}"
94
107
  }
95
108
  ).
96
109
  to_return(:status => 200, :body => "", :headers => {})
97
110
 
98
111
  http = described_class.new("MYKEY", flush_interval: 0.1)
99
- http.write("test log message 1")
100
- http.write("test log message 2")
112
+ log_entry = Timber::LogEntry.new("INFO", time, nil, "test log message 1", nil, nil)
113
+ http.write(log_entry)
114
+ log_entry = Timber::LogEntry.new("INFO", time, nil, "test log message 2", nil, nil)
115
+ http.write(log_entry)
101
116
  sleep 0.3
102
117
 
103
118
  expect(stub).to have_been_requested.times(1)
@@ -93,32 +93,8 @@ describe Timber::Logger, :rails_23 => true do
93
93
  context "with the HTTP log device" do
94
94
  let(:io) { Timber::LogDevices::HTTP.new("my_key") }
95
95
 
96
- it "should use the msgpack formatter" do
97
- expect(logger.formatter).to be_kind_of(Timber::Logger::MsgPackFormatter)
98
- end
99
-
100
- it "should log properly with the msgpack format" do
101
- event = Timber::Events::Custom.new(
102
- type: :payment_rejected,
103
- message: "Payment rejected",
104
- data: {customer_id: "abcd1234", amount: 100}
105
- )
106
- expect(io).to receive(:write).exactly(1).times
107
- logger.error(event)
108
- end
109
- end
110
- end
111
-
112
- describe described_class::MsgPackFormatter do
113
- describe "#call" do
114
- let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
115
-
116
- it "should produce a message pack formatted message" do
117
- formatter = described_class.new
118
- result = formatter.call("INFO", time, nil, "this is a test message")
119
- hash = {"level"=>"info", "dt"=>"2016-09-01T12:00:00.000000Z", "message"=>"this is a test message"}
120
- expected = hash.to_msgpack + "\n"
121
- expect(result).to eq(expected)
96
+ it "should use the PassThroughFormatter" do
97
+ expect(logger.formatter).to be_kind_of(Timber::Logger::PassThroughFormatter)
122
98
  end
123
99
  end
124
100
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timber
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.11
4
+ version: 1.0.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Timber Technologies, Inc.