semantic_logger 4.0.0 → 4.1.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 (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +55 -8
  3. data/lib/semantic_logger.rb +1 -2
  4. data/lib/semantic_logger/ansi_colors.rb +1 -2
  5. data/lib/semantic_logger/appender.rb +17 -15
  6. data/lib/semantic_logger/appender/bugsnag.rb +5 -4
  7. data/lib/semantic_logger/appender/elasticsearch.rb +102 -16
  8. data/lib/semantic_logger/appender/elasticsearch_http.rb +76 -0
  9. data/lib/semantic_logger/appender/file.rb +9 -25
  10. data/lib/semantic_logger/appender/graylog.rb +43 -38
  11. data/lib/semantic_logger/appender/honeybadger.rb +3 -5
  12. data/lib/semantic_logger/appender/http.rb +12 -15
  13. data/lib/semantic_logger/appender/kafka.rb +183 -0
  14. data/lib/semantic_logger/appender/mongodb.rb +3 -3
  15. data/lib/semantic_logger/appender/new_relic.rb +3 -7
  16. data/lib/semantic_logger/appender/sentry.rb +2 -5
  17. data/lib/semantic_logger/appender/splunk.rb +7 -10
  18. data/lib/semantic_logger/appender/splunk_http.rb +16 -16
  19. data/lib/semantic_logger/appender/syslog.rb +43 -122
  20. data/lib/semantic_logger/appender/tcp.rb +28 -9
  21. data/lib/semantic_logger/appender/udp.rb +4 -7
  22. data/lib/semantic_logger/appender/wrapper.rb +3 -7
  23. data/lib/semantic_logger/base.rb +47 -7
  24. data/lib/semantic_logger/formatters/base.rb +29 -10
  25. data/lib/semantic_logger/formatters/color.rb +75 -45
  26. data/lib/semantic_logger/formatters/default.rb +53 -28
  27. data/lib/semantic_logger/formatters/json.rb +7 -8
  28. data/lib/semantic_logger/formatters/raw.rb +97 -1
  29. data/lib/semantic_logger/formatters/syslog.rb +46 -80
  30. data/lib/semantic_logger/formatters/syslog_cee.rb +57 -0
  31. data/lib/semantic_logger/log.rb +17 -67
  32. data/lib/semantic_logger/logger.rb +17 -27
  33. data/lib/semantic_logger/processor.rb +70 -46
  34. data/lib/semantic_logger/semantic_logger.rb +130 -69
  35. data/lib/semantic_logger/subscriber.rb +18 -32
  36. data/lib/semantic_logger/version.rb +1 -1
  37. data/test/appender/elasticsearch_http_test.rb +75 -0
  38. data/test/appender/elasticsearch_test.rb +34 -27
  39. data/test/appender/file_test.rb +2 -2
  40. data/test/appender/honeybadger_test.rb +1 -1
  41. data/test/appender/kafka_test.rb +36 -0
  42. data/test/appender/new_relic_test.rb +1 -1
  43. data/test/appender/sentry_test.rb +1 -1
  44. data/test/appender/syslog_test.rb +2 -2
  45. data/test/appender/wrapper_test.rb +1 -1
  46. data/test/formatters/color_test.rb +154 -0
  47. data/test/formatters/default_test.rb +176 -0
  48. data/test/loggable_test.rb +1 -1
  49. data/test/logger_test.rb +47 -4
  50. data/test/measure_test.rb +2 -2
  51. data/test/semantic_logger_test.rb +34 -6
  52. data/test/test_helper.rb +8 -0
  53. metadata +14 -3
@@ -29,17 +29,13 @@ class SemanticLogger::Appender::NewRelic < SemanticLogger::Subscriber
29
29
  # regular expression. All other messages will be ignored.
30
30
  # Proc: Only include log messages where the supplied Proc returns true
31
31
  # The Proc must return true or false.
32
- def initialize(options = {}, &block)
33
- # Backward compatibility
34
- options = {level: options} unless options.is_a?(Hash)
35
- options = options.dup
36
- options[:level] = :error unless options.has_key?(:level)
37
- super(options)
32
+ def initialize(level: :error, formatter: nil, filter: nil, application: nil, host: nil, &block)
33
+ super(level: level, formatter: formatter, filter: filter, application: application, host: host, &block)
38
34
  end
39
35
 
40
36
  # Returns [Hash] of parameters to send to New Relic.
41
37
  def call(log, logger)
42
- h = log.to_h(host, application)
38
+ h = SemanticLogger::Formatters::Raw.new.call(log, logger)
43
39
  h.delete(:time)
44
40
  h.delete(:exception)
45
41
  {metric: log.metric, custom_params: h}
@@ -35,11 +35,8 @@ class SemanticLogger::Appender::Sentry < SemanticLogger::Subscriber
35
35
  # application: [String]
36
36
  # Name of this application to appear in log messages.
37
37
  # Default: SemanticLogger.application
38
- def initialize(options = {}, &block)
39
- options = options.is_a?(Hash) ? options.dup : {level: options}
40
- options[:level] ||= :error
41
-
42
- super(options, &block)
38
+ def initialize(level: :error, formatter: nil, filter: nil, application: nil, host: nil, &block)
39
+ super(level: level, formatter: formatter, filter: filter, application: application, host: host, &block)
43
40
  end
44
41
 
45
42
  # Send an error notification to sentry
@@ -86,15 +86,11 @@ class SemanticLogger::Appender::Splunk < SemanticLogger::Subscriber
86
86
  # regular expression. All other messages will be ignored.
87
87
  # Proc: Only include log messages where the supplied Proc returns true
88
88
  # The Proc must return true or false.
89
- def initialize(options = {}, _deprecated_level = nil, &block)
90
- @config = options.dup
91
- @config[:level] = _deprecated_level if _deprecated_level
92
- @index = @config.delete(:index) || 'main'
93
- @source_type = options.delete(:source_type)
89
+ def initialize(index: 'main', source_type: nil, level: nil, formatter: nil, filter: nil, application: nil, host: nil, &block)
90
+ @index = index
91
+ @source_type = source_type
94
92
 
95
- options = extract_subscriber_options!(@config)
96
- # Pass on the level and custom formatter if supplied
97
- super(options, &block)
93
+ super(level: level, formatter: formatter, filter: filter, application: application, host: host, &block)
98
94
  reopen
99
95
  end
100
96
 
@@ -116,11 +112,12 @@ class SemanticLogger::Appender::Splunk < SemanticLogger::Subscriber
116
112
  true
117
113
  end
118
114
 
119
- # Returns [Hash] To send to Splunk
115
+ # Returns [Hash] To send to Splunk.
116
+ #
120
117
  # For splunk format requirements see:
121
118
  # http://dev.splunk.com/view/event-collector/SP-CAAAE6P
122
119
  def call(log, logger)
123
- h = log.to_h(nil, nil)
120
+ h = SemanticLogger::Formatters::Raw.new.call(log, logger)
124
121
  h.delete(:time)
125
122
  message = {
126
123
  source: logger.application,
@@ -13,6 +13,8 @@ require 'json'
13
13
  # token: '70CA900C-3D7E-42A4-9C79-7975D1C422A8'
14
14
  # )
15
15
  class SemanticLogger::Appender::SplunkHttp < SemanticLogger::Appender::Http
16
+ attr_accessor :source_type, :index
17
+
16
18
  # Create Splunk appender over persistent HTTP(S)
17
19
  #
18
20
  # Parameters:
@@ -42,7 +44,7 @@ class SemanticLogger::Appender::SplunkHttp < SemanticLogger::Appender::Http
42
44
  # Default: SemanticLogger.host
43
45
  #
44
46
  # compress: [true|false]
45
- # Whether to compress the JSON string with GZip.
47
+ # Splunk supports HTTP Compression, enable by default.
46
48
  # Default: true
47
49
  #
48
50
  # ssl: [Hash]
@@ -64,26 +66,26 @@ class SemanticLogger::Appender::SplunkHttp < SemanticLogger::Appender::Http
64
66
  # regular expression. All other messages will be ignored.
65
67
  # Proc: Only include log messages where the supplied Proc returns true
66
68
  # The Proc must return true or false.
67
- def initialize(options, &block)
68
- options = options.dup
69
- @source_type = options.delete(:source_type)
70
- @index = options.delete(:index)
71
- token = options.delete(:token)
72
- raise(ArgumentError, 'Missing mandatory parameter :token') unless token
69
+ def initialize(token: nil, source_type: nil, index: nil,
70
+ url:, compress: true, ssl: {}, open_timeout: 2.0, read_timeout: 1.0, continue_timeout: 1.0,
71
+ level: nil, formatter: nil, filter: nil, application: nil, host: nil, &block)
73
72
 
74
- # Splunk supports HTTP Compression, enable by default
75
- options[:compress] = true unless options.key?(:compress)
73
+ @source_type = source_type
74
+ @index = index
76
75
 
77
- super(options, &block)
76
+ super(url: url, compress: compress, ssl: ssl, open_timeout: 2.0, read_timeout: open_timeout, continue_timeout: continue_timeout,
77
+ level: level, formatter: formatter, filter: filter, application: application, host: host, &block)
78
78
 
79
+ # Put splunk auth token in the header of every HTTP post.
79
80
  @header['Authorization'] = "Splunk #{token}"
80
81
  end
81
82
 
82
- # Returns [String] JSON to send to Splunk
83
+ # Returns [String] JSON to send to Splunk.
84
+ #
83
85
  # For splunk format requirements see:
84
86
  # http://dev.splunk.com/view/event-collector/SP-CAAAE6P
85
87
  def call(log, logger)
86
- h = log.to_h(nil, nil)
88
+ h = SemanticLogger::Formatters::Raw.new.call(log, logger)
87
89
  h.delete(:time)
88
90
  message = {
89
91
  source: logger.application,
@@ -91,10 +93,8 @@ class SemanticLogger::Appender::SplunkHttp < SemanticLogger::Appender::Http
91
93
  time: log.time.utc.to_f,
92
94
  event: h
93
95
  }
94
- message[:source_type] = @source_type if @source_type
95
- message[:index] = @index if @index
96
-
97
- # Render to JSON
96
+ message[:source_type] = source_type if source_type
97
+ message[:index] = index if index
98
98
  message.to_json
99
99
  end
100
100
 
@@ -1,46 +1,37 @@
1
1
  require 'syslog'
2
2
  require 'uri'
3
3
  require 'socket'
4
-
5
4
  # Send log messages to local syslog, or remote syslog servers over TCP or UDP.
6
5
  #
7
- # Example: Log to a local Syslog daemon
6
+ # Example:
7
+ # # Log to a local Syslog daemon
8
8
  # SemanticLogger.add_appender(appender: :syslog)
9
9
  #
10
- # Example: Log to a remote Syslog server using TCP:
10
+ # Example:
11
+ # # Log to a remote Syslog server over TCP:
11
12
  # SemanticLogger.add_appender(
12
13
  # appender: :syslog,
13
14
  # url: 'tcp://myloghost:514'
14
15
  # )
15
16
  #
16
- # Example: Log to a remote Syslog server using UDP:
17
+ # Example:
18
+ # # Log to a remote Syslog server over UDP:
17
19
  # SemanticLogger.add_appender(
18
20
  # appender: :syslog,
19
21
  # url: 'udp://myloghost:514'
20
22
  # )
23
+ #
24
+ # Example:
25
+ # # Log to a remote Syslog server using the CEE format over TCP:
26
+ # SemanticLogger.add_appender(
27
+ # appender: :syslog,
28
+ # url: 'tcp://myloghost:514'
29
+ # )
30
+ #
21
31
  module SemanticLogger
22
32
  module Appender
23
33
  class Syslog < SemanticLogger::Subscriber
24
-
25
- # Default mapping of ruby log levels to syslog log levels
26
- #
27
- # ::Syslog::LOG_EMERG - "System is unusable"
28
- # ::Syslog::LOG_ALERT - "Action needs to be taken immediately"
29
- # ::Syslog::LOG_CRIT - "A critical condition has occurred"
30
- # ::Syslog::LOG_ERR - "An error occurred"
31
- # ::Syslog::LOG_WARNING - "Warning of a possible problem"
32
- # ::Syslog::LOG_NOTICE - "A normal but significant condition occurred"
33
- # ::Syslog::LOG_INFO - "Informational message"
34
- # ::Syslog::LOG_DEBUG - "Debugging information"
35
- DEFAULT_LEVEL_MAP = {
36
- fatal: ::Syslog::LOG_CRIT,
37
- error: ::Syslog::LOG_ERR,
38
- warn: ::Syslog::LOG_WARNING,
39
- info: ::Syslog::LOG_NOTICE,
40
- debug: ::Syslog::LOG_INFO,
41
- trace: ::Syslog::LOG_DEBUG
42
- }
43
- attr_reader :remote_syslog, :url, :server, :port, :protocol, :facility
34
+ attr_reader :remote_syslog, :url, :server, :port, :protocol, :facility, :options, :level_map
44
35
 
45
36
  # Create a Syslog appender instance.
46
37
  #
@@ -72,11 +63,6 @@ module SemanticLogger
72
63
  # Override the log level for this appender.
73
64
  # Default: SemanticLogger.default_level
74
65
  #
75
- # formatter: [Object|Proc]
76
- # An instance of a class that implements #call, or a Proc to be used to format
77
- # the output from this appender
78
- # Default: Use the built-in formatter (See: #call)
79
- #
80
66
  # filter: [Regexp|Proc]
81
67
  # RegExp: Only include log messages where the class name matches the supplied.
82
68
  # regular expression. All other messages will be ignored.
@@ -96,6 +82,9 @@ module SemanticLogger
96
82
  # ::Syslog::LOG_ODELAY
97
83
  # ::Syslog::LOG_PERROR
98
84
  # ::Syslog::LOG_PID
85
+ # Note:
86
+ # - Only applicable when logging to a local syslog instance.
87
+ # I.e. When `url: 'syslog://localhost'`
99
88
  #
100
89
  # facility: [Integer]
101
90
  # Default: ::Syslog::LOG_USER
@@ -124,46 +113,30 @@ module SemanticLogger
124
113
  # ::Syslog::LOG_LOCAL6
125
114
  # ::Syslog::LOG_LOCAL7
126
115
  #
127
- # level_map: [Hash]
116
+ # level_map: [Hash | SemanticLogger::Formatters::Syslog::LevelMap]
128
117
  # Supply a custom map of SemanticLogger levels to syslog levels.
129
- # For example, passing in { warn: ::Syslog::LOG_NOTICE }
130
- # would result in a log mapping that matches the default level map,
131
- # except for :warn, which ends up with a LOG_NOTICE level instead of a
132
- # LOG_WARNING one.
133
- # Without overriding any parameters, the level map will be
134
- # LEVEL_MAP = {
135
- # fatal: ::Syslog::LOG_CRIT,
136
- # error: ::Syslog::LOG_ERR,
137
- # warn: ::Syslog::LOG_WARNING,
138
- # info: ::Syslog::LOG_NOTICE,
139
- # debug: ::Syslog::LOG_INFO,
140
- # trace: ::Syslog::LOG_DEBUG
141
- # }
142
118
  #
143
- # format: [Symbol]
144
- # Format for the Syslog message
145
- # :syslog uses the default syslog format
146
- # :json uses the CEE JSON Syslog format
147
- # Example: "@cee: #{JSON.dump(data)}"
148
- # Default: :syslog
149
- def initialize(options = {}, &block)
150
- options = options.dup
151
- @options = options.delete(:options) || (::Syslog::LOG_PID | ::Syslog::LOG_CONS)
152
- @facility = options.delete(:facility) || ::Syslog::LOG_USER
153
- level_map = options.delete(:level_map)
154
- @url = options.delete(:url) || options.delete(:server) || 'syslog://localhost'
119
+ # Example:
120
+ # # Change the warn level to LOG_NOTICE level instead of a the default of LOG_WARNING.
121
+ # SemanticLogger.add_appender(appender: :syslog, level_map: {warn: ::Syslog::LOG_NOTICE})
122
+ def initialize(url: 'syslog://localhost',
123
+ facility: ::Syslog::LOG_USER, level_map: SemanticLogger::Formatters::Syslog::LevelMap.new, options: ::Syslog::LOG_PID|::Syslog::LOG_CONS,
124
+ tcp_client: {},
125
+ level: nil, formatter: nil, filter: nil, application: nil, host: nil, &block)
126
+
127
+ @options = options
128
+ @facility = facility
129
+ @level_map = level_map
130
+ @url = url
155
131
  uri = URI(@url)
156
132
  @server = uri.host || 'localhost'
157
133
  @protocol = (uri.scheme || :syslog).to_sym
158
134
  @port = uri.port || 514
159
135
  @server = 'localhost' if @protocol == :syslog
160
- @tcp_client_options = options.delete(:tcp_client)
136
+ @tcp_client_options = tcp_client
161
137
 
162
138
  raise "Unknown protocol #{@protocol}!" unless [:syslog, :tcp, :udp].include?(@protocol)
163
139
 
164
- @level_map = DEFAULT_LEVEL_MAP.dup
165
- @level_map.update(level_map) if level_map
166
-
167
140
  # The syslog_protocol gem is required when logging over TCP or UDP.
168
141
  if [:tcp, :udp].include?(@protocol)
169
142
  begin
@@ -174,8 +147,6 @@ module SemanticLogger
174
147
 
175
148
  # The net_tcp_client gem is required when logging over TCP.
176
149
  if protocol == :tcp
177
- @tcp_client_options ||= {}
178
- @tcp_client_options[:server] = "#{@server}:#{@port}"
179
150
  begin
180
151
  require 'net/tcp_client'
181
152
  rescue LoadError
@@ -184,7 +155,7 @@ module SemanticLogger
184
155
  end
185
156
  end
186
157
 
187
- super(options, &block)
158
+ super(level: level, formatter: formatter, filter: filter, application: application, host: host, &block)
188
159
  reopen
189
160
  end
190
161
 
@@ -193,10 +164,11 @@ module SemanticLogger
193
164
  def reopen
194
165
  case @protocol
195
166
  when :syslog
196
- ::Syslog.open(application, @options, @facility)
167
+ ::Syslog.open(application, options, facility)
197
168
  when :tcp
198
169
  # Use the local logger for @remote_syslog so errors with the remote logger can be recorded locally.
199
170
  @tcp_client_options[:logger] = SemanticLogger::Processor.logger.clone
171
+ @tcp_client_options[:server] = "#{@server}:#{@port}"
200
172
  @remote_syslog = Net::TCPClient.new(@tcp_client_options)
201
173
  when :udp
202
174
  @remote_syslog = UDPSocket.new
@@ -215,9 +187,9 @@ module SemanticLogger
215
187
  message = formatter.call(log, self).gsub '%', '%%'
216
188
  ::Syslog.log @level_map[log.level], message
217
189
  when :tcp
218
- @remote_syslog.retry_on_connection_failure { @remote_syslog.write("#{syslog_packet_formatter(log)}\r\n") }
190
+ @remote_syslog.retry_on_connection_failure { @remote_syslog.write("#{formatter.call(log, self)}\r\n") }
219
191
  when :udp
220
- @remote_syslog.send syslog_packet_formatter(log), 0, @server, @port
192
+ @remote_syslog.send(formatter.call(log, self), 0, @server, @port)
221
193
  else
222
194
  raise "Unsupported protocol: #{protocol}"
223
195
  end
@@ -229,67 +201,16 @@ module SemanticLogger
229
201
  @remote_syslog.flush if @remote_syslog && @remote_syslog.respond_to?(:flush)
230
202
  end
231
203
 
232
- # Custom log formatter for syslog.
233
- # Only difference is the removal of the timestamp string since it is in the syslog packet.
234
- def call(log, logger)
235
- # Header with date, time, log level and process info
236
- message = "#{log.level_to_s} [#{log.process_info}]"
237
-
238
- # Tags
239
- message << ' ' << log.tags.collect { |tag| "[#{tag}]" }.join(' ') if log.tags && (log.tags.size > 0)
240
-
241
- # Duration
242
- message << " (#{log.duration_human})" if log.duration
243
-
244
- # Class / app name
245
- message << " #{log.name}"
246
-
247
- # Log message
248
- message << " -- #{log.message}" if log.message
249
-
250
- # Payload
251
- if payload = log.payload_to_s
252
- message << ' -- ' << payload
253
- end
254
-
255
- # Exceptions
256
- if log.exception
257
- message << " -- Exception: #{log.exception.class}: #{log.exception.message}\n"
258
- message << log.backtrace_to_s
259
- end
260
- message
261
- end
262
-
263
- private
264
-
265
- # Extract Syslog formatter options
266
- def format_options(options, protocol, &block)
267
- opts = options.delete(:options)
268
- facility = options.delete(:facility)
269
- level_map = options.delete(:level_map)
270
- if formatter = options.delete(:formatter)
271
- extract_formatter(formatter)
204
+ # Returns [SemanticLogger::Formatters::Base] default formatter for this Appender depending on the protocal selected
205
+ def default_formatter
206
+ if protocol == :syslog
207
+ # Format is text output without the time
208
+ SemanticLogger::Formatters::Default.new(time_format: nil)
272
209
  else
273
- case protocol
274
- when :syslog
275
- extract_formatter(syslog: {options: opts, facility: facility, level_map: level_map})
276
- when :tcp, :udp
277
- extract_formatter(syslog: {options: opts, facility: facility, level_map: level_map})
278
- end
210
+ SemanticLogger::Formatters::Syslog.new(facility: facility, level_map: level_map)
279
211
  end
280
212
  end
281
213
 
282
- # Format the syslog packet so it can be sent over TCP or UDP
283
- def syslog_packet_formatter(log)
284
- packet = SyslogProtocol::Packet.new
285
- packet.hostname = host
286
- packet.facility = @facility
287
- packet.severity = @level_map[log.level]
288
- packet.tag = application.gsub(' ', '')
289
- packet.content = formatter.call(log, self)
290
- packet.time = log.time
291
- packet.to_s
292
- end
293
214
  end
294
215
  end
295
216
  end
@@ -181,22 +181,41 @@ module SemanticLogger
181
181
  # connect_retry_interval: 0.1,
182
182
  # connect_retry_count: 5
183
183
  # )
184
- def initialize(options = {}, &block)
185
- @tcp_client = nil
186
- @options = options.dup
187
- @separator = @options.delete(:separator) || "\n"
184
+ def initialize(server: nil, servers: nil, separator: "\n",
185
+ policy: :ordered, buffered: true, #keepalive: true,
186
+ connect_timeout: 10.0, read_timeout: 60.0, write_timeout: 60.0,
187
+ connect_retry_count: 10, retry_count: 3, connect_retry_interval: 0.5, close_on_error: true,
188
+ on_connect: nil, proxy_server: nil, ssl: nil,
189
+ level: nil, formatter: nil, filter: nil, application: nil, host: nil, &block
190
+ )
191
+ @separator = separator
192
+ @options = {
193
+ server: server,
194
+ servers: servers,
195
+ policy: policy,
196
+ buffered: buffered,
197
+ #keepalive: keepalive,
198
+ connect_timeout: connect_timeout,
199
+ read_timeout: read_timeout,
200
+ write_timeout: write_timeout,
201
+ connect_retry_count: connect_retry_count,
202
+ retry_count: retry_count,
203
+ connect_retry_interval: connect_retry_interval,
204
+ close_on_error: close_on_error,
205
+ on_connect: on_connect,
206
+ proxy_server: proxy_server,
207
+ ssl: ssl
208
+ }
188
209
 
189
210
  # Use the internal logger so that errors with remote logging are only written locally.
190
211
  Net::TCPClient.logger = SemanticLogger::Processor.logger.clone
191
212
  Net::TCPClient.logger.name = 'Net::TCPClient'
192
213
 
193
- options = extract_subscriber_options!(@options)
194
- super(options, &block)
214
+ super(level: level, formatter: formatter, filter: filter, application: application, host: host, &block)
195
215
  reopen
196
216
  end
197
217
 
198
- # After forking an active process call #reopen to re-open
199
- # open the handles to resources
218
+ # After forking an active process call #reopen to re-open the handles to resources.
200
219
  def reopen
201
220
  close
202
221
  @tcp_client = Net::TCPClient.new(@options)
@@ -215,7 +234,7 @@ module SemanticLogger
215
234
 
216
235
  # Flush is called by the semantic_logger during shutdown.
217
236
  def flush
218
- @tcp_client.flush if @tcp_client && @tcp_client.respond_to?(:flush)
237
+ @tcp_client.flush if @tcp_client
219
238
  end
220
239
 
221
240
  # Close is called during shutdown, or with reopen