semantic_logger 3.2.1 → 3.3.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/semantic_logger.rb +22 -2
  4. data/lib/semantic_logger/appender/bugsnag.rb +2 -2
  5. data/lib/semantic_logger/appender/elasticsearch.rb +9 -1
  6. data/lib/semantic_logger/appender/file.rb +1 -1
  7. data/lib/semantic_logger/appender/graylog.rb +18 -21
  8. data/lib/semantic_logger/appender/honeybadger.rb +27 -12
  9. data/lib/semantic_logger/appender/http.rb +19 -11
  10. data/lib/semantic_logger/appender/mongodb.rb +23 -30
  11. data/lib/semantic_logger/appender/new_relic.rb +2 -2
  12. data/lib/semantic_logger/appender/splunk.rb +32 -21
  13. data/lib/semantic_logger/appender/splunk_http.rb +1 -3
  14. data/lib/semantic_logger/appender/syslog.rb +25 -10
  15. data/lib/semantic_logger/appender/tcp.rb +231 -0
  16. data/lib/semantic_logger/appender/udp.rb +106 -0
  17. data/lib/semantic_logger/appender/wrapper.rb +1 -1
  18. data/lib/semantic_logger/base.rb +9 -3
  19. data/lib/semantic_logger/formatters/base.rb +36 -0
  20. data/lib/semantic_logger/formatters/color.rb +13 -10
  21. data/lib/semantic_logger/formatters/default.rb +8 -5
  22. data/lib/semantic_logger/formatters/json.rb +10 -3
  23. data/lib/semantic_logger/formatters/raw.rb +13 -0
  24. data/lib/semantic_logger/formatters/syslog.rb +119 -0
  25. data/lib/semantic_logger/log.rb +7 -5
  26. data/lib/semantic_logger/loggable.rb +5 -0
  27. data/lib/semantic_logger/logger.rb +45 -10
  28. data/lib/semantic_logger/metrics/new_relic.rb +1 -1
  29. data/lib/semantic_logger/metrics/statsd.rb +5 -1
  30. data/lib/semantic_logger/metrics/udp.rb +80 -0
  31. data/lib/semantic_logger/semantic_logger.rb +23 -27
  32. data/lib/semantic_logger/subscriber.rb +127 -0
  33. data/lib/semantic_logger/version.rb +1 -1
  34. data/test/appender/elasticsearch_test.rb +6 -4
  35. data/test/appender/file_test.rb +12 -12
  36. data/test/appender/honeybadger_test.rb +7 -1
  37. data/test/appender/http_test.rb +4 -2
  38. data/test/appender/mongodb_test.rb +1 -2
  39. data/test/appender/splunk_http_test.rb +8 -6
  40. data/test/appender/splunk_test.rb +48 -45
  41. data/test/appender/syslog_test.rb +3 -3
  42. data/test/appender/tcp_test.rb +68 -0
  43. data/test/appender/udp_test.rb +61 -0
  44. data/test/appender/wrapper_test.rb +5 -5
  45. data/test/concerns/compatibility_test.rb +6 -6
  46. data/test/debug_as_trace_logger_test.rb +2 -2
  47. data/test/loggable_test.rb +2 -2
  48. data/test/logger_test.rb +48 -45
  49. metadata +13 -3
  50. data/lib/semantic_logger/appender/base.rb +0 -101
@@ -4,7 +4,7 @@
4
4
  #
5
5
  module SemanticLogger
6
6
  module Appender
7
- class Wrapper < SemanticLogger::Appender::Base
7
+ class Wrapper < SemanticLogger::Subscriber
8
8
  attr_reader :logger
9
9
 
10
10
  # Forward all logging calls to the supplied logging instance.
@@ -211,9 +211,15 @@ module SemanticLogger
211
211
  # or Proc
212
212
  raise ':filter must be a Regexp or Proc' unless filter.nil? || filter.is_a?(Regexp) || filter.is_a?(Proc)
213
213
 
214
- @filter = filter.is_a?(Regexp) ? filter.freeze : filter
215
- @name = klass.is_a?(String) ? klass : klass.name
216
- self.level = level unless level.nil?
214
+ @filter = filter.is_a?(Regexp) ? filter.freeze : filter
215
+ @name = klass.is_a?(String) ? klass : klass.name
216
+ if level.nil?
217
+ # Allow the global default level to determine this loggers log level
218
+ @level_index = nil
219
+ @level = nil
220
+ else
221
+ self.level = level
222
+ end
217
223
  end
218
224
 
219
225
  # Return the level index for fast comparisons
@@ -0,0 +1,36 @@
1
+ module SemanticLogger
2
+ module Formatters
3
+ class Base
4
+ attr_accessor :time_format, :precision, :log_host, :log_application
5
+
6
+ # Parameters
7
+ # time_format: [String|Symbol|nil]
8
+ # See Time#strftime for the format of this string
9
+ # :iso_8601 Outputs an ISO8601 Formatted timestamp
10
+ # nil: Returns Empty string for time ( no time is output ).
11
+ # Default: '%Y-%m-%d %H:%M:%S.%6N'
12
+ def initialize(options = {})
13
+ options = options.dup
14
+ @precision = defined?(JRuby) ? 3 : 6
15
+ default_format = "%Y-%m-%d %H:%M:%S.%#{precision}N"
16
+ @time_format = options.has_key?(:time_format) ? options.delete(:time_format) : default_format
17
+ @log_host = options.has_key?(:log_host) ? options.delete(:log_host) : true
18
+ @log_application = options.has_key?(:log_application) ? options.delete(:log_application) : true
19
+ raise(ArgumentError, "Unknown options: #{options.inspect}") if options.size > 0
20
+ end
21
+
22
+ # Return the Time as a formatted string
23
+ def format_time(time)
24
+ case time_format
25
+ when :iso_8601
26
+ time.utc.iso8601(precision)
27
+ when nil
28
+ ''
29
+ else
30
+ time.strftime(time_format)
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
@@ -6,17 +6,18 @@ end
6
6
 
7
7
  module SemanticLogger
8
8
  module Formatters
9
- class Color
9
+ class Color < Base
10
10
  # Parameters:
11
- # Any valid AwesomePrint option for rendering data.
12
- # These options can also be changed be creating a `~/.aprc` file.
13
- # See: https://github.com/michaeldv/awesome_print
11
+ # ap: Any valid AwesomePrint option for rendering data.
12
+ # These options can also be changed be creating a `~/.aprc` file.
13
+ # See: https://github.com/michaeldv/awesome_print
14
14
  #
15
- # Note: The option :multiline is set to false if not supplied.
16
- # Note: Has no effect if Awesome Print is not installed.
17
- def initialize(options={})
18
- @ai_options = options.dup
19
- @ai_options[:multiline] = false unless @ai_options.has_key?(:multiline)
15
+ # Note: The option :multiline is set to false if not supplied.
16
+ # Note: Has no effect if Awesome Print is not installed.
17
+ def initialize(options = {})
18
+ options = options.dup
19
+ @ai_options = options.delete(:ap) || {multiline: false}
20
+ super(options)
20
21
  end
21
22
 
22
23
  # Adds color to the default log formatter
@@ -26,8 +27,10 @@ module SemanticLogger
26
27
  colors = SemanticLogger::AnsiColors
27
28
  level_color = colors::LEVEL_MAP[log.level]
28
29
 
30
+ message = time_format.nil? ? '' : "#{format_time(log.time)} "
31
+
29
32
  # Header with date, time, log level and process info
30
- message = "#{log.formatted_time} #{level_color}#{log.level_to_s}#{colors::CLEAR} [#{log.process_info}]"
33
+ message << "#{level_color}#{log.level_to_s}#{colors::CLEAR} [#{log.process_info}]"
31
34
 
32
35
  # Tags
33
36
  message << ' ' << log.tags.collect { |tag| "[#{level_color}#{tag}#{colors::CLEAR}]" }.join(' ') if log.tags && (log.tags.size > 0)
@@ -1,12 +1,15 @@
1
1
  module SemanticLogger
2
2
  module Formatters
3
- class Default
4
- # Default log formatter
3
+ class Default < Base
4
+ # Default text log format
5
5
  # Generates logs of the form:
6
- # 2011-07-19 14:36:15.660 D [1149:ScriptThreadProcess] Rails -- Hello World
6
+ # 2011-07-19 14:36:15.660235 D [1149:ScriptThreadProcess] Rails -- Hello World
7
7
  def call(log, logger)
8
- # Header with date, time, log level and process info
9
- message = "#{log.formatted_time} #{log.level_to_s} [#{log.process_info}]"
8
+ # Date & time
9
+ message = time_format.nil? ? '' : "#{format_time(log.time)} "
10
+
11
+ # Log level and process info
12
+ message << "#{log.level_to_s} [#{log.process_info}]"
10
13
 
11
14
  # Tags
12
15
  message << ' ' << log.tags.collect { |tag| "[#{tag}]" }.join(' ') if log.tags && (log.tags.size > 0)
@@ -1,12 +1,19 @@
1
1
  require 'json'
2
2
  module SemanticLogger
3
3
  module Formatters
4
- class Json
4
+ class Json < Raw
5
+ # Default JSON time format is ISO8601
6
+ def initialize(options = {})
7
+ options = options.dup
8
+ options[:time_format] = :iso_8601 unless options.has_key?(:time_format)
9
+ super(options)
10
+ end
11
+
5
12
  # Returns log messages in JSON format
6
13
  def call(log, logger)
7
- h = log.to_h
14
+ h = super(log, logger)
8
15
  h.delete(:time)
9
- h[:timestamp] = log.time.utc.iso8601(defined?(JRuby) ? 3 : 6)
16
+ h[:timestamp] = format_time(log.time)
10
17
  h.to_json
11
18
  end
12
19
 
@@ -0,0 +1,13 @@
1
+ require 'json'
2
+ module SemanticLogger
3
+ module Formatters
4
+ class Raw < Base
5
+ # Returns log messages in Hash format
6
+ def call(log, logger)
7
+ log.to_h(log_host ? logger.host : nil, log_application ? logger.application : nil)
8
+ end
9
+
10
+ end
11
+ end
12
+ end
13
+
@@ -0,0 +1,119 @@
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 Syslog < Default
10
+ attr_accessor :level_map, :options, :facility
11
+
12
+ # Default mapping of ruby log levels to syslog log levels
13
+ #
14
+ # ::Syslog::LOG_EMERG - "System is unusable"
15
+ # ::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
30
+
31
+ # Create a Syslog Log Formatter
32
+ #
33
+ # 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
+ # facility: [Integer]
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
+ #
71
+ # level_map: [Hash]
72
+ # 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)
97
+ end
98
+
99
+ def call(log, logger)
100
+ message = super(log, logger)
101
+ create_syslog_packet(log, message)
102
+ end
103
+
104
+ # Create Syslog Packet
105
+ def create_syslog_packet(log, message)
106
+ packet = SyslogProtocol::Packet.new
107
+ packet.hostname = host
108
+ packet.facility = facility
109
+ packet.tag = application.gsub(' ', '')
110
+ packet.content = message
111
+ packet.time = log.time
112
+ packet.severity = level_map[log.level]
113
+ packet.to_s
114
+ end
115
+
116
+ end
117
+ end
118
+ end
119
+
@@ -163,23 +163,23 @@ module SemanticLogger
163
163
  if defined? JRuby
164
164
  # Return the Time as a formatted string
165
165
  # JRuby only supports time in ms
166
+ # DEPRECATED
166
167
  def formatted_time
167
168
  "#{time.strftime('%Y-%m-%d %H:%M:%S')}.#{'%03d' % (time.usec/1000)}"
168
169
  end
169
170
  else
170
171
  # Return the Time as a formatted string
171
172
  # Ruby MRI supports micro seconds
173
+ # DEPRECATED
172
174
  def formatted_time
173
175
  "#{time.strftime('%Y-%m-%d %H:%M:%S')}.#{'%06d' % (time.usec)}"
174
176
  end
175
177
  end
176
178
 
177
179
  # Returns [Hash] representation of this log entry
178
- def to_h
180
+ def to_h(host = SemanticLogger.host, application = SemanticLogger.application)
179
181
  # Header
180
- h = {
181
- host: SemanticLogger.host,
182
- application: SemanticLogger.application,
182
+ h = {
183
183
  name: name,
184
184
  pid: $$,
185
185
  thread: thread_name,
@@ -187,7 +187,9 @@ module SemanticLogger
187
187
  level: level,
188
188
  level_index: level_index,
189
189
  }
190
- file, line = file_name_and_line
190
+ h[:host] = host if host
191
+ h[:application] = application if application
192
+ file, line = file_name_and_line
191
193
  if file
192
194
  h[:file] = file
193
195
  h[:line] = line.to_i
@@ -34,6 +34,11 @@ module SemanticLogger
34
34
  @semantic_logger ||= SemanticLogger[self]
35
35
  end
36
36
 
37
+ # Replace instance class level logger
38
+ def self.logger=(logger)
39
+ @semantic_logger = logger
40
+ end
41
+
37
42
  # Returns [SemanticLogger::Logger] instance level logger
38
43
  def logger
39
44
  @semantic_logger ||= self.class.logger
@@ -44,12 +44,28 @@ module SemanticLogger
44
44
  # Flush all queued log entries disk, database, etc.
45
45
  # All queued log messages are written and then each appender is flushed in turn
46
46
  def self.flush
47
- return false unless appender_thread_active?
47
+ msg = "Flushing appenders with #{queue_size} log messages on the queue"
48
+ if queue_size > 1_000
49
+ logger.warn msg
50
+ elsif queue_size > 100
51
+ logger.info msg
52
+ elsif queue_size > 0
53
+ logger.trace msg
54
+ end
55
+ process_request(:flush)
56
+ end
48
57
 
49
- logger.trace "Flushing appenders with #{queue_size} log messages on the queue"
50
- reply_queue = Queue.new
51
- queue << {command: :flush, reply_queue: reply_queue}
52
- reply_queue.pop
58
+ # Close all appenders and flush any outstanding messages
59
+ def self.close
60
+ msg = "Closing appenders with #{queue_size} log messages on the queue"
61
+ if queue_size > 1_000
62
+ logger.warn msg
63
+ elsif queue_size > 100
64
+ logger.info msg
65
+ elsif queue_size > 0
66
+ logger.trace msg
67
+ end
68
+ process_request(:close)
53
69
  end
54
70
 
55
71
  @@lag_check_interval = 5000
@@ -73,10 +89,6 @@ module SemanticLogger
73
89
  @@lag_threshold_s
74
90
  end
75
91
 
76
- def self.time_threshold_s=(time_threshold_s)
77
- @@lag_threshold_s = time_threshold_s
78
- end
79
-
80
92
  # Allow the internal logger to be overridden from its default to STDERR
81
93
  # Can be replaced with another Ruby logger or Rails logger, but never to
82
94
  # SemanticLogger::Logger itself since it is for reporting problems
@@ -111,7 +123,7 @@ module SemanticLogger
111
123
  appender = block || options.delete(:appender)
112
124
 
113
125
  # Convert symbolized metrics appender to an actual object
114
- appender = named_appender(appender, 'SemanticLogger::Appender::Metrics').new(options) if appender.is_a?(Symbol)
126
+ appender = SemanticLogger.constantize_symbol(appender, 'SemanticLogger::Metrics').new(options) if appender.is_a?(Symbol)
115
127
 
116
128
  raise('When supplying a metrics appender, it must support the #call method') unless appender.is_a?(Proc) || appender.respond_to?(:call)
117
129
  (@@metric_subscribers ||= Concurrent::Array.new) << appender
@@ -202,6 +214,20 @@ module SemanticLogger
202
214
  end
203
215
  end
204
216
 
217
+ message[:reply_queue] << true if message[:reply_queue]
218
+ logger.trace 'Appender thread: All appenders flushed'
219
+ when :close
220
+ SemanticLogger.appenders.each do |appender|
221
+ begin
222
+ logger.trace "Appender thread: Closing appender: #{appender.name}"
223
+ appender.flush
224
+ appender.close
225
+ SemanticLogger.remove_appender(appender)
226
+ rescue Exception => exc
227
+ logger.error "Appender thread: Failed to close appender: #{appender.inspect}", exc
228
+ end
229
+ end
230
+
205
231
  message[:reply_queue] << true if message[:reply_queue]
206
232
  logger.trace 'Appender thread: All appenders flushed'
207
233
  else
@@ -242,5 +268,14 @@ module SemanticLogger
242
268
  end
243
269
  end
244
270
 
271
+ # Close all appenders and flush any outstanding messages
272
+ def self.process_request(command)
273
+ return false unless appender_thread_active?
274
+
275
+ reply_queue = Queue.new
276
+ queue << {command: command, reply_queue: reply_queue}
277
+ reply_queue.pop
278
+ end
279
+
245
280
  end
246
281
  end
@@ -1,6 +1,6 @@
1
1
  module SemanticLogger
2
2
  module Metrics
3
- class NewRelic
3
+ class NewRelic < Subscriber
4
4
  attr_accessor :prefix
5
5
 
6
6
  # Parameters:
@@ -7,7 +7,7 @@ end
7
7
 
8
8
  module SemanticLogger
9
9
  module Metrics
10
- class Statsd
10
+ class Statsd < Subscriber
11
11
  # Create Statsd metrics subscriber
12
12
  #
13
13
  # Parameters:
@@ -18,6 +18,10 @@ module SemanticLogger
18
18
  # Example, send all metrics to a particular namespace:
19
19
  # udp://localhost:8125/namespace
20
20
  # Default: udp://localhost:8125
21
+ #
22
+ # Example:
23
+ # subscriber = SemanticLogger::Metrics::Statsd.new(url: 'udp://localhost:8125')
24
+ # SemanticLogger.on_metric(subscriber)
21
25
  def initialize(options = {})
22
26
  options = options.dup
23
27
  @url = options.delete(:url) || 'udp://localhost:8125'