mirror of
https://github.com/meineerde/rackstash.git
synced 2025-12-27 18:01:14 +00:00
296 lines
8.2 KiB
Ruby
296 lines
8.2 KiB
Ruby
# Copyright 2017 Holger Just
|
|
#
|
|
# This software may be modified and distributed under the terms
|
|
# of the MIT license. See the LICENSE.txt file for details.
|
|
|
|
require 'concurrent'
|
|
require 'forwardable'
|
|
|
|
require 'rackstash/buffer_stack'
|
|
require 'rackstash/formatter'
|
|
require 'rackstash/message'
|
|
require 'rackstash/sink'
|
|
|
|
module Rackstash
|
|
# The Logger is the main entry point for Rackstash. It provides an interface
|
|
# very similar to the Logger class in Ruby's Stamdard Library but extends it
|
|
# with facilities for structured logging.
|
|
class Logger
|
|
extend Forwardable
|
|
|
|
# Logging formatter, a `Proc`-like object which take four arguments and
|
|
# returns the formatted message. The arguments are:
|
|
#
|
|
# * `severity` - The Severity of the log message.
|
|
# * `time` - A Time instance representing when the message was logged.
|
|
# * `progname` - The progname configured passed to the logger method.
|
|
# * `msg` - The `Object` the user passed to the log message; not necessarily
|
|
# a String.
|
|
#
|
|
# The formatter should return a String. When no formatter is set, an
|
|
# instance of {Formatter} is used.
|
|
#
|
|
# @return [#call] the log formatter for each individual buffered line
|
|
attr_accessor :formatter
|
|
|
|
# @return [Integer] a numeric log level, normally you'd use one of the
|
|
# `SEVERITIES` constants, i.e. an integer between 0 and 5.
|
|
attr_reader :level
|
|
|
|
# @return [String] the logger's progname, used as the default for log
|
|
# messages if none is passed to {#add} and passed to the {#formatter}.
|
|
# By default we use {PROGNAME}.
|
|
attr_accessor :progname
|
|
|
|
# @return [Sink] the log {Sink} which flushes a {Buffer} to one or more
|
|
# external log targets like a file, a socket, ...
|
|
attr_reader :sink
|
|
|
|
def initialize(targets)
|
|
@sink = Sink.new(targets)
|
|
|
|
@level = DEBUG
|
|
@progname = PROGNAME
|
|
@formatter = Formatter.new
|
|
|
|
@buffer_stack = Concurrent::ThreadLocalVar.new
|
|
end
|
|
|
|
# Add a message to the current buffer without any further formatting. If
|
|
# the current {Buffer} is bufering, the message will just be added. Else,
|
|
# it will be flushed to the {#sink} directly.
|
|
#
|
|
# @param msg [Object]
|
|
# @return [String] the passed `msg`
|
|
def <<(msg)
|
|
buffer.add_message Message.new(
|
|
msg,
|
|
time: Time.now.utc.freeze
|
|
)
|
|
msg
|
|
end
|
|
|
|
# Set the base log level as either one of the {SEVERITIES} or a
|
|
# String/Symbol describing the level. When logging a message, it will only
|
|
# be added if its log level is at or above the base level defined here
|
|
#
|
|
# @param severity [Integer, String, Symbol] one of the {SEVERITIES} or its
|
|
# name
|
|
def level=(severity)
|
|
if severity.is_a?(Integer)
|
|
@level = severity
|
|
else
|
|
case severity.to_s.downcase
|
|
when 'debug'.freeze
|
|
@level = DEBUG
|
|
when 'info'.freeze
|
|
@level = INFO
|
|
when 'warn'.freeze
|
|
@level = WARN
|
|
when 'error'.freeze
|
|
@level = ERROR
|
|
when 'fatal'.freeze
|
|
@level = FATAL
|
|
when 'unknown'.freeze
|
|
@level = UNKNOWN
|
|
else
|
|
raise ArgumentError, "invalid log level: #{severity}"
|
|
end
|
|
end
|
|
end
|
|
|
|
# (see Buffer#fields)
|
|
def fields
|
|
buffer.fields
|
|
end
|
|
|
|
# (see Buffer#tags)
|
|
def tags
|
|
buffer.tags
|
|
end
|
|
|
|
# Log a `message` at the DEBUG log level.
|
|
#
|
|
# @param message (see #add)
|
|
# @yield (see #add)
|
|
# @return (see #add)
|
|
def debug(message = nil)
|
|
if block_given?
|
|
add(DEBUG, message) { yield }
|
|
else
|
|
add(DEBUG, message)
|
|
end
|
|
end
|
|
|
|
# @return [Boolean] `true` if messages on the DEBUG level will be logged
|
|
def debug?
|
|
@level <= DEBUG
|
|
end
|
|
|
|
# Log a `message` at the INFO log level.
|
|
#
|
|
# @param message (see #add)
|
|
# @yield (see #add)
|
|
# @return (see #add)
|
|
def info(message = nil)
|
|
if block_given?
|
|
add(INFO, message) { yield }
|
|
else
|
|
add(INFO, message)
|
|
end
|
|
end
|
|
|
|
# @return [Boolean] `true` if messages on the INFO level will be logged
|
|
def info?
|
|
@level <= INFO
|
|
end
|
|
|
|
# Log a `message` at the WARN log level.
|
|
#
|
|
# @param message (see #add)
|
|
# @yield (see #add)
|
|
# @return (see #add)
|
|
def warn(message = nil)
|
|
if block_given?
|
|
add(WARN, message) { yield }
|
|
else
|
|
add(WARN, message)
|
|
end
|
|
end
|
|
|
|
# @return [Boolean] `true` if messages on the WARN level will be logged
|
|
def warn?
|
|
@level <= WARN
|
|
end
|
|
|
|
# Log a `message` at the ERROR log level.
|
|
#
|
|
# @param message (see #add)
|
|
# @yield (see #add)
|
|
# @return (see #add)
|
|
def error(message = nil)
|
|
if block_given?
|
|
add(ERROR, message) { yield }
|
|
else
|
|
add(ERROR, message)
|
|
end
|
|
end
|
|
|
|
# @return [Boolean] `true` if messages on the ERROR level will be logged
|
|
def error?
|
|
@level <= ERROR
|
|
end
|
|
|
|
# Log a `message` at the FATAL log level.
|
|
#
|
|
# @param message (see #add)
|
|
# @yield (see #add)
|
|
# @return (see #add)
|
|
def fatal(message = nil)
|
|
if block_given?
|
|
add(FATAL, message) { yield }
|
|
else
|
|
add(FATAL, message)
|
|
end
|
|
end
|
|
|
|
# @return [Boolean] `true` if messages on the FATAL level will be logged
|
|
def fatal?
|
|
@level <= FATAL
|
|
end
|
|
|
|
# Log a `message` at the UNKNOWN log level.
|
|
#
|
|
# @param message (see #add)
|
|
# @yield (see #add)
|
|
# @return (see #add)
|
|
def unknown(message = nil)
|
|
if block_given?
|
|
add(UNKNOWN, message) { yield }
|
|
else
|
|
add(UNKNOWN, message)
|
|
end
|
|
end
|
|
|
|
# @return [Boolean] `true` if messages on the UNKNOWN level will be logged
|
|
def unknown?
|
|
@level <= UNKNOWN
|
|
end
|
|
|
|
# Log a message if the given severity is high enough. This is the generic
|
|
# logging method. Users will be more inclined to use {#debug}, {#info},
|
|
# {#warn}, {#error}, or {#fatal}.
|
|
#
|
|
# The message will be added to the current log buffer. If we are currently
|
|
# buffering (i.e. if we are inside a {#with_buffer} block), the message is
|
|
# merely added but not flushed to the underlying logger. Else, the message
|
|
# along with any previously defined fields and tags will be flushed to the
|
|
# base logger immediately.
|
|
#
|
|
# @param severity [Integer] The log severity. One of the {SEVERITIES}
|
|
# consants.
|
|
# @param msg [#to_s, Exception, nil] The log message. A `String` or
|
|
# `Exception`. If unset, we try to use the return value of the optional
|
|
# block.
|
|
# @param progname [String, nil] The program name. Can be omitted. It's
|
|
# treated as a message if no `msg` and `block` are given.
|
|
# @yield If `message` is `nil`, we yield to the block to get a message
|
|
# string.
|
|
# @return [String] The resolved unformatted message string
|
|
def add(severity, msg = nil, progname = nil)
|
|
severity = severity ? Integer(severity) : UNKNOWN
|
|
return if @level > severity
|
|
|
|
progname ||= @progname
|
|
if msg.nil?
|
|
if block_given?
|
|
msg = yield
|
|
else
|
|
msg = progname
|
|
progname = @progname
|
|
end
|
|
end
|
|
|
|
buffer.add_message Message.new(
|
|
msg,
|
|
time: Time.now.utc.freeze,
|
|
progname: progname,
|
|
severity: severity,
|
|
formatter: formatter
|
|
)
|
|
|
|
msg
|
|
end
|
|
alias log add
|
|
|
|
# Create a new buffering {Buffer} and puts in on the {BufferStack} for the
|
|
# current Thread. For the duration of the block, all new logged messages
|
|
# and any access to fields and tags will be sent to this new buffer.
|
|
# Previous buffers will only be visible after the execition left the block.
|
|
#
|
|
# @param buffer_args [Hash<Symbol => Object>] optional arguments for the new
|
|
# {Buffer}. See {Buffer#initialize} for allowed values.
|
|
# @return [Object] the return value of the block
|
|
def with_buffer(**buffer_args)
|
|
raise ArgumentError, 'block required' unless block_given?
|
|
|
|
buffer_stack.push(**buffer_args)
|
|
begin
|
|
yield
|
|
ensure
|
|
buffer_stack.flush_and_pop
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def buffer_stack
|
|
@buffer_stack.value ||= BufferStack.new(@sink)
|
|
end
|
|
|
|
def buffer
|
|
buffer_stack.current
|
|
end
|
|
end
|
|
end
|