semantic_logger 4.7.4 → 4.9.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.
@@ -57,7 +57,7 @@ module SemanticLogger
57
57
  # Only used with the TCP protocol.
58
58
  # Specify custom parameters to pass into Net::TCPClient.new
59
59
  # For a list of options see the net_tcp_client documentation:
60
- # https://github.com/rocketjob/net_tcp_client/blob/master/lib/net/tcp_client/tcp_client.rb
60
+ # https://github.com/reidmorrison/net_tcp_client/blob/master/lib/net/tcp_client/tcp_client.rb
61
61
  #
62
62
  # level: [:trace | :debug | :info | :warn | :error | :fatal]
63
63
  # Override the log level for this appender.
@@ -151,7 +151,8 @@ module SemanticLogger
151
151
  begin
152
152
  require "syslog_protocol"
153
153
  rescue LoadError
154
- raise LoadError, "Missing gem: syslog_protocol. This gem is required when logging over TCP or UDP. To fix this error: gem install syslog_protocol"
154
+ raise LoadError,
155
+ "Missing gem: syslog_protocol. This gem is required when logging over TCP or UDP. To fix this error: gem install syslog_protocol"
155
156
  end
156
157
 
157
158
  # The net_tcp_client gem is required when logging over TCP.
@@ -159,7 +160,8 @@ module SemanticLogger
159
160
  begin
160
161
  require "net/tcp_client"
161
162
  rescue LoadError
162
- raise LoadError, "Missing gem: net_tcp_client. This gem is required when logging over TCP. To fix this error: gem install net_tcp_client"
163
+ raise LoadError,
164
+ "Missing gem: net_tcp_client. This gem is required when logging over TCP. To fix this error: gem install net_tcp_client"
163
165
  end
164
166
  end
165
167
  end
@@ -32,7 +32,7 @@ module SemanticLogger
32
32
  # require 'logger'
33
33
  # require 'semantic_logger'
34
34
  #
35
- # ruby_logger = Logger.new(STDOUT)
35
+ # ruby_logger = Logger.new($stdout)
36
36
  # SemanticLogger.add_appender(logger: ruby_logger)
37
37
  #
38
38
  # logger = SemanticLogger['test']
@@ -45,7 +45,8 @@ module SemanticLogger
45
45
  # Check if the custom appender responds to all the log levels. For example Ruby ::Logger
46
46
  does_not_implement = LEVELS[1..-1].find { |i| !@logger.respond_to?(i) }
47
47
  if does_not_implement
48
- raise(ArgumentError, "Supplied logger does not implement:#{does_not_implement}. It must implement all of #{LEVELS[1..-1].inspect}")
48
+ raise(ArgumentError,
49
+ "Supplied logger does not implement:#{does_not_implement}. It must implement all of #{LEVELS[1..-1].inspect}")
49
50
  end
50
51
 
51
52
  super(**args, &block)
@@ -9,6 +9,7 @@ module SemanticLogger
9
9
  autoload :File, "semantic_logger/appender/file"
10
10
  autoload :Graylog, "semantic_logger/appender/graylog"
11
11
  autoload :Honeybadger, "semantic_logger/appender/honeybadger"
12
+ autoload :IO, "semantic_logger/appender/io"
12
13
  autoload :Kafka, "semantic_logger/appender/kafka"
13
14
  autoload :Sentry, "semantic_logger/appender/sentry"
14
15
  autoload :Http, "semantic_logger/appender/http"
@@ -32,7 +33,7 @@ module SemanticLogger
32
33
  appender = build(**args, &block)
33
34
 
34
35
  # If appender implements #batch, then it should use the batch proxy by default.
35
- batch = true if batch.nil? && appender.respond_to?(:batch)
36
+ batch = true if batch.nil? && appender.respond_to?(:batch)
36
37
 
37
38
  if batch == true
38
39
  Appender::AsyncBatch.new(
@@ -56,8 +57,10 @@ module SemanticLogger
56
57
 
57
58
  # Returns [Subscriber] instance from the supplied options.
58
59
  def self.build(io: nil, file_name: nil, appender: nil, metric: nil, logger: nil, **args, &block)
59
- if io || file_name
60
- SemanticLogger::Appender::File.new(io: io, file_name: file_name, **args, &block)
60
+ if file_name
61
+ SemanticLogger::Appender::File.new(file_name, **args, &block)
62
+ elsif io
63
+ SemanticLogger::Appender::IO.new(io, **args, &block)
61
64
  elsif logger
62
65
  SemanticLogger::Appender::Wrapper.new(logger: logger, **args, &block)
63
66
  elsif appender
@@ -66,7 +69,8 @@ module SemanticLogger
66
69
  elsif appender.is_a?(Subscriber)
67
70
  appender
68
71
  else
69
- raise(ArgumentError, "Parameter :appender must be either a Symbol or an object derived from SemanticLogger::Subscriber, not: #{appender.inspect}")
72
+ raise(ArgumentError,
73
+ "Parameter :appender must be either a Symbol or an object derived from SemanticLogger::Subscriber, not: #{appender.inspect}")
70
74
  end
71
75
  elsif metric
72
76
  if metric.is_a?(Symbol)
@@ -74,10 +78,12 @@ module SemanticLogger
74
78
  elsif metric.is_a?(Subscriber)
75
79
  metric
76
80
  else
77
- raise(ArgumentError, "Parameter :metric must be either a Symbol or an object derived from SemanticLogger::Subscriber, not: #{appender.inspect}")
81
+ raise(ArgumentError,
82
+ "Parameter :metric must be either a Symbol or an object derived from SemanticLogger::Subscriber, not: #{appender.inspect}")
78
83
  end
79
84
  else
80
- raise(ArgumentError, "To create an appender it must supply one of the following: :io, :file_name, :appender, :metric, or :logger")
85
+ raise(ArgumentError,
86
+ "To create an appender it must supply one of the following: :io, :file_name, :appender, :metric, or :logger")
81
87
  end
82
88
  end
83
89
 
@@ -10,42 +10,48 @@ module SemanticLogger
10
10
 
11
11
  def add(**args, &block)
12
12
  appender = SemanticLogger::Appender.factory(**args, &block)
13
+
14
+ if appender.respond_to?(:console_output?) && appender.console_output? && console_output?
15
+ logger.warn "Ignoring attempt to add a second console appender: #{appender.class.name} since it would result in duplicate console output."
16
+ return
17
+ end
18
+
13
19
  self << appender
14
20
  appender
15
21
  end
16
22
 
23
+ # Whether any of the existing appenders already output to the console?
24
+ # I.e. Writes to stdout or stderr.
25
+ def console_output?
26
+ any? { |appender| appender.respond_to?(:console_output?) && appender.console_output? }
27
+ end
28
+
17
29
  def log(log)
18
30
  each do |appender|
19
- begin
20
- appender.log(log) if appender.should_log?(log)
21
- rescue Exception => e
22
- logger.error "Failed to log to appender: #{appender.name}", e
23
- end
31
+ appender.log(log) if appender.should_log?(log)
32
+ rescue Exception => e
33
+ logger.error "Failed to log to appender: #{appender.name}", e
24
34
  end
25
35
  end
26
36
 
27
37
  def flush
28
38
  each do |appender|
29
- begin
30
- logger.trace "Flushing appender: #{appender.name}"
31
- appender.flush
32
- rescue Exception => e
33
- logger.error "Failed to flush appender: #{appender.name}", e
34
- end
39
+ logger.trace "Flushing appender: #{appender.name}"
40
+ appender.flush
41
+ rescue Exception => e
42
+ logger.error "Failed to flush appender: #{appender.name}", e
35
43
  end
36
44
  logger.trace "All appenders flushed"
37
45
  end
38
46
 
39
47
  def close
40
- each do |appender|
41
- begin
42
- logger.trace "Closing appender: #{appender.name}"
43
- appender.flush
44
- appender.close
45
- delete(appender)
46
- rescue Exception => e
47
- logger.error "Failed to close appender: #{appender.name}", e
48
- end
48
+ to_a.each do |appender|
49
+ logger.trace "Closing appender: #{appender.name}"
50
+ delete(appender)
51
+ appender.flush
52
+ appender.close
53
+ rescue Exception => e
54
+ logger.error "Failed to close appender: #{appender.name}", e
49
55
  end
50
56
  logger.trace "All appenders closed and removed from appender list"
51
57
  end
@@ -53,14 +59,12 @@ module SemanticLogger
53
59
  # After a fork the appender thread is not running, start it if it is not running.
54
60
  def reopen
55
61
  each do |appender|
56
- begin
57
- next unless appender.respond_to?(:reopen)
62
+ next unless appender.respond_to?(:reopen)
58
63
 
59
- logger.trace "Reopening appender: #{appender.name}"
60
- appender.reopen
61
- rescue Exception => e
62
- logger.error "Failed to re-open appender: #{appender.name}", e
63
- end
64
+ logger.trace "Reopening appender: #{appender.name}"
65
+ appender.reopen
66
+ rescue Exception => e
67
+ logger.error "Failed to re-open appender: #{appender.name}", e
64
68
  end
65
69
  logger.trace "All appenders re-opened"
66
70
  end
@@ -63,7 +63,7 @@ module SemanticLogger
63
63
  # SemanticLogger.default_level = :info
64
64
  #
65
65
  # # Log to screen
66
- # SemanticLogger.add_appender(io: STDOUT, formatter: :color)
66
+ # SemanticLogger.add_appender(io: $stdout, formatter: :color)
67
67
  #
68
68
  # # And log to a file at the same time
69
69
  # SemanticLogger.add_appender(file_name: 'application.log', formatter: :color)
@@ -136,7 +136,7 @@ module SemanticLogger
136
136
 
137
137
  backtrace =
138
138
  if thread == Thread.current
139
- Utils.extract_backtrace
139
+ Utils.extract_backtrace(caller)
140
140
  else
141
141
  log.thread_name = thread.name
142
142
  log.tags = (thread[:semantic_logger_tags] || []).clone
@@ -188,7 +188,8 @@ module SemanticLogger
188
188
  # - For better performance with clean tags, see `SemanticLogger.tagged`.
189
189
  def tagged(*tags, &block)
190
190
  # Allow named tags to be passed into the logger
191
- if tags.size == 1
191
+ # Rails::Rack::Logger passes logs as an array with a single argument
192
+ if tags.size == 1 && !tags.first.is_a?(Array)
192
193
  tag = tags[0]
193
194
  return yield if tag.nil? || tag == ""
194
195
 
@@ -376,7 +377,7 @@ module SemanticLogger
376
377
  exception = e
377
378
  ensure
378
379
  # Must use ensure block otherwise a `return` in the yield above will skip the log entry
379
- log = Log.new(name, level, index)
380
+ log = Log.new(name, level, index)
380
381
  exception ||= params[:exception]
381
382
  message = params[:message] if params[:message]
382
383
  duration =
@@ -70,6 +70,7 @@ module SemanticLogger
70
70
 
71
71
  # Return the Time as a formatted string
72
72
  def format_time(time)
73
+ time = time.dup
73
74
  case time_format
74
75
  when :rfc_3339
75
76
  time.utc.to_datetime.rfc3339
@@ -109,14 +109,14 @@ module SemanticLogger
109
109
  def payload
110
110
  return unless log.payload?
111
111
 
112
- if !log.payload.respond_to?(:ai)
113
- super
114
- else
112
+ if log.payload.respond_to?(:ai)
115
113
  begin
116
114
  "-- #{log.payload.ai(@ai_options)}"
117
115
  rescue StandardError
118
116
  super
119
117
  end
118
+ else
119
+ super
120
120
  end
121
121
  end
122
122
 
@@ -0,0 +1,72 @@
1
+ require "json"
2
+
3
+ module SemanticLogger
4
+ module Formatters
5
+ # Produces logfmt formatted messages
6
+ #
7
+ # The following fields are extracted from the raw log and included in the formatted message:
8
+ # :timestamp, :level, :name, :message, :duration, :tags, :named_tags
9
+ #
10
+ # E.g.
11
+ # timestamp="2020-07-20T08:32:05.375276Z" level=info name="DefaultTest" base="breakfast" spaces="second breakfast" double_quotes="\"elevensies\"" single_quotes="'lunch'" tag="success"
12
+ #
13
+ # All timestamps are ISO8601 formatteed
14
+ # All user supplied values are escaped and surrounded by double quotes to avoid ambiguious message delimeters
15
+ # `tags` are treated as keys with boolean values. Tag names are not formatted or validated, ensure you use valid logfmt format for tag names.
16
+ # `named_tags` are flattened are merged into the top level message field. Any conflicting fields are overridden.
17
+ # `payload` values take precedence over `tags` and `named_tags`. Any conflicting fields are overridden.
18
+ #
19
+ # Futher Reading https://brandur.org/logfmt
20
+ class Logfmt < Raw
21
+ def initialize(time_format: :iso_8601, time_key: :timestamp, **args)
22
+ super(time_format: time_format, time_key: time_key, **args)
23
+ end
24
+
25
+ def call(log, logger)
26
+ @raw = super(log, logger)
27
+
28
+ raw_to_logfmt
29
+ end
30
+
31
+ private
32
+
33
+ def raw_to_logfmt
34
+ @parsed = @raw.slice(time_key, :level, :name, :message, :duration).merge(tag: "success")
35
+ handle_tags
36
+ handle_payload
37
+ handle_exception
38
+
39
+ flatten_log
40
+ end
41
+
42
+ def handle_tags
43
+ tags = @raw.fetch(:tags){ [] }
44
+ .each_with_object({}){ |tag, accum| accum[tag] = true }
45
+
46
+ @parsed = @parsed.merge(tags)
47
+ .merge(@raw.fetch(:named_tags){ {} })
48
+ end
49
+
50
+ def handle_payload
51
+ return unless @raw.key? :payload
52
+
53
+ @parsed = @parsed.merge(@raw[:payload])
54
+ end
55
+
56
+ def handle_exception
57
+ return unless @raw.key? :exception
58
+
59
+ @parsed[:tag] = "exception"
60
+ @parsed = @parsed.merge(@raw[:exception])
61
+ end
62
+
63
+ def flatten_log
64
+ flattened = @parsed.map do |key, value|
65
+ "#{key}=#{value.to_json}"
66
+ end
67
+
68
+ flattened.join(" ")
69
+ end
70
+ end
71
+ end
72
+ end
@@ -1,7 +1,8 @@
1
1
  begin
2
2
  require "syslog_protocol"
3
3
  rescue LoadError
4
- raise LoadError, 'Gem syslog_protocol is required for remote logging using the Syslog protocol. Please add the gem "syslog_protocol" to your Gemfile.'
4
+ raise LoadError,
5
+ 'Gem syslog_protocol is required for remote logging using the Syslog protocol. Please add the gem "syslog_protocol" to your Gemfile.'
5
6
  end
6
7
 
7
8
  module SemanticLogger
@@ -1,7 +1,8 @@
1
1
  begin
2
2
  require "syslog_protocol"
3
3
  rescue LoadError
4
- raise LoadError, 'Gem syslog_protocol is required for remote logging using the Syslog protocol. Please add the gem "syslog_protocol" to your Gemfile.'
4
+ raise LoadError,
5
+ 'Gem syslog_protocol is required for remote logging using the Syslog protocol. Please add the gem "syslog_protocol" to your Gemfile.'
5
6
  end
6
7
 
7
8
  module SemanticLogger
@@ -1,16 +1,15 @@
1
1
  module SemanticLogger
2
2
  module Formatters
3
- # @formatter:off
4
- autoload :Base, "semantic_logger/formatters/base"
5
- autoload :Color, "semantic_logger/formatters/color"
6
- autoload :Default, "semantic_logger/formatters/default"
7
- autoload :Json, "semantic_logger/formatters/json"
8
- autoload :Raw, "semantic_logger/formatters/raw"
9
- autoload :OneLine, "semantic_logger/formatters/one_line"
10
- autoload :Signalfx, "semantic_logger/formatters/signalfx"
11
- autoload :Syslog, "semantic_logger/formatters/syslog"
12
- autoload :Fluentd, "semantic_logger/formatters/fluentd"
13
- # @formatter:on
3
+ autoload :Base, "semantic_logger/formatters/base"
4
+ autoload :Color, "semantic_logger/formatters/color"
5
+ autoload :Default, "semantic_logger/formatters/default"
6
+ autoload :Json, "semantic_logger/formatters/json"
7
+ autoload :Raw, "semantic_logger/formatters/raw"
8
+ autoload :OneLine, "semantic_logger/formatters/one_line"
9
+ autoload :Signalfx, "semantic_logger/formatters/signalfx"
10
+ autoload :Syslog, "semantic_logger/formatters/syslog"
11
+ autoload :Fluentd, "semantic_logger/formatters/fluentd"
12
+ autoload :Logfmt, "semantic_logger/formatters/logfmt"
14
13
 
15
14
  # Return formatter that responds to call.
16
15
  #
@@ -114,14 +114,12 @@ module SemanticLogger
114
114
 
115
115
  # Elastic logging: Log when :duration exceeds :min_duration
116
116
  # Except if there is an exception when it will always be logged
117
- if duration
118
- return false if (duration < min_duration) && exception.nil?
119
- end
117
+ return false if duration && ((duration < min_duration) && exception.nil?)
120
118
 
121
119
  if backtrace
122
120
  self.backtrace = Utils.extract_backtrace(backtrace)
123
121
  elsif level_index >= SemanticLogger.backtrace_level_index
124
- self.backtrace = Utils.extract_backtrace
122
+ self.backtrace = Utils.extract_backtrace(caller)
125
123
  end
126
124
 
127
125
  true
@@ -8,7 +8,7 @@
8
8
  # Example:
9
9
  # require 'semantic_logger'
10
10
  # SemanticLogger.default_level = :debug
11
- # SemanticLogger.add_appender(io: STDOUT, formatter: :color)
11
+ # SemanticLogger.add_appender(io: $stdout, formatter: :color)
12
12
  #
13
13
  # class ExternalSupplier
14
14
  # # Create class and instance logger methods
@@ -32,7 +32,14 @@ module SemanticLogger
32
32
  module Loggable
33
33
  def self.included(base)
34
34
  base.extend ClassMethods
35
+ base.singleton_class.class_eval do
36
+ undef_method :logger if method_defined?(:logger)
37
+ undef_method :logger= if method_defined?(:logger=)
38
+ end
35
39
  base.class_eval do
40
+ undef_method :logger if method_defined?(:logger)
41
+ undef_method :logger= if method_defined?(:logger=)
42
+
36
43
  # Returns [SemanticLogger::Logger] class level logger
37
44
  def self.logger
38
45
  @semantic_logger ||= SemanticLogger[self]
@@ -21,7 +21,19 @@ module SemanticLogger
21
21
  end
22
22
 
23
23
  def self.processor
24
- @processor ||= SemanticLogger.sync? ? SyncProcessor.new : Processor.new
24
+ @processor ||= Processor.new
25
+ end
26
+
27
+ # Switch to the synchronous processor
28
+ def self.sync!
29
+ return if @processor.is_a?(SyncProcessor)
30
+
31
+ @processor = SyncProcessor.new(@processor&.appenders)
32
+ end
33
+
34
+ # Running without the background logging thread?
35
+ def self.sync?
36
+ processor.is_a?(SyncProcessor)
25
37
  end
26
38
 
27
39
  # Returns a Logger instance
@@ -70,11 +82,9 @@ module SemanticLogger
70
82
  return unless @subscribers
71
83
 
72
84
  @subscribers.each do |subscriber|
73
- begin
74
- subscriber.call(log)
75
- rescue Exception => e
76
- processor.logger.error("Exception calling :on_log subscriber", e)
77
- end
85
+ subscriber.call(log)
86
+ rescue Exception => e
87
+ processor.logger.error("Exception calling :on_log subscriber", e)
78
88
  end
79
89
  end
80
90
  end
@@ -1,7 +1,7 @@
1
1
  module SemanticLogger
2
2
  # Thread that submits and processes log requests
3
3
  class Processor < Appender::Async
4
- # Allow the internal logger to be overridden from its default of STDERR
4
+ # Allow the internal logger to be overridden from its default of $stderr
5
5
  # Can be replaced with another Ruby logger or Rails logger, but never to
6
6
  # SemanticLogger::Logger itself since it is for reporting problems
7
7
  # while trying to log to the various appenders
@@ -11,11 +11,11 @@ module SemanticLogger
11
11
 
12
12
  # Internal logger for SemanticLogger
13
13
  # For example when an appender is not working etc..
14
- # By default logs to STDERR
14
+ # By default logs to $stderr
15
15
  def self.logger
16
16
  @logger ||=
17
17
  begin
18
- l = SemanticLogger::Appender::File.new(io: STDERR, level: :warn)
18
+ l = SemanticLogger::Appender::IO.new($stderr, level: :warn)
19
19
  l.name = name
20
20
  l
21
21
  end
@@ -101,7 +101,7 @@ module SemanticLogger
101
101
  # Or,
102
102
  # io: [IO]
103
103
  # An IO Stream to log to.
104
- # For example STDOUT, STDERR, etc.
104
+ # For example $stdout, $stderr, etc.
105
105
  #
106
106
  # Or,
107
107
  # appender: [Symbol|SemanticLogger::Subscriber]
@@ -122,7 +122,7 @@ module SemanticLogger
122
122
  # Default: SemanticLogger.default_level
123
123
  #
124
124
  # formatter: [Symbol|Object|Proc]
125
- # Any of the following symbol values: :default, :color, :json
125
+ # Any of the following symbol values: :default, :color, :json, :logfmt, etc...
126
126
  # Or,
127
127
  # An instance of a class that implements #call
128
128
  # Or,
@@ -138,14 +138,14 @@ module SemanticLogger
138
138
  # Examples:
139
139
  #
140
140
  # # Send all logging output to Standard Out (Screen)
141
- # SemanticLogger.add_appender(io: STDOUT)
141
+ # SemanticLogger.add_appender(io: $stdout)
142
142
  #
143
143
  # # Send all logging output to a file
144
144
  # SemanticLogger.add_appender(file_name: 'logfile.log')
145
145
  #
146
146
  # # Send all logging output to a file and only :info and above to standard output
147
147
  # SemanticLogger.add_appender(file_name: 'logfile.log')
148
- # SemanticLogger.add_appender(io: STDOUT, level: :info)
148
+ # SemanticLogger.add_appender(io: $stdout, level: :info)
149
149
  #
150
150
  # Log to log4r, Logger, etc.:
151
151
  #
@@ -154,7 +154,7 @@ module SemanticLogger
154
154
  # require 'semantic_logger'
155
155
  #
156
156
  # # Built-in Ruby logger
157
- # log = Logger.new(STDOUT)
157
+ # log = Logger.new($stdout)
158
158
  # log.level = Logger::DEBUG
159
159
  #
160
160
  # SemanticLogger.default_level = :debug
@@ -164,7 +164,7 @@ module SemanticLogger
164
164
  # logger.info "Hello World"
165
165
  # logger.debug("Login time", user: 'Joe', duration: 100, ip_address: '127.0.0.1')
166
166
  def self.add_appender(**args, &block)
167
- appender = Logger.processor.appenders.add(**args, &block)
167
+ appender = appenders.add(**args, &block)
168
168
  # Start appender thread if it is not already running
169
169
  Logger.processor.start
170
170
  appender
@@ -173,7 +173,15 @@ module SemanticLogger
173
173
  # Remove an existing appender
174
174
  # Currently only supports appender instances
175
175
  def self.remove_appender(appender)
176
- Logger.processor.appenders.delete(appender)
176
+ return unless appender
177
+
178
+ appenders.delete(appender)
179
+ appender.close
180
+ end
181
+
182
+ # Clear out all previously registered appenders
183
+ def self.clear_appenders!
184
+ Logger.processor.close
177
185
  end
178
186
 
179
187
  # Returns [SemanticLogger::Subscriber] a copy of the list of active
@@ -181,7 +189,7 @@ module SemanticLogger
181
189
  # Use SemanticLogger.add_appender and SemanticLogger.remove_appender
182
190
  # to manipulate the active appenders list
183
191
  def self.appenders
184
- Logger.processor.appenders.to_a
192
+ Logger.processor.appenders
185
193
  end
186
194
 
187
195
  # Flush all queued log entries disk, database, etc.
@@ -494,12 +502,12 @@ module SemanticLogger
494
502
  # I.e. Instead of logging messages in a separate thread for better performance,
495
503
  # log them using the current thread.
496
504
  def self.sync!
497
- @sync = true
505
+ Logger.sync!
498
506
  end
499
507
 
500
508
  # Running in synchronous mode?
501
509
  def self.sync?
502
- @sync
510
+ Logger.sync?
503
511
  end
504
512
 
505
513
  # Initial default Level for all new instances of SemanticLogger::Logger
@@ -22,6 +22,11 @@ module SemanticLogger
22
22
  # NOOP
23
23
  end
24
24
 
25
+ # Method called to log an event
26
+ def log(log)
27
+ raise NotImplementedError
28
+ end
29
+
25
30
  # Returns [SemanticLogger::Formatters::Default] default formatter for this subscriber.
26
31
  def default_formatter
27
32
  SemanticLogger::Formatters::Default.new
@@ -68,6 +73,11 @@ module SemanticLogger
68
73
  super(log) && (log.metric_only? ? metrics? : true)
69
74
  end
70
75
 
76
+ # Whether this appender is logging to stdout or stderror
77
+ def console_output?
78
+ false
79
+ end
80
+
71
81
  private
72
82
 
73
83
  # Initializer for Abstract Class SemanticLogger::Subscriber
@@ -10,7 +10,7 @@ module SemanticLogger
10
10
  def_delegator :@appenders, :close
11
11
  def_delegator :@appenders, :reopen
12
12
 
13
- # Allow the internal logger to be overridden from its default of STDERR
13
+ # Allow the internal logger to be overridden from its default of $stderr
14
14
  # Can be replaced with another Ruby logger or Rails logger, but never to
15
15
  # SemanticLogger::Logger itself since it is for reporting problems
16
16
  # while trying to log to the various appenders
@@ -20,11 +20,11 @@ module SemanticLogger
20
20
 
21
21
  # Internal logger for SemanticLogger
22
22
  # For example when an appender is not working etc..
23
- # By default logs to STDERR
23
+ # By default logs to $stderr
24
24
  def self.logger
25
25
  @logger ||=
26
26
  begin
27
- l = SemanticLogger::Appender::File.new(io: STDERR, level: :warn)
27
+ l = SemanticLogger::Appender::IO.new($stderr, level: :warn)
28
28
  l.name = name
29
29
  l
30
30
  end
@@ -32,8 +32,8 @@ module SemanticLogger
32
32
 
33
33
  attr_reader :appenders
34
34
 
35
- def initialize
36
- @appenders = Appenders.new(self.class.logger.dup)
35
+ def initialize(appenders = nil)
36
+ @appenders = appenders || Appenders.new(self.class.logger.dup)
37
37
  end
38
38
 
39
39
  def start
@@ -0,0 +1,34 @@
1
+ module SemanticLogger
2
+ module Test
3
+ # Logging class to captures all logging events in memory.
4
+ #
5
+ # Example:
6
+ #
7
+ # class UserTest < ActiveSupport::TestCase
8
+ # describe User do
9
+ # let(:capture_logger) { SemanticLogger::Test::CaptureLogEvents.new }
10
+ # let(:user) { User.new }
11
+ #
12
+ # it "logs message" do
13
+ # user.stub(:logger, capture_logger) do
14
+ # user.enable!
15
+ # end
16
+ # assert_equal "Hello World", capture_logger.events.last.message
17
+ # assert_equal :info, capture_logger.events.last.level
18
+ # end
19
+ # end
20
+ # end
21
+ class CaptureLogEvents < SemanticLogger::Subscriber
22
+ attr_accessor :events
23
+
24
+ # By default collect all log levels, and collect metric only log events.
25
+ def initialize(level: :trace, metrics: true)
26
+ super(level: level, metrics: true)
27
+ end
28
+
29
+ def log(log)
30
+ (@events ||= []) << log
31
+ end
32
+ end
33
+ end
34
+ end