lib/semantic_logger/log.rb in semantic_logger-3.4.1 vs lib/semantic_logger/log.rb in semantic_logger-4.0.0.beta1
- old
+ new
@@ -1,9 +1,9 @@
module SemanticLogger
- # Log Struct
+ # Log
#
- # Structure for holding all log entries
+ # Class to hold all log entry information
#
# level
# Log level of the supplied log call
# :trace, :debug, :info, :warn, :error, :fatal
#
@@ -41,12 +41,108 @@
# The backtrace captured at source when the log level >= SemanticLogger.backtrace_level
#
# metric_amount [Numeric]
# Used for numeric or counter metrics.
# For example, the number of inquiries or, the amount purchased etc.
- Log = Struct.new(:level, :thread_name, :name, :message, :payload, :time, :duration, :tags, :level_index, :exception, :metric, :backtrace, :metric_amount) do
+ class Log
+ attr_accessor :level, :thread_name, :name, :message, :payload, :time, :duration, :tags, :level_index, :exception, :metric, :backtrace, :metric_amount, :named_tags
+ def initialize(name, level, index = nil)
+ @level = level
+ @thread_name = Thread.current.name
+ @name = name
+ @time = Time.now
+ @tags = SemanticLogger.tags
+ @named_tags = SemanticLogger.named_tags
+ @level_index = index.nil? ? SemanticLogger.level_to_index(level) : index
+ end
+
+ # Assign named arguments to this log entry, supplying defaults where applicable
+ #
+ # Returns [true|false] whether this log entry should be logged
+ #
+ # Example:
+ # logger.info(name: 'value')
+ def assign(message: nil, payload: nil, min_duration: 0.0, exception: nil, metric: nil, metric_amount: 1, duration: nil, backtrace: nil, log_exception: :full, on_exception_level: nil)
+ # Elastic logging: Log when :duration exceeds :min_duration
+ # Except if there is an exception when it will always be logged
+ if duration
+ self.duration = duration
+ return false if (duration <= min_duration) && exception.nil?
+ end
+
+ self.message = message
+ self.payload = payload
+
+ if exception
+ case log_exception
+ when :full
+ self.exception = exception
+ when :partial
+ self.message = "#{message} -- Exception: #{exception.class}: #{exception.message}"
+ when nil, :none
+ # Log the message without the exception that was raised
+ else
+ raise(ArgumentError, "Invalid value:#{log_exception.inspect} for argument :log_exception")
+ end
+ # On exception change the log level
+ if on_exception_level
+ self.level = on_exception_level
+ self.level_index = SemanticLogger.level_to_index(level)
+ end
+ end
+
+ if backtrace
+ self.backtrace = self.class.cleanse_backtrace(backtrace)
+ elsif level_index >= SemanticLogger.backtrace_level_index
+ self.backtrace = self.class.cleanse_backtrace
+ end
+
+ if metric
+ self.metric = metric
+ self.metric_amount = metric_amount
+ end
+
+ self.payload = payload if payload && (payload.size > 0)
+ true
+ end
+
+ # Assign positional arguments to this log entry, supplying defaults where applicable
+ #
+ # Returns [true|false] whether this log entry should be logged
+ #
+ # Example:
+ # logger.info('value', :debug, 0, "hello world")
+ def assign_positional(message = nil, payload = nil, exception = nil)
+ # Exception being logged?
+ # Under JRuby a java exception is not a Ruby Exception
+ # Java::JavaLang::ClassCastException.new.is_a?(Exception) => false
+ if exception.nil? && payload.nil? && message.respond_to?(:backtrace) && message.respond_to?(:message)
+ exception = message
+ message = nil
+ elsif exception.nil? && payload && payload.respond_to?(:backtrace) && payload.respond_to?(:message)
+ exception = payload
+ payload = nil
+ end
+
+ # Add result of block as message or payload if not nil
+ if block_given? && (result = yield)
+ if result.is_a?(String)
+ message = message.nil? ? result : "#{message} -- #{result}"
+ assign(message: message, payload: payload, exception: exception)
+ elsif message.nil? && result.is_a?(Hash)
+ assign(result)
+ elsif payload && payload.respond_to?(:merge)
+ assign(message: message, payload: payload.merge(result), exception: exception)
+ else
+ assign(message: message, payload: result, exception: exception)
+ end
+ else
+ assign(message: message, payload: payload, exception: exception)
+ end
+ end
+
MAX_EXCEPTIONS_TO_UNWRAP = 5
# Call the block for exception and any nested exception
def each_exception
# With thanks to https://github.com/bugsnag/bugsnag-ruby/blob/6348306e44323eee347896843d16c690cd7c4362/lib/bugsnag/notification.rb#L81
depth = 0
@@ -194,11 +290,12 @@
h[:file] = file
h[:line] = line.to_i
end
# Tags
- h[:tags] = tags if tags && (tags.size > 0)
+ h[:tags] = tags if tags && !tags.empty?
+ h[:named_tags] = named_tags if named_tags && !named_tags.empty?
# Duration
if duration
h[:duration_ms] = duration
h[:duration] = duration_human
@@ -206,17 +303,11 @@
# Log message
h[:message] = cleansed_message if message
# Payload
- if payload
- if payload.is_a?(Hash)
- h.merge!(payload)
- else
- h[:payload] = payload
- end
- end
+ h[:payload] = payload if payload && payload.respond_to?(:empty?) && !payload.empty?
# Exceptions
if exception
root = h
each_exception do |exception, i|
@@ -229,12 +320,24 @@
root = root[name]
end
end
# Metric
- h[:metric] = metric if metric
+ h[:metric] = metric if metric
h[:metric_amount] = metric_amount if metric_amount
h
+ end
+
+ private
+
+ SELF_PATTERN = File.join('lib', 'semantic_logger')
+
+ # Extract the backtrace leaving out Semantic Logger
+ def self.cleanse_backtrace(stack = caller)
+ while (first = stack.first) && first.include?(SELF_PATTERN)
+ stack.shift
+ end
+ stack
end
end
end