timber 1.0.11 → 1.0.12

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: 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.