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
@@ -57,14 +57,11 @@ module SemanticLogger
57
57
  # appender: :udp,
58
58
  # server: 'server:3300'
59
59
  # )
60
- def initialize(options = {}, &block)
61
- @socket = nil
62
- options = options.dup
63
- @server = options.delete(:server)
64
- @udp_flags = options.delete(:udp_flags) || 0
65
- raise(ArgumentError, 'Missing mandatory argument: :server') unless @server
60
+ def initialize(server:, udp_flags: 0, level: nil, formatter: nil, filter: nil, application: nil, host: nil, &block)
61
+ @server = server
62
+ @udp_flags = udp_flags
66
63
 
67
- super(options, &block)
64
+ super(level: level, formatter: formatter, filter: filter, application: application, host: host, &block)
68
65
  reopen
69
66
  end
70
67
 
@@ -39,19 +39,15 @@ module SemanticLogger
39
39
  # logger.info('Hello World', some: :payload)
40
40
  #
41
41
  # Install the `rails_semantic_logger` gem to replace the Rails logger with Semantic Logger.
42
- def initialize(options, &block)
43
- # Backward compatibility
44
- options = {logger: options} unless options.is_a?(Hash)
45
- options = options.dup
46
- @logger = options.delete(:logger)
42
+ def initialize(logger:, level: nil, formatter: nil, filter: nil, application: nil, host: nil, &block)
43
+ @logger = logger
47
44
 
48
45
  # Check if the custom appender responds to all the log levels. For example Ruby ::Logger
49
46
  if does_not_implement = LEVELS[1..-1].find { |i| !@logger.respond_to?(i) }
50
47
  raise(ArgumentError, "Supplied logger does not implement:#{does_not_implement}. It must implement all of #{LEVELS[1..-1].inspect}")
51
48
  end
52
49
 
53
- raise 'SemanticLogging::Appender::Wrapper missing mandatory parameter :logger' unless @logger
54
- super(options, &block)
50
+ super(level: level, formatter: formatter, filter: filter, application: application, host: host, &block)
55
51
  end
56
52
 
57
53
  # Pass log calls to the underlying Rails, log4j or Ruby logger
@@ -132,7 +132,7 @@ module SemanticLogger
132
132
  else
133
133
  log.thread_name = thread.name
134
134
  log.tags = (thread[:semantic_logger_tags] || []).clone
135
- log.named_tags = (thread[:semantic_logger_named_tags] || []).clone
135
+ log.named_tags = (thread[:semantic_logger_named_tags] || {}).clone
136
136
  thread.backtrace
137
137
  end
138
138
  # TODO: Keep backtrace instead of transforming into a text message at this point
@@ -147,9 +147,42 @@ module SemanticLogger
147
147
  end
148
148
  end
149
149
 
150
- # :nodoc:
150
+ # Add the tags or named tags to the list of tags to log for this thread whilst the supplied block is active.
151
+ #
152
+ # Returns result of block.
153
+ #
154
+ # Tagged example:
155
+ # SemanticLogger.tagged(12345, 'jack') do
156
+ # logger.debug('Hello World')
157
+ # end
158
+ #
159
+ # Named Tags (Hash) example:
160
+ # SemanticLogger.tagged(tracking_number: 12345) do
161
+ # logger.debug('Hello World')
162
+ # end
163
+ #
164
+ # Notes:
165
+ # - Named tags are the recommended approach since the tag consists of a name value pair this is more useful
166
+ # than just a string value in the logs, or centralized logging system.
167
+ # - This method is slow when using multiple text tags since it needs to flatten the tags and
168
+ # remove empty elements to support Rails 4.
169
+ # - It is recommended to keep tags as a list without any empty values, or contain any child arrays.
170
+ # However, this api will convert:
171
+ # `logger.tagged([['first', nil], nil, ['more'], 'other'])`
172
+ # to:
173
+ # `logger.tagged('first', 'more', 'other')`
174
+ # - For better performance with clean tags, see `SemanticLogger.tagged`.
151
175
  def tagged(*tags, &block)
152
- SemanticLogger.tagged(*tags, &block)
176
+ # Allow named tags to be passed into the logger
177
+ if tags.size == 1
178
+ tag = tags[0]
179
+ return yield if tag.nil? || tag == ''
180
+ return tag.is_a?(Hash) ? SemanticLogger.named_tagged(tag, &block) : SemanticLogger.fast_tag(tag.to_s, &block)
181
+ end
182
+
183
+ # Need to flatten and reject empties to support calls from Rails 4
184
+ new_tags = tags.flatten.collect(&:to_s).reject(&:empty?)
185
+ SemanticLogger.tagged(*new_tags, &block)
153
186
  end
154
187
 
155
188
  # :nodoc:
@@ -160,9 +193,16 @@ module SemanticLogger
160
193
  SemanticLogger.tags
161
194
  end
162
195
 
163
- # :nodoc:
196
+ # Returns the list of tags pushed after flattening them out and removing blanks
197
+ #
198
+ # Note:
199
+ # - This method is slow since it needs to flatten the tags and remove empty elements
200
+ # to support Rails 4.
201
+ # - For better performance with clean tags, use `SemanticLogger.push_tags`
164
202
  def push_tags(*tags)
165
- SemanticLogger.push_tags(*tags)
203
+ # Need to flatten and reject empties to support calls from Rails 4
204
+ new_tags = tags.flatten.collect(&:to_s).reject(&:empty?)
205
+ SemanticLogger.push_tags(*new_tags)
166
206
  end
167
207
 
168
208
  # :nodoc:
@@ -175,7 +215,7 @@ module SemanticLogger
175
215
  SemanticLogger.silence(new_level, &block)
176
216
  end
177
217
 
178
- # :nodoc:
218
+ # Deprecated. Use `SemanticLogger.tagged`
179
219
  def fast_tag(tag, &block)
180
220
  SemanticLogger.fast_tag(tag, &block)
181
221
  end
@@ -286,7 +326,7 @@ module SemanticLogger
286
326
  if silence_level = params[:silence]
287
327
  # In case someone accidentally sets `silence: true` instead of `silence: :error`
288
328
  silence_level = :error if silence_level == true
289
- silence(silence_level) { yield(params) }
329
+ silence(silence_level) {yield(params)}
290
330
  else
291
331
  yield(params)
292
332
  end
@@ -1,7 +1,23 @@
1
+ require 'time'
1
2
  module SemanticLogger
2
3
  module Formatters
3
4
  class Base
4
- attr_accessor :time_format, :precision, :log_host, :log_application
5
+ attr_accessor :time_format, :log_host, :log_application
6
+
7
+ # Time precision varies by Ruby interpreter
8
+ # JRuby 9.1.8.0 supports microseconds
9
+ PRECISION =
10
+ if defined?(JRuby)
11
+ if (JRUBY_VERSION.to_f >= 9.1)
12
+ maint = JRUBY_VERSION.match(/\A\d\.\d\.(\d)\./)[1].to_i
13
+ (maint >= 8) || (JRUBY_VERSION.to_f > 9.1) ? 6 : 3
14
+ else
15
+ 3
16
+ end
17
+ else
18
+ 6
19
+ end
20
+ TIME_FORMAT = "%Y-%m-%d %H:%M:%S.%#{PRECISION}N"
5
21
 
6
22
  # Parameters
7
23
  # time_format: [String|Symbol|nil]
@@ -9,21 +25,24 @@ module SemanticLogger
9
25
  # :iso_8601 Outputs an ISO8601 Formatted timestamp
10
26
  # nil: Returns Empty string for time ( no time is output ).
11
27
  # 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
28
+ def initialize(time_format: TIME_FORMAT, log_host: true, log_application: true)
29
+ @time_format = time_format
30
+ @log_host = log_host
31
+ @log_application = log_application
20
32
  end
21
33
 
34
+ # Date & time
35
+ def time
36
+ format_time(log.time) if time_format
37
+ end
38
+
39
+ private
40
+
22
41
  # Return the Time as a formatted string
23
42
  def format_time(time)
24
43
  case time_format
25
44
  when :iso_8601
26
- time.utc.iso8601(precision)
45
+ time.utc.iso8601(PRECISION)
27
46
  when nil
28
47
  ''
29
48
  else
@@ -6,68 +6,98 @@ end
6
6
 
7
7
  module SemanticLogger
8
8
  module Formatters
9
- class Color < Base
10
- # Parameters:
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
- #
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)
9
+ class Color < Default
10
+ attr_accessor :color_map, :color
11
+
12
+ # Supply a custom color map for every log level
13
+ class ColorMap
14
+ attr_accessor :trace, :debug, :info, :warn, :error, :fatal, :bold, :clear
15
+
16
+ def initialize(trace: AnsiColors::MAGENTA, debug: AnsiColors::GREEN, info: AnsiColors::CYAN, warn: AnsiColors::BOLD, error: AnsiColors::RED, fatal: AnsiColors::RED, bold: AnsiColors::BOLD, clear: AnsiColors::CLEAR)
17
+ @trace = trace
18
+ @debug = debug
19
+ @info = info
20
+ @warn = warn
21
+ @error = error
22
+ @fatal = fatal
23
+ @bold = bold
24
+ @clear = clear
25
+ end
26
+
27
+ def [](level)
28
+ public_send(level)
29
+ end
21
30
  end
22
31
 
23
32
  # Adds color to the default log formatter
33
+ #
24
34
  # Example:
35
+ # # Use a colorized output logger.
25
36
  # SemanticLogger.add_appender(io: $stdout, formatter: :color)
26
- def call(log, logger)
27
- colors = SemanticLogger::AnsiColors
28
- level_color = colors::LEVEL_MAP[log.level]
29
-
30
- message = time_format.nil? ? '' : "#{format_time(log.time)} "
37
+ #
38
+ # Example:
39
+ # # Use a colorized output logger chenging the color for info to green.
40
+ # SemanticLogger.add_appender(io: $stdout, formatter: :color, color_map: {info: SemanticLogger::AnsiColors::YELLOW})
41
+ #
42
+ # Parameters:
43
+ # ap: [Hash]
44
+ # Any valid AwesomePrint option for rendering data.
45
+ # These options can also be changed be creating a `~/.aprc` file.
46
+ # See: https://github.com/michaeldv/awesome_print
47
+ #
48
+ # Note: The option :multiline is set to false if not supplied.
49
+ # Note: Has no effect if Awesome Print is not installed.
50
+ #
51
+ # color_map: [Hash | SemanticLogger::Formatters::Color::ColorMap]
52
+ # ColorMaps each of the log levels to a color
53
+ def initialize(ap: {multiline: false}, color_map: ColorMap.new, time_format: TIME_FORMAT, log_host: false, log_application: false)
54
+ @ai_options = ap
55
+ @color_map = color_map.is_a?(ColorMap) ? color_map : ColorMap.new(color_map)
56
+ super(time_format: time_format, log_host: log_host, log_application: log_application)
57
+ end
31
58
 
32
- # Header with date, time, log level and process info
33
- message << "#{level_color}#{log.level_to_s}#{colors::CLEAR} [#{log.process_info}]"
59
+ def level
60
+ "#{color}#{super}#{color_map.clear}"
61
+ end
34
62
 
35
- # Tags
36
- message << ' ' << log.tags.collect { |tag| "[#{level_color}#{tag}#{colors::CLEAR}]" }.join(' ') if log.tags && (log.tags.size > 0)
63
+ def tags
64
+ "[#{color}#{log.tags.join("#{color_map.clear}] [#{color}")}#{color_map.clear}]" if log.tags && !log.tags.empty?
65
+ end
37
66
 
38
- # Named Tags
67
+ # Named Tags
68
+ def named_tags
39
69
  if (named_tags = log.named_tags) && !named_tags.empty?
40
70
  list = []
41
- named_tags.each_pair { |name, value| list << "[#{level_color}#{name}: #{value}#{colors::CLEAR}]" }
42
- message << ' ' << list.join(' ')
71
+ named_tags.each_pair { |name, value| list << "#{color}#{name}: #{value}#{color_map.clear}" }
72
+ "{#{list.join(', ')}}"
43
73
  end
74
+ end
44
75
 
45
- # Duration
46
- message << " (#{colors::BOLD}#{log.duration_human}#{colors::CLEAR})" if log.duration
76
+ def duration
77
+ "(#{color_map.bold}#{log.duration_human}#{color_map.clear})" if log.duration
78
+ end
47
79
 
48
- # Class / app name
49
- message << " #{level_color}#{log.name}#{colors::CLEAR}"
80
+ def name
81
+ "#{color}#{super}#{color_map.clear}"
82
+ end
50
83
 
51
- # Log message
52
- message << " -- #{log.message}" if log.message
84
+ def payload
85
+ return unless log.has_payload?
53
86
 
54
- # Payload: Colorize the payload if the AwesomePrint gem is loaded
55
- if log.has_payload?
56
- payload = log.payload
57
- message << ' -- ' <<
58
- if !defined?(AwesomePrint) || !payload.respond_to?(:ai)
59
- payload.inspect
60
- else
61
- payload.ai(@ai_options) rescue payload.inspect
62
- end
87
+ if !defined?(AwesomePrint) || !log.payload.respond_to?(:ai)
88
+ super
89
+ else
90
+ "-- #{log.payload.ai(@ai_options)}" rescue super
63
91
  end
92
+ end
64
93
 
65
- # Exceptions
66
- if log.exception
67
- message << " -- Exception: #{colors::BOLD}#{log.exception.class}: #{log.exception.message}#{colors::CLEAR}\n"
68
- message << log.backtrace_to_s
69
- end
70
- message
94
+ def exception
95
+ "-- Exception: #{color}#{log.exception.class}: #{log.exception.message}#{color_map.clear}\n#{log.backtrace_to_s}" if log.exception
96
+ end
97
+
98
+ def call(log, logger)
99
+ self.color = color_map[log.level]
100
+ super(log, logger)
71
101
  end
72
102
 
73
103
  end
@@ -1,46 +1,71 @@
1
1
  module SemanticLogger
2
2
  module Formatters
3
+ # Default non-colored text log output
3
4
  class Default < Base
4
- # Default text log format
5
- # Generates logs of the form:
6
- # 2011-07-19 14:36:15.660235 D [1149:ScriptThreadProcess] Rails -- Hello World
7
- def call(log, logger)
8
- # Date & time
9
- message = time_format.nil? ? '' : "#{format_time(log.time)} "
5
+ attr_accessor :log, :logger
10
6
 
11
- # Log level and process info
12
- message << "#{log.level_to_s} [#{log.process_info}]"
7
+ # Formatting methods, must return nil, or a string
8
+ # Nil values are ignored
13
9
 
14
- # Tags
15
- message << ' ' << log.tags.collect { |tag| "[#{tag}]" }.join(' ') if log.tags && (log.tags.size > 0)
10
+ # Log level
11
+ def level
12
+ log.level_to_s
13
+ end
16
14
 
17
- # Named Tags
15
+ # Process info
16
+ def process_info
17
+ "[#{log.process_info}]"
18
+ end
19
+
20
+ # Tags
21
+ def tags
22
+ "[#{log.tags.join('] [')}]" if log.tags && !log.tags.empty?
23
+ end
24
+
25
+ # Named Tags
26
+ def named_tags
18
27
  if (named_tags = log.named_tags) && !named_tags.empty?
19
28
  list = []
20
- named_tags.each_pair { |name, value| list << "[#{name}: #{value}]" }
21
- message << ' ' << list.join(' ')
29
+ named_tags.each_pair { |name, value| list << "#{name}: #{value}" }
30
+ "{#{list.join(', ')}}"
22
31
  end
32
+ end
23
33
 
24
- # Duration
25
- message << " (#{log.duration_human})" if log.duration
34
+ # Duration
35
+ def duration
36
+ "(#{log.duration_human})" if log.duration
37
+ end
26
38
 
27
- # Class / app name
28
- message << " #{log.name}"
39
+ # Class / app name
40
+ def name
41
+ log.name
42
+ end
29
43
 
30
- # Log message
31
- message << " -- #{log.message}" if log.message
44
+ # Log message
45
+ def message
46
+ "-- #{log.message}" if log.message
47
+ end
32
48
 
33
- # Payload
34
- if payload = log.payload_to_s
35
- message << ' -- ' << payload
49
+ # Payload
50
+ def payload
51
+ if pl = log.payload_to_s
52
+ "-- #{pl}"
36
53
  end
54
+ end
37
55
 
38
- # Exceptions
39
- if log.exception
40
- message << " -- Exception: #{log.exception.class}: #{log.exception.message}\n"
41
- message << log.backtrace_to_s
42
- end
43
- message
56
+ # Exception
57
+ def exception
58
+ "-- Exception: #{log.exception.class}: #{log.exception.message}\n#{log.backtrace_to_s}" if log.exception
59
+ end
60
+
61
+ # Default text log format
62
+ # Generates logs of the form:
63
+ # 2011-07-19 14:36:15.660235 D [1149:ScriptThreadProcess] Rails -- Hello World
64
+ def call(log, logger)
65
+ self.log = log
66
+ self.logger = logger
67
+
68
+ [time, level, process_info, tags, named_tags, duration, name, message, payload, exception].compact.join(' ')
44
69
  end
45
70
 
46
71
  end
@@ -3,18 +3,17 @@ module SemanticLogger
3
3
  module Formatters
4
4
  class Json < Raw
5
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)
6
+ def initialize(time_format: :iso_8601, log_host: true, log_application: true)
7
+ super(time_format: time_format, log_host: log_host, log_application: log_application)
8
+ end
9
+
10
+ def time
11
+ hash[:timestamp] = format_time(log.time)
10
12
  end
11
13
 
12
14
  # Returns log messages in JSON format
13
15
  def call(log, logger)
14
- h = super(log, logger)
15
- h.delete(:time)
16
- h[:timestamp] = format_time(log.time)
17
- h.to_json
16
+ super(log, logger).to_json
18
17
  end
19
18
 
20
19
  end