semantic_logger 3.2.1 → 3.3.0

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