semantic_logger 4.5.0 → 4.12.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 +4 -4
- data/README.md +51 -21
- data/Rakefile +7 -7
- data/lib/semantic_logger/ansi_colors.rb +0 -10
- data/lib/semantic_logger/appender/async.rb +12 -10
- data/lib/semantic_logger/appender/async_batch.rb +7 -3
- data/lib/semantic_logger/appender/bugsnag.rb +43 -30
- data/lib/semantic_logger/appender/elasticsearch.rb +34 -15
- data/lib/semantic_logger/appender/elasticsearch_http.rb +4 -4
- data/lib/semantic_logger/appender/file.rb +249 -67
- data/lib/semantic_logger/appender/graylog.rb +15 -10
- data/lib/semantic_logger/appender/honeybadger.rb +3 -3
- data/lib/semantic_logger/appender/http.rb +41 -20
- data/lib/semantic_logger/appender/io.rb +68 -0
- data/lib/semantic_logger/appender/kafka.rb +46 -31
- data/lib/semantic_logger/appender/mongodb.rb +6 -6
- data/lib/semantic_logger/appender/new_relic.rb +2 -2
- data/lib/semantic_logger/appender/rabbitmq.rb +5 -5
- data/lib/semantic_logger/appender/sentry.rb +7 -7
- data/lib/semantic_logger/appender/sentry_ruby.rb +138 -0
- data/lib/semantic_logger/appender/splunk.rb +7 -5
- data/lib/semantic_logger/appender/splunk_http.rb +6 -5
- data/lib/semantic_logger/appender/syslog.rb +23 -15
- data/lib/semantic_logger/appender/tcp.rb +9 -9
- data/lib/semantic_logger/appender/udp.rb +2 -2
- data/lib/semantic_logger/appender/wrapper.rb +3 -2
- data/lib/semantic_logger/appender.rb +62 -65
- data/lib/semantic_logger/appenders.rb +36 -53
- data/lib/semantic_logger/base.rb +61 -39
- data/lib/semantic_logger/formatters/base.rb +16 -6
- data/lib/semantic_logger/formatters/color.rb +14 -15
- data/lib/semantic_logger/formatters/default.rb +18 -5
- data/lib/semantic_logger/formatters/fluentd.rb +7 -18
- data/lib/semantic_logger/formatters/json.rb +3 -5
- data/lib/semantic_logger/formatters/logfmt.rb +77 -0
- data/lib/semantic_logger/formatters/raw.rb +39 -10
- data/lib/semantic_logger/formatters/signalfx.rb +14 -21
- data/lib/semantic_logger/formatters/syslog.rb +8 -6
- data/lib/semantic_logger/formatters/syslog_cee.rb +9 -7
- data/lib/semantic_logger/formatters.rb +13 -13
- data/lib/semantic_logger/jruby/garbage_collection_logger.rb +4 -2
- data/lib/semantic_logger/levels.rb +9 -7
- data/lib/semantic_logger/log.rb +58 -73
- data/lib/semantic_logger/loggable.rb +8 -1
- data/lib/semantic_logger/logger.rb +19 -11
- data/lib/semantic_logger/metric/new_relic.rb +3 -3
- data/lib/semantic_logger/metric/signalfx.rb +3 -3
- data/lib/semantic_logger/metric/statsd.rb +7 -7
- data/lib/semantic_logger/processor.rb +9 -7
- data/lib/semantic_logger/reporters/minitest.rb +4 -4
- data/lib/semantic_logger/semantic_logger.rb +57 -23
- data/lib/semantic_logger/subscriber.rb +24 -7
- data/lib/semantic_logger/sync.rb +12 -0
- data/lib/semantic_logger/sync_processor.rb +58 -0
- data/lib/semantic_logger/test/capture_log_events.rb +34 -0
- data/lib/semantic_logger/utils.rb +32 -13
- data/lib/semantic_logger/version.rb +1 -1
- data/lib/semantic_logger.rb +27 -22
- metadata +15 -10
@@ -1,3 +1,4 @@
|
|
1
|
+
require "date"
|
1
2
|
# File appender
|
2
3
|
#
|
3
4
|
# Writes log messages to a file or open iostream
|
@@ -5,104 +6,285 @@
|
|
5
6
|
module SemanticLogger
|
6
7
|
module Appender
|
7
8
|
class File < SemanticLogger::Subscriber
|
8
|
-
|
9
|
+
attr_accessor :file_name, :retry_count, :append, :exclusive_lock, :encoding,
|
10
|
+
:reopen_period, :reopen_count, :reopen_size
|
11
|
+
attr_reader :log_count, :log_size, :current_file_name, :reopen_at
|
12
|
+
|
13
|
+
# Create an appender to log to a named file.
|
9
14
|
#
|
10
15
|
# Parameters
|
11
|
-
#
|
12
|
-
#
|
13
|
-
# Or,
|
14
|
-
# :io [IO]
|
15
|
-
# An IO stream to which to write the log messages to.
|
16
|
-
#
|
17
|
-
# :level [:trace | :debug | :info | :warn | :error | :fatal]
|
18
|
-
# Override the log level for this appender.
|
19
|
-
# Default: SemanticLogger.default_level
|
20
|
-
#
|
21
|
-
# :formatter: [Object|Proc]
|
22
|
-
# An instance of a class that implements #call, or a Proc to be used to format
|
23
|
-
# the output from this appender
|
24
|
-
# Default: Use the built-in formatter (See: #call)
|
25
|
-
#
|
26
|
-
# :filter [Regexp|Proc]
|
27
|
-
# RegExp: Only include log messages where the class name matches the supplied
|
28
|
-
# regular expression. All other messages will be ignored.
|
29
|
-
# Proc: Only include log messages where the supplied Proc returns true
|
30
|
-
# The Proc must return true or false.
|
16
|
+
# file_name [String]
|
17
|
+
# Name of the file to write to.
|
31
18
|
#
|
32
|
-
#
|
33
|
-
#
|
19
|
+
# File name format directives:
|
20
|
+
# %p - Process Id
|
21
|
+
# %n - Short hostname (SemanticLogger.host). Everything before the first period in the hostname.
|
22
|
+
# %N - Full hostname (SemanticLogger.host)
|
23
|
+
# %a - Application name (SemanticLogger.application)
|
24
|
+
# %e - Environment name (SemanticLogger.environment)
|
25
|
+
# %D - Current Date. Equivalent to "%Y%m%d"
|
26
|
+
# %T - Current Time. Equivalent to "%H%M%S"
|
27
|
+
# %% - Literal `%` character
|
34
28
|
#
|
35
|
-
#
|
36
|
-
#
|
29
|
+
# Date:
|
30
|
+
# %Y - Year with century
|
31
|
+
# %C - year / 100 (round down. 20 in 2009)
|
32
|
+
# %y - year % 100 (00..99)
|
33
|
+
# %m - Month of the year, zero-padded (01..12)
|
34
|
+
# %d - Day of the month, zero-padded (01..31)
|
35
|
+
# %j - Day of the year (001..366)
|
36
|
+
# %U - Week number of the year. The week starts with Sunday. (00..53)
|
37
|
+
# %W - Week number of the year. The week starts with Monday. (00..53)
|
37
38
|
#
|
38
|
-
#
|
39
|
-
#
|
39
|
+
# Time:
|
40
|
+
# %H - 24 Hour of the day, zero-padded (00..23)
|
41
|
+
# %M - Minute of the hour (00..59)
|
42
|
+
# %S - Second of the minute (00..60)
|
40
43
|
#
|
41
|
-
#
|
42
|
-
#
|
44
|
+
# Examples:
|
45
|
+
# Create a log file name consisting of the short host name, process id, date, and time.
|
46
|
+
# "log/production-%n-%p-%D-%T.log"
|
43
47
|
#
|
44
|
-
#
|
45
|
-
#
|
48
|
+
# :level [:trace | :debug | :info | :warn | :error | :fatal]
|
49
|
+
# Override the log level for this appender.
|
50
|
+
# Default: SemanticLogger.default_level
|
46
51
|
#
|
47
|
-
#
|
52
|
+
# :formatter: [Object|Proc]
|
53
|
+
# An instance of a class that implements #call, or a Proc to be used to format
|
54
|
+
# the output from this appender
|
55
|
+
# Default: Use the built-in formatter (See: #call)
|
48
56
|
#
|
49
|
-
#
|
57
|
+
# :filter [Regexp|Proc]
|
58
|
+
# RegExp: Only include log messages where the class name matches the supplied
|
59
|
+
# regular expression. All other messages will be ignored.
|
60
|
+
# Proc: Only include log messages where the supplied Proc returns true
|
61
|
+
# The Proc must return true or false.
|
50
62
|
#
|
51
|
-
#
|
52
|
-
#
|
63
|
+
# :append [true|false]
|
64
|
+
# Append to the log file if already present?
|
65
|
+
# Default: true
|
53
66
|
#
|
54
|
-
#
|
55
|
-
#
|
67
|
+
# :exclusive_lock [true|false]
|
68
|
+
# Obtain an exclusive lock on the file, for operating systems that support it.
|
69
|
+
# Prevents multiple processes from trying to write to the same log file.
|
70
|
+
# Default: false
|
56
71
|
#
|
57
|
-
#
|
58
|
-
#
|
72
|
+
# :encoding ["UTF-8", "UTF-16", etc.]
|
73
|
+
# Encoding to use when writing to the file.
|
74
|
+
# Default: Encoding::BINARY
|
59
75
|
#
|
60
|
-
#
|
61
|
-
#
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
76
|
+
# :retry_count [Integer]
|
77
|
+
# Number of times to attempt to re-open the file name when an error occurs trying to
|
78
|
+
# write to the file.
|
79
|
+
# Note: Set to 0 to disable retries.
|
80
|
+
# Default: 1
|
81
|
+
#
|
82
|
+
# :reopen_period [String]
|
83
|
+
# Specify a period after which to re-open the log file, specified in minutes, hours, or days.
|
84
|
+
# The format of the duration must start with an Integer or Float number,
|
85
|
+
# followed by the duration specified as:
|
86
|
+
# "m" : minutes
|
87
|
+
# "h" : hours
|
88
|
+
# "d" : days
|
89
|
+
# The time is rounded down to the specified time interval, so that:
|
90
|
+
# - "1h" will re-open every hour at the beginning of the hour.
|
91
|
+
# - "30m" will re-open every 30 minutes at the beginning of the 30th minute.
|
92
|
+
# - "1d" will re-open every day at midnight.
|
93
|
+
# Examples:
|
94
|
+
# "60m" : Every 60 minutes at the beginning of the minute: 10:24:00, 11:24:00, 12:24:00, ...
|
95
|
+
# "1h" : Every hour at the beginning of the hour: 10:00:00, 11:00:00, 12:00:00, ...
|
96
|
+
# "1d" : Every day at the beginning of the day: "20211008 00:00:00", "20211009 00:00:00", ...
|
97
|
+
# Default: nil (Disabled)
|
98
|
+
#
|
99
|
+
# :reopen_count [Integer]
|
100
|
+
# Close and re-open the log file after every `reopen_count` number of logged entries.
|
101
|
+
# Default: 0 (Disabled)
|
102
|
+
#
|
103
|
+
# :reopen_size [Integer]
|
104
|
+
# Approximate number of bytes to write to a log file by this process before closing and re-opening it.
|
105
|
+
# Notes:
|
106
|
+
# - When `append: true` and the file already exists, it reads the size of the current log file
|
107
|
+
# and starts with that size.
|
108
|
+
# - If the current log file size already exceeds the `reopen_size`, its current size is ignored.
|
109
|
+
# - The `reopen_size` is only the amount of bytes written by this process, it excludes data
|
110
|
+
# written by other processes. Use a unique filename to prevent multiple processes from writing to
|
111
|
+
# the same log file at the same time.
|
112
|
+
# Default: 0 (Disabled)
|
113
|
+
#
|
114
|
+
# Example
|
115
|
+
# require "semantic_logger"
|
116
|
+
#
|
117
|
+
# # Enable trace level logging
|
118
|
+
# SemanticLogger.default_level = :info
|
119
|
+
#
|
120
|
+
# # Log to a file
|
121
|
+
# SemanticLogger.add_appender(file_name: "application.log", formatter: :color)
|
122
|
+
#
|
123
|
+
# logger = SemanticLogger["test"]
|
124
|
+
# logger.info "Hello World"
|
125
|
+
def initialize(file_name, retry_count: 1, append: true, reopen_period: nil, reopen_count: 0, reopen_size: 0, encoding: Encoding::BINARY, exclusive_lock: false, **args, &block)
|
126
|
+
if !file_name.is_a?(String) || file_name.empty?
|
127
|
+
raise(ArgumentError, "SemanticLogging::Appender::File file_name must be a non-empty string")
|
69
128
|
end
|
70
129
|
|
130
|
+
@file_name = file_name
|
131
|
+
@retry_count = retry_count
|
132
|
+
@file = nil
|
133
|
+
@append = append
|
134
|
+
@reopen_period = reopen_period
|
135
|
+
@reopen_count = reopen_count
|
136
|
+
@reopen_size = reopen_size
|
137
|
+
@encoding = encoding
|
138
|
+
@exclusive_lock = exclusive_lock
|
139
|
+
@log_count = 0
|
140
|
+
@log_size = 0
|
141
|
+
@reopen_at = nil
|
142
|
+
|
71
143
|
super(**args, &block)
|
72
144
|
end
|
73
145
|
|
74
146
|
# After forking an active process call #reopen to re-open
|
75
|
-
# open the file handles etc to resources
|
76
|
-
#
|
77
|
-
# Note: This method will only work if :file_name was supplied
|
78
|
-
# on the initializer.
|
79
|
-
# If :io was supplied, it will need to be re-opened manually.
|
147
|
+
# open the file handles etc to resources.
|
80
148
|
def reopen
|
81
|
-
|
149
|
+
begin
|
150
|
+
@file&.close
|
151
|
+
rescue StandardError
|
152
|
+
nil
|
153
|
+
end
|
82
154
|
|
83
|
-
|
155
|
+
self.current_file_name = apply_format_directives(file_name)
|
156
|
+
if ::File.directory?(file_name)
|
157
|
+
raise(ArgumentError, "The supplied log file_name: #{current_file_name} is already a directory.")
|
158
|
+
end
|
159
|
+
|
160
|
+
self.log_count = 0
|
161
|
+
if append && reopen_size && ::File.exist?(current_file_name)
|
162
|
+
self.log_size = ::File.size(current_file_name)
|
163
|
+
self.log_size = 0 if log_size >= reopen_size
|
164
|
+
else
|
165
|
+
self.log_size = 0
|
166
|
+
end
|
167
|
+
|
168
|
+
self.reopen_at = reopen_period ? next_reopen_period(reopen_period) : nil
|
169
|
+
|
170
|
+
options = ::File::WRONLY | ::File::CREAT
|
171
|
+
options |= ::File::APPEND if append
|
172
|
+
@file = ::File.open(current_file_name, options)
|
84
173
|
# Force all log entries to write immediately without buffering
|
85
174
|
# Allows multiple processes to write to the same log file simultaneously
|
86
|
-
@
|
87
|
-
@
|
88
|
-
@
|
175
|
+
@file.sync = true
|
176
|
+
@file.set_encoding(encoding) if @file.respond_to?(:set_encoding)
|
177
|
+
@file.flock(::File::LOCK_EX) if exclusive_lock
|
178
|
+
@file
|
89
179
|
end
|
90
180
|
|
91
|
-
#
|
92
|
-
#
|
93
|
-
# Ruby or Rails Loggers
|
181
|
+
# Since only one appender thread will be writing to the file at a time
|
182
|
+
# it is not necessary to protect access to the file with a semaphore.
|
94
183
|
def log(log)
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
184
|
+
reopen if time_to_reopen?
|
185
|
+
|
186
|
+
count = 0
|
187
|
+
begin
|
188
|
+
message = formatter.call(log, self) << "\n"
|
189
|
+
@file.write(message)
|
190
|
+
@log_count += 1
|
191
|
+
@log_size += message.size
|
192
|
+
rescue StandardError => e
|
193
|
+
if count < retry_count
|
194
|
+
count += 1
|
195
|
+
reopen
|
196
|
+
retry
|
197
|
+
end
|
198
|
+
raise(e)
|
199
|
+
end
|
99
200
|
true
|
100
201
|
end
|
101
202
|
|
102
203
|
# Flush all pending logs to disk.
|
103
|
-
# Waits for all sent documents to be
|
204
|
+
# Waits for all sent documents to be written to disk
|
104
205
|
def flush
|
105
|
-
@
|
206
|
+
@file&.flush
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
attr_writer :log_count, :log_size, :current_file_name, :reopen_at
|
212
|
+
|
213
|
+
def time_to_reopen?
|
214
|
+
return true unless @file
|
215
|
+
|
216
|
+
(reopen_count.positive? && (log_count >= reopen_count)) ||
|
217
|
+
(reopen_size.positive? && (log_size >= reopen_size)) ||
|
218
|
+
(reopen_at && (Time.now > reopen_at))
|
219
|
+
end
|
220
|
+
|
221
|
+
def apply_format_directives(file_name)
|
222
|
+
return file_name unless file_name.include?("%")
|
223
|
+
|
224
|
+
file_name.gsub(/%(.)/) { format_directive(Regexp.last_match(1)) }
|
225
|
+
end
|
226
|
+
|
227
|
+
def format_directive(directive)
|
228
|
+
case directive
|
229
|
+
when "p"
|
230
|
+
$$
|
231
|
+
when "n"
|
232
|
+
SemanticLogger.host.split(".")[0]
|
233
|
+
when "N"
|
234
|
+
SemanticLogger.host
|
235
|
+
when "a"
|
236
|
+
SemanticLogger.application
|
237
|
+
when "e"
|
238
|
+
SemanticLogger.environment
|
239
|
+
when "D"
|
240
|
+
Date.today.strftime("%Y%m%d")
|
241
|
+
when "Y", "C", "y", "m", "d", "j", "U", "W"
|
242
|
+
Date.today.strftime("%#{directive}")
|
243
|
+
when "T"
|
244
|
+
Time.now.strftime("%H%M%S")
|
245
|
+
when "H", "M", "S"
|
246
|
+
Time.now.strftime("%#{directive}")
|
247
|
+
when "%"
|
248
|
+
"%"
|
249
|
+
else
|
250
|
+
raise(ArgumentError, "Format Directive '#{directive}' in file_name: #{file_name} is not supported.")
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
def next_reopen_period(period_string)
|
255
|
+
return unless period_string
|
256
|
+
|
257
|
+
duration, period = parse_period(period_string)
|
258
|
+
calculate_reopen_at(duration, period)
|
259
|
+
end
|
260
|
+
|
261
|
+
def parse_period(period_string)
|
262
|
+
match = period_string.to_s.downcase.gsub(/\s+/, "").match(/([\d.]+)([mhd])/)
|
263
|
+
unless match
|
264
|
+
raise(ArgumentError,
|
265
|
+
"Invalid period definition: #{period_string}, must begin with an integer, followed by m,h, or d.")
|
266
|
+
end
|
267
|
+
|
268
|
+
duration = match[1]
|
269
|
+
period = match[2]
|
270
|
+
raise(ArgumentError, "Invalid or missing duration in: #{period_string}, must begin with an integer.") unless duration
|
271
|
+
raise(ArgumentError, "Invalid or missing period in: #{period_string}, must end with m,h, or d.") unless period
|
272
|
+
|
273
|
+
[duration.to_i, period]
|
274
|
+
end
|
275
|
+
|
276
|
+
# Round down the current time based on the period, then add on the duration for that period
|
277
|
+
def calculate_reopen_at(duration, period, time = Time.now)
|
278
|
+
case period
|
279
|
+
when "m"
|
280
|
+
Time.new(time.year, time.month, time.day, time.hour, time.min, 0) + (duration * 60)
|
281
|
+
when "h"
|
282
|
+
Time.new(time.year, time.month, time.day, time.hour, 0, 0) + (duration * 60 * 60)
|
283
|
+
when "d"
|
284
|
+
Time.new(time.year, time.month, time.day, 0, 0, 0) + (duration * 24 * 60 * 60)
|
285
|
+
else
|
286
|
+
raise(ArgumentError, "Invalid or missing period in: #{reopen_period}, must end with m,h, or d.")
|
287
|
+
end
|
106
288
|
end
|
107
289
|
end
|
108
290
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
1
|
+
require "uri"
|
2
2
|
begin
|
3
|
-
require
|
3
|
+
require "gelf"
|
4
4
|
rescue LoadError
|
5
|
-
raise 'Gem gelf is required for logging to Graylog. Please add the gem "gelf" to your Gemfile.'
|
5
|
+
raise LoadError, 'Gem gelf is required for logging to Graylog. Please add the gem "gelf" to your Gemfile.'
|
6
6
|
end
|
7
7
|
|
8
8
|
# Forward log entries to a Graylog server.
|
@@ -82,8 +82,8 @@ module SemanticLogger
|
|
82
82
|
# application: [String]
|
83
83
|
# Name of this application to appear in log messages.
|
84
84
|
# Default: SemanticLogger.application
|
85
|
-
def initialize(url:
|
86
|
-
max_size:
|
85
|
+
def initialize(url: "udp://localhost:12201",
|
86
|
+
max_size: "WAN",
|
87
87
|
gelf_options: {},
|
88
88
|
level_map: LevelMap.new,
|
89
89
|
**args,
|
@@ -105,7 +105,9 @@ module SemanticLogger
|
|
105
105
|
@port = uri.port
|
106
106
|
@protocol = uri.scheme.to_sym
|
107
107
|
|
108
|
-
|
108
|
+
unless %i[udp tcp].include?(@protocol)
|
109
|
+
raise(ArgumentError, "Invalid protocol value: #{@protocol}. Must be :udp or :tcp")
|
110
|
+
end
|
109
111
|
|
110
112
|
gelf_options[:protocol] ||= (@protocol == :tcp ? GELF::Protocol::TCP : GELF::Protocol::UDP)
|
111
113
|
gelf_options[:facility] ||= application
|
@@ -118,10 +120,13 @@ module SemanticLogger
|
|
118
120
|
def call(log, logger)
|
119
121
|
h = default_formatter.call(log, logger)
|
120
122
|
|
121
|
-
h[:short_message] = h.delete(:message)
|
122
|
-
h[:
|
123
|
-
|
124
|
-
|
123
|
+
h[:short_message] = h.delete(:message)
|
124
|
+
if h[:short_message].nil?
|
125
|
+
h[:short_message] = log.exception.nil? ? "<no-exception-message>" : log.exception.message
|
126
|
+
end
|
127
|
+
h[:level] = logger.level_map[log.level]
|
128
|
+
h[:level_str] = log.level.to_s
|
129
|
+
h[:duration_str] = h.delete(:duration)
|
125
130
|
h
|
126
131
|
end
|
127
132
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
begin
|
2
|
-
require
|
2
|
+
require "honeybadger"
|
3
3
|
rescue LoadError
|
4
|
-
raise 'Gem honeybadger is required for logging purposes. Please add the gem "honeybadger" to your Gemfile.'
|
4
|
+
raise LoadError, 'Gem honeybadger is required for logging purposes. Please add the gem "honeybadger" to your Gemfile.'
|
5
5
|
end
|
6
6
|
|
7
7
|
# Send log messages to honeybadger
|
@@ -48,7 +48,7 @@ module SemanticLogger
|
|
48
48
|
context.delete(:exception)
|
49
49
|
::Honeybadger.notify(log.exception, context)
|
50
50
|
else
|
51
|
-
message
|
51
|
+
message = {
|
52
52
|
error_class: context.delete(:name),
|
53
53
|
error_message: context.delete(:message),
|
54
54
|
context: context
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
1
|
+
require "net/http"
|
2
|
+
require "uri"
|
3
|
+
require "socket"
|
4
|
+
require "json"
|
5
|
+
require "openssl"
|
6
6
|
|
7
7
|
# Log to any HTTP(S) server that accepts log messages in JSON form
|
8
8
|
#
|
@@ -21,7 +21,7 @@ module SemanticLogger
|
|
21
21
|
class Http < SemanticLogger::Subscriber
|
22
22
|
attr_accessor :username, :compress, :header,
|
23
23
|
:open_timeout, :read_timeout, :continue_timeout
|
24
|
-
attr_reader :http, :url, :server, :port, :path, :ssl_options
|
24
|
+
attr_reader :http, :url, :server, :port, :path, :ssl_options, :proxy_url
|
25
25
|
|
26
26
|
# Create HTTP(S) log appender
|
27
27
|
#
|
@@ -57,6 +57,16 @@ module SemanticLogger
|
|
57
57
|
# ca_file, ca_path, cert, cert_store, ciphers, key, ssl_timeout,
|
58
58
|
# ssl_version, verify_callback, verify_depth and verify_mode.
|
59
59
|
#
|
60
|
+
# proxy_url: [String]
|
61
|
+
# URL of proxy server to use for HTTP(s) connections. Should
|
62
|
+
# include username and password if required.
|
63
|
+
# Example: http://user@pass:example.com/some_path
|
64
|
+
# To enable SSL include https in the URL.
|
65
|
+
# Example: https://example.com/some_path
|
66
|
+
# If this is set to :ENV, Net::HTTP will use the environment http_proxy*
|
67
|
+
# variables if they are set. If set to nil then no proxy will be used,
|
68
|
+
# even if the environment variables are set.
|
69
|
+
#
|
60
70
|
# level: [:trace | :debug | :info | :warn | :error | :fatal]
|
61
71
|
# Override the log level for this appender.
|
62
72
|
# Default: SemanticLogger.default_level
|
@@ -85,6 +95,7 @@ module SemanticLogger
|
|
85
95
|
ssl: {},
|
86
96
|
username: nil,
|
87
97
|
password: nil,
|
98
|
+
proxy_url: :ENV,
|
88
99
|
open_timeout: 2.0,
|
89
100
|
read_timeout: 1.0,
|
90
101
|
continue_timeout: 1.0,
|
@@ -92,6 +103,7 @@ module SemanticLogger
|
|
92
103
|
&block)
|
93
104
|
|
94
105
|
@url = url
|
106
|
+
@proxy_url = proxy_url
|
95
107
|
@ssl_options = ssl
|
96
108
|
@username = username
|
97
109
|
@password = password
|
@@ -101,32 +113,37 @@ module SemanticLogger
|
|
101
113
|
@continue_timeout = continue_timeout
|
102
114
|
|
103
115
|
# On Ruby v2.0 and greater, Net::HTTP.new already uses a persistent connection if the server allows it
|
104
|
-
@header
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
116
|
+
@header = {
|
117
|
+
"Accept" => "application/json",
|
118
|
+
"Content-Type" => "application/json",
|
119
|
+
"Connection" => "keep-alive",
|
120
|
+
"Keep-Alive" => "300"
|
109
121
|
}
|
110
|
-
@header[
|
122
|
+
@header["Content-Encoding"] = "gzip" if @compress
|
111
123
|
|
112
124
|
uri = URI.parse(@url)
|
113
125
|
@server = uri.host
|
114
|
-
|
126
|
+
unless @server
|
127
|
+
raise(ArgumentError, "Invalid format for :url: #{@url.inspect}. Should be similar to: 'http://hostname:port/path'")
|
128
|
+
end
|
115
129
|
|
116
130
|
@port = uri.port
|
117
131
|
@username = uri.user if !@username && uri.user
|
118
132
|
@password = uri.password if !@password && uri.password
|
119
133
|
@path = uri.path
|
120
134
|
# Path cannot be empty
|
121
|
-
@path =
|
135
|
+
@path = "/" if @path == ""
|
122
136
|
|
123
|
-
if uri.scheme ==
|
124
|
-
@ssl_options[:use_ssl]
|
137
|
+
if uri.scheme == "https"
|
138
|
+
@ssl_options[:use_ssl] = true
|
125
139
|
@ssl_options[:verify_mode] ||= OpenSSL::SSL::VERIFY_PEER
|
126
140
|
@port ||= HTTP.https_default_port
|
127
141
|
else
|
128
142
|
@port ||= HTTP.http_default_port
|
129
143
|
end
|
144
|
+
|
145
|
+
@proxy_uri = URI.parse(@proxy_url) if @proxy_url && @proxy_url != :ENV
|
146
|
+
|
130
147
|
@http = nil
|
131
148
|
|
132
149
|
super(**args, &block)
|
@@ -142,7 +159,11 @@ module SemanticLogger
|
|
142
159
|
nil
|
143
160
|
end
|
144
161
|
|
145
|
-
@http =
|
162
|
+
@http = if @proxy_uri
|
163
|
+
Net::HTTP.new(server, port, @proxy_uri.host, @proxy_uri.port, @proxy_uri.user, @proxy_uri.password)
|
164
|
+
else
|
165
|
+
Net::HTTP.new(server, port, @proxy_url)
|
166
|
+
end
|
146
167
|
|
147
168
|
if @ssl_options
|
148
169
|
@http.methods.grep(/\A(\w+)=\z/) do |meth|
|
@@ -205,16 +226,16 @@ module SemanticLogger
|
|
205
226
|
end
|
206
227
|
request.basic_auth(@username, @password) if @username
|
207
228
|
response = @http.request(request)
|
208
|
-
if response.code ==
|
229
|
+
if response.code == "200" || response.code == "201"
|
209
230
|
true
|
210
231
|
else
|
211
232
|
# Failures are logged to the global semantic logger failsafe logger (Usually stderr or file)
|
212
233
|
logger.error("Bad HTTP response from: #{url} code: #{response.code}, #{response.body}")
|
213
234
|
false
|
214
235
|
end
|
215
|
-
rescue RuntimeError =>
|
236
|
+
rescue RuntimeError => e
|
216
237
|
reopen
|
217
|
-
raise
|
238
|
+
raise e
|
218
239
|
end
|
219
240
|
end
|
220
241
|
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# File appender
|
2
|
+
#
|
3
|
+
# Writes log messages to a file or open iostream
|
4
|
+
#
|
5
|
+
module SemanticLogger
|
6
|
+
module Appender
|
7
|
+
class IO < SemanticLogger::Subscriber
|
8
|
+
# Create a Stream Logger appender instance.
|
9
|
+
#
|
10
|
+
# Parameters
|
11
|
+
# io [IO]
|
12
|
+
# An IO stream to which to write the log messages to.
|
13
|
+
#
|
14
|
+
# :level [:trace | :debug | :info | :warn | :error | :fatal]
|
15
|
+
# Override the log level for this appender.
|
16
|
+
# Default: SemanticLogger.default_level
|
17
|
+
#
|
18
|
+
# :formatter: [Object|Proc]
|
19
|
+
# An instance of a class that implements #call, or a Proc to be used to format
|
20
|
+
# the output from this appender
|
21
|
+
# Default: Use the built-in formatter (See: #call)
|
22
|
+
#
|
23
|
+
# :filter [Regexp|Proc]
|
24
|
+
# RegExp: Only include log messages where the class name matches the supplied
|
25
|
+
# regular expression. All other messages will be ignored.
|
26
|
+
# Proc: Only include log messages where the supplied Proc returns true
|
27
|
+
# The Proc must return true or false.
|
28
|
+
#
|
29
|
+
# Example
|
30
|
+
# require "semantic_logger"
|
31
|
+
#
|
32
|
+
# # Enable trace level logging
|
33
|
+
# SemanticLogger.default_level = :info
|
34
|
+
#
|
35
|
+
# # Log to screen
|
36
|
+
# SemanticLogger.add_appender(io: $stdout, formatter: :color)
|
37
|
+
#
|
38
|
+
# logger = SemanticLogger['test']
|
39
|
+
# logger.info 'Hello World'
|
40
|
+
def initialize(io, **args, &block)
|
41
|
+
@io = io
|
42
|
+
unless @io.respond_to?(:write)
|
43
|
+
raise(ArgumentError, "SemanticLogging::Appender::IO io is not a valid IO instance: #{io.inspect}")
|
44
|
+
end
|
45
|
+
|
46
|
+
super(**args, &block)
|
47
|
+
end
|
48
|
+
|
49
|
+
def log(log)
|
50
|
+
# Since only one appender thread will be writing to the file at a time
|
51
|
+
# it is not necessary to protect access to the file with a semaphore
|
52
|
+
# Allow this logger to filter out log levels lower than it's own
|
53
|
+
@io.write(formatter.call(log, self) << "\n")
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
# Flush all pending logs to disk.
|
58
|
+
# Waits for all sent documents to be written to disk
|
59
|
+
def flush
|
60
|
+
@io.flush if @io.respond_to?(:flush)
|
61
|
+
end
|
62
|
+
|
63
|
+
def console_output?
|
64
|
+
[$stderr, $stdout].include?(@io)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|