Class: Rage::Logger

Inherits:
Object
  • Object
show all
Defined in:
lib/rage/logger/logger.rb

Overview

All logs in rage consist of two parts: keys and tags. A sample log entry might look like this:

[fecbba0735355738] timestamp=2023-10-19T11:12:56+00:00 pid=1825 level=info message=hello

In the log entry above, timestamp, pid, level, and message are keys, while fecbba0735355738 is a tag.

Use #tagged to add custom tags to an entry:

Rage.logger.tagged("ApiCall") do
  perform_api_call
  Rage.logger.info "success"
end
# => [fecbba0735355738][ApiCall] timestamp=2023-10-19T11:12:56+00:00 pid=1825 level=info message=success

#with_context can be used to add custom keys:

cache_key = "mykey"
Rage.logger.with_context(cache_key: cache_key) do
  get_from_cache(cache_key)
  Rage.logger.info "cache miss"
end
# => [fecbba0735355738] timestamp=2023-10-19T11:12:56+00:00 pid=1825 level=info cache_key=mykey message=cache miss

Rage::Logger also implements the interface of Ruby’s native Logger:

Rage.logger.info("Initializing")
Rage.logger.debug { "This is a " + potentially + " expensive operation" }

Using the logger

The recommended approach to logging with Rage is to make sure your code always logs the same message no matter what the input is. You can achieve this by using the #with_context and #tagged methods. So, a code like this:

def process_purchase(user_id:, product_id:)
  Rage.logger.info "processing purchase with user_id = #{user_id}; product_id = #{product_id}"
end

turns into this:

def process_purchase(user_id:, product_id:)
  Rage.logger.with_context(user_id: user_id, product_id: product_id) do
    Rage.logger.info "processing purchase"
  end
end

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(log, level: Logger::DEBUG, formatter: Rage::TextFormatter.new, shift_age: 0, shift_size: 104857600, shift_period_suffix: "%Y%m%d", binmode: false) ⇒ Logger

Create a new logger.

Parameters:

  • log (Object)

    a filename (String), IO object (typically STDOUT, STDERR, or an open file), nil (it writes nothing) or File::NULL (same as nil)

  • level (Integer) (defaults to: Logger::DEBUG)

    logging severity threshold

  • formatter (#call) (defaults to: Rage::TextFormatter.new)

    logging formatter

  • shift_age (Integer, String) (defaults to: 0)

    number of old log files to keep, or frequency of rotation ("daily", "weekly" or "monthly"). Default value is 0, which disables log file rotation

  • shift_size (Integer) (defaults to: 104857600)

    maximum log file size in bytes (only applies when shift_age is a positive Integer)

  • shift_period_suffix (String) (defaults to: "%Y%m%d")

    the log file suffix format for daily, weekly or monthly rotation

  • binmode (defaults to: false)

    sets whether the logger writes in binary mode



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/rage/logger/logger.rb', line 87

def initialize(log, level: Logger::DEBUG, formatter: Rage::TextFormatter.new, shift_age: 0, shift_size: 104857600, shift_period_suffix: "%Y%m%d", binmode: false)
  @logdev = if log.class.name.start_with?("Rage::Logger::External::")
    @external_logger = log
    Logger::LogDevice.new(STDERR)
  elsif log && log != File::NULL
    Logger::LogDevice.new(log, shift_age:, shift_size:, shift_period_suffix:, binmode:)
  end

  if Rage.env.development? && log.respond_to?(:sync=)
    log.sync = true
  end

  @formatter = formatter
  @level = @logdev ? level : Logger::UNKNOWN
  rebuild!
end

Instance Attribute Details

#formatterObject

Returns the value of attribute formatter.



64
65
66
# File 'lib/rage/logger/logger.rb', line 64

def formatter
  @formatter
end

#levelObject

Returns the value of attribute level.



64
65
66
# File 'lib/rage/logger/logger.rb', line 64

def level
  @level
end

Instance Method Details

#<<(msg) ⇒ Object

Write the given msg to the log with no formatting.



119
120
121
# File 'lib/rage/logger/logger.rb', line 119

def <<(msg)
  @logdev&.write(msg)
end

#debug?Boolean

Check if the debug level is enabled.

Returns:

  • (Boolean)


174
175
# File 'lib/rage/logger/logger.rb', line 174

def debug? = @level <= Logger::DEBUG
# Check if the error level is enabled.

#error?Boolean

Check if the error level is enabled.

Returns:

  • (Boolean)


176
177
# File 'lib/rage/logger/logger.rb', line 176

def error? = @level <= Logger::ERROR
# Check if the fatal level is enabled.

#fatal?Boolean

Check if the fatal level is enabled.

Returns:

  • (Boolean)


178
179
# File 'lib/rage/logger/logger.rb', line 178

def fatal? = @level <= Logger::FATAL
# Check if the info level is enabled.

#info?Boolean

Check if the info level is enabled.

Returns:

  • (Boolean)


180
181
# File 'lib/rage/logger/logger.rb', line 180

def info? = @level <= Logger::INFO
# Check if the warn level is enabled.

#tagged(*tags) ⇒ Object Also known as: with_tag

Add a custom tag to an entry.

Examples:

Rage.logger.tagged("ApiCall") do
  Rage.logger.info "success"
end

Parameters:

  • tags (String)

    the tag to add to an entry



163
164
165
166
167
168
169
# File 'lib/rage/logger/logger.rb', line 163

def tagged(*tags)
  old_tags = (Thread.current[:rage_logger] ||= { tags: [], context: {} })[:tags]
  Thread.current[:rage_logger][:tags] = old_tags + tags
  yield(self)
ensure
  Thread.current[:rage_logger][:tags] = old_tags
end

#unknown?Boolean

Check if the unknown level is enabled.

Returns:

  • (Boolean)


184
# File 'lib/rage/logger/logger.rb', line 184

def unknown? = @level <= Logger::UNKNOWN

#warn?Boolean

Check if the warn level is enabled.

Returns:

  • (Boolean)


182
183
# File 'lib/rage/logger/logger.rb', line 182

def warn? = @level <= Logger::WARN
# Check if the unknown level is enabled.

#with_context(context) ⇒ Object

Add custom keys to an entry.

Examples:

Rage.logger.with_context(key: "mykey") do
  Rage.logger.info "cache miss"
end

Parameters:

  • context (Hash)

    a hash of custom keys



142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/rage/logger/logger.rb', line 142

def with_context(context)
  old_context = (Thread.current[:rage_logger] ||= { tags: [], context: {} })[:context]

  if old_context.empty? # there's nothing in the context yet
    Thread.current[:rage_logger][:context] = context
  else # it's not the first `with_context` call in the chain
    Thread.current[:rage_logger][:context] = old_context.merge(context)
  end

  yield(self)
ensure
  Thread.current[:rage_logger][:context] = old_context
end