logtail-ruby 0.1.0
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 +7 -0
- data/.github/workflows/main.yml +33 -0
- data/.gitignore +24 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +10 -0
- data/LICENSE.md +15 -0
- data/README.md +4 -0
- data/Rakefile +72 -0
- data/lib/logtail.rb +36 -0
- data/lib/logtail/config.rb +154 -0
- data/lib/logtail/config/integrations.rb +17 -0
- data/lib/logtail/context.rb +9 -0
- data/lib/logtail/contexts.rb +12 -0
- data/lib/logtail/contexts/http.rb +31 -0
- data/lib/logtail/contexts/release.rb +52 -0
- data/lib/logtail/contexts/runtime.rb +23 -0
- data/lib/logtail/contexts/session.rb +24 -0
- data/lib/logtail/contexts/system.rb +29 -0
- data/lib/logtail/contexts/user.rb +28 -0
- data/lib/logtail/current_context.rb +168 -0
- data/lib/logtail/event.rb +36 -0
- data/lib/logtail/events.rb +10 -0
- data/lib/logtail/events/controller_call.rb +44 -0
- data/lib/logtail/events/error.rb +40 -0
- data/lib/logtail/events/exception.rb +10 -0
- data/lib/logtail/events/sql_query.rb +26 -0
- data/lib/logtail/events/template_render.rb +25 -0
- data/lib/logtail/integration.rb +40 -0
- data/lib/logtail/integrator.rb +50 -0
- data/lib/logtail/log_devices.rb +8 -0
- data/lib/logtail/log_devices/http.rb +368 -0
- data/lib/logtail/log_devices/http/flushable_dropping_sized_queue.rb +52 -0
- data/lib/logtail/log_devices/http/request_attempt.rb +20 -0
- data/lib/logtail/log_entry.rb +110 -0
- data/lib/logtail/logger.rb +270 -0
- data/lib/logtail/logtail.rb +36 -0
- data/lib/logtail/timer.rb +21 -0
- data/lib/logtail/util.rb +7 -0
- data/lib/logtail/util/non_nil_hash_builder.rb +40 -0
- data/lib/logtail/version.rb +3 -0
- data/logtail-ruby.gemspec +43 -0
- data/spec/README.md +13 -0
- data/spec/logtail/current_context_spec.rb +113 -0
- data/spec/logtail/events/controller_call_spec.rb +12 -0
- data/spec/logtail/events/error_spec.rb +15 -0
- data/spec/logtail/log_devices/http_spec.rb +185 -0
- data/spec/logtail/log_entry_spec.rb +22 -0
- data/spec/logtail/logger_spec.rb +227 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/logtail.rb +5 -0
- data/spec/support/socket_hostname.rb +12 -0
- data/spec/support/timecop.rb +3 -0
- data/spec/support/webmock.rb +3 -0
- metadata +238 -0
@@ -0,0 +1,52 @@
|
|
1
|
+
require "logtail/config"
|
2
|
+
require "logtail/context"
|
3
|
+
require "logtail/util"
|
4
|
+
|
5
|
+
module Logtail
|
6
|
+
module Contexts
|
7
|
+
# @private
|
8
|
+
class Release < Context
|
9
|
+
class << self
|
10
|
+
# Builds a release context based on environment variables. Simply add the
|
11
|
+
# `RELEASE_COMMIT`, `RELEASE_CREATED_AT`, or the `RELEASE_VERSION` env vars
|
12
|
+
# to get this context automatially. All are optional, but at least one
|
13
|
+
# must be present.
|
14
|
+
#
|
15
|
+
# If you're on Heroku, simply enable dyno metadata to get this automatically:
|
16
|
+
# https://devcenter.heroku.com/articles/dyno-metadata
|
17
|
+
def from_env
|
18
|
+
commit_hash = ENV['RELEASE_COMMIT'] || ENV['HEROKU_SLUG_COMMIT']
|
19
|
+
created_at = ENV['RELEASE_CREATED_AT'] || ENV['HEROKU_RELEASE_CREATED_AT']
|
20
|
+
version = ENV['RELEASE_VERSION'] || ENV['HEROKU_RELEASE_VERSION']
|
21
|
+
|
22
|
+
if commit_hash || created_at || version
|
23
|
+
Logtail::Config.instance.debug { "Release env vars detected, adding to context" }
|
24
|
+
new(commit_hash: commit_hash, created_at: created_at, version: version)
|
25
|
+
else
|
26
|
+
Logtail::Config.instance.debug { "Release env vars _not_ detected" }
|
27
|
+
nil
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :commit_hash, :created_at, :version
|
33
|
+
|
34
|
+
def initialize(attributes)
|
35
|
+
@commit_hash = attributes[:commit_hash]
|
36
|
+
@created_at = attributes[:created_at]
|
37
|
+
@version = attributes[:version]
|
38
|
+
end
|
39
|
+
|
40
|
+
# Builds a hash representation containing simple objects, suitable for serialization (JSON).
|
41
|
+
def to_hash
|
42
|
+
@to_hash ||= {
|
43
|
+
release: Util::NonNilHashBuilder.build do |h|
|
44
|
+
h.add(:commit_hash, commit_hash)
|
45
|
+
h.add(:created_at, created_at)
|
46
|
+
h.add(:version, version)
|
47
|
+
end
|
48
|
+
}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "logtail/context"
|
2
|
+
|
3
|
+
module Logtail
|
4
|
+
module Contexts
|
5
|
+
# @private
|
6
|
+
class Runtime < Context
|
7
|
+
attr_reader :thread_id
|
8
|
+
|
9
|
+
def initialize(attributes)
|
10
|
+
@thread_id = attributes[:thread_id]
|
11
|
+
end
|
12
|
+
|
13
|
+
# Builds a hash representation containing simple objects, suitable for serialization (JSON).
|
14
|
+
def to_hash
|
15
|
+
@to_hash ||= {
|
16
|
+
runtime: Util::NonNilHashBuilder.build do |h|
|
17
|
+
h.add(:thread_id, thread_id)
|
18
|
+
end
|
19
|
+
}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "logtail/context"
|
2
|
+
require "logtail/util"
|
3
|
+
|
4
|
+
module Logtail
|
5
|
+
module Contexts
|
6
|
+
# @private
|
7
|
+
class Session < Context
|
8
|
+
attr_reader :id
|
9
|
+
|
10
|
+
def initialize(attributes)
|
11
|
+
@id = attributes[:id]
|
12
|
+
end
|
13
|
+
|
14
|
+
# Builds a hash representation containing simple objects, suitable for serialization (JSON).
|
15
|
+
def to_hash
|
16
|
+
@to_hash ||= {
|
17
|
+
session: Util::NonNilHashBuilder.build do |h|
|
18
|
+
h.add(:id, id)
|
19
|
+
end
|
20
|
+
}
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require "logtail/context"
|
2
|
+
require "logtail/util"
|
3
|
+
|
4
|
+
module Logtail
|
5
|
+
module Contexts
|
6
|
+
# The system context tracks OS level process information, such as the process ID.
|
7
|
+
#
|
8
|
+
# @note This is tracked automatically in {CurrentContext}. When the current context
|
9
|
+
# is initialized, the system context gets added automatically.
|
10
|
+
class System < Context
|
11
|
+
attr_reader :hostname, :pid
|
12
|
+
|
13
|
+
def initialize(attributes)
|
14
|
+
@hostname = attributes[:hostname]
|
15
|
+
@pid = attributes[:pid]
|
16
|
+
end
|
17
|
+
|
18
|
+
# Builds a hash representation containing simple objects, suitable for serialization (JSON).
|
19
|
+
def to_hash
|
20
|
+
@to_hash ||= {
|
21
|
+
system: Util::NonNilHashBuilder.build do |h|
|
22
|
+
h.add(:hostname, hostname)
|
23
|
+
h.add(:pid, pid)
|
24
|
+
end
|
25
|
+
}
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "logtail/context"
|
2
|
+
require "logtail/util"
|
3
|
+
|
4
|
+
module Logtail
|
5
|
+
module Contexts
|
6
|
+
# @private
|
7
|
+
class User < Context
|
8
|
+
attr_reader :id, :name, :email
|
9
|
+
|
10
|
+
def initialize(attributes)
|
11
|
+
@id = attributes[:id]
|
12
|
+
@name = attributes[:name]
|
13
|
+
@email = attributes[:email]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Builds a hash representation containing simple objects, suitable for serialization (JSON).
|
17
|
+
def to_hash
|
18
|
+
@to_hash ||= {
|
19
|
+
user: Util::NonNilHashBuilder.build do |h|
|
20
|
+
h.add(:id, id)
|
21
|
+
h.add(:name, name)
|
22
|
+
h.add(:email, email)
|
23
|
+
end
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,168 @@
|
|
1
|
+
require "socket"
|
2
|
+
|
3
|
+
require "logtail/config"
|
4
|
+
|
5
|
+
module Logtail
|
6
|
+
# Holds the current context in a thread safe memory storage. This context is
|
7
|
+
# appended to every log line. Think of context as join data between your log lines,
|
8
|
+
# allowing you to relate them and filter them appropriately.
|
9
|
+
#
|
10
|
+
# @note Because context is appended to every log line, it is recommended that you limit this
|
11
|
+
# to only necessary data needed to relate your log lines.
|
12
|
+
class CurrentContext
|
13
|
+
THREAD_NAMESPACE = :_logtail_current_context.freeze
|
14
|
+
|
15
|
+
class << self
|
16
|
+
# Implements the Singleton pattern in a thread specific way. Each thread receives
|
17
|
+
# its own context.
|
18
|
+
def instance
|
19
|
+
Thread.current[THREAD_NAMESPACE] ||= new
|
20
|
+
end
|
21
|
+
|
22
|
+
# Convenience method for {CurrentContext#add}. See {CurrentContext#add} for more info.
|
23
|
+
def add(*args)
|
24
|
+
instance.add(*args)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Convenience method for {CurrentContext#fetch}. See {CurrentContext#fetch} for more info.
|
28
|
+
def fetch(*args)
|
29
|
+
instance.fetch(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Convenience method for {CurrentContext#remove}. See {CurrentContext#remove} for more info.
|
33
|
+
def remove(*args)
|
34
|
+
instance.remove(*args)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Convenience method for {CurrentContext#reset}. See {CurrentContext#reset} for more info.
|
38
|
+
def reset(*args)
|
39
|
+
instance.reset(*args)
|
40
|
+
end
|
41
|
+
|
42
|
+
# Convenience method for {CurrentContext#with}. See {CurrentContext#with} for more info.
|
43
|
+
def with(*args, &block)
|
44
|
+
instance.with(*args, &block)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Adds contexts but does not remove them. See {#with} for automatic maintenance and {#remove}
|
49
|
+
# to remove them yourself.
|
50
|
+
#
|
51
|
+
# @note Because context is included with every log line, it is recommended that you limit this
|
52
|
+
# to only necessary data.
|
53
|
+
def add(*objects)
|
54
|
+
objects.each do |object|
|
55
|
+
hash.merge!(object.to_hash)
|
56
|
+
end
|
57
|
+
expire_cache!
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
# Fetch a specific context by key.
|
62
|
+
def fetch(*args)
|
63
|
+
hash.fetch(*args)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Removes a context. If you wish to remove by key, or some other way, use {#hash} and
|
67
|
+
# modify the hash accordingly.
|
68
|
+
def remove(*keys)
|
69
|
+
keys.each do |keys|
|
70
|
+
hash.delete(keys)
|
71
|
+
end
|
72
|
+
expire_cache!
|
73
|
+
self
|
74
|
+
end
|
75
|
+
|
76
|
+
def replace(hash)
|
77
|
+
@hash = hash
|
78
|
+
expire_cache!
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
# Resets the context to be blank. Use this carefully! This will remove *any* context,
|
83
|
+
# include context that is automatically included with Logtail.
|
84
|
+
def reset
|
85
|
+
hash.clear
|
86
|
+
expire_cache!
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
# Snapshots the current context so that you get a moment in time representation of the context,
|
91
|
+
# since the context can change as execution proceeds. Note that individual contexts
|
92
|
+
# should be immutable, and we implement snapshot caching as a result of this assumption.
|
93
|
+
def snapshot
|
94
|
+
@snapshot ||= hash.clone
|
95
|
+
end
|
96
|
+
|
97
|
+
# Adds a context and then removes it when the block is finished executing.
|
98
|
+
#
|
99
|
+
# @note Because context is included with every log line, it is recommended that you limit this
|
100
|
+
# to only necessary data.
|
101
|
+
#
|
102
|
+
# @example Adding a custom context
|
103
|
+
# Logtail::CurrentContext.with({build: {version: "1.0.0"}}) do
|
104
|
+
# # ... anything logged here will include the context ...
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# @note Any custom context needs to have a single root key to be valid. i.e. instead of:
|
108
|
+
# Logtail::CurrentContext.with(job_id: "123", job_name: "Refresh User Account")
|
109
|
+
#
|
110
|
+
# do
|
111
|
+
#
|
112
|
+
# Logtail::CurrentContext.with(job: {job_id: "123", job_name: "Refresh User Account"})
|
113
|
+
#
|
114
|
+
# @example Adding multiple contexts
|
115
|
+
# Logtail::CurrentContext.with(context1, context2) { ... }
|
116
|
+
def with(*objects)
|
117
|
+
old_hash = hash.clone
|
118
|
+
begin
|
119
|
+
add(*objects)
|
120
|
+
yield
|
121
|
+
ensure
|
122
|
+
replace(old_hash)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
# The internal hash that is maintained. Use {#with} and {#add} for hash maintenance.
|
128
|
+
def hash
|
129
|
+
@hash ||= build_initial_hash
|
130
|
+
end
|
131
|
+
|
132
|
+
# Builds the initial hash. This is extract into a method to support a threaded
|
133
|
+
# environment. Each thread holds it's own context and also needs to instantiate
|
134
|
+
# it's hash properly.
|
135
|
+
def build_initial_hash
|
136
|
+
new_hash = {}
|
137
|
+
|
138
|
+
# Release context
|
139
|
+
release_context = Util::NonNilHashBuilder.build do |h|
|
140
|
+
h.add(:commit_hash, ENV['RELEASE_COMMIT'] || ENV['HEROKU_SLUG_COMMIT'])
|
141
|
+
h.add(:created_at, ENV['RELEASE_CREATED_AT'] || ENV['HEROKU_RELEASE_CREATED_AT'])
|
142
|
+
h.add(:version, ENV['RELEASE_VERSION'] || ENV['HEROKU_RELEASE_VERSION'])
|
143
|
+
end
|
144
|
+
|
145
|
+
if release_context != {}
|
146
|
+
new_hash.merge!({release: release_context})
|
147
|
+
end
|
148
|
+
|
149
|
+
# System context
|
150
|
+
hostname = Socket.gethostname
|
151
|
+
pid = Process.pid
|
152
|
+
system_context = Contexts::System.new(hostname: hostname, pid: pid)
|
153
|
+
new_hash.merge!(system_context.to_hash)
|
154
|
+
|
155
|
+
# Runtime context
|
156
|
+
thread_object_id = Thread.current.object_id
|
157
|
+
runtime_context = {thread_id: thread_object_id}
|
158
|
+
new_hash.merge!({runtime: runtime_context})
|
159
|
+
|
160
|
+
new_hash
|
161
|
+
end
|
162
|
+
|
163
|
+
# Hook to clear any caching implement in this class
|
164
|
+
def expire_cache!
|
165
|
+
@snapshot = nil
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Logtail
|
2
|
+
# Base class for `Logtail::Events::*`
|
3
|
+
# @private
|
4
|
+
class Event
|
5
|
+
attr_reader :message, :metadata
|
6
|
+
def initialize(message, metadata)
|
7
|
+
@message = message || ""
|
8
|
+
@metadata = metadata || {}
|
9
|
+
end
|
10
|
+
|
11
|
+
# This ensures that Logtail events get logged as messages if they are passed to
|
12
|
+
# the standard ::Logger.
|
13
|
+
#
|
14
|
+
# See: https://github.com/ruby/ruby/blob/f6e77b9d3555c1fbaa8aab1cdc0bd6bde95f62c6/lib/logger.rb#L615
|
15
|
+
def inspect
|
16
|
+
message
|
17
|
+
end
|
18
|
+
|
19
|
+
def to_json(options = {})
|
20
|
+
metadata.to_json(options)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_hash
|
24
|
+
metadata
|
25
|
+
end
|
26
|
+
alias to_h to_hash
|
27
|
+
|
28
|
+
def to_msgpack(*args)
|
29
|
+
metadata.to_msgpack(*args)
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
message
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require "logtail/util"
|
2
|
+
require "logtail/event"
|
3
|
+
|
4
|
+
module Logtail
|
5
|
+
module Events
|
6
|
+
# @private
|
7
|
+
class ControllerCall < Logtail::Event
|
8
|
+
attr_reader :controller, :action, :params, :params_json, :format
|
9
|
+
|
10
|
+
def initialize(attributes)
|
11
|
+
@controller = attributes[:controller]
|
12
|
+
@action = attributes[:action]
|
13
|
+
@params = attributes[:params]
|
14
|
+
|
15
|
+
if @params
|
16
|
+
@params_json = @params.to_json
|
17
|
+
end
|
18
|
+
|
19
|
+
@format = attributes[:format]
|
20
|
+
end
|
21
|
+
|
22
|
+
def message
|
23
|
+
message = "Processing by #{controller}##{action}"
|
24
|
+
if !message.nil?
|
25
|
+
message << " as #{format}"
|
26
|
+
end
|
27
|
+
if !params.nil? && params.length > 0
|
28
|
+
message << "\n Parameters: #{params.inspect}"
|
29
|
+
end
|
30
|
+
message
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_hash
|
34
|
+
{
|
35
|
+
controller_called: Util::NonNilHashBuilder.build do |h|
|
36
|
+
h.add(:controller, controller)
|
37
|
+
h.add(:action, action)
|
38
|
+
h.add(:params_json, params_json)
|
39
|
+
end
|
40
|
+
}
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "logtail/util"
|
2
|
+
require "logtail/event"
|
3
|
+
|
4
|
+
module Logtail
|
5
|
+
module Events
|
6
|
+
# @private
|
7
|
+
class Error < Logtail::Event
|
8
|
+
attr_reader :name, :error_message, :backtrace_json
|
9
|
+
|
10
|
+
def initialize(attributes)
|
11
|
+
@name = attributes[:name]
|
12
|
+
@error_message = attributes[:error_message]
|
13
|
+
|
14
|
+
if attributes[:backtrace]
|
15
|
+
@backtrace_json = attributes[:backtrace].to_json
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def message
|
20
|
+
message = "#{name}"
|
21
|
+
|
22
|
+
if !error_message.nil?
|
23
|
+
message << " (#{error_message})"
|
24
|
+
end
|
25
|
+
|
26
|
+
message
|
27
|
+
end
|
28
|
+
|
29
|
+
def to_hash
|
30
|
+
{
|
31
|
+
error: {
|
32
|
+
name: name,
|
33
|
+
message: error_message,
|
34
|
+
backtrace_json: backtrace_json
|
35
|
+
}
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|