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
@@ -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