semantic_logger 4.0.0 → 4.1.0

Sign up to get free protection for your applications and to get access to all the features.
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