semantic_logger 4.7.4 → 4.17.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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +59 -25
  3. data/lib/semantic_logger/appender/async.rb +2 -3
  4. data/lib/semantic_logger/appender/async_batch.rb +4 -5
  5. data/lib/semantic_logger/appender/bugsnag.rb +3 -2
  6. data/lib/semantic_logger/appender/cloudwatch_logs.rb +150 -0
  7. data/lib/semantic_logger/appender/elasticsearch.rb +25 -7
  8. data/lib/semantic_logger/appender/elasticsearch_http.rb +1 -1
  9. data/lib/semantic_logger/appender/file.rb +253 -68
  10. data/lib/semantic_logger/appender/graylog.rb +2 -2
  11. data/lib/semantic_logger/appender/honeybadger.rb +1 -1
  12. data/lib/semantic_logger/appender/honeybadger_insights.rb +61 -0
  13. data/lib/semantic_logger/appender/http.rb +29 -5
  14. data/lib/semantic_logger/appender/io.rb +68 -0
  15. data/lib/semantic_logger/appender/kafka.rb +43 -28
  16. data/lib/semantic_logger/appender/loki.rb +62 -0
  17. data/lib/semantic_logger/appender/mongodb.rb +2 -2
  18. data/lib/semantic_logger/appender/new_relic.rb +3 -2
  19. data/lib/semantic_logger/appender/new_relic_logs.rb +68 -0
  20. data/lib/semantic_logger/appender/open_telemetry.rb +83 -0
  21. data/lib/semantic_logger/appender/rabbitmq.rb +8 -2
  22. data/lib/semantic_logger/appender/sentry.rb +3 -2
  23. data/lib/semantic_logger/appender/sentry_ruby.rb +138 -0
  24. data/lib/semantic_logger/appender/splunk.rb +4 -3
  25. data/lib/semantic_logger/appender/splunk_http.rb +2 -3
  26. data/lib/semantic_logger/appender/syslog.rb +7 -6
  27. data/lib/semantic_logger/appender/tcp.rb +3 -2
  28. data/lib/semantic_logger/appender/wrapper.rb +12 -5
  29. data/lib/semantic_logger/appender.rb +38 -26
  30. data/lib/semantic_logger/appenders.rb +35 -27
  31. data/lib/semantic_logger/base.rb +23 -26
  32. data/lib/semantic_logger/debug_as_trace_logger.rb +4 -15
  33. data/lib/semantic_logger/formatters/base.rb +3 -2
  34. data/lib/semantic_logger/formatters/color.rb +4 -4
  35. data/lib/semantic_logger/formatters/fluentd.rb +1 -1
  36. data/lib/semantic_logger/formatters/json.rb +2 -2
  37. data/lib/semantic_logger/formatters/logfmt.rb +77 -0
  38. data/lib/semantic_logger/formatters/loki.rb +157 -0
  39. data/lib/semantic_logger/formatters/new_relic_logs.rb +126 -0
  40. data/lib/semantic_logger/formatters/open_telemetry.rb +40 -0
  41. data/lib/semantic_logger/formatters/raw.rb +1 -1
  42. data/lib/semantic_logger/formatters/signalfx.rb +8 -3
  43. data/lib/semantic_logger/formatters/syslog.rb +5 -3
  44. data/lib/semantic_logger/formatters/syslog_cee.rb +6 -4
  45. data/lib/semantic_logger/formatters.rb +14 -11
  46. data/lib/semantic_logger/levels.rb +18 -22
  47. data/lib/semantic_logger/log.rb +32 -17
  48. data/lib/semantic_logger/loggable.rb +9 -2
  49. data/lib/semantic_logger/logger.rb +17 -8
  50. data/lib/semantic_logger/metric/new_relic.rb +2 -1
  51. data/lib/semantic_logger/metric/signalfx.rb +0 -1
  52. data/lib/semantic_logger/metric/statsd.rb +1 -0
  53. data/lib/semantic_logger/processor.rb +3 -3
  54. data/lib/semantic_logger/reporters/minitest.rb +6 -3
  55. data/lib/semantic_logger/semantic_logger.rb +48 -17
  56. data/lib/semantic_logger/subscriber.rb +13 -2
  57. data/lib/semantic_logger/sync_processor.rb +28 -13
  58. data/lib/semantic_logger/test/capture_log_events.rb +55 -0
  59. data/lib/semantic_logger/test/minitest.rb +90 -0
  60. data/lib/semantic_logger/utils.rb +31 -11
  61. data/lib/semantic_logger/version.rb +1 -1
  62. data/lib/semantic_logger.rb +16 -39
  63. metadata +23 -12
@@ -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,105 +6,289 @@
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
-
69
- 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,
126
+ encoding: Encoding::BINARY, exclusive_lock: false, **args, &block)
127
+ if !file_name.is_a?(String) || file_name.empty?
128
+ raise(ArgumentError, "SemanticLogging::Appender::File file_name must be a non-empty string")
70
129
  end
71
130
 
131
+ @file_name = file_name
132
+ @retry_count = retry_count
133
+ @file = nil
134
+ @append = append
135
+ @reopen_period = reopen_period
136
+ @reopen_count = reopen_count
137
+ @reopen_size = reopen_size
138
+ @encoding = encoding
139
+ @exclusive_lock = exclusive_lock
140
+ @log_count = 0
141
+ @log_size = 0
142
+ @reopen_at = nil
143
+
72
144
  super(**args, &block)
73
145
  end
74
146
 
75
147
  # After forking an active process call #reopen to re-open
76
- # open the file handles etc to resources
77
- #
78
- # Note: This method will only work if :file_name was supplied
79
- # on the initializer.
80
- # If :io was supplied, it will need to be re-opened manually.
148
+ # open the file handles etc to resources.
81
149
  def reopen
82
- return unless @file_name
150
+ begin
151
+ @file&.close
152
+ rescue StandardError
153
+ nil
154
+ end
83
155
 
84
- @log = ::File.open(@file_name, ::File::WRONLY | ::File::APPEND | ::File::CREAT)
156
+ self.current_file_name = apply_format_directives(file_name)
157
+ if ::File.directory?(file_name)
158
+ raise(ArgumentError, "The supplied log file_name: #{current_file_name} is already a directory.")
159
+ end
160
+
161
+ self.log_count = 0
162
+ if append && reopen_size && ::File.exist?(current_file_name)
163
+ self.log_size = ::File.size(current_file_name)
164
+ self.log_size = 0 if log_size >= reopen_size
165
+ else
166
+ self.log_size = 0
167
+ end
168
+
169
+ self.reopen_at = reopen_period ? next_reopen_period(reopen_period) : nil
170
+
171
+ options = ::File::WRONLY | ::File::CREAT
172
+ options |= ::File::APPEND if append
173
+ @file = ::File.open(current_file_name, options)
85
174
  # Force all log entries to write immediately without buffering
86
175
  # Allows multiple processes to write to the same log file simultaneously
87
- @log.sync = true
88
- @log.set_encoding(Encoding::BINARY) if @log.respond_to?(:set_encoding)
89
- @log
176
+ @file.sync = true
177
+ @file.set_encoding(encoding) if @file.respond_to?(:set_encoding)
178
+ @file.flock(::File::LOCK_EX) if exclusive_lock
179
+ @file
90
180
  end
91
181
 
92
- # Pass log calls to the underlying Rails, log4j or Ruby logger
93
- # trace entries are mapped to debug since :trace is not supported by the
94
- # Ruby or Rails Loggers
182
+ # Since only one appender thread will be writing to the file at a time
183
+ # it is not necessary to protect access to the file with a semaphore.
95
184
  def log(log)
96
- # Since only one appender thread will be writing to the file at a time
97
- # it is not necessary to protect access to the file with a semaphore
98
- # Allow this logger to filter out log levels lower than it's own
99
- @log.write(formatter.call(log, self) << "\n")
185
+ reopen if time_to_reopen?
186
+
187
+ count = 0
188
+ begin
189
+ message = formatter.call(log, self) << "\n"
190
+ @file.write(message)
191
+ @log_count += 1
192
+ @log_size += message.size
193
+ rescue StandardError => e
194
+ if count < retry_count
195
+ count += 1
196
+ reopen
197
+ retry
198
+ end
199
+ raise(e)
200
+ end
100
201
  true
101
202
  end
102
203
 
103
204
  # Flush all pending logs to disk.
104
- # Waits for all sent documents to be writted to disk
205
+ # Waits for all sent documents to be written to disk
105
206
  def flush
106
- @log.flush if @log.respond_to?(:flush)
207
+ @file&.flush
208
+ end
209
+
210
+ private
211
+
212
+ attr_writer :log_count, :log_size, :current_file_name, :reopen_at
213
+
214
+ def time_to_reopen?
215
+ return true unless @file
216
+
217
+ (reopen_count.positive? && (log_count >= reopen_count)) ||
218
+ (reopen_size.positive? && (log_size >= reopen_size)) ||
219
+ (reopen_at && (Time.now > reopen_at))
220
+ end
221
+
222
+ def apply_format_directives(file_name)
223
+ return file_name unless file_name.include?("%")
224
+
225
+ file_name.gsub(/%(.)/) { format_directive(Regexp.last_match(1)) }
226
+ end
227
+
228
+ def format_directive(directive)
229
+ case directive
230
+ when "p"
231
+ $$
232
+ when "n"
233
+ SemanticLogger.host.split(".")[0]
234
+ when "N"
235
+ SemanticLogger.host
236
+ when "a"
237
+ SemanticLogger.application
238
+ when "e"
239
+ SemanticLogger.environment
240
+ when "D"
241
+ Date.today.strftime("%Y%m%d")
242
+ when "Y", "C", "y", "m", "d", "j", "U", "W"
243
+ Date.today.strftime("%#{directive}")
244
+ when "T"
245
+ Time.now.strftime("%H%M%S")
246
+ when "H", "M", "S"
247
+ Time.now.strftime("%#{directive}")
248
+ when "%"
249
+ "%"
250
+ else
251
+ raise(ArgumentError, "Format Directive '#{directive}' in file_name: #{file_name} is not supported.")
252
+ end
253
+ end
254
+
255
+ def next_reopen_period(period_string)
256
+ return unless period_string
257
+
258
+ duration, period = parse_period(period_string)
259
+ calculate_reopen_at(duration, period)
260
+ end
261
+
262
+ def parse_period(period_string)
263
+ match = period_string.to_s.downcase.gsub(/\s+/, "").match(/([\d.]+)([mhd])/)
264
+ unless match
265
+ raise(ArgumentError,
266
+ "Invalid period definition: #{period_string}, must begin with an integer, followed by m,h, or d.")
267
+ end
268
+
269
+ duration = match[1]
270
+ period = match[2]
271
+ unless duration
272
+ raise(ArgumentError,
273
+ "Invalid or missing duration in: #{period_string}, must begin with an integer.")
274
+ end
275
+ raise(ArgumentError, "Invalid or missing period in: #{period_string}, must end with m,h, or d.") unless period
276
+
277
+ [duration.to_i, period]
278
+ end
279
+
280
+ # Round down the current time based on the period, then add on the duration for that period
281
+ def calculate_reopen_at(duration, period, time = Time.now)
282
+ case period
283
+ when "m"
284
+ Time.new(time.year, time.month, time.day, time.hour, time.min, 0) + (duration * 60)
285
+ when "h"
286
+ Time.new(time.year, time.month, time.day, time.hour, 0, 0) + (duration * 60 * 60)
287
+ when "d"
288
+ Time.new(time.year, time.month, time.day, 0, 0, 0) + (duration * 24 * 60 * 60)
289
+ else
290
+ raise(ArgumentError, "Invalid or missing period in: #{reopen_period}, must end with m,h, or d.")
291
+ end
107
292
  end
108
293
  end
109
294
  end
@@ -25,7 +25,8 @@ module SemanticLogger
25
25
  class LevelMap
26
26
  attr_accessor :trace, :debug, :info, :warn, :error, :fatal
27
27
 
28
- def initialize(trace: GELF::DEBUG, debug: GELF::DEBUG, info: GELF::INFO, warn: GELF::WARN, error: GELF::ERROR, fatal: GELF::FATAL)
28
+ def initialize(trace: GELF::DEBUG, debug: GELF::DEBUG, info: GELF::INFO, warn: GELF::WARN, error: GELF::ERROR,
29
+ fatal: GELF::FATAL)
29
30
  @trace = trace
30
31
  @debug = debug
31
32
  @info = info
@@ -88,7 +89,6 @@ module SemanticLogger
88
89
  level_map: LevelMap.new,
89
90
  **args,
90
91
  &block)
91
-
92
92
  @url = url
93
93
  @max_size = max_size
94
94
  @gelf_options = gelf_options
@@ -38,7 +38,7 @@ module SemanticLogger
38
38
  # Name of this application to appear in log messages.
39
39
  # Default: SemanticLogger.application
40
40
  def initialize(level: :error, **args, &block)
41
- super(level: level, **args, &block)
41
+ super
42
42
  end
43
43
 
44
44
  # Send an error notification to honeybadger
@@ -0,0 +1,61 @@
1
+ begin
2
+ require "honeybadger"
3
+ rescue LoadError
4
+ raise LoadError, 'Gem honeybadger is required for logging purposes. Please add the gem "honeybadger" to your Gemfile.'
5
+ end
6
+
7
+ # Send log messages to honeybadger events/insights API
8
+ #
9
+ # Example:
10
+ # SemanticLogger.add_appender(appender: :honeybadger_insights)
11
+ #
12
+ module SemanticLogger
13
+ module Appender
14
+ class HoneybadgerInsights < SemanticLogger::Subscriber
15
+ # Honeybadger Appender
16
+ #
17
+ # Parameters
18
+ # level: [:trace | :debug | :info | :warn | :error | :fatal]
19
+ # Override the log level for this appender.
20
+ # Default: :error
21
+ #
22
+ # formatter: [Object|Proc|Symbol|Hash]
23
+ # An instance of a class that implements #call, or a Proc to be used to format
24
+ # the output from this appender
25
+ # Default: Use the built-in formatter (See: #call)
26
+ #
27
+ # filter: [Regexp|Proc]
28
+ # RegExp: Only include log messages where the class name matches the supplied.
29
+ # regular expression. All other messages will be ignored.
30
+ # Proc: Only include log messages where the supplied Proc returns true
31
+ # The Proc must return true or false.
32
+ #
33
+ # host: [String]
34
+ # Name of this host to appear in log messages.
35
+ # Default: SemanticLogger.host
36
+ #
37
+ # application: [String]
38
+ # Name of this application to appear in log messages.
39
+ # Default: SemanticLogger.application
40
+ def initialize(level: :info, **args, &block)
41
+ super
42
+ end
43
+
44
+ # Send log to honeybadger events API
45
+ def log(log)
46
+ event = formatter.call(log, self)
47
+
48
+ ::Honeybadger.event(event)
49
+
50
+ true
51
+ end
52
+
53
+ private
54
+
55
+ # Use Raw Formatter by default
56
+ def default_formatter
57
+ SemanticLogger::Formatters::Raw.new(time_key: :ts, time_format: :rfc_3339)
58
+ end
59
+ end
60
+ end
61
+ end
@@ -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
  #
@@ -48,6 +48,11 @@ module SemanticLogger
48
48
  # password: [String]
49
49
  # Password for basic Authentication.
50
50
  #
51
+ # header: [Hash]
52
+ # Custom HTTP headers to send with each request.
53
+ # Default: {} ( do not send any custom headers)
54
+ # Example: {"Authorization" => "Bearer BEARER_TOKEN"}
55
+ #
51
56
  # compress: [true|false]
52
57
  # Whether to compress the JSON string with GZip.
53
58
  # Default: false
@@ -57,6 +62,16 @@ module SemanticLogger
57
62
  # ca_file, ca_path, cert, cert_store, ciphers, key, ssl_timeout,
58
63
  # ssl_version, verify_callback, verify_depth and verify_mode.
59
64
  #
65
+ # proxy_url: [String]
66
+ # URL of proxy server to use for HTTP(s) connections. Should
67
+ # include username and password if required.
68
+ # Example: http://user@pass:example.com/some_path
69
+ # To enable SSL include https in the URL.
70
+ # Example: https://example.com/some_path
71
+ # If this is set to :ENV, Net::HTTP will use the environment http_proxy*
72
+ # variables if they are set. If set to nil then no proxy will be used,
73
+ # even if the environment variables are set.
74
+ #
60
75
  # level: [:trace | :debug | :info | :warn | :error | :fatal]
61
76
  # Override the log level for this appender.
62
77
  # Default: SemanticLogger.default_level
@@ -85,13 +100,15 @@ module SemanticLogger
85
100
  ssl: {},
86
101
  username: nil,
87
102
  password: nil,
103
+ header: {},
104
+ proxy_url: :ENV,
88
105
  open_timeout: 2.0,
89
106
  read_timeout: 1.0,
90
107
  continue_timeout: 1.0,
91
108
  **args,
92
109
  &block)
93
-
94
110
  @url = url
111
+ @proxy_url = proxy_url
95
112
  @ssl_options = ssl
96
113
  @username = username
97
114
  @password = password
@@ -106,7 +123,7 @@ module SemanticLogger
106
123
  "Content-Type" => "application/json",
107
124
  "Connection" => "keep-alive",
108
125
  "Keep-Alive" => "300"
109
- }
126
+ }.merge(header)
110
127
  @header["Content-Encoding"] = "gzip" if @compress
111
128
 
112
129
  uri = URI.parse(@url)
@@ -129,6 +146,9 @@ module SemanticLogger
129
146
  else
130
147
  @port ||= HTTP.http_default_port
131
148
  end
149
+
150
+ @proxy_uri = URI.parse(@proxy_url) if @proxy_url && @proxy_url != :ENV
151
+
132
152
  @http = nil
133
153
 
134
154
  super(**args, &block)
@@ -144,7 +164,11 @@ module SemanticLogger
144
164
  nil
145
165
  end
146
166
 
147
- @http = Net::HTTP.new(server, port)
167
+ @http = if @proxy_uri
168
+ Net::HTTP.new(server, port, @proxy_uri.host, @proxy_uri.port, @proxy_uri.user, @proxy_uri.password)
169
+ else
170
+ Net::HTTP.new(server, port, @proxy_url)
171
+ end
148
172
 
149
173
  if @ssl_options
150
174
  @http.methods.grep(/\A(\w+)=\z/) do |meth|
@@ -207,7 +231,7 @@ module SemanticLogger
207
231
  end
208
232
  request.basic_auth(@username, @password) if @username
209
233
  response = @http.request(request)
210
- if response.code == "200" || response.code == "201"
234
+ if response.is_a?(Net::HTTPSuccess)
211
235
  true
212
236
  else
213
237
  # Failures are logged to the global semantic logger failsafe logger (Usually stderr or file)
@@ -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