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.
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