semantic_logger 4.6.1 → 4.10.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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +50 -20
  3. data/Rakefile +7 -7
  4. data/lib/semantic_logger/appender/async.rb +10 -9
  5. data/lib/semantic_logger/appender/async_batch.rb +7 -3
  6. data/lib/semantic_logger/appender/bugsnag.rb +43 -30
  7. data/lib/semantic_logger/appender/elasticsearch.rb +32 -14
  8. data/lib/semantic_logger/appender/elasticsearch_http.rb +4 -4
  9. data/lib/semantic_logger/appender/file.rb +249 -67
  10. data/lib/semantic_logger/appender/graylog.rb +12 -10
  11. data/lib/semantic_logger/appender/honeybadger.rb +3 -3
  12. data/lib/semantic_logger/appender/http.rb +20 -18
  13. data/lib/semantic_logger/appender/io.rb +68 -0
  14. data/lib/semantic_logger/appender/kafka.rb +9 -5
  15. data/lib/semantic_logger/appender/mongodb.rb +6 -6
  16. data/lib/semantic_logger/appender/new_relic.rb +2 -2
  17. data/lib/semantic_logger/appender/rabbitmq.rb +5 -5
  18. data/lib/semantic_logger/appender/sentry.rb +7 -7
  19. data/lib/semantic_logger/appender/sentry_ruby.rb +138 -0
  20. data/lib/semantic_logger/appender/splunk.rb +6 -5
  21. data/lib/semantic_logger/appender/splunk_http.rb +6 -6
  22. data/lib/semantic_logger/appender/syslog.rb +23 -15
  23. data/lib/semantic_logger/appender/tcp.rb +5 -5
  24. data/lib/semantic_logger/appender/udp.rb +2 -2
  25. data/lib/semantic_logger/appender/wrapper.rb +3 -2
  26. data/lib/semantic_logger/appender.rb +42 -36
  27. data/lib/semantic_logger/appenders.rb +34 -30
  28. data/lib/semantic_logger/base.rb +57 -27
  29. data/lib/semantic_logger/formatters/base.rb +9 -3
  30. data/lib/semantic_logger/formatters/color.rb +12 -8
  31. data/lib/semantic_logger/formatters/default.rb +18 -5
  32. data/lib/semantic_logger/formatters/fluentd.rb +3 -3
  33. data/lib/semantic_logger/formatters/json.rb +1 -1
  34. data/lib/semantic_logger/formatters/logfmt.rb +72 -0
  35. data/lib/semantic_logger/formatters/raw.rb +31 -7
  36. data/lib/semantic_logger/formatters/signalfx.rb +10 -9
  37. data/lib/semantic_logger/formatters/syslog.rb +8 -6
  38. data/lib/semantic_logger/formatters/syslog_cee.rb +8 -6
  39. data/lib/semantic_logger/formatters.rb +12 -13
  40. data/lib/semantic_logger/jruby/garbage_collection_logger.rb +4 -2
  41. data/lib/semantic_logger/levels.rb +9 -7
  42. data/lib/semantic_logger/log.rb +51 -61
  43. data/lib/semantic_logger/loggable.rb +8 -1
  44. data/lib/semantic_logger/logger.rb +19 -11
  45. data/lib/semantic_logger/metric/new_relic.rb +3 -3
  46. data/lib/semantic_logger/metric/signalfx.rb +3 -3
  47. data/lib/semantic_logger/metric/statsd.rb +7 -7
  48. data/lib/semantic_logger/processor.rb +9 -7
  49. data/lib/semantic_logger/reporters/minitest.rb +4 -4
  50. data/lib/semantic_logger/semantic_logger.rb +40 -19
  51. data/lib/semantic_logger/subscriber.rb +16 -5
  52. data/lib/semantic_logger/sync.rb +12 -0
  53. data/lib/semantic_logger/sync_processor.rb +43 -0
  54. data/lib/semantic_logger/test/capture_log_events.rb +34 -0
  55. data/lib/semantic_logger/utils.rb +32 -13
  56. data/lib/semantic_logger/version.rb +1 -1
  57. data/lib/semantic_logger.rb +27 -22
  58. 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
- # Create a File Logger appender instance.
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
- # :file_name [String]
12
- # Name of file to write to.
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
- # Example
33
- # require 'semantic_logger'
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
- # # Enable trace level logging
36
- # SemanticLogger.default_level = :info
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
- # # Log to screen
39
- # SemanticLogger.add_appender(io: STDOUT, formatter: :color)
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
- # # And log to a file at the same time
42
- # SemanticLogger.add_appender(file_name: 'application.log', formatter: :color)
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
- # logger = SemanticLogger['test']
45
- # logger.info 'Hello World'
48
+ # :level [:trace | :debug | :info | :warn | :error | :fatal]
49
+ # Override the log level for this appender.
50
+ # Default: SemanticLogger.default_level
46
51
  #
47
- # Example 2. To log all levels to file and only :info and above to screen:
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
- # require 'semantic_logger'
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
- # # Enable trace level logging
52
- # SemanticLogger.default_level = :trace
63
+ # :append [true|false]
64
+ # Append to the log file if already present?
65
+ # Default: true
53
66
  #
54
- # # Log to screen but only display :info and above
55
- # SemanticLogger.add_appender(io: STDOUT, level: :info)
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
- # # And log to a file at the same time, including all :trace level data
58
- # SemanticLogger.add_appender(file_name: 'application.log')
72
+ # :encoding ["UTF-8", "UTF-16", etc.]
73
+ # Encoding to use when writing to the file.
74
+ # Default: Encoding::BINARY
59
75
  #
60
- # logger = SemanticLogger['test']
61
- # logger.info 'Hello World'
62
- def initialize(io: nil, file_name: nil, **args, &block)
63
- if io
64
- @log = io
65
- else
66
- @file_name = file_name
67
- raise 'SemanticLogging::Appender::File missing mandatory parameter :file_name or :io' unless file_name
68
- reopen
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
- return unless @file_name
149
+ begin
150
+ @file&.close
151
+ rescue StandardError
152
+ nil
153
+ end
82
154
 
83
- @log = ::File.open(@file_name, ::File::WRONLY | ::File::APPEND | ::File::CREAT)
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
- @log.sync = true
87
- @log.set_encoding(Encoding::BINARY) if @log.respond_to?(:set_encoding)
88
- @log
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
- # Pass log calls to the underlying Rails, log4j or Ruby logger
92
- # trace entries are mapped to debug since :trace is not supported by the
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
- # Since only one appender thread will be writing to the file at a time
96
- # it is not necessary to protect access to the file with a semaphore
97
- # Allow this logger to filter out log levels lower than it's own
98
- @log.write(formatter.call(log, self) << "\n")
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 writted to disk
204
+ # Waits for all sent documents to be written to disk
104
205
  def flush
105
- @log.flush if @log.respond_to?(:flush)
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 'uri'
1
+ require "uri"
2
2
  begin
3
- require 'gelf'
3
+ require "gelf"
4
4
  rescue LoadError
5
- raise LoadError.new('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: 'udp://localhost:12201',
86
- max_size: 'WAN',
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
- raise(ArgumentError, "Invalid protocol value: #{@protocol}. Must be :udp or :tcp") unless %i[udp tcp].include?(@protocol)
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? ? '<no-exception-message>' : log.exception.message
125
+ h[:short_message] = log.exception.nil? ? "<no-exception-message>" : log.exception.message
124
126
  end
125
- h[:level] = logger.level_map[log.level]
126
- h[:level_str] = log.level.to_s
127
- h[:duration_str] = h.delete(:duration)
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 'honeybadger'
2
+ require "honeybadger"
3
3
  rescue LoadError
4
- raise LoadError.new('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 'net/http'
2
- require 'uri'
3
- require 'socket'
4
- require 'json'
5
- require 'openssl'
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
- 'Accept' => 'application/json',
106
- 'Content-Type' => 'application/json',
107
- 'Connection' => 'keep-alive',
108
- 'Keep-Alive' => '300'
104
+ @header = {
105
+ "Accept" => "application/json",
106
+ "Content-Type" => "application/json",
107
+ "Connection" => "keep-alive",
108
+ "Keep-Alive" => "300"
109
109
  }
110
- @header['Content-Encoding'] = 'gzip' if @compress
110
+ @header["Content-Encoding"] = "gzip" if @compress
111
111
 
112
112
  uri = URI.parse(@url)
113
113
  @server = uri.host
114
- raise(ArgumentError, "Invalid format for :url: #{@url.inspect}. Should be similar to: 'http://hostname:port/path'") unless @server
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 = '/' if @path == ''
123
+ @path = "/" if @path == ""
122
124
 
123
- if uri.scheme == 'https'
124
- @ssl_options[:use_ssl] = true
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 == '200' || response.code == '201'
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 => exc
217
+ rescue RuntimeError => e
216
218
  reopen
217
- raise exc
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 'kafka'
2
+ require "kafka"
3
3
  rescue LoadError
4
- raise LoadError.new('Gem ruby-kafka is required for logging to Elasticsearch. Please add the gem "ruby-kafka" to your Gemfile.')
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 'date'
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: 'semantic-logger', connect_timeout: nil, socket_timeout: nil,
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: 'log_messages', partition: nil, partition_key: nil, key: nil,
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 'socket'
1
+ require "socket"
2
2
  begin
3
- require 'mongo'
3
+ require "mongo"
4
4
  rescue LoadError
5
- raise LoadError.new('Gem mongo is required for logging to MongoDB. Please add the gem "mongo" v2.0 or greater to your Gemfile.')
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: 'semantic_logger',
107
+ collection_name: "semantic_logger",
108
108
  write_concern: 0,
109
- collection_size: 1024 ** 3,
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] = collection_max if collection_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 'newrelic_rpm'
2
+ require "newrelic_rpm"
3
3
  rescue LoadError
4
- raise LoadError.new('Gem newrelic_rpm is required for logging to New Relic. Please add the gem "newrelic_rpm" to your Gemfile.')
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