semantic_logger 4.5.0 → 4.12.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +51 -21
  3. data/Rakefile +7 -7
  4. data/lib/semantic_logger/ansi_colors.rb +0 -10
  5. data/lib/semantic_logger/appender/async.rb +12 -10
  6. data/lib/semantic_logger/appender/async_batch.rb +7 -3
  7. data/lib/semantic_logger/appender/bugsnag.rb +43 -30
  8. data/lib/semantic_logger/appender/elasticsearch.rb +34 -15
  9. data/lib/semantic_logger/appender/elasticsearch_http.rb +4 -4
  10. data/lib/semantic_logger/appender/file.rb +249 -67
  11. data/lib/semantic_logger/appender/graylog.rb +15 -10
  12. data/lib/semantic_logger/appender/honeybadger.rb +3 -3
  13. data/lib/semantic_logger/appender/http.rb +41 -20
  14. data/lib/semantic_logger/appender/io.rb +68 -0
  15. data/lib/semantic_logger/appender/kafka.rb +46 -31
  16. data/lib/semantic_logger/appender/mongodb.rb +6 -6
  17. data/lib/semantic_logger/appender/new_relic.rb +2 -2
  18. data/lib/semantic_logger/appender/rabbitmq.rb +5 -5
  19. data/lib/semantic_logger/appender/sentry.rb +7 -7
  20. data/lib/semantic_logger/appender/sentry_ruby.rb +138 -0
  21. data/lib/semantic_logger/appender/splunk.rb +7 -5
  22. data/lib/semantic_logger/appender/splunk_http.rb +6 -5
  23. data/lib/semantic_logger/appender/syslog.rb +23 -15
  24. data/lib/semantic_logger/appender/tcp.rb +9 -9
  25. data/lib/semantic_logger/appender/udp.rb +2 -2
  26. data/lib/semantic_logger/appender/wrapper.rb +3 -2
  27. data/lib/semantic_logger/appender.rb +62 -65
  28. data/lib/semantic_logger/appenders.rb +36 -53
  29. data/lib/semantic_logger/base.rb +61 -39
  30. data/lib/semantic_logger/formatters/base.rb +16 -6
  31. data/lib/semantic_logger/formatters/color.rb +14 -15
  32. data/lib/semantic_logger/formatters/default.rb +18 -5
  33. data/lib/semantic_logger/formatters/fluentd.rb +7 -18
  34. data/lib/semantic_logger/formatters/json.rb +3 -5
  35. data/lib/semantic_logger/formatters/logfmt.rb +77 -0
  36. data/lib/semantic_logger/formatters/raw.rb +39 -10
  37. data/lib/semantic_logger/formatters/signalfx.rb +14 -21
  38. data/lib/semantic_logger/formatters/syslog.rb +8 -6
  39. data/lib/semantic_logger/formatters/syslog_cee.rb +9 -7
  40. data/lib/semantic_logger/formatters.rb +13 -13
  41. data/lib/semantic_logger/jruby/garbage_collection_logger.rb +4 -2
  42. data/lib/semantic_logger/levels.rb +9 -7
  43. data/lib/semantic_logger/log.rb +58 -73
  44. data/lib/semantic_logger/loggable.rb +8 -1
  45. data/lib/semantic_logger/logger.rb +19 -11
  46. data/lib/semantic_logger/metric/new_relic.rb +3 -3
  47. data/lib/semantic_logger/metric/signalfx.rb +3 -3
  48. data/lib/semantic_logger/metric/statsd.rb +7 -7
  49. data/lib/semantic_logger/processor.rb +9 -7
  50. data/lib/semantic_logger/reporters/minitest.rb +4 -4
  51. data/lib/semantic_logger/semantic_logger.rb +57 -23
  52. data/lib/semantic_logger/subscriber.rb +24 -7
  53. data/lib/semantic_logger/sync.rb +12 -0
  54. data/lib/semantic_logger/sync_processor.rb +58 -0
  55. data/lib/semantic_logger/test/capture_log_events.rb +34 -0
  56. data/lib/semantic_logger/utils.rb +32 -13
  57. data/lib/semantic_logger/version.rb +1 -1
  58. data/lib/semantic_logger.rb +27 -22
  59. 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 '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
@@ -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) || log.exception.message
122
- h[:level] = logger.level_map[log.level]
123
- h[:level_str] = log.level.to_s
124
- h[:duration_str] = h.delete(:duration)
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 'honeybadger'
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 '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
  #
@@ -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
- 'Accept' => 'application/json',
106
- 'Content-Type' => 'application/json',
107
- 'Connection' => 'keep-alive',
108
- 'Keep-Alive' => '300'
116
+ @header = {
117
+ "Accept" => "application/json",
118
+ "Content-Type" => "application/json",
119
+ "Connection" => "keep-alive",
120
+ "Keep-Alive" => "300"
109
121
  }
110
- @header['Content-Encoding'] = 'gzip' if @compress
122
+ @header["Content-Encoding"] = "gzip" if @compress
111
123
 
112
124
  uri = URI.parse(@url)
113
125
  @server = uri.host
114
- raise(ArgumentError, "Invalid format for :url: #{@url.inspect}. Should be similar to: 'http://hostname:port/path'") unless @server
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 = '/' if @path == ''
135
+ @path = "/" if @path == ""
122
136
 
123
- if uri.scheme == 'https'
124
- @ssl_options[:use_ssl] = true
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 = Net::HTTP.new(server, port)
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 == '200' || response.code == '201'
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 => exc
236
+ rescue RuntimeError => e
216
237
  reopen
217
- raise exc
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