vigilant-ruby 0.0.2 → 0.0.4
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/lib/vigilant-ruby/logger.rb +275 -0
- data/lib/{vigilant → vigilant-ruby}/version.rb +1 -1
- data/lib/vigilant-ruby.rb +6 -4
- metadata +3 -17
- data/lib/vigilant/logger.rb +0 -170
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f5caf9f04f2b348390785adcecf394cdc4580b753e6811bf18f9750f2c0dffd
|
4
|
+
data.tar.gz: fc84b996a2a961bcb614de7e4fb38edd35773960f159f400e8950278175cb3cf
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2faaac39af894099aebf8b00679607eb535e6b4331d4f8da16239b463415c2a985e850f24fad682793549ddcb56a2574836de24d31f26633be7fe45faf7db2d7
|
7
|
+
data.tar.gz: 4d5844559329553a0e334c1dc5d7238a130e75c7c016ae162859303b0268052a212a9361c4588eaef251556b75b30f3cd6ece2e3d336198ae1a40682c5d91c70
|
@@ -0,0 +1,275 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'json'
|
5
|
+
require 'time'
|
6
|
+
require 'vigilant-ruby/version'
|
7
|
+
|
8
|
+
module Vigilant
|
9
|
+
DEBUG = 'DEBUG'
|
10
|
+
INFO = 'INFO'
|
11
|
+
WARNING = 'WARNING'
|
12
|
+
ERROR = 'ERROR'
|
13
|
+
|
14
|
+
DEFAULT_BATCH_SIZE = 10
|
15
|
+
DEFAULT_FLUSH_INTERVAL = 5
|
16
|
+
|
17
|
+
# A thread-safe logger that batches logs and sends them to Vigilant asynchronously
|
18
|
+
class Logger
|
19
|
+
# Initialize a Vigilant::Logger instance.
|
20
|
+
#
|
21
|
+
# @param endpoint [String] The base endpoint for the Vigilant API (e.g. "ingress.vigilant.run").
|
22
|
+
# @param token [String] The authentication token for the Vigilant API.
|
23
|
+
# @param insecure [Boolean] Whether to use HTTP instead of HTTPS (optional, defaults to false).
|
24
|
+
# @param passthrough [Boolean] Whether to also print logs to stdout/stderr (optional, defaults to true).
|
25
|
+
def initialize(endpoint:, token:, insecure: false, passthrough: true)
|
26
|
+
@token = token
|
27
|
+
|
28
|
+
protocol = insecure ? 'http://' : 'https://'
|
29
|
+
endpoint = endpoint.sub(%r{^https?://}, '') # remove any existing protocol
|
30
|
+
@endpoint = URI.parse("#{protocol}#{endpoint}/api/message")
|
31
|
+
|
32
|
+
@insecure = insecure
|
33
|
+
@passthrough = passthrough
|
34
|
+
|
35
|
+
@batch_size = DEFAULT_BATCH_SIZE
|
36
|
+
@flush_interval = DEFAULT_FLUSH_INTERVAL
|
37
|
+
|
38
|
+
@queue = Queue.new
|
39
|
+
@mutex = Mutex.new
|
40
|
+
@batch = []
|
41
|
+
|
42
|
+
@original_stdout = $stdout
|
43
|
+
@original_stderr = $stderr
|
44
|
+
|
45
|
+
@autocapture_enabled = false
|
46
|
+
|
47
|
+
start_dispatcher
|
48
|
+
end
|
49
|
+
|
50
|
+
# Logs a debug message.
|
51
|
+
def debug(body, attributes = {})
|
52
|
+
enqueue_log(DEBUG, body, attributes)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Logs an info message.
|
56
|
+
def info(body, attributes = {})
|
57
|
+
enqueue_log(INFO, body, attributes)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Logs a warning message.
|
61
|
+
def warn(body, attributes = {})
|
62
|
+
enqueue_log(WARNING, body, attributes)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Logs an error message.
|
66
|
+
def error(body, error = nil, attributes = {})
|
67
|
+
if error.nil?
|
68
|
+
enqueue_log(ERROR, body, attributes)
|
69
|
+
else
|
70
|
+
attributes_with_error = { error: error.message, **attributes }
|
71
|
+
enqueue_log(ERROR, body, attributes_with_error)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Enables stdout/stderr autocapture.
|
76
|
+
def autocapture_enable
|
77
|
+
return if @autocapture_enabled
|
78
|
+
|
79
|
+
@autocapture_enabled = true
|
80
|
+
$stdout = StdoutInterceptor.new(self, @original_stdout)
|
81
|
+
$stderr = StderrInterceptor.new(self, @original_stderr)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Disables stdout/stderr autocapture.
|
85
|
+
def autocapture_disable
|
86
|
+
return unless @autocapture_enabled
|
87
|
+
|
88
|
+
@autocapture_enabled = false
|
89
|
+
$stdout = @original_stdout
|
90
|
+
$stderr = @original_stderr
|
91
|
+
end
|
92
|
+
|
93
|
+
# Shuts down the logger, flushing any pending logs.
|
94
|
+
def shutdown
|
95
|
+
flush_if_needed(true)
|
96
|
+
@mutex.synchronize { @shutdown = true }
|
97
|
+
@dispatcher_thread&.join
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def enqueue_log(level, body, attributes)
|
103
|
+
autocaptured = attributes.delete(:_autocapture)
|
104
|
+
|
105
|
+
log_msg = {
|
106
|
+
timestamp: Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S.%9NZ'),
|
107
|
+
body: body.to_s,
|
108
|
+
level: level.to_s,
|
109
|
+
attributes: attributes.transform_values(&:to_s)
|
110
|
+
}
|
111
|
+
|
112
|
+
@queue << log_msg
|
113
|
+
|
114
|
+
return unless @passthrough && !autocaptured
|
115
|
+
|
116
|
+
@original_stdout.puts(body)
|
117
|
+
end
|
118
|
+
|
119
|
+
def start_dispatcher
|
120
|
+
@shutdown = false
|
121
|
+
@dispatcher_thread = Thread.new do
|
122
|
+
until @mutex.synchronize { @shutdown }
|
123
|
+
flush_if_needed
|
124
|
+
sleep @flush_interval
|
125
|
+
end
|
126
|
+
flush_if_needed(true)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def flush_if_needed(force: false)
|
131
|
+
until @queue.empty?
|
132
|
+
msg = @queue.pop
|
133
|
+
@mutex.synchronize { @batch << msg }
|
134
|
+
end
|
135
|
+
|
136
|
+
@mutex.synchronize do
|
137
|
+
flush! if force || @batch.size >= @batch_size || !@batch.empty?
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def flush!
|
142
|
+
return if @batch.empty?
|
143
|
+
|
144
|
+
logs_to_send = @batch.dup
|
145
|
+
@batch.clear
|
146
|
+
|
147
|
+
request_body = {
|
148
|
+
token: @token,
|
149
|
+
type: 'logs',
|
150
|
+
logs: logs_to_send
|
151
|
+
}
|
152
|
+
|
153
|
+
post_logs(request_body)
|
154
|
+
rescue StandardError => e
|
155
|
+
warn("Failed to send logs: #{e.message}")
|
156
|
+
end
|
157
|
+
|
158
|
+
def post_logs(batch_data)
|
159
|
+
http = Net::HTTP.new(@endpoint.host, @endpoint.port)
|
160
|
+
http.use_ssl = !@insecure
|
161
|
+
|
162
|
+
request = Net::HTTP::Post.new(@endpoint)
|
163
|
+
request['Content-Type'] = 'application/json'
|
164
|
+
request.body = JSON.dump(batch_data)
|
165
|
+
|
166
|
+
http.request(request)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
# Interceptor for capturing stdout
|
171
|
+
class StdoutInterceptor
|
172
|
+
def initialize(logger, original)
|
173
|
+
@logger = logger
|
174
|
+
@original = original
|
175
|
+
end
|
176
|
+
|
177
|
+
def write(message)
|
178
|
+
@logger.info(message.strip, _autocapture: true) unless message.strip.empty?
|
179
|
+
@original.write(message)
|
180
|
+
end
|
181
|
+
|
182
|
+
def puts(*messages)
|
183
|
+
messages.each do |m|
|
184
|
+
@logger.info(m.to_s.strip, _autocapture: true) unless m.to_s.strip.empty?
|
185
|
+
end
|
186
|
+
@original.puts(*messages)
|
187
|
+
end
|
188
|
+
|
189
|
+
def print(*messages)
|
190
|
+
messages.each do |m|
|
191
|
+
@logger.info(m.to_s.strip, _autocapture: true) unless m.to_s.strip.empty?
|
192
|
+
end
|
193
|
+
@original.print(*messages)
|
194
|
+
end
|
195
|
+
|
196
|
+
def printf(*args)
|
197
|
+
formatted = sprintf(*args)
|
198
|
+
@logger.info(formatted.strip, _autocapture: true) unless formatted.strip.empty?
|
199
|
+
@original.printf(*args)
|
200
|
+
end
|
201
|
+
|
202
|
+
def flush
|
203
|
+
@original.flush
|
204
|
+
end
|
205
|
+
|
206
|
+
def close
|
207
|
+
@original.close
|
208
|
+
end
|
209
|
+
|
210
|
+
def tty?
|
211
|
+
@original.tty?
|
212
|
+
end
|
213
|
+
|
214
|
+
def respond_to_missing?(meth, include_private = false)
|
215
|
+
@original.respond_to?(meth, include_private)
|
216
|
+
end
|
217
|
+
|
218
|
+
def method_missing(meth, *args, &blk)
|
219
|
+
@original.send(meth, *args, &blk)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Interceptor for capturing stderr
|
224
|
+
class StderrInterceptor
|
225
|
+
def initialize(logger, original)
|
226
|
+
@logger = logger
|
227
|
+
@original = original
|
228
|
+
end
|
229
|
+
|
230
|
+
def write(message)
|
231
|
+
@logger.error(message.strip, nil, _autocapture: true) unless message.strip.empty?
|
232
|
+
@original.write(message)
|
233
|
+
end
|
234
|
+
|
235
|
+
def puts(*messages)
|
236
|
+
messages.each do |m|
|
237
|
+
@logger.error(m.to_s.strip, nil, _autocapture: true) unless m.to_s.strip.empty?
|
238
|
+
end
|
239
|
+
@original.puts(*messages)
|
240
|
+
end
|
241
|
+
|
242
|
+
def print(*messages)
|
243
|
+
messages.each do |m|
|
244
|
+
@logger.error(m.to_s.strip, nil, _autocapture: true) unless m.to_s.strip.empty?
|
245
|
+
end
|
246
|
+
@original.print(*messages)
|
247
|
+
end
|
248
|
+
|
249
|
+
def printf(*args)
|
250
|
+
formatted = sprintf(*args)
|
251
|
+
@logger.error(formatted.strip, nil, _autocapture: true) unless formatted.strip.empty?
|
252
|
+
@original.printf(*args)
|
253
|
+
end
|
254
|
+
|
255
|
+
def flush
|
256
|
+
@original.flush
|
257
|
+
end
|
258
|
+
|
259
|
+
def close
|
260
|
+
@original.close
|
261
|
+
end
|
262
|
+
|
263
|
+
def tty?
|
264
|
+
@original.tty?
|
265
|
+
end
|
266
|
+
|
267
|
+
def respond_to_missing?(meth, include_private = false)
|
268
|
+
@original.respond_to?(meth, include_private)
|
269
|
+
end
|
270
|
+
|
271
|
+
def method_missing(meth, *args, &blk)
|
272
|
+
@original.send(meth, *args, &blk)
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
data/lib/vigilant-ruby.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'vigilant/version'
|
4
|
-
require 'vigilant/logger'
|
3
|
+
require 'vigilant-ruby/version'
|
4
|
+
require 'vigilant-ruby/logger'
|
5
5
|
|
6
6
|
# Vigilant is a logging service that provides structured logging capabilities
|
7
7
|
# with asynchronous batch processing and thread-safe operations.
|
@@ -10,12 +10,13 @@ module Vigilant
|
|
10
10
|
|
11
11
|
# Configuration for the Vigilant logging service.
|
12
12
|
class Configuration
|
13
|
-
attr_accessor :endpoint, :token, :insecure
|
13
|
+
attr_accessor :endpoint, :token, :insecure, :passthrough
|
14
14
|
|
15
15
|
def initialize
|
16
16
|
@endpoint = 'ingress.vigilant.run'
|
17
17
|
@insecure = false
|
18
18
|
@token = 'tk_1234567890'
|
19
|
+
@passthrough = true
|
19
20
|
end
|
20
21
|
end
|
21
22
|
|
@@ -32,7 +33,8 @@ module Vigilant
|
|
32
33
|
@logger ||= Vigilant::Logger.new(
|
33
34
|
endpoint: configuration.endpoint,
|
34
35
|
insecure: configuration.insecure,
|
35
|
-
token: configuration.token
|
36
|
+
token: configuration.token,
|
37
|
+
passthrough: configuration.passthrough
|
36
38
|
)
|
37
39
|
end
|
38
40
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vigilant-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Vigilant
|
@@ -37,20 +37,6 @@ dependencies:
|
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
39
|
version: '2.0'
|
40
|
-
- !ruby/object:Gem::Dependency
|
41
|
-
name: rake
|
42
|
-
requirement: !ruby/object:Gem::Requirement
|
43
|
-
requirements:
|
44
|
-
- - "~>"
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: '13.0'
|
47
|
-
type: :development
|
48
|
-
prerelease: false
|
49
|
-
version_requirements: !ruby/object:Gem::Requirement
|
50
|
-
requirements:
|
51
|
-
- - "~>"
|
52
|
-
- !ruby/object:Gem::Version
|
53
|
-
version: '13.0'
|
54
40
|
- !ruby/object:Gem::Dependency
|
55
41
|
name: rspec
|
56
42
|
requirement: !ruby/object:Gem::Requirement
|
@@ -102,8 +88,8 @@ extra_rdoc_files: []
|
|
102
88
|
files:
|
103
89
|
- README.md
|
104
90
|
- lib/vigilant-ruby.rb
|
105
|
-
- lib/vigilant/logger.rb
|
106
|
-
- lib/vigilant/version.rb
|
91
|
+
- lib/vigilant-ruby/logger.rb
|
92
|
+
- lib/vigilant-ruby/version.rb
|
107
93
|
homepage: https://vigilant.run
|
108
94
|
licenses:
|
109
95
|
- MIT
|
data/lib/vigilant/logger.rb
DELETED
@@ -1,170 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'net/http'
|
4
|
-
require 'json'
|
5
|
-
require 'time'
|
6
|
-
require 'vigilant/version'
|
7
|
-
|
8
|
-
module Vigilant
|
9
|
-
TRACE = 'TRACE'
|
10
|
-
DEBUG = 'DEBUG'
|
11
|
-
INFO = 'INFO'
|
12
|
-
WARNING = 'WARNING'
|
13
|
-
ERROR = 'ERROR'
|
14
|
-
FATAL = 'FATAL'
|
15
|
-
|
16
|
-
DEFAULT_BATCH_SIZE = 10
|
17
|
-
DEFAULT_FLUSH_INTERVAL = 5
|
18
|
-
|
19
|
-
# A thread-safe logger that batches logs and sends them to Vigilant asynchronously
|
20
|
-
class Logger
|
21
|
-
# Initialize a Vigilant::Logger instance.
|
22
|
-
#
|
23
|
-
# @param endpoint [String] The base endpoint for the Vigilant API (e.g. "ingress.vigilant.run").
|
24
|
-
# @param token [String] The authentication token for the Vigilant API.
|
25
|
-
# @param insecure [Boolean] Whether to use HTTP instead of HTTPS (optional, defaults to false).
|
26
|
-
def initialize(endpoint:, token:, insecure: false)
|
27
|
-
@token = token
|
28
|
-
protocol = insecure ? 'http://' : 'https://'
|
29
|
-
endpoint = endpoint.sub(%r{^https?://}, '') # Remove any existing protocol
|
30
|
-
@endpoint = URI.parse("#{protocol}#{endpoint}/api/message")
|
31
|
-
@insecure = insecure
|
32
|
-
|
33
|
-
@batch_size = DEFAULT_BATCH_SIZE
|
34
|
-
@flush_interval = DEFAULT_FLUSH_INTERVAL
|
35
|
-
|
36
|
-
@queue = Queue.new
|
37
|
-
@mutex = Mutex.new
|
38
|
-
@batch = []
|
39
|
-
|
40
|
-
start_dispatcher
|
41
|
-
end
|
42
|
-
|
43
|
-
# Logs a TRACE message.
|
44
|
-
#
|
45
|
-
# @param body [String] The main text of the trace message.
|
46
|
-
# @param attributes [Hash] Additional attributes for the log (optional).
|
47
|
-
def trace(body, attributes = {})
|
48
|
-
enqueue_log(TRACE, body, attributes)
|
49
|
-
end
|
50
|
-
|
51
|
-
# Logs a DEBUG message.
|
52
|
-
#
|
53
|
-
# @param body [String] The main text of the debug message.
|
54
|
-
# @param attributes [Hash] Additional attributes for the log (optional).
|
55
|
-
def debug(body, attributes = {})
|
56
|
-
enqueue_log(DEBUG, body, attributes)
|
57
|
-
end
|
58
|
-
|
59
|
-
# Logs an INFO message.
|
60
|
-
#
|
61
|
-
# @param body [String] The main text of the log message.
|
62
|
-
# @param attributes [Hash] Additional attributes for the log (optional).
|
63
|
-
def info(body, attributes = {})
|
64
|
-
enqueue_log(INFO, body, attributes)
|
65
|
-
end
|
66
|
-
|
67
|
-
# Logs a WARNING message.
|
68
|
-
#
|
69
|
-
# @param body [String] The main text of the warning message.
|
70
|
-
# @param attributes [Hash] Additional attributes for the log (optional).
|
71
|
-
def warn(body, attributes = {})
|
72
|
-
enqueue_log(WARNING, body, attributes)
|
73
|
-
end
|
74
|
-
|
75
|
-
# Logs an ERROR message.
|
76
|
-
#
|
77
|
-
# @param body [String] The main text of the error message.
|
78
|
-
# @param error [Exception] The error object.
|
79
|
-
# @param attributes [Hash] Additional attributes for the log (optional).
|
80
|
-
def error(body, error = nil, attributes = {})
|
81
|
-
if error.nil?
|
82
|
-
enqueue_log(ERROR, body, attributes)
|
83
|
-
else
|
84
|
-
attributes_with_error = { error: error.message, **attributes }
|
85
|
-
enqueue_log(ERROR, body, attributes_with_error)
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
# Logs a FATAL message.
|
90
|
-
#
|
91
|
-
# @param body [String] The main text of the fatal message.
|
92
|
-
# @param attributes [Hash] Additional attributes for the log (optional).
|
93
|
-
def fatal(body, attributes = {})
|
94
|
-
enqueue_log(FATAL, body, attributes)
|
95
|
-
end
|
96
|
-
|
97
|
-
def shutdown
|
98
|
-
flush_if_needed(true)
|
99
|
-
|
100
|
-
@mutex.synchronize do
|
101
|
-
@shutdown = true
|
102
|
-
end
|
103
|
-
|
104
|
-
@dispatcher_thread&.join
|
105
|
-
end
|
106
|
-
|
107
|
-
private
|
108
|
-
|
109
|
-
def enqueue_log(level, body, attributes)
|
110
|
-
string_attributes = attributes.transform_values(&:to_s)
|
111
|
-
log_msg = {
|
112
|
-
timestamp: Time.now.utc.strftime('%Y-%m-%dT%H:%M:%S.%9NZ'),
|
113
|
-
body: body.to_s,
|
114
|
-
level: level.to_s,
|
115
|
-
attributes: string_attributes
|
116
|
-
}
|
117
|
-
@queue << log_msg
|
118
|
-
end
|
119
|
-
|
120
|
-
def start_dispatcher
|
121
|
-
@shutdown = false
|
122
|
-
@dispatcher_thread = Thread.new do
|
123
|
-
until @mutex.synchronize { @shutdown }
|
124
|
-
flush_if_needed
|
125
|
-
sleep @flush_interval
|
126
|
-
end
|
127
|
-
flush_if_needed(true)
|
128
|
-
end
|
129
|
-
end
|
130
|
-
|
131
|
-
def flush_if_needed(force = false)
|
132
|
-
until @queue.empty?
|
133
|
-
msg = @queue.pop
|
134
|
-
@mutex.synchronize { @batch << msg }
|
135
|
-
end
|
136
|
-
|
137
|
-
@mutex.synchronize do
|
138
|
-
flush! if force || @batch.size >= @batch_size || !@batch.empty?
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def flush!
|
143
|
-
return if @batch.empty?
|
144
|
-
|
145
|
-
logs_to_send = @batch.dup
|
146
|
-
@batch.clear
|
147
|
-
|
148
|
-
request_body = {
|
149
|
-
token: @token,
|
150
|
-
type: 'logs',
|
151
|
-
logs: logs_to_send
|
152
|
-
}
|
153
|
-
|
154
|
-
post_logs(request_body)
|
155
|
-
rescue StandardError => e
|
156
|
-
warn("Failed to send logs: #{e.message}")
|
157
|
-
end
|
158
|
-
|
159
|
-
def post_logs(batch_data)
|
160
|
-
http = Net::HTTP.new(@endpoint.host, @endpoint.port)
|
161
|
-
http.use_ssl = !@insecure
|
162
|
-
|
163
|
-
request = Net::HTTP::Post.new(@endpoint)
|
164
|
-
request['Content-Type'] = 'application/json'
|
165
|
-
request.body = JSON.dump(batch_data)
|
166
|
-
|
167
|
-
http.request(request)
|
168
|
-
end
|
169
|
-
end
|
170
|
-
end
|