semantic_logger 4.6.1 → 4.10.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +50 -20
- data/Rakefile +7 -7
- data/lib/semantic_logger/appender/async.rb +10 -9
- 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 +32 -14
- 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 +12 -10
- data/lib/semantic_logger/appender/honeybadger.rb +3 -3
- data/lib/semantic_logger/appender/http.rb +20 -18
- data/lib/semantic_logger/appender/io.rb +68 -0
- data/lib/semantic_logger/appender/kafka.rb +9 -5
- 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 +6 -5
- data/lib/semantic_logger/appender/splunk_http.rb +6 -6
- data/lib/semantic_logger/appender/syslog.rb +23 -15
- data/lib/semantic_logger/appender/tcp.rb +5 -5
- data/lib/semantic_logger/appender/udp.rb +2 -2
- data/lib/semantic_logger/appender/wrapper.rb +3 -2
- data/lib/semantic_logger/appender.rb +42 -36
- data/lib/semantic_logger/appenders.rb +34 -30
- data/lib/semantic_logger/base.rb +57 -27
- data/lib/semantic_logger/formatters/base.rb +9 -3
- data/lib/semantic_logger/formatters/color.rb +12 -8
- data/lib/semantic_logger/formatters/default.rb +18 -5
- data/lib/semantic_logger/formatters/fluentd.rb +3 -3
- data/lib/semantic_logger/formatters/json.rb +1 -1
- data/lib/semantic_logger/formatters/logfmt.rb +72 -0
- data/lib/semantic_logger/formatters/raw.rb +31 -7
- data/lib/semantic_logger/formatters/signalfx.rb +10 -9
- data/lib/semantic_logger/formatters/syslog.rb +8 -6
- data/lib/semantic_logger/formatters/syslog_cee.rb +8 -6
- data/lib/semantic_logger/formatters.rb +12 -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 +51 -61
- 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 +40 -19
- data/lib/semantic_logger/subscriber.rb +16 -5
- data/lib/semantic_logger/sync.rb +12 -0
- data/lib/semantic_logger/sync_processor.rb +43 -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 LoadError
|
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
|
@@ -120,11 +122,11 @@ module SemanticLogger
|
|
120
122
|
|
121
123
|
h[:short_message] = h.delete(:message)
|
122
124
|
if h[:short_message].nil?
|
123
|
-
h[:short_message] = log.exception.nil?
|
125
|
+
h[:short_message] = log.exception.nil? ? "<no-exception-message>" : log.exception.message
|
124
126
|
end
|
125
|
-
h[:level]
|
126
|
-
h[:level_str]
|
127
|
-
h[:duration_str]
|
127
|
+
h[:level] = logger.level_map[log.level]
|
128
|
+
h[:level_str] = log.level.to_s
|
129
|
+
h[:duration_str] = h.delete(:duration)
|
128
130
|
h
|
129
131
|
end
|
130
132
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
begin
|
2
|
-
require
|
2
|
+
require "honeybadger"
|
3
3
|
rescue LoadError
|
4
|
-
raise LoadError
|
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
|
#
|
@@ -101,27 +101,29 @@ module SemanticLogger
|
|
101
101
|
@continue_timeout = continue_timeout
|
102
102
|
|
103
103
|
# 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
|
-
|
104
|
+
@header = {
|
105
|
+
"Accept" => "application/json",
|
106
|
+
"Content-Type" => "application/json",
|
107
|
+
"Connection" => "keep-alive",
|
108
|
+
"Keep-Alive" => "300"
|
109
109
|
}
|
110
|
-
@header[
|
110
|
+
@header["Content-Encoding"] = "gzip" if @compress
|
111
111
|
|
112
112
|
uri = URI.parse(@url)
|
113
113
|
@server = uri.host
|
114
|
-
|
114
|
+
unless @server
|
115
|
+
raise(ArgumentError, "Invalid format for :url: #{@url.inspect}. Should be similar to: 'http://hostname:port/path'")
|
116
|
+
end
|
115
117
|
|
116
118
|
@port = uri.port
|
117
119
|
@username = uri.user if !@username && uri.user
|
118
120
|
@password = uri.password if !@password && uri.password
|
119
121
|
@path = uri.path
|
120
122
|
# Path cannot be empty
|
121
|
-
@path =
|
123
|
+
@path = "/" if @path == ""
|
122
124
|
|
123
|
-
if uri.scheme ==
|
124
|
-
@ssl_options[:use_ssl]
|
125
|
+
if uri.scheme == "https"
|
126
|
+
@ssl_options[:use_ssl] = true
|
125
127
|
@ssl_options[:verify_mode] ||= OpenSSL::SSL::VERIFY_PEER
|
126
128
|
@port ||= HTTP.https_default_port
|
127
129
|
else
|
@@ -205,16 +207,16 @@ module SemanticLogger
|
|
205
207
|
end
|
206
208
|
request.basic_auth(@username, @password) if @username
|
207
209
|
response = @http.request(request)
|
208
|
-
if response.code ==
|
210
|
+
if response.code == "200" || response.code == "201"
|
209
211
|
true
|
210
212
|
else
|
211
213
|
# Failures are logged to the global semantic logger failsafe logger (Usually stderr or file)
|
212
214
|
logger.error("Bad HTTP response from: #{url} code: #{response.code}, #{response.body}")
|
213
215
|
false
|
214
216
|
end
|
215
|
-
rescue RuntimeError =>
|
217
|
+
rescue RuntimeError => e
|
216
218
|
reopen
|
217
|
-
raise
|
219
|
+
raise e
|
218
220
|
end
|
219
221
|
end
|
220
222
|
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
|
@@ -1,10 +1,10 @@
|
|
1
1
|
begin
|
2
|
-
require
|
2
|
+
require "kafka"
|
3
3
|
rescue LoadError
|
4
|
-
raise LoadError
|
4
|
+
raise LoadError, 'Gem ruby-kafka is required for logging to Elasticsearch. Please add the gem "ruby-kafka" to your Gemfile.'
|
5
5
|
end
|
6
6
|
|
7
|
-
require
|
7
|
+
require "date"
|
8
8
|
|
9
9
|
# Forward all log messages to Apache Kafka.
|
10
10
|
#
|
@@ -115,9 +115,9 @@ module SemanticLogger
|
|
115
115
|
# metrics: [Boolean]
|
116
116
|
# Send metrics only events to kafka.
|
117
117
|
# Default: true
|
118
|
-
def initialize(seed_brokers:, client_id:
|
118
|
+
def initialize(seed_brokers:, client_id: "semantic-logger", connect_timeout: nil, socket_timeout: nil,
|
119
119
|
ssl_ca_cert: nil, ssl_client_cert: nil, ssl_client_cert_key: nil,
|
120
|
-
topic:
|
120
|
+
topic: "log_messages", partition: nil, partition_key: nil, key: nil,
|
121
121
|
delivery_threshold: 100, delivery_interval: 10,
|
122
122
|
metrics: true, **args, &block)
|
123
123
|
|
@@ -183,6 +183,10 @@ module SemanticLogger
|
|
183
183
|
delivery_interval: delivery_interval
|
184
184
|
)
|
185
185
|
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
attr_reader :producer
|
186
190
|
end
|
187
191
|
end
|
188
192
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
1
|
+
require "socket"
|
2
2
|
begin
|
3
|
-
require
|
3
|
+
require "mongo"
|
4
4
|
rescue LoadError
|
5
|
-
raise LoadError
|
5
|
+
raise LoadError, 'Gem mongo is required for logging to MongoDB. Please add the gem "mongo" v2.0 or greater to your Gemfile.'
|
6
6
|
end
|
7
7
|
|
8
8
|
module SemanticLogger
|
@@ -104,9 +104,9 @@ module SemanticLogger
|
|
104
104
|
# Name of this application to appear in log messages.
|
105
105
|
# Default: SemanticLogger.application
|
106
106
|
def initialize(uri:,
|
107
|
-
collection_name:
|
107
|
+
collection_name: "semantic_logger",
|
108
108
|
write_concern: 0,
|
109
|
-
collection_size: 1024
|
109
|
+
collection_size: 1024**3,
|
110
110
|
collection_max: nil,
|
111
111
|
**args,
|
112
112
|
&block)
|
@@ -118,7 +118,7 @@ module SemanticLogger
|
|
118
118
|
size: collection_size,
|
119
119
|
write: {w: write_concern}
|
120
120
|
}
|
121
|
-
@options[:max]
|
121
|
+
@options[:max] = collection_max if collection_max
|
122
122
|
|
123
123
|
reopen
|
124
124
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
begin
|
2
|
-
require
|
2
|
+
require "newrelic_rpm"
|
3
3
|
rescue LoadError
|
4
|
-
raise LoadError
|
4
|
+
raise LoadError, 'Gem newrelic_rpm is required for logging to New Relic. Please add the gem "newrelic_rpm" to your Gemfile.'
|
5
5
|
end
|
6
6
|
|
7
7
|
# Send log messages to NewRelic
|