logtail 0.1.20 → 0.1.21

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.
@@ -78,6 +78,19 @@ describe Logtail::Logger do
78
78
  expect(io.string).to eq("")
79
79
  end
80
80
 
81
+ # Ruby's Logger formats these severities as "ANY"
82
+ it "should log a nil severity at the unknown level" do
83
+ logger.add(nil, "log message")
84
+ expect(io.string).to include("log message")
85
+ expect(io.string).to include('"level":"unknown"')
86
+ end
87
+
88
+ it "should log a severity above UNKNOWN at the unknown level" do
89
+ logger.add(99, "log message")
90
+ expect(io.string).to include("log message")
91
+ expect(io.string).to include('"level":"unknown"')
92
+ end
93
+
81
94
  it "should not lose message when logging hashes with multiple io devices" do
82
95
  io1 = StringIO.new
83
96
  io2 = StringIO.new
@@ -147,6 +160,20 @@ describe Logtail::Logger do
147
160
  expect(tags).to eq(["tag1", "tag2"])
148
161
  end
149
162
 
163
+ it "should allow a single String in :tags" do
164
+ logger.info("event complete", tags: "tag1")
165
+ expect(io.string).to include("\"tags\":[\"tag1\"]")
166
+ end
167
+
168
+ it "should skip nil tags" do
169
+ logger.info("event complete", tag: nil, tags: nil)
170
+ logger.info("event complete", tags: ["tag1", nil])
171
+ lines = io.string.lines
172
+ expect(lines[0]).to start_with("event complete @metadata")
173
+ expect(lines[0]).not_to include("\"tags\"")
174
+ expect(lines[1]).to include("\"tags\":[\"tag1\"]")
175
+ end
176
+
150
177
  it "should allow functions" do
151
178
  logger.info do
152
179
  {message: "payment rejected", payment_rejected: {customer_id: "abcde1234", amount: 100}}
@@ -179,6 +206,21 @@ describe Logtail::Logger do
179
206
  end
180
207
  end
181
208
 
209
+ describe "#<<" do
210
+ it "logs the line at the info level" do
211
+ http_device = Logtail::LogDevices::HTTP.new("MYKEY", flush_continuously: false)
212
+ logger = Logtail::Logger.new(http_device)
213
+
214
+ logger << "appended line\n"
215
+
216
+ http_device.send(:flush_async)
217
+ request = http_device.instance_variable_get(:@request_queue).deq.request
218
+ entries = MessagePack.unpack(Zlib::Inflate.inflate(request.body))
219
+ expect(entries.size).to eq(1)
220
+ expect(entries[0]).to include("level" => "info", "message" => "appended line")
221
+ end
222
+ end
223
+
182
224
  describe "#error" do
183
225
  let(:io) { StringIO.new }
184
226
  let(:logger) { Logtail::Logger.new(io) }
@@ -197,6 +239,41 @@ describe Logtail::Logger do
197
239
  end
198
240
  end
199
241
 
242
+ describe "#unknown" do
243
+ let(:io) { StringIO.new }
244
+ let(:logger) { Logtail::Logger.new(io) }
245
+
246
+ it "should allow default usage" do
247
+ logger.unknown("log message")
248
+ expect(io.string).to include("log message")
249
+ expect(io.string).to include('"level":"unknown"')
250
+ end
251
+ end
252
+
253
+ describe "#flush" do
254
+ let(:device) { Logtail::LogDevices::HTTP.new("my_source_token") }
255
+ let(:extra_device) { Logtail::LogDevices::HTTP.new("my_source_token") }
256
+ let(:logger) { Logtail::Logger.new(device, extra_device) }
257
+
258
+ it "flushes the log devices, so that what was logged is delivered" do
259
+ expect(device).to receive(:flush)
260
+ expect(extra_device).to receive(:flush)
261
+ logger.flush
262
+ end
263
+
264
+ it "returns right away when Rails flushes it after every request" do
265
+ # Stands in for ActiveSupport::LogSubscriber.flush_all!, which calls Rails.logger.flush
266
+ log_subscriber = Module.new do
267
+ def self.flush_all!(logger)
268
+ logger.flush
269
+ end
270
+ end
271
+ expect(device).not_to receive(:flush)
272
+ expect(extra_device).not_to receive(:flush)
273
+ log_subscriber.flush_all!(logger)
274
+ end
275
+ end
276
+
200
277
  describe "#formatter=" do
201
278
  it "should not allow changing the formatter when the device is HTTP" do
202
279
  http_device = Logtail::LogDevices::HTTP.new("source_token")
data/spec/spec_helper.rb CHANGED
@@ -12,6 +12,7 @@ require File.join(File.dirname(__FILE__), 'support', 'socket_hostname')
12
12
  require File.join(File.dirname(__FILE__), 'support', 'timecop')
13
13
  require File.join(File.dirname(__FILE__), 'support', 'webmock')
14
14
  require File.join(File.dirname(__FILE__), 'support', 'logtail')
15
+ require File.join(File.dirname(__FILE__), 'support', 'processes')
15
16
 
16
17
  RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = 5_000
17
18
 
@@ -0,0 +1,91 @@
1
+ require "msgpack"
2
+ require "rbconfig"
3
+ require "socket"
4
+ require "tmpdir"
5
+ require "zlib"
6
+
7
+ # A local stand-in for the Better Stack ingesting host, for tests that run logtail in a separate
8
+ # process: it answers every request with 202 and records the message of every line it receives,
9
+ # and how many lines each request held.
10
+ class LocalIngestServer
11
+ def initialize
12
+ @server = TCPServer.new("127.0.0.1", 0)
13
+ @messages = []
14
+ @batch_sizes = []
15
+ @lock = Mutex.new
16
+ @connections = []
17
+ @thread = Thread.new do
18
+ loop { @connections << Thread.new(@server.accept) { |socket| serve(socket) } }
19
+ end
20
+ end
21
+
22
+ # The options for Logtail::LogDevices::HTTP.new that deliver to this server, as Ruby code.
23
+ def device_options
24
+ %(ingesting_host: "127.0.0.1", ingesting_port: #{@server.addr[1]}, ingesting_scheme: "http")
25
+ end
26
+
27
+ def messages
28
+ @lock.synchronize { @messages.dup }
29
+ end
30
+
31
+ def batch_sizes
32
+ @lock.synchronize { @batch_sizes.dup }
33
+ end
34
+
35
+ def stop
36
+ @thread.kill.join
37
+ @connections.each { |connection| connection.kill.join }
38
+ @server.close
39
+ end
40
+
41
+ private
42
+
43
+ def serve(socket)
44
+ while socket.gets
45
+ headers = {}
46
+ while (header = socket.gets) && header != "\r\n"
47
+ name, value = header.split(":", 2)
48
+ headers[name.downcase] = value.strip
49
+ end
50
+ lines = MessagePack.unpack(Zlib::Inflate.inflate(socket.read(headers["content-length"].to_i)))
51
+ @lock.synchronize do
52
+ @messages.concat(lines.map { |line| line["message"] })
53
+ @batch_sizes << lines.size
54
+ end
55
+ socket.write("HTTP/1.1 202 Accepted\r\nContent-Length: 0\r\n\r\n")
56
+ end
57
+ rescue IOError, SystemCallError
58
+ ensure
59
+ socket.close
60
+ end
61
+ end
62
+
63
+ module ProcessHelpers
64
+ ProcessResult = Struct.new(:stdout, :stderr, :status)
65
+
66
+ # Runs the Ruby script in a new process with logtail from lib/ and waits for it to exit, at
67
+ # most `timeout` seconds (generous: TruffleRuby starts slowly on CI).
68
+ def run_ruby(script, timeout: 120)
69
+ Dir.mktmpdir do |dir|
70
+ File.write(File.join(dir, "script.rb"), script)
71
+ lib = File.expand_path("../../lib", __dir__)
72
+ pid = Process.spawn(RbConfig.ruby, "-I", lib, File.join(dir, "script.rb"),
73
+ out: File.join(dir, "stdout"), err: File.join(dir, "stderr"))
74
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
75
+ status = nil
76
+ until (status = Process.waitpid2(pid, Process::WNOHANG))
77
+ if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
78
+ Process.kill("KILL", pid)
79
+ Process.wait(pid)
80
+ raise "The Ruby process didn't exit within #{timeout} seconds:\n#{File.read(File.join(dir, "stderr"))}"
81
+ end
82
+ sleep 0.05
83
+ end
84
+ ProcessResult.new(File.read(File.join(dir, "stdout")), File.read(File.join(dir, "stderr")), status[1])
85
+ end
86
+ end
87
+ end
88
+
89
+ RSpec.configure do |config|
90
+ config.include ProcessHelpers
91
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logtail
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.20
4
+ version: 0.1.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Better Stack
@@ -192,11 +192,16 @@ files:
192
192
  - spec/logtail/current_context_spec.rb
193
193
  - spec/logtail/events/controller_call_spec.rb
194
194
  - spec/logtail/events/error_spec.rb
195
+ - spec/logtail/json_encoding_spec.rb
196
+ - spec/logtail/log_devices/http_exit_spec.rb
197
+ - spec/logtail/log_devices/http_fork_spec.rb
195
198
  - spec/logtail/log_devices/http_spec.rb
196
199
  - spec/logtail/log_entry_spec.rb
200
+ - spec/logtail/logger_flush_all_spec.rb
197
201
  - spec/logtail/logger_spec.rb
198
202
  - spec/spec_helper.rb
199
203
  - spec/support/logtail.rb
204
+ - spec/support/processes.rb
200
205
  - spec/support/socket_hostname.rb
201
206
  - spec/support/timecop.rb
202
207
  - spec/support/webmock.rb