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



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

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.



66
67
68
# File 'lib/rage/logger/logger.rb', line 66

def formatter
  @formatter
end

#levelObject

Returns the value of attribute level.



66
67
68
# File 'lib/rage/logger/logger.rb', line 66

def level
  @level
end

Instance Method Details

#<<(msg) ⇒ Object

Write the given msg to the log with no formatting.



121
122
123
# File 'lib/rage/logger/logger.rb', line 121

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

#debug?Boolean

Check if the debug level is enabled.

Returns:

  • (Boolean)


189
190
# File 'lib/rage/logger/logger.rb', line 189

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

#error?Boolean

Check if the error level is enabled.

Returns:

  • (Boolean)


191
192
# File 'lib/rage/logger/logger.rb', line 191

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

#fatal?Boolean

Check if the fatal level is enabled.

Returns:

  • (Boolean)


193
194
# File 'lib/rage/logger/logger.rb', line 193

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

#info?Boolean

Check if the info level is enabled.

Returns:

  • (Boolean)


195
196
# File 'lib/rage/logger/logger.rb', line 195

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



178
179
180
181
182
183
184
# File 'lib/rage/logger/logger.rb', line 178

def tagged(*tags)
  old_tags = (Fiber[:__rage_logger_tags] ||= [])
  Fiber[:__rage_logger_tags] = old_tags + tags
  yield(self)
ensure
  Fiber[:__rage_logger_tags] = old_tags
end

#unknown?Boolean

Check if the unknown level is enabled.

Returns:

  • (Boolean)


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

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

#warn?Boolean

Check if the warn level is enabled.

Returns:

  • (Boolean)


197
198
# File 'lib/rage/logger/logger.rb', line 197

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



157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/rage/logger/logger.rb', line 157

def with_context(context)
  old_context = (Fiber[:__rage_logger_context] ||= {})

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

  yield(self)
ensure
  Fiber[:__rage_logger_context] = old_context
end