DebugLogging
Unobtrusive, inheritable-overridable-configurable, drop-in debug logging, instrumented via method decorators.
Don't leave a mess behind when it is time to remove logging!
Supports ActiveSupport::Notifications
(thanks @jgillson). Optional ActiveRecord callback-style hooks that you can decorate your methods with. Hooks logic was taken from the slippy_method_hooks
gem, (thanks @guckin), and prefaced with debug_
for this implementation. DebugLogging::Finalize
is lightly modified from this stackoverflow answer.
What do I mean by "unobtrusive"?
Ugly debug logging is added inside the body of a method, so it runs when a method is called. This can create a mess of your git history, and can even introduce new bugs to your code. Don't puts
all over your codebase... Instead use this gem.
Unobtrusive debug logging stays out of the method, changes no logic, can't break your code, and yet it still runs when your method is called, and tells you everything you wanted to know. It doesn't mess with the git history of the method at all!
install | bundle add debug_logging |
compatibility | Ruby >= 3.1 (use version 3.x for Ruby 2.4 - 2.7 compatibility) |
license |  |
download rank |  |
version |  |
code triage |  |
documentation | on RDoc.info |
live chat |  |
expert support |  |
Spread ♡ⓛⓞⓥⓔ♡ | 🌏, 👼,  |
Gives you (all are optional):
- benchmarking
- colorization by class/method
- robust argument printer with customizable ellipsis
- unique invocation identifiers
- simple single line global config, or per class/instance/method config
- separate loggers, if needed
- log method calls, also when exit scope
- Prevents heavy computation of strings with
logger.debug { 'log me' }
block format, since v1.0.12
- ActiveSupport::Notifications integration for instrumenting/logging events on class and instance methods, since v3.1.3
- Optional instance, and class-instance, variable logging, since v3.1.3
- ActiveRecord style callback-hooks (optional:
require 'debug_logging/hooks'
and include DebugLogging::Hooks
), since v3.1.3
- All configuration is inheritable to, and overridable by, child classes, since v3.1.3
- Class finalization hook (optional:
require 'debug_logging/finalize'
and extend DebugLogging::Finalize
), since v3.1.3
- Error handling hooks you can use to log problems when they happen, since v3.1.7
- Fixed: Works with benchmarking options since v4.0.2
- so many free ponies 🎠🐴🎠🐴🎠🐴
Next Level Magic
Herein you will find:
Classes inheriting from Module Refactored to use standard Modules and prepend
!
- Zero tolerance policy on global monkey patching
- When the gem is loaded there are no monkey patches.
- Rather, your own classes/methods get "patched" and "hooked" as you configure them.
- 100% clean, 0% obtrusive
- Greater than 94% test coverage & 82% branch coverage
- 100% Ruby 2.1+ compatible
- use version
gem "debug_logging", "~> 1.0"
for Ruby < 2.3
- use version
gem "debug_logging", "~> 2.0"
for Ruby 2.3
- use version
gem "debug_logging", "~> 3.1"
for Ruby >= 2.4, < 3
- apologies to Ruby 3.0, which is hiding under a blanket
- use version
gem "debug_logging", "~> 4.0"
for Ruby >= 3.1
Installation
Add this line to your application's Gemfile:
gem "debug_logging", "~> 4.0"
And then execute:
$ bundle
Or install it yourself as:
$ gem install debug_logging
Usage
Crack open the specs for more complex usage examples than the ones below.
First, how do I turn it off when I need some silence?
For example, in your test suite, before you require "config/environment"
or equivalent, do this:
require "logger"
require "debug_logging"
logger = Logger.new($stdout)
logger.level = Logger::UNKNOWN
DebugLogging.configuration.logger = logger
It will silence all of the places that have extend DebugLogger
, unless those places have overridden the logger config they inherited from the global config.
Without Rails
It just works. ;)
Configuration can go anywhere you want. Configuration is the same regardless; see below.
With Rails
Recommend creating config/initializers/debug_logging.rb
, or adding to config/application.rb
with:
DebugLogging.configuration.logger = Logger.new($stdout)
DebugLogging.configuration.log_level = :debug
DebugLogging.configuration.multiple_last_hashes = false
DebugLogging.configuration.last_hash_to_s_proc = nil
DebugLogging.configuration.last_hash_max_length = 1_000
DebugLogging.configuration.args_to_s_proc = nil
DebugLogging.configuration.args_max_length = 1_000
DebugLogging.configuration.instance_benchmarks = false
DebugLogging.configuration.class_benchmarks = false
DebugLogging.configuration.active_support_notifications = false
DebugLogging.configuration.colorized_chain_for_method = false
DebugLogging.configuration.colorized_chain_for_class = false
DebugLogging.configuration.add_invocation_id = true
DebugLogging.configuration.time_formatter_proc = DebugLogging::Constants::DEFAULT_TIME_FORMATTER
DebugLogging.configuration.add_timestamp = false
DebugLogging.configuration.ellipsis = " ✂️ …".freeze
DebugLogging.configuration.mark_scope_exit = true
DebugLogging.configuration.add_payload = false
DebugLogging.configuration.payload_max_length = 1000
DebugLogging.configuration.error_handler_proc = nil
If you prefer to use the block style:
DebugLogging.configure do |config|
config.logger = Logger.new($stdout)
config.log_level = :debug
config.multiple_last_hashes = false
config.last_hash_to_s_proc = nil
config.last_hash_max_length = 1_000
config.args_to_s_proc = nil
config.args_max_length = 1_000
config.instance_benchmarks = false
config.class_benchmarks = false
config.active_support_notifications = false
config.colorized_chain_for_method = false
config.colorized_chain_for_class = false
config.time_formatter_proc = DebugLogging::Constants::DEFAULT_TIME_FORMATTER
config.add_timestamp = false
config.add_invocation_id = true
config.ellipsis = " ✂️ …".freeze
config.mark_scope_exit = true
config.add_payload = false
config.payload_max_length = 1000
config.error_handler_proc = nil
end
All of the above config is inheritable and configurable at the per-class level as well!
Just prepend debug_
to any config value you want to override in a class.
All of the above config is inheritable and configurable at the per-instance level as well!
Just prepend debug_
to any config value you want to override on an instance of a class.
All of the above config is inheritable and configurable at the per-method level as well!
Just send along a hash of the config options, similar to the following:
logged :drive, { ellipsis: " ✂️ it out" }
i_logged [:drive, :stop], { ellipsis: " ✂️ 2 much" }
notified :drive, { ellipsis: " ✂️ it out" }
i_notified [:drive, :stop], { ellipsis: " ✂️ 2 much" }
See the example class below, and the specs.
NOTE ON Rails.logger
- It will probably be nil in your initializer, so setting the config.logger
to Rails.logger
there will result in setting it to nil
, which means the default will end up being used: Logger.new($stdout)
. Instead just config the logger in your application.rb, or anytime later, but before your classes get loaded and start inheriting the config:
DebugLogging.configuration.logger = Rails.logger
Every time a method is called, you can now get logs, optionally with arguments, a benchmark, and a unique invocation identifier:
class Car
extend DebugLogging
self.debug_class_benchmarks = true
self.debug_instance_benchmarks = true
extend DebugLogging::ClassLogger
extend DebugLogging::InstanceLogger
logged def self.make
new
end
def self.design(*_args)
new
end
def self.safety(*_args)
new
end
def self.dealer_options(*_args)
new
end
logged :design, :safety
logged :dealer_options, {
something: "here",
multiple_last_hashes: true,
error_handler_proc: nil,
}
def self.will_not_be_logged
false
end
i_notified [
:drive,
:stop,
[:turn, {instance_variables: %i[direction angle]}],
]
def drive(speed)
speed
end
def stop(**_opts)
0
end
i_logged instance_methods(false)
def faster(**_opts)
5
end
i_logged [:faster], {
logger: Logger.new($stdout),
log_level: :debug,
multiple_last_hashes: false,
last_hash_to_s_proc: nil,
last_hash_max_length: 1_000,
args_to_s_proc: nil,
args_max_length: 1_000,
colorized_chain_for_method: false,
colorized_chain_for_class: false,
add_invocation_id: true,
ellipsis: " ✂️ …".freeze,
mark_scope_exit: false,
add_payload: true,
payload_max_length: 1_000,
error_handler_proc: nil,
time_formatter_proc: DebugLogging::Constants::DEFAULT_TIME_FORMATTER,
add_timestamp: false,
instance_benchmarks: false,
class_benchmarks: false,
}
i_logged def slower
2
end
def will_not_be_logged
false
end
end
ActiveSupport::Notifications integration
To use ActiveSupport::Notifications
integration, enable active_support_notifications
in the config, either single line or block style:
DebugLogging.configuration.active_support_notifications = true
or
DebugLogging.configure do |config|
config.active_support_notifications = true
end
Every time a method is called, class and instance method events are instrumented, consumed and logged:
class Car
extend DebugLogging
extend DebugLogging::InstanceNotifier
extend DebugLogging::ClassNotifier
i_notified [
:drive,
:stop,
[:turn, {instance_variables: %i[direction angle]}],
]
notified def self.make
new
end
def self.design(*_args)
new
end
def self.safety(*_args)
new
end
def self.dealer_options(*_args)
new
end
notified :design, :safety
notified :dealer_options, {
something: "here",
add_invocation_id: false,
}
def self.will_not_be_notified
false
end
def drive(speed)
speed
end
def stop(**_opts)
0
end
i_notified instance_methods(false)
def faster(**_opts)
0
end
i_notified [:faster], {add_invocation_id: false}
def will_not_be_notified
false
end
end
Development
Run tests!
bin/setup
bin/rake
Contributing
See CONTRIBUTING.md
🪇 Code of Conduct
Everyone interacting in this project's codebases, issue trackers,
chat rooms and mailing lists is expected to follow the code of conduct.
📌 Versioning
This Library adheres to Semantic Versioning 2.0.0.
Violations of this scheme should be reported as bugs.
Specifically, if a minor or patch version is released that breaks backward compatibility,
a new version should be immediately released that restores compatibility.
Breaking changes to the public API will only be introduced with new major versions.
To get a better understanding of how SemVer is intended to work over a project's lifetime,
read this article from the creator of SemVer:
As a result of this policy, you can (and should) specify a dependency on these libraries using
the Pessimistic Version Constraint with two digits of precision.
For example:
spec.add_dependency("debug_logging", "~> 4.0")
📄 License
The gem is available as open source under the terms of
the MIT License
, with one exception:
See LICENSE.txt for the official Copyright Notice.