logtail 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/main.yml +33 -0
  3. data/.gitignore +24 -0
  4. data/.rspec +2 -0
  5. data/CHANGELOG.md +12 -0
  6. data/Gemfile +10 -0
  7. data/LICENSE.md +15 -0
  8. data/README.md +4 -0
  9. data/Rakefile +72 -0
  10. data/lib/logtail.rb +36 -0
  11. data/lib/logtail/config.rb +154 -0
  12. data/lib/logtail/config/integrations.rb +17 -0
  13. data/lib/logtail/context.rb +9 -0
  14. data/lib/logtail/contexts.rb +12 -0
  15. data/lib/logtail/contexts/http.rb +31 -0
  16. data/lib/logtail/contexts/release.rb +52 -0
  17. data/lib/logtail/contexts/runtime.rb +23 -0
  18. data/lib/logtail/contexts/session.rb +24 -0
  19. data/lib/logtail/contexts/system.rb +29 -0
  20. data/lib/logtail/contexts/user.rb +28 -0
  21. data/lib/logtail/current_context.rb +168 -0
  22. data/lib/logtail/event.rb +36 -0
  23. data/lib/logtail/events.rb +10 -0
  24. data/lib/logtail/events/controller_call.rb +44 -0
  25. data/lib/logtail/events/error.rb +40 -0
  26. data/lib/logtail/events/exception.rb +10 -0
  27. data/lib/logtail/events/sql_query.rb +26 -0
  28. data/lib/logtail/events/template_render.rb +25 -0
  29. data/lib/logtail/integration.rb +40 -0
  30. data/lib/logtail/integrator.rb +50 -0
  31. data/lib/logtail/log_devices.rb +8 -0
  32. data/lib/logtail/log_devices/http.rb +368 -0
  33. data/lib/logtail/log_devices/http/flushable_dropping_sized_queue.rb +52 -0
  34. data/lib/logtail/log_devices/http/request_attempt.rb +20 -0
  35. data/lib/logtail/log_entry.rb +110 -0
  36. data/lib/logtail/logger.rb +270 -0
  37. data/lib/logtail/logtail.rb +36 -0
  38. data/lib/logtail/timer.rb +21 -0
  39. data/lib/logtail/util.rb +7 -0
  40. data/lib/logtail/util/non_nil_hash_builder.rb +40 -0
  41. data/lib/logtail/version.rb +3 -0
  42. data/logtail.gemspec +43 -0
  43. data/spec/README.md +13 -0
  44. data/spec/logtail/current_context_spec.rb +113 -0
  45. data/spec/logtail/events/controller_call_spec.rb +12 -0
  46. data/spec/logtail/events/error_spec.rb +15 -0
  47. data/spec/logtail/log_devices/http_spec.rb +185 -0
  48. data/spec/logtail/log_entry_spec.rb +22 -0
  49. data/spec/logtail/logger_spec.rb +227 -0
  50. data/spec/spec_helper.rb +22 -0
  51. data/spec/support/logtail.rb +5 -0
  52. data/spec/support/socket_hostname.rb +12 -0
  53. data/spec/support/timecop.rb +3 -0
  54. data/spec/support/webmock.rb +3 -0
  55. metadata +238 -0
@@ -0,0 +1,22 @@
1
+ require "spec_helper"
2
+
3
+ describe Logtail::LogEntry do
4
+ let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
5
+
6
+ describe "#to_msgpack" do
7
+ it "should encode properly with an event and context" do
8
+ event = {
9
+ message: "event_message",
10
+ event: {
11
+ event_type: {
12
+ a: 1
13
+ }
14
+ }
15
+ }
16
+ context = {custom: {a: "b"}}
17
+ log_entry = described_class.new("INFO", time, nil, "log message", context, event)
18
+ msgpack = log_entry.to_msgpack
19
+ expect(msgpack).to start_with("\x85\xA5level\xA4INFO\xA2dt\xBB2016-09-01T12:00:00.000000Z".force_encoding("ASCII-8BIT"))
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,227 @@
1
+ require "spec_helper"
2
+
3
+ describe Logtail::Logger do
4
+ describe "#initialize" do
5
+ it "shoud select the augmented formatter" do
6
+ logger = described_class.new(nil)
7
+ expect(logger.formatter).to be_kind_of(Logtail::Logger::JSONFormatter)
8
+ end
9
+
10
+ context "development environment" do
11
+ around(:each) do |example|
12
+ old_env = Logtail::Config.instance.environment
13
+ Logtail::Config.instance.environment = "development"
14
+ example.run
15
+ Logtail::Config.instance.environment = old_env
16
+ end
17
+
18
+ it "shoud select the message only formatter" do
19
+ logger = described_class.new(nil)
20
+ expect(logger.formatter).to be_kind_of(Logtail::Logger::MessageOnlyFormatter)
21
+ end
22
+ end
23
+
24
+ it "should allow multiple io devices" do
25
+ io1 = StringIO.new
26
+ io2 = StringIO.new
27
+ logger = Logtail::Logger.new(io1, io2)
28
+ logger.info("hello world")
29
+ expect(io1.string).to include("hello world")
30
+ expect(io2.string).to include("hello world")
31
+ end
32
+
33
+ it "should allow multiple io devices and loggers" do
34
+ io1 = StringIO.new
35
+ io2 = StringIO.new
36
+ io3 = StringIO.new
37
+ extra_logger = ::Logger.new(io3)
38
+ logger = Logtail::Logger.new(io1, io2, extra_logger)
39
+ logger.info("hello world")
40
+ expect(io1.string).to include("hello world")
41
+ expect(io2.string).to include("hello world")
42
+ expect(io3.string).to end_with("hello world\n")
43
+ end
44
+ end
45
+
46
+ describe "#add" do
47
+ let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
48
+ let(:io) { StringIO.new }
49
+ let(:logger) { Logtail::Logger.new(io) }
50
+
51
+ around(:each) do |example|
52
+ Timecop.freeze(time) { example.run }
53
+ end
54
+
55
+ it "should respect the level via Logger constants" do
56
+ logger.formatter = Logtail::Logger::MessageOnlyFormatter.new
57
+
58
+ logger.level = ::Logger::DEBUG
59
+ logger.info("message")
60
+ expect(io.string).to eq("message\n")
61
+
62
+ io.string = ""
63
+ logger.level = ::Logger::WARN
64
+ logger.info("message")
65
+ expect(io.string).to eq("")
66
+ end
67
+
68
+ it "should respect the level via level symbols" do
69
+ logger.formatter = Logtail::Logger::MessageOnlyFormatter.new
70
+
71
+ logger.level = :debug
72
+ logger.info("message")
73
+ expect(io.string).to eq("message\n")
74
+
75
+ io.string = ""
76
+ logger.level = :warn
77
+ logger.info("message")
78
+ expect(io.string).to eq("")
79
+ end
80
+
81
+ context "with the AugmentedFormatter" do
82
+ before(:each) { logger.formatter = Logtail::Logger::AugmentedFormatter.new }
83
+
84
+ it "should accept strings" do
85
+ logger.info("this is a test")
86
+ expect(io.string).to start_with("this is a test @metadata {\"level\":\"info\",\"dt\":\"2016-09-01T12:00:00.000000Z\"")
87
+ end
88
+
89
+ it "should accept non-strings" do
90
+ logger.info(true)
91
+ expect(io.string).to include("true")
92
+ end
93
+
94
+ context "with a context" do
95
+ let(:http_context) do
96
+ Logtail::Contexts::HTTP.new(
97
+ method: "POST",
98
+ path: "/checkout",
99
+ remote_addr: "123.456.789.10",
100
+ request_id: "abcd1234"
101
+ )
102
+ end
103
+
104
+ before(:each) do |example|
105
+ Logtail::CurrentContext.add(http_context)
106
+ end
107
+ after(:each) do |example|
108
+ Logtail::CurrentContext.remove(:http)
109
+ end
110
+
111
+ it "should snapshot and include the context" do
112
+ expect(Logtail::CurrentContext.instance).to receive(:snapshot).and_call_original
113
+ logger.info("this is a test")
114
+ expect(io.string).to start_with("this is a test @metadata {\"level\":\"info\",\"dt\":\"2016-09-01T12:00:00.000000Z\"")
115
+ expect(io.string).to include("\"http\":{\"method\":\"POST\",\"path\":\"/checkout\",\"remote_addr\":\"123.456.789.10\",\"request_id\":\"abcd1234\"}")
116
+ end
117
+ end
118
+
119
+ it "should pass hash as metadata" do
120
+ message = {message: "payment rejected", payment_rejected: {customer_id: "abcde1234", amount: 100}}
121
+ logger.info(message)
122
+ expect(io.string).to start_with("payment rejected @metadata {\"level\":\"info\",\"dt\":\"2016-09-01T12:00:00.000000Z\",")
123
+ expect(io.string).to include("\"payment_rejected\":{\"customer_id\":\"abcde1234\",\"amount\":100}")
124
+ end
125
+
126
+ it "should allow :tag" do
127
+ logger.info("event complete", tag: "tag1")
128
+ expect(io.string).to include("\"tags\":[\"tag1\"]")
129
+ end
130
+
131
+ it "should allow :tags" do
132
+ tags = ["tag1", "tag2"]
133
+ logger.info("event complete", tags: tags)
134
+ expect(io.string).to include("\"tags\":[\"tag1\",\"tag2\"]")
135
+
136
+ # Ensure the tags object is not modified
137
+ expect(tags).to eq(["tag1", "tag2"])
138
+ end
139
+
140
+ it "should allow functions" do
141
+ logger.info do
142
+ {message: "payment rejected", payment_rejected: {customer_id: "abcde1234", amount: 100}}
143
+ end
144
+ expect(io.string).to start_with("payment rejected @metadata {\"level\":\"info\",\"dt\":\"2016-09-01T12:00:00.000000Z\",")
145
+ expect(io.string).to include("\"payment_rejected\":{\"customer_id\":\"abcde1234\",\"amount\":100}")
146
+ end
147
+
148
+ it "should escape new lines" do
149
+ logger.info "first\nsecond"
150
+ expect(io.string).to start_with("first\\nsecond @metadata")
151
+ end
152
+ end
153
+
154
+ context "with the JSONFormatter" do
155
+ before(:each) { logger.formatter = Logtail::Logger::JSONFormatter.new }
156
+
157
+ it "should log in the correct format" do
158
+ logger.info("this is a test")
159
+ expect(io.string).to start_with("{\"level\":\"info\",\"dt\":\"2016-09-01T12:00:00.000000Z\",\"message\":\"this is a test\"")
160
+ end
161
+ end
162
+
163
+ context "with the HTTP log device" do
164
+ let(:io) { Logtail::LogDevices::HTTP.new("my_key") }
165
+
166
+ it "should use the PassThroughFormatter" do
167
+ expect(logger.formatter).to be_kind_of(Logtail::Logger::PassThroughFormatter)
168
+ end
169
+ end
170
+ end
171
+
172
+ describe "#error" do
173
+ let(:io) { StringIO.new }
174
+ let(:logger) { Logtail::Logger.new(io) }
175
+
176
+ it "should allow default usage" do
177
+ logger.error("log message")
178
+ expect(io.string).to include("log message")
179
+ expect(io.string).to include('"level":"error"')
180
+ end
181
+
182
+ it "should allow messages with options" do
183
+ logger.error("log message", tag: "tag")
184
+ expect(io.string).to include("log message")
185
+ expect(io.string).to include('"level":"error"')
186
+ expect(io.string).to include('"tags":["tag"]')
187
+ end
188
+ end
189
+
190
+ describe "#formatter=" do
191
+ it "should not allow changing the formatter when the device is HTTP" do
192
+ http_device = Logtail::LogDevices::HTTP.new("api_key")
193
+ logger = Logtail::Logger.new(http_device)
194
+ expect { logger.formatter = ::Logger::Formatter.new }.to raise_error(ArgumentError)
195
+ end
196
+
197
+ it "should set the formatter" do
198
+ logger = Logtail::Logger.new(STDOUT)
199
+ formatter = ::Logger::Formatter.new
200
+ logger.formatter = formatter
201
+ expect(logger.formatter).to eq(formatter)
202
+ end
203
+ end
204
+
205
+ describe "#info" do
206
+ let(:io) { StringIO.new }
207
+ let(:logger) { Logtail::Logger.new(io) }
208
+
209
+ it "should allow default usage" do
210
+ logger.info("log message")
211
+ expect(io.string).to include("log message")
212
+ expect(io.string).to include('"level":"info"')
213
+ end
214
+
215
+ it "should allow messages with options" do
216
+ logger.info("log message", tag: "tag")
217
+ expect(io.string).to include("log message")
218
+ expect(io.string).to include('"level":"info"')
219
+ expect(io.string).to include('"tags":["tag"]')
220
+ end
221
+
222
+ it "should accept non-string messages" do
223
+ logger.info(true)
224
+ expect(io.string).to include("true")
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,22 @@
1
+ # Base
2
+ require 'rubygems'
3
+ require 'bundler/setup'
4
+
5
+ # Testing
6
+ require 'rspec'
7
+ require 'rspec/its'
8
+ require 'rspec/mocks'
9
+
10
+ # Support files, order is relevant
11
+ require File.join(File.dirname(__FILE__), 'support', 'socket_hostname')
12
+ require File.join(File.dirname(__FILE__), 'support', 'timecop')
13
+ require File.join(File.dirname(__FILE__), 'support', 'webmock')
14
+ require File.join(File.dirname(__FILE__), 'support', 'logtail')
15
+
16
+ RSpec::Support::ObjectFormatter.default_instance.max_formatted_output_length = 5_000
17
+
18
+ RSpec.configure do |config|
19
+ config.color = true
20
+ config.order = :random
21
+ config.warnings = false
22
+ end
@@ -0,0 +1,5 @@
1
+ require "logtail"
2
+ require "logtail/config"
3
+
4
+ config = Logtail::Config.instance
5
+ config.environment = "production"
@@ -0,0 +1,12 @@
1
+ require "socket"
2
+
3
+ # Stub out the hostname for tests only. This can't use a normal stub in the
4
+ # test life cycle since our test rails app is loaded once upon initialization.
5
+ # In other words, the rails app gets loaded with the server context inserted
6
+ # before any tests are run.
7
+
8
+ class ::Socket
9
+ def self.gethostname
10
+ "computer-name.domain.com"
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ require 'timecop'
2
+
3
+ Timecop.safe_mode = true
@@ -0,0 +1,3 @@
1
+ require 'webmock/rspec'
2
+
3
+ WebMock.disable_net_connect!
metadata ADDED
@@ -0,0 +1,238 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: logtail
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Logtail
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-03-26 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: msgpack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler-audit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rails_stdout_logging
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.4'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.4'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec-its
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: timecop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: webmock
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '2.3'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '2.3'
125
+ - !ruby/object:Gem::Dependency
126
+ name: sqlite3
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ description:
140
+ email:
141
+ - hi@logtail.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".github/workflows/main.yml"
147
+ - ".gitignore"
148
+ - ".rspec"
149
+ - CHANGELOG.md
150
+ - Gemfile
151
+ - LICENSE.md
152
+ - README.md
153
+ - Rakefile
154
+ - lib/logtail.rb
155
+ - lib/logtail/config.rb
156
+ - lib/logtail/config/integrations.rb
157
+ - lib/logtail/context.rb
158
+ - lib/logtail/contexts.rb
159
+ - lib/logtail/contexts/http.rb
160
+ - lib/logtail/contexts/release.rb
161
+ - lib/logtail/contexts/runtime.rb
162
+ - lib/logtail/contexts/session.rb
163
+ - lib/logtail/contexts/system.rb
164
+ - lib/logtail/contexts/user.rb
165
+ - lib/logtail/current_context.rb
166
+ - lib/logtail/event.rb
167
+ - lib/logtail/events.rb
168
+ - lib/logtail/events/controller_call.rb
169
+ - lib/logtail/events/error.rb
170
+ - lib/logtail/events/exception.rb
171
+ - lib/logtail/events/sql_query.rb
172
+ - lib/logtail/events/template_render.rb
173
+ - lib/logtail/integration.rb
174
+ - lib/logtail/integrator.rb
175
+ - lib/logtail/log_devices.rb
176
+ - lib/logtail/log_devices/http.rb
177
+ - lib/logtail/log_devices/http/flushable_dropping_sized_queue.rb
178
+ - lib/logtail/log_devices/http/request_attempt.rb
179
+ - lib/logtail/log_entry.rb
180
+ - lib/logtail/logger.rb
181
+ - lib/logtail/logtail.rb
182
+ - lib/logtail/timer.rb
183
+ - lib/logtail/util.rb
184
+ - lib/logtail/util/non_nil_hash_builder.rb
185
+ - lib/logtail/version.rb
186
+ - logtail.gemspec
187
+ - spec/README.md
188
+ - spec/logtail/current_context_spec.rb
189
+ - spec/logtail/events/controller_call_spec.rb
190
+ - spec/logtail/events/error_spec.rb
191
+ - spec/logtail/log_devices/http_spec.rb
192
+ - spec/logtail/log_entry_spec.rb
193
+ - spec/logtail/logger_spec.rb
194
+ - spec/spec_helper.rb
195
+ - spec/support/logtail.rb
196
+ - spec/support/socket_hostname.rb
197
+ - spec/support/timecop.rb
198
+ - spec/support/webmock.rb
199
+ homepage: https://github.com/logtail/logtail-ruby
200
+ licenses:
201
+ - ISC
202
+ metadata:
203
+ bug_tracker_uri: https://github.com/logtail/logtail-ruby/issues
204
+ changelog_uri: https://github.com/logtail/logtail-ruby/tree/master/CHANGELOG.md
205
+ homepage_uri: https://github.com/logtail/logtail-ruby
206
+ source_code_uri: https://github.com/logtail/logtail-ruby
207
+ post_install_message:
208
+ rdoc_options: []
209
+ require_paths:
210
+ - lib
211
+ required_ruby_version: !ruby/object:Gem::Requirement
212
+ requirements:
213
+ - - ">="
214
+ - !ruby/object:Gem::Version
215
+ version: 2.2.0
216
+ required_rubygems_version: !ruby/object:Gem::Requirement
217
+ requirements:
218
+ - - ">="
219
+ - !ruby/object:Gem::Version
220
+ version: '0'
221
+ requirements: []
222
+ rubygems_version: 3.2.3
223
+ signing_key:
224
+ specification_version: 4
225
+ summary: Query logs like you query your database. https://logtail.com
226
+ test_files:
227
+ - spec/README.md
228
+ - spec/logtail/current_context_spec.rb
229
+ - spec/logtail/events/controller_call_spec.rb
230
+ - spec/logtail/events/error_spec.rb
231
+ - spec/logtail/log_devices/http_spec.rb
232
+ - spec/logtail/log_entry_spec.rb
233
+ - spec/logtail/logger_spec.rb
234
+ - spec/spec_helper.rb
235
+ - spec/support/logtail.rb
236
+ - spec/support/socket_hostname.rb
237
+ - spec/support/timecop.rb
238
+ - spec/support/webmock.rb