Module: JsonLogging

Defined in:
lib/json_logging.rb,
lib/json_logging/helpers.rb,
lib/json_logging/version.rb,
lib/json_logging/formatter.rb,
lib/json_logging/sanitizer.rb,
lib/json_logging/json_logger.rb,
lib/json_logging/message_parser.rb,
lib/json_logging/payload_builder.rb,
lib/json_logging/formatter_with_tags.rb,
lib/activesupport/json_logging/railtie.rb,
lib/json_logging/json_logger_extension.rb

Defined Under Namespace

Modules: Helpers, JsonLoggerExtension, LocalTagStorage, MessageParser, PayloadBuilder, Sanitizer Classes: Formatter, FormatterWithTags, JsonLogger, Railtie

Constant Summary collapse

THREAD_CONTEXT_KEY =
:__json_logging_context
VERSION =
"1.2.0"

Class Method Summary collapse

Class Method Details

.additional_context(*args, &block) ⇒ Object

Returns the current thread-local context when called without arguments, or sets a transformer when called with a block or assigned a proc.

Examples:

Getting context

JsonLogging.additional_context  # => {user_id: 123, ...}

Setting transformer with block

JsonLogging.additional_context do |context|
  context.merge(environment: Rails.env, hostname: Socket.gethostname)
end

Setting transformer with assignment

JsonLogging.additional_context = ->(context) { context.merge(env: Rails.env) }


39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/json_logging.rb', line 39

def self.additional_context(*args, &block)
  if args.any? || block_given?
    return public_send(:additional_context=, args.first || block)
  end

  begin
    base_context = (Thread.current[THREAD_CONTEXT_KEY] || {}).dup
  rescue
    base_context = {}
  end

  transformer = @additional_context_transformer
  if transformer.is_a?(Proc)
    begin
      transformer.call(base_context)
    rescue
      base_context
    end
  else
    base_context
  end
end

.additional_context=(proc_or_block) ⇒ Object



62
63
64
# File 'lib/json_logging.rb', line 62

def self.additional_context=(proc_or_block)
  @additional_context_transformer = proc_or_block
end

.logger(*args, **kwargs) ⇒ Logger

Returns an ‘ActiveSupport::Logger` that has already been wrapped with JSON logging concern.

Examples:

logger = JsonLogging.logger($stdout)
logger.info("Stuff")  # Logs JSON formatted entry

Parameters:

  • *args

    Arguments passed to ActiveSupport::Logger.new

  • **kwargs

    Keyword arguments passed to ActiveSupport::Logger.new

Returns:

  • (Logger)

    A logger wrapped with JSON formatting and tagged logging support



81
82
83
# File 'lib/json_logging.rb', line 81

def self.logger(*args, **kwargs)
  new(ActiveSupport::Logger.new(*args, **kwargs))
end

.new(logger) ⇒ Logger

Wraps any standard Logger object to provide JSON formatting capabilities. Similar to ActiveSupport::TaggedLogging.new

Examples:

logger = JsonLogging.new(Logger.new(STDOUT))
logger.info("Stuff")  # Logs JSON formatted entry

With tagged logging

logger = JsonLogging.new(Logger.new(STDOUT))
logger.tagged("BCX") { logger.info("Stuff") }  # Logs with tags
logger.tagged("BCX").info("Stuff")  # Logs with tags (non-block form)

Parameters:

  • logger (Logger)

    Any standard Logger object (Logger, ActiveSupport::Logger, etc.)

Returns:

  • (Logger)

    A logger extended with JSON formatting and tagged logging support



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
# File 'lib/json_logging.rb', line 99

def self.new(logger)
  logger = logger.clone
  if logger.formatter
    logger.formatter = logger.formatter.clone

    # Workaround for https://bugs.ruby-lang.org/issues/20250
    # Can be removed when Ruby 3.4 is the least supported version.
    logger.formatter.object_id if logger.formatter.is_a?(Proc)
  else
    # Ensure we set a default formatter so we aren't extending nil!
    # Use ActiveSupport::Logger::SimpleFormatter if available, otherwise default Logger::Formatter
    logger.formatter = if defined?(ActiveSupport::Logger::SimpleFormatter)
      ActiveSupport::Logger::SimpleFormatter.new
    else
      ::Logger::Formatter.new
    end
  end

  logger.extend(JsonLoggerExtension)
  formatter_with_tags = FormatterWithTags.new(logger)
  logger.instance_variable_set(:@formatter_with_tags, formatter_with_tags)
  logger.formatter = formatter_with_tags

  logger
end

.safe_hash(obj) ⇒ Object



66
67
68
69
70
# File 'lib/json_logging.rb', line 66

def self.safe_hash(obj)
  obj.is_a?(Hash) ? obj : {}
rescue
  {}
end

.with_context(extra_context) ⇒ Object



18
19
20
21
22
23
24
# File 'lib/json_logging.rb', line 18

def self.with_context(extra_context)
  original = Thread.current[THREAD_CONTEXT_KEY]
  Thread.current[THREAD_CONTEXT_KEY] = (original || {}).merge(safe_hash(extra_context))
  yield
ensure
  Thread.current[THREAD_CONTEXT_KEY] = original
end