1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236
|
# frozen_string_literal: true
require "rbconfig"
require "time"
require "logger"
require "fiber"
require "pathname"
# Lumberjack is a flexible logging framework for Ruby that extends the standard
# Logger functionality with structured logging, context isolation, and advanced
# formatting capabilities.
#
# The main features include:
# - Structured logging with attributes for machine-readable metadata
# - Context isolation for scoping logging behavior to specific code blocks
# - Flexible formatters for customizing log output
# - Multiple output devices and templates
# - Built-in testing utilities
#
# @example Basic usage
# logger = Lumberjack::Logger.new(STDOUT)
# logger.info("Hello world")
#
# @example Using contexts
# Lumberjack.context do
# Lumberjack.tag(user_id: 123)
# logger.info("User action") # Will include user_id: 123
# end
#
# @see Lumberjack::Logger
# @see Lumberjack::ContextLogger
module Lumberjack
VERSION = "2.0.4".freeze
LINE_SEPARATOR = ((RbConfig::CONFIG["host_os"] =~ /mswin/i) ? "\r\n" : "\n")
require_relative "lumberjack/device_registry"
require_relative "lumberjack/template_registry"
require_relative "lumberjack/formatter_registry"
require_relative "lumberjack/attribute_formatter"
require_relative "lumberjack/attributes_helper"
require_relative "lumberjack/context"
require_relative "lumberjack/context_logger"
require_relative "lumberjack/context_locals"
require_relative "lumberjack/io_compatibility"
require_relative "lumberjack/log_entry"
require_relative "lumberjack/log_entry_matcher"
require_relative "lumberjack/device"
require_relative "lumberjack/entry_formatter"
require_relative "lumberjack/formatter"
require_relative "lumberjack/forked_logger"
require_relative "lumberjack/logger"
require_relative "lumberjack/local_log_template"
require_relative "lumberjack/message_attributes"
require_relative "lumberjack/remap_attribute"
require_relative "lumberjack/rack"
require_relative "lumberjack/severity"
require_relative "lumberjack/template"
require_relative "lumberjack/utils"
# Deprecated
require_relative "lumberjack/tag_context"
require_relative "lumberjack/tag_formatter"
require_relative "lumberjack/tags"
@global_contexts = {}
@global_contexts_mutex = Mutex.new
@deprecation_mode = nil
@raise_logger_errors = false
@isolation_level = :fiber
extend ContextLocals
class << self
# Contexts can be used to store attributes that will be attached to all log entries in the block.
# The context will apply to all Lumberjack loggers that are used within the block.
#
# If this method is called with a block, it will set a logging context for the scope of a block.
# If there is already a context in scope, a new one will be created that inherits
# all the attributes of the parent context.
#
# Otherwise, it will return the current context. If one doesn't exist, it will return a new one
# but that context will not be in any scope.
#
# @return [Object] The result
# of the block
def context(&block)
use_context(Context.new(current_context), &block)
end
# Ensure that the block of code is wrapped by a global context. If there is not already
# a context in scope, one will be created.
#
# @return [Object] The result of the block.
def ensure_context(&block)
if in_context?
yield
else
context(&block)
end
end
# Set the context to use within a block.
#
# @param context [Lumberjack::Context] The context to use within the block.
# @return [Object] The result of the block.
# @api private
def use_context(context, &block)
unless block_given?
raise ArgumentError, "A block must be provided to the context method"
end
new_context = Context.new(context)
new_context.parent = current_context
new_context_locals do |locals|
locals.context = new_context
yield
end
end
# Return true if inside a context block.
#
# @return [Boolean]
def in_context?
!current_context.nil?
end
def context?
Utils.deprecated("Lumberjack.context?", "Lumberjack.context? is deprecated and will be removed in version 2.1; use in_context? instead.") do
in_context?
end
end
# Return attributes that will be applied to all Lumberjack loggers.
#
# @return [Hash, nil]
def context_attributes
current_context&.attributes
end
# Set the isolation level for global contexts to be either per fiber or per thread. Default is :fiber.
#
# @param value [Symbol] The isolation level, either :fiber or :thread.
# @return [void]
def isolation_level=(value)
value = value&.to_sym
value = :fiber unless [:fiber, :thread].include?(value)
@isolation_level = value
end
# @return [Symbol] The current isolation level.
attr_reader :isolation_level
# Alias for context_attributes to provide API compatibility with version 1.x.
# This method will eventually be removed.
#
# @return [Hash, nil]
# @deprecated Use {.context_attributes}
def context_tags
Utils.deprecated("Lumberjack.context_tags", "Lumberjack.context_tags is deprecated and will be removed in version 2.1; use context_attributes instead.") do
context_attributes
end
end
# Tag all loggers with attributes on the current context.
#
# @param attributes [Hash] The attributes to set.
# @param block [Proc] optional context block in which to set the attributes.
# @return [void]
def tag(attributes, &block)
if block
context do
current_context.assign_attributes(attributes)
block.call
end
else
current_context&.assign_attributes(attributes)
end
end
# Helper method to build an entry formatter.
#
# @param block [Proc] The block to use for building the entry formatter.
# @return [Lumberjack::EntryFormatter] The built entry formatter.
# @see Lumberjack::EntryFormatter.build
def build_formatter(&block)
EntryFormatter.build(&block)
end
# Control how use of deprecated methods is handled. The default is to print a warning
# the first time a deprecated method is called. Setting this to :verbose will print
# a warning every time a deprecated method is called. Setting this to :silent will
# suppress all deprecation warnings. Setting this to :raise will raise an exception
# when a deprecated method is called.
#
# The default value can be set with the +LUMBERJACK_DEPRECATION_WARNINGS+ environment variable.
#
# @param value [Symbol, String, nil] The deprecation mode to set. Valid values are :normal,
# :verbose, :silent, and :raise.
def deprecation_mode=(value)
@deprecation_mode = value&.to_sym
end
# @return [Symbol] The current deprecation mode.
# @api private
def deprecation_mode
@deprecation_mode ||= ENV.fetch("LUMBERJACK_DEPRECATION_WARNINGS", "normal").to_sym
end
# Set whether errors encountered while logging entries should be raised. The default behavior
# is to rescue these errors and print them to standard error. Otherwise there can be no way
# to record the error since it cannot be logged.
#
# You can set this to true in you test and development environments to catch logging errors
# before they make it to production.
#
# @param value [Boolean] Whether to raise logger errors.
# @return [void]
def raise_logger_errors=(value)
@raise_logger_errors = !!value
end
# @return [Boolean] Whether logger errors should be raised.
# @api private
def raise_logger_errors?
@raise_logger_errors
end
private
def current_context
current_context_locals&.context
end
end
end
|