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.
- checksums.yaml +4 -4
- data/Gemfile +3 -0
- data/lib/logtail/event.rb +1 -1
- data/lib/logtail/events/controller_call.rb +1 -1
- data/lib/logtail/events/error.rb +1 -1
- data/lib/logtail/log_devices/http/request_attempt.rb +3 -2
- data/lib/logtail/log_devices/http.rb +362 -28
- data/lib/logtail/log_entry.rb +19 -7
- data/lib/logtail/logger.rb +32 -7
- data/lib/logtail/util/non_nil_hash_builder.rb +1 -1
- data/lib/logtail/util.rb +8 -0
- data/lib/logtail/version.rb +1 -1
- data/spec/logtail/json_encoding_spec.rb +121 -0
- data/spec/logtail/log_devices/http_exit_spec.rb +142 -0
- data/spec/logtail/log_devices/http_fork_spec.rb +85 -0
- data/spec/logtail/log_devices/http_spec.rb +470 -1
- data/spec/logtail/log_entry_spec.rb +73 -0
- data/spec/logtail/logger_flush_all_spec.rb +82 -0
- data/spec/logtail/logger_spec.rb +77 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/support/processes.rb +91 -0
- metadata +6 -1
data/spec/logtail/logger_spec.rb
CHANGED
|
@@ -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.
|
|
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
|