Skip to content

Commit

Permalink
Allow to use custom reporters (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
fatkodima committed Oct 13, 2023
1 parent b8b348f commit 40bc8ff
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 70 deletions.
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
## master (unreleased)

- Allow to use custom reporters

```ruby
class MyReporter
def report(title, created_records)
# do actual reporting
end
end

ColumnsTrace.reporter = MyReporter.new
```

`ColumnsTrace.logger=` setting is removed in favor of using `ColumnsTrace.reporter=`.
If you configured custom logger, you should now configure it via:

```ruby
logger = ActiveSupport::Logger.new("/my/custom/logger.log")
ColumnsTrace.reporter = ColumnsTrace::LogReporter.new(logger)
```

## 0.1.0 (2023-10-04)

- First release
33 changes: 29 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,10 @@ ColumnsTrace.configure do |config|
# config.ignored_columns = [:updated_at, { User => :admin }]
config.ignored_columns = []

# The logger to log to.
# Defaults to logger, that outputs to `log/columns_trace.log` file
# when the gem is used in the Rails app.
config.logger = nil
# The reporter that is used for reporting.
# Defaults to log reporter that outputs to `log/columns_trace.log` file
# when inside a Rails application.
config.reporter = nil

# Controls the contents of the printed backtrace.
# Is set to the default Rails.backtrace_cleaner when the gem is used in the Rails app.
Expand All @@ -94,6 +94,31 @@ end
ColumnsTrace.enable_sidekiq_tracing!
```

### Custom reporters

By default offenses are reported to a log reporter that outputs to `log/columns_trace.log` file
when inside a Rails application.

You can set your custom reporter by defining a class responding to `#report` method.

```ruby
class MyReporter
def report(title, created_records)
title # => "controller#action"
created_records # => [#<ColumnsTrace::CreatedRecord>]
created_records.each do |record|
record.model # class of ActiveRecord model
record.accessed_fields # array of accessed fields
record.unused_fields # array of unused fields
record.backtrace # array of strings
record.record # ActiveRecord model instance
end
end
end

ColumnsTrace.reporter = MyReporter.new
```

## Development

After checking out the repo, run `bundle install` to install dependencies. Then, run `rake` to run the linter and tests.
Expand Down
9 changes: 5 additions & 4 deletions lib/columns_trace.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

require "active_record"

require_relative "columns_trace/created_record"
require_relative "columns_trace/registry"
require_relative "columns_trace/rails_integration"
require_relative "columns_trace/reporter"
require_relative "columns_trace/log_reporter"
require_relative "columns_trace/version"
require_relative "columns_trace/railtie" if defined?(Rails)

Expand Down Expand Up @@ -54,11 +55,11 @@ def ignored_column?(model, column)
end
end

# Allows to set the logger.
# Defaults to logger, that outputs to `log/columns_trace.log` file
# Allows to set the reporter.
# Defaults to log reporter that outputs to `log/columns_trace.log` file
# when inside a rails application.
#
attr_accessor :logger
attr_accessor :reporter

# @private
attr_reader :backtrace_cleaner
Expand Down
44 changes: 44 additions & 0 deletions lib/columns_trace/created_record.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# frozen_string_literal: true

module ColumnsTrace
# Class that is used to store metadata about created ActiveRecord records.
class CreatedRecord
# Model class
# @return [Class]
#
attr_reader :model

# Model instance
# @return [ActiveRecord::Base]
#
attr_reader :record

# Backtrace where the instance was created
# @return [Array<String>]
#
attr_reader :backtrace

def initialize(record, backtrace)
@model = record.class
@record = record
@backtrace = backtrace
end

# Get accessed fields on model instance
# @return [Array<String>]
#
def accessed_fields
@accessed_fields ||= record.accessed_fields
end

# Get unused fields on model instance
# @return [Array<String>]
#
def unused_fields
# We need to store this into local variable, because `record.attributes`
# will access all attributes.
accessed = accessed_fields
record.attributes.keys - accessed
end
end
end
57 changes: 57 additions & 0 deletions lib/columns_trace/log_reporter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# frozen_string_literal: true

module ColumnsTrace
# Reporter that reports into the provided logger.
class LogReporter
def initialize(logger)
@logger = logger
end

# Main reporter's method
#
# @param title [String] title of the reporting, e.g. controller action etc
# @param created_records [Array<ColumnsTrace::CreatedRecord>] items that hold
# metadata about created records
#
def report(title, created_records)
lines = []

created_records.group_by(&:model).each do |model, grouped_created_records|
lines.concat(lines_for_created_records(model, grouped_created_records))
end

if lines.any?
@logger.info("#{title}\n#{lines.join("\n")}")
end
end

private
def lines_for_created_records(model, created_records)
lines = []

created_records.group_by(&:backtrace).each do |backtrace, grouped_created_records|
accessed = accessed_fields(grouped_created_records)
unused = grouped_created_records.first.unused_fields - accessed
unused.reject! { |column| ColumnsTrace.ignored_column?(model, column) }

if unused.any?
records_text = "record".pluralize(grouped_created_records.size)
lines << <<-MSG
#{grouped_created_records.size} #{model.name} #{records_text}: unused columns - #{format_columns(unused)}; used columns - #{format_columns(accessed)}
#{backtrace.join("\n ")}
MSG
end
end

lines
end

def accessed_fields(created_records)
created_records.map(&:accessed_fields).flatten.uniq
end

def format_columns(columns)
columns.map(&:inspect).join(", ")
end
end
end
10 changes: 8 additions & 2 deletions lib/columns_trace/rails_integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,17 @@ module ColumnsTrace

ActiveSupport.on_load(:action_controller) do
before_action { Registry.clear }
after_action { Reporter.report("#{self.class.name}##{action_name}") }

after_action do
ColumnsTrace.reporter.report("#{self.class.name}##{action_name}", Registry.created_records)
end
end

ActiveSupport.on_load(:active_job) do
before_perform { Registry.clear }
after_perform { Reporter.report(self.class.name) }

after_perform do
ColumnsTrace.reporter.report(self.class.name, Registry.created_records)
end
end
end
4 changes: 3 additions & 1 deletion lib/columns_trace/railtie.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ module ColumnsTrace
class Railtie < Rails::Railtie
initializer "columns_trace.set_configs" do
ColumnsTrace.backtrace_cleaner = Rails.backtrace_cleaner
ColumnsTrace.logger = ActiveSupport::Logger.new(Rails.root.join("log", "columns_trace.log"))

logger = ActiveSupport::Logger.new(Rails.root.join("log", "columns_trace.log"))
ColumnsTrace.reporter = LogReporter.new(logger)
end
end
end
6 changes: 2 additions & 4 deletions lib/columns_trace/registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,16 @@ module ColumnsTrace
# @private
# Note: can use ActiveSupport::IsolatedExecutionState instead of this module for rails 7.0+.
module Registry
Entry = Struct.new(:model, :record, :backtrace)

class << self
def register(record, backtrace)
state << Entry.new(record.class, record, backtrace)
state << CreatedRecord.new(record, backtrace)
end

def clear
state.clear
end

def entries
def created_records
state
end

Expand Down
53 changes: 0 additions & 53 deletions lib/columns_trace/reporter.rb

This file was deleted.

2 changes: 1 addition & 1 deletion lib/columns_trace/sidekiq_integration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ class SidekiqMiddleware
def call(worker, _job, _queue)
Registry.clear
yield
Reporter.report(worker.class.name)
ColumnsTrace.reporter.report(worker.class.name, Registry.created_records)
end
end
end
Expand Down
3 changes: 2 additions & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ class TestCase
def capture_logging(&block)
out = StringIO.new
logger = Logger.new(out)
ColumnsTrace.stub(:logger, logger, &block)
reporter = ColumnsTrace::LogReporter.new(logger)
ColumnsTrace.stub(:reporter, reporter, &block)
out.string
end
end
Expand Down

0 comments on commit 40bc8ff

Please sign in to comment.