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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5231f90612c18b1f92a9a2b0b1dcfb088ffc1a3c784061d61b15620a501d61d
4
- data.tar.gz: 2a7089a1d46c396cd9d83eb594ac0cce35318f658af0b6df417415413f11f623
3
+ metadata.gz: 8f5caf9f04f2b348390785adcecf394cdc4580b753e6811bf18f9750f2c0dffd
4
+ data.tar.gz: fc84b996a2a961bcb614de7e4fb38edd35773960f159f400e8950278175cb3cf
5
5
  SHA512:
6
- metadata.gz: 7c4f8e6f74900e48d2dd4efa4fdcd731ae5d937dd4c0ef7e5663e1ccdca17367a193cab9498f15365ba37c619af479cb527326e1e459a51ff43a6ae2ac7471ba
7
- data.tar.gz: 67973be9091d86c9c02feef4f88f15b929d78d2eb612b5347245ce6e358ee42db9186a56b1ea3e79944c9074b334425f063495abb7679d139bcc53b4d056569c
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Vigilant
4
- VERSION = '0.0.2'
4
+ VERSION = '0.0.4'
5
5
  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.2
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
@@ -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