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
@@ -2,9 +2,105 @@ require 'json'
2
2
  module SemanticLogger
3
3
  module Formatters
4
4
  class Raw < Base
5
+
6
+ # Fields are added by populating this hash.
7
+ attr_accessor :hash, :log, :logger
8
+
9
+ # Host name
10
+ def host
11
+ hash[:host] = logger.host if log_host && logger.host
12
+ end
13
+
14
+ # Application name
15
+ def application
16
+ hash[:application] = logger.application if log_application && logger.application
17
+ end
18
+
19
+ # Date & time
20
+ def time
21
+ hash[:time] = log.time
22
+ end
23
+
24
+ # Log level
25
+ def level
26
+ hash[:level] = log.level
27
+ hash[:level_index] = log.level_index
28
+ end
29
+
30
+ # Process info
31
+ def process_info
32
+ hash[:pid] = $$
33
+ hash[:thread] = log.thread_name
34
+
35
+ file, line = log.file_name_and_line
36
+ if file
37
+ hash[:file] = file
38
+ hash[:line] = line.to_i
39
+ end
40
+ end
41
+
42
+ # Tags
43
+ def tags
44
+ hash[:tags] = log.tags if log.tags && !log.tags.empty?
45
+ end
46
+
47
+ # Named Tags
48
+ def named_tags
49
+ hash[:named_tags] = log.named_tags if log.named_tags && !log.named_tags.empty?
50
+ end
51
+
52
+ # Duration
53
+ def duration
54
+ return unless log.duration
55
+
56
+ hash[:duration_ms] = log.duration
57
+ hash[:duration] = log.duration_human
58
+ end
59
+
60
+ # Class / app name
61
+ def name
62
+ hash[:name] = log.name
63
+ end
64
+
65
+ # Log message
66
+ def message
67
+ hash[:message] = log.cleansed_message if log.message
68
+ end
69
+
70
+ # Payload
71
+ def payload
72
+ hash[:payload] = log.payload if log.payload && log.payload.respond_to?(:empty?) && !log.payload.empty?
73
+ end
74
+
75
+ # Exception
76
+ def exception
77
+ return unless log.exception
78
+ root = hash
79
+ log.each_exception do |exception, i|
80
+ name = i == 0 ? :exception : :cause
81
+ root[name] = {
82
+ name: exception.class.name,
83
+ message: exception.message,
84
+ stack_trace: exception.backtrace
85
+ }
86
+ root = root[name]
87
+ end
88
+ end
89
+
90
+ # Metric
91
+ def metric
92
+ hash[:metric] = log.metric if log.metric
93
+ hash[:metric_amount] = log.metric_amount if log.metric_amount
94
+ end
95
+
5
96
  # Returns log messages in Hash format
6
97
  def call(log, logger)
7
- log.to_h(log_host ? logger.host : nil, log_application ? logger.application : nil)
98
+ self.hash = {}
99
+ self.log = log
100
+ self.logger = logger
101
+
102
+ host; application; time; level; process_info; duration; tags; named_tags; name; message; payload; exception; metric
103
+ hash
8
104
  end
9
105
 
10
106
  end
@@ -7,112 +7,78 @@ end
7
7
  module SemanticLogger
8
8
  module Formatters
9
9
  class Syslog < Default
10
- attr_accessor :level_map, :options, :facility
10
+ attr_accessor :level_map, :facility
11
11
 
12
- # Default mapping of ruby log levels to syslog log levels
12
+ # Default level map for every log level
13
13
  #
14
+ # :fatal => ::Syslog::LOG_CRIT - "A critical condition has occurred"
15
+ # :error => ::Syslog::LOG_ERR - "An error occurred"
16
+ # :warning =>::Syslog::LOG_WARNING - "Warning of a possible problem"
17
+ # :info => ::Syslog::LOG_NOTICE - "A normal but significant condition occurred"
18
+ # :debug => ::Syslog::LOG_INFO - "Informational message"
19
+ # :trace => ::Syslog::LOG_DEBUG - "Debugging information"
20
+ #
21
+ # The following levels are not used by default.
14
22
  # ::Syslog::LOG_EMERG - "System is unusable"
15
23
  # ::Syslog::LOG_ALERT - "Action needs to be taken immediately"
16
- # ::Syslog::LOG_CRIT - "A critical condition has occurred"
17
- # ::Syslog::LOG_ERR - "An error occurred"
18
- # ::Syslog::LOG_WARNING - "Warning of a possible problem"
19
- # ::Syslog::LOG_NOTICE - "A normal but significant condition occurred"
20
- # ::Syslog::LOG_INFO - "Informational message"
21
- # ::Syslog::LOG_DEBUG - "Debugging information"
22
- DEFAULT_LEVEL_MAP = {
23
- fatal: ::Syslog::LOG_CRIT,
24
- error: ::Syslog::LOG_ERR,
25
- warn: ::Syslog::LOG_WARNING,
26
- info: ::Syslog::LOG_NOTICE,
27
- debug: ::Syslog::LOG_INFO,
28
- trace: ::Syslog::LOG_DEBUG
29
- }.freeze
24
+ class LevelMap
25
+ attr_accessor :trace, :debug, :info, :warn, :error, :fatal
26
+
27
+ def initialize(trace: ::Syslog::LOG_DEBUG, debug: ::Syslog::LOG_INFO, info: ::Syslog::LOG_NOTICE, warn: ::Syslog::LOG_WARNING, error: ::Syslog::LOG_ERR, fatal: ::Syslog::LOG_CRIT)
28
+ @trace = trace
29
+ @debug = debug
30
+ @info = info
31
+ @warn = warn
32
+ @error = error
33
+ @fatal = fatal
34
+ end
35
+
36
+ def [](level)
37
+ public_send(level)
38
+ end
39
+ end
30
40
 
31
41
  # Create a Syslog Log Formatter
32
42
  #
33
43
  # Parameters:
34
- # options: [Integer]
35
- # Default: ::Syslog::LOG_PID | ::Syslog::LOG_CONS
36
- # Any of the following (options can be logically OR'd together)
37
- # ::Syslog::LOG_CONS
38
- # ::Syslog::LOG_NDELAY
39
- # ::Syslog::LOG_NOWAIT
40
- # ::Syslog::LOG_ODELAY
41
- # ::Syslog::LOG_PERROR
42
- # ::Syslog::LOG_PID
43
- #
44
44
  # facility: [Integer]
45
45
  # Default: ::Syslog::LOG_USER
46
- # Type of program (can be logically OR'd together)
47
- # ::Syslog::LOG_AUTH
48
- # ::Syslog::LOG_AUTHPRIV
49
- # ::Syslog::LOG_CONSOLE
50
- # ::Syslog::LOG_CRON
51
- # ::Syslog::LOG_DAEMON
52
- # ::Syslog::LOG_FTP
53
- # ::Syslog::LOG_KERN
54
- # ::Syslog::LOG_LRP
55
- # ::Syslog::LOG_MAIL
56
- # ::Syslog::LOG_NEWS
57
- # ::Syslog::LOG_NTP
58
- # ::Syslog::LOG_SECURITY
59
- # ::Syslog::LOG_SYSLOG
60
- # ::Syslog::LOG_USER
61
- # ::Syslog::LOG_UUCP
62
- # ::Syslog::LOG_LOCAL0
63
- # ::Syslog::LOG_LOCAL1
64
- # ::Syslog::LOG_LOCAL2
65
- # ::Syslog::LOG_LOCAL3
66
- # ::Syslog::LOG_LOCAL4
67
- # ::Syslog::LOG_LOCAL5
68
- # ::Syslog::LOG_LOCAL6
69
- # ::Syslog::LOG_LOCAL7
70
46
  #
71
- # level_map: [Hash]
47
+ # level_map: [Hash | SemanticLogger::Formatters::Syslog::LevelMap]
72
48
  # Supply a custom map of SemanticLogger levels to syslog levels.
73
- # For example, passing in { warn: ::Syslog::LOG_NOTICE }
74
- # would result in a log mapping that matches the default level map,
75
- # except for :warn, which ends up with a LOG_NOTICE level instead of a
76
- # LOG_WARNING one.
77
- # Without overriding any parameters, the level map will be
78
- # LEVEL_MAP = {
79
- # fatal: ::Syslog::LOG_CRIT,
80
- # error: ::Syslog::LOG_ERR,
81
- # warn: ::Syslog::LOG_WARNING,
82
- # info: ::Syslog::LOG_NOTICE,
83
- # debug: ::Syslog::LOG_INFO,
84
- # trace: ::Syslog::LOG_DEBUG
85
- # }
86
- def initialize(options = {})
87
- options = options.dup
88
- @options = options.delete(:options) || (::Syslog::LOG_PID | ::Syslog::LOG_CONS)
89
- @facility = options.delete(:facility) || ::Syslog::LOG_USER
90
- @level_map = DEFAULT_LEVEL_MAP.dup
91
- if level_map = options.delete(:level_map)
92
- @level_map.update(level_map)
93
- end
94
- # Time is already part of Syslog packet
95
- options[:time_format] = nil unless options.has_key?(:time_format)
96
- super(options)
49
+ #
50
+ # Example:
51
+ # # Change the warn level to LOG_NOTICE level instead of a the default of LOG_WARNING.
52
+ # SemanticLogger.add_appender(appender: :syslog, level_map: {warn: ::Syslog::LOG_NOTICE})
53
+ def initialize(facility: ::Syslog::LOG_USER, level_map: LevelMap.new)
54
+ @facility = facility
55
+ @level_map = level_map.is_a?(LevelMap) ? level_map : LevelMap.new(level_map)
56
+ super()
57
+ end
58
+
59
+ # Time is part of the syslog packet and is not included in the formatted message.
60
+ def time
61
+ nil
97
62
  end
98
63
 
99
64
  def call(log, logger)
100
65
  message = super(log, logger)
101
- create_syslog_packet(log, message)
66
+ create_syslog_packet(message)
102
67
  end
103
68
 
69
+ private
70
+
104
71
  # Create Syslog Packet
105
- def create_syslog_packet(log, message)
72
+ def create_syslog_packet(message)
106
73
  packet = SyslogProtocol::Packet.new
107
- packet.hostname = host
74
+ packet.hostname = logger.host
108
75
  packet.facility = facility
109
- packet.tag = application.gsub(' ', '')
76
+ packet.tag = logger.application.gsub(' ', '')
110
77
  packet.content = message
111
78
  packet.time = log.time
112
79
  packet.severity = level_map[log.level]
113
80
  packet.to_s
114
81
  end
115
-
116
82
  end
117
83
  end
118
84
  end
@@ -0,0 +1,57 @@
1
+ begin
2
+ require 'syslog_protocol'
3
+ rescue LoadError
4
+ raise 'Gem syslog_protocol is required for remote logging using the Syslog protol. Please add the gem "syslog_protocol" to your Gemfile.'
5
+ end
6
+
7
+ module SemanticLogger
8
+ module Formatters
9
+ class SyslogCee < Raw
10
+ attr_accessor :level_map, :facility
11
+
12
+ # CEE JSON Syslog format
13
+ # Untested prototype code. Based on documentation only.
14
+ # If this works for you, please let us know by opening an issue.
15
+ #
16
+ # Parameters:
17
+ # facility: [Integer]
18
+ # Default: ::Syslog::LOG_USER
19
+ #
20
+ # level_map: [Hash | SemanticLogger::Formatters::Syslog::LevelMap]
21
+ # Supply a custom map of SemanticLogger levels to syslog levels.
22
+ #
23
+ # Example:
24
+ # # Log via udp to a remote syslog server on host: `server1` and port `8514`, using the CEE format.
25
+ # SemanticLogger.add_appender(appender: :syslog, formatter: syslog_cee, url: 'udp://server1:8514')
26
+ def initialize(facility: ::Syslog::LOG_USER, level_map: SemanticLogger::Formatters::Syslog::LevelMap.new)
27
+ @facility = facility
28
+ @level_map = level_map.is_a?(SemanticLogger::Formatters::Syslog::LevelMap) ? level_map : SemanticLogger::Formatters::Syslog::LevelMap.new(level_map)
29
+ super()
30
+ end
31
+
32
+ # Time is part of the syslog packet and is not included in the formatted message.
33
+ def time
34
+ end
35
+
36
+ def call(log, logger)
37
+ hash = super(log, logger)
38
+ create_syslog_packet("@cee: #{hash.to_json}")
39
+ end
40
+
41
+ private
42
+
43
+ # Create Syslog Packet
44
+ def create_syslog_packet(message)
45
+ packet = SyslogProtocol::Packet.new
46
+ packet.hostname = logger.host
47
+ packet.facility = facility
48
+ packet.tag = logger.application.gsub(' ', '')
49
+ packet.content = message
50
+ packet.time = log.time
51
+ packet.severity = level_map[log.level]
52
+ packet.to_s
53
+ end
54
+ end
55
+ end
56
+ end
57
+
@@ -43,8 +43,11 @@ module SemanticLogger
43
43
  # metric_amount [Numeric]
44
44
  # Used for numeric or counter metrics.
45
45
  # For example, the number of inquiries or, the amount purchased etc.
46
+ #
47
+ # context [Hash]
48
+ # Named contexts that were captured when the log entry was created.
46
49
  class Log
47
- attr_accessor :level, :thread_name, :name, :message, :payload, :time, :duration, :tags, :level_index, :exception, :metric, :backtrace, :metric_amount, :named_tags
50
+ attr_accessor :level, :thread_name, :name, :message, :payload, :time, :duration, :tags, :level_index, :exception, :metric, :backtrace, :metric_amount, :named_tags, :context
48
51
 
49
52
  def initialize(name, level, index = nil)
50
53
  @level = level
@@ -180,7 +183,7 @@ module SemanticLogger
180
183
  # Returns [String] duration of the log entry as a string
181
184
  # Returns nil if their is no duration
182
185
  # Java time precision does not include microseconds
183
- if defined? JRuby
186
+ if Formatters::Base::PRECISION == 3
184
187
  def duration_to_s
185
188
  "#{duration.to_i}ms" if duration
186
189
  end
@@ -256,75 +259,22 @@ module SemanticLogger
256
259
  !(payload.nil? || (payload.respond_to?(:empty?) && payload.empty?))
257
260
  end
258
261
 
259
- if defined? JRuby
260
- # Return the Time as a formatted string
261
- # JRuby only supports time in ms
262
- # DEPRECATED
263
- def formatted_time
264
- "#{time.strftime('%Y-%m-%d %H:%M:%S')}.#{'%03d' % (time.usec/1000)}"
265
- end
266
- else
267
- # Return the Time as a formatted string
268
- # Ruby MRI supports micro seconds
269
- # DEPRECATED
270
- def formatted_time
271
- "#{time.strftime('%Y-%m-%d %H:%M:%S')}.#{'%06d' % (time.usec)}"
272
- end
262
+ # DEPRECATED
263
+ def formatted_time
264
+ time.strftime(Formatters::Base::TIME_FORMAT)
273
265
  end
274
266
 
275
- # Returns [Hash] representation of this log entry
276
- def to_h(host = SemanticLogger.host, application = SemanticLogger.application)
277
- # Header
278
- h = {
279
- name: name,
280
- pid: $$,
281
- thread: thread_name,
282
- time: time,
283
- level: level,
284
- level_index: level_index,
285
- }
286
- h[:host] = host if host
287
- h[:application] = application if application
288
- file, line = file_name_and_line
289
- if file
290
- h[:file] = file
291
- h[:line] = line.to_i
292
- end
293
-
294
- # Tags
295
- h[:tags] = tags if tags && !tags.empty?
296
- h[:named_tags] = named_tags if named_tags && !named_tags.empty?
267
+ DeprecatedLogger = Struct.new(:host, :application)
297
268
 
298
- # Duration
299
- if duration
300
- h[:duration_ms] = duration
301
- h[:duration] = duration_human
302
- end
303
-
304
- # Log message
305
- h[:message] = cleansed_message if message
306
-
307
- # Payload
308
- h[:payload] = payload if payload && payload.respond_to?(:empty?) && !payload.empty?
309
-
310
- # Exceptions
311
- if exception
312
- root = h
313
- each_exception do |exception, i|
314
- name = i == 0 ? :exception : :cause
315
- root[name] = {
316
- name: exception.class.name,
317
- message: exception.message,
318
- stack_trace: exception.backtrace
319
- }
320
- root = root[name]
321
- end
322
- end
269
+ # DEPRECATED: Use SemanticLogger::Formatters::Raw
270
+ def to_h(host = SemanticLogger.host, application = SemanticLogger.application)
271
+ logger = DeprecatedLogger.new(host, application)
272
+ SemanticLogger::Formatters::Raw.new.call(self, logger)
273
+ end
323
274
 
324
- # Metric
325
- h[:metric] = metric if metric
326
- h[:metric_amount] = metric_amount if metric_amount
327
- h
275
+ # Lazy initializes the context hash and assigns a key value pair.
276
+ def set_context(key, value)
277
+ (self.context ||= {})[key] = value
328
278
  end
329
279
 
330
280
  private
@@ -13,7 +13,7 @@ module SemanticLogger
13
13
  # logger = SemanticLogger::Logger.new('MyClass')
14
14
  #
15
15
  # Parameters:
16
- # application
16
+ # klass
17
17
  # A class, module or a string with the application/class name
18
18
  # to be used in the logger
19
19
  #
@@ -26,47 +26,37 @@ module SemanticLogger
26
26
  # regular expression. All other messages will be ignored
27
27
  # Proc: Only include log messages where the supplied Proc returns true
28
28
  # The Proc must return true or false
29
- def initialize(klass, level=nil, filter=nil)
30
- super
29
+ def initialize(klass, level = nil, filter = nil)
30
+ super(klass, level, filter)
31
31
  end
32
32
 
33
- # Returns [Integer] the number of log entries that have not been written
34
- # to the appenders
35
- #
36
- # When this number grows it is because the logging appender thread is not
37
- # able to write to the appenders fast enough. Either reduce the amount of
38
- # logging, increase the log level, reduce the number of appenders, or
39
- # look into speeding up the appenders themselves
33
+ # Place log request on the queue for the Appender thread to write to each
34
+ # appender in the order that they were registered
35
+ def log(log, message = nil, progname = nil, &block)
36
+ # Compatibility with ::Logger
37
+ return add(log, message, progname, &block) unless log.is_a?(SemanticLogger::Log)
38
+ Processor << log
39
+ end
40
+
41
+ # DEPRECATED
40
42
  def self.queue_size
41
43
  Processor.queue_size
42
44
  end
43
45
 
44
- # Flush all queued log entries disk, database, etc.
45
- # All queued log messages are written and then each appender is flushed in turn
46
+ # DEPRECATED
46
47
  def self.flush
47
- Processor.submit_request(:flush)
48
+ Processor.flush
48
49
  end
49
50
 
50
- # Close all appenders and flush any outstanding messages
51
+ # DEPRECATED
51
52
  def self.close
52
- Processor.submit_request(:close)
53
+ Processor.close
53
54
  end
54
55
 
55
- # Allow the internal logger to be overridden from its default of STDERR
56
- # Can be replaced with another Ruby logger or Rails logger, but never to
57
- # SemanticLogger::Logger itself since it is for reporting problems
58
- # while trying to log to the various appenders
56
+ # DEPRECATED
59
57
  def self.logger=(logger)
60
58
  Processor.logger = logger
61
59
  end
62
60
 
63
- # Place log request on the queue for the Appender thread to write to each
64
- # appender in the order that they were registered
65
- def log(log, message = nil, progname = nil, &block)
66
- # Compatibility with ::Logger
67
- return add(log, message, progname, &block) unless log.is_a?(SemanticLogger::Log)
68
- Processor << log
69
- end
70
-
71
61
  end
72
62
  end