Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@
/tmp/
.DS_Store
test/dummy/log/*.log
test/dummy/db/*.sqlite3
4 changes: 4 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ GEM
net-smtp
mini_mime (1.1.5)
minitest (5.25.1)
mocha (2.4.5)
ruby2_keywords (>= 0.0.5)
net-imap (0.4.14)
date
net-protocol
Expand Down Expand Up @@ -157,6 +159,7 @@ GEM
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
securerandom (0.3.1)
sqlite3 (2.0.4-arm64-darwin)
sqlite3 (2.0.4-x86_64-linux-gnu)
Expand Down Expand Up @@ -189,6 +192,7 @@ PLATFORMS

DEPENDENCIES
minitest (~> 5.0)
mocha
rake (~> 13.0)
solid_errors!
sqlite3
Expand Down
17 changes: 16 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ You can configure Solid Errors via the Rails configuration object, under the `so
* `email_from` - The email address to send a notification from. See [Email notifications](#email-notifications) for more information.
* `email_to` - The email address(es) to send a notification to. See [Email notifications](#email-notifications) for more information.
* `email_subject_prefix` - Prefix added to the subject line for email notifications. See [Email notifications](#email-notifications) for more information.
* `destroy_after` - If set, Solid Errors will periodically destroy resolved records that are older than the value specified. See [Automatically destroying old records](#automatically-destroying-old-records) for more information.

### Database Configuration

Expand Down Expand Up @@ -242,6 +243,20 @@ config.solid_errors.email_subject_prefix = "[#{Rails.application.name}][#{Rails.

If you have set `send_emails` to `true` and have set an `email_to` address, Solid Errors will send an email notification whenever an error occurs. If you have not set `send_emails` to `true` or have not set an `email_to` address, Solid Errors will not send any email notifications.

#### Automatically destroying old records

Setting `destroy_after` to a duration will allow Solid Errors to be self-maintaining by peridically destroying **resolved** records that are older than that value. The value provided must respond to `.ago`.

```ruby
# Automatically destroy records older than 30 days
config.solid_errors.destroy_after = 30.days
```

```ruby
# Automatically destroy records older than 6 months
config.solid_errors.destroy_after = 6.months
```

### Examples

There are only two screens in the dashboard.
Expand Down Expand Up @@ -337,7 +352,7 @@ You can always take control of the views by creating your own views and/or parti

## Development

After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. If you want to set up a local development database, run `rake db:migrate`.

To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).

Expand Down
14 changes: 9 additions & 5 deletions Rakefile
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
# frozen_string_literal: true

require "bundler/setup"

APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
load "rails/tasks/engine.rake"

require "bundler/gem_tasks"
require "rake/testtask"
require "standard/rake"

task default: %i[test standard]

Rake::TestTask.new(:test) do |t|
t.libs << "test"
t.libs << "lib"
t.test_files = FileList["test/**/test_*.rb"]
t.test_files = FileList["test/**/*_test.rb"]
end

require "standard/rake"

task default: %i[test standard]
36 changes: 36 additions & 0 deletions app/models/solid_errors/cleaner.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
module SolidErrors
class Cleaner
class << self
def go!
destroy_records if destroy_after_last_create?
end

private

def destroy_after_last_create?
SolidErrors.destroy_after.respond_to?(:ago) && (SolidErrors::Occurrence.last.id % 100).zero?
end

def destroy_records
ActiveRecord::Base.transaction do
destroy_occurrences
destroy_errors
end
end

def destroy_occurrences
SolidErrors::Occurrence.joins(:error)
.merge(SolidErrors::Error.resolved)
.where(created_at: ...SolidErrors.destroy_after.ago)
.delete_all
end

def destroy_errors
SolidErrors::Error.resolved
.where
.missing(:occurrences)
.delete_all
end
end
end
end
1 change: 1 addition & 0 deletions app/models/solid_errors/occurrence.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ class Occurrence < Record
belongs_to :error, class_name: "SolidErrors::Error"

after_create_commit :send_email, if: -> { SolidErrors.send_emails? && SolidErrors.email_to.present? }
after_create_commit -> { SolidErrors::Cleaner.go! }

# The parsed exception backtrace. Lines in this backtrace that are from installed gems
# have the base path for gem installs replaced by "[GEM_ROOT]", while those in the project
Expand Down
5 changes: 5 additions & 0 deletions lib/solid_errors.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ module SolidErrors
mattr_writer :email_from
mattr_writer :email_to
mattr_writer :email_subject_prefix
mattr_writer :destroy_after

class << self
# use method instead of attr_accessor to ensure
Expand Down Expand Up @@ -42,5 +43,9 @@ def email_to
def email_subject_prefix
@email_subject_prefix ||= ENV["SOLIDERRORS_EMAIL_SUBJECT_PREFIX"] || @@email_subject_prefix
end

def destroy_after
@destroy_after ||= ENV["SOLIDERRORS_DESTROY_AFTER"] || @@destroy_after
end
end
end
1 change: 1 addition & 0 deletions solid_errors.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@ Gem::Specification.new do |spec|
end

spec.add_development_dependency "sqlite3"
spec.add_development_dependency "mocha"
end
6 changes: 6 additions & 0 deletions test/dummy/Rakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Add your own tasks in files placed in lib/tasks ending in .rake,
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.

require_relative "config/application"

Rails.application.load_tasks
33 changes: 3 additions & 30 deletions test/dummy/config/database.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,15 @@ default: &default
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
timeout: 5000

primary: &primary
<<: *default
database: storage/<%= ENV.fetch("RAILS_ENV", "development") %>.sqlite3

queue: &queue
<<: *default
migrations_paths: db/queue_migrate
database: storage/queue.sqlite3

errors: &errors
<<: *default
migrations_paths: db/errors_migrate
database: storage/errors.sqlite3

development:
primary:
<<: *primary
database: storage/<%= `git branch --show-current`.chomp || 'development' %>.sqlite3
queue: *queue
errors: *errors
<<: *default
database: db/development.sqlite3

# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
primary:
<<: *primary
<<: *default
database: db/test.sqlite3
queue:
<<: *queue
database: db/queue.sqlite3
errors:
<<: *errors
database: db/errors.sqlite3

production:
primary: *primary
queue: *queue
errors: *errors
Binary file removed test/dummy/db/errors.sqlite3
Binary file not shown.
27 changes: 27 additions & 0 deletions test/dummy/db/migrate/20241115125349_add_solid_errors_tables.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
class AddSolidErrorsTables < ActiveRecord::Migration[7.1]
def change
create_table "solid_errors", force: :cascade do |t|
t.text "exception_class", null: false
t.text "message", null: false
t.text "severity", null: false
t.text "source"
t.datetime "resolved_at"
t.string "fingerprint", limit: 64, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["fingerprint"], name: "index_solid_errors_on_fingerprint", unique: true
t.index ["resolved_at"], name: "index_solid_errors_on_resolved_at"
end

create_table "solid_errors_occurrences", force: :cascade do |t|
t.bigint "error_id", null: false
t.text "backtrace"
t.json "context"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["error_id"], name: "index_solid_errors_occurrences_on_error_id"
end

add_foreign_key "solid_errors_occurrences", "solid_errors", column: "error_id"
end
end
Binary file removed test/dummy/db/queue.sqlite3
Binary file not shown.
37 changes: 37 additions & 0 deletions test/dummy/db/schema.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema[7.1].define(version: 2024_11_15_125349) do
create_table "solid_errors", force: :cascade do |t|
t.text "exception_class", null: false
t.text "message", null: false
t.text "severity", null: false
t.text "source"
t.datetime "resolved_at"
t.string "fingerprint", limit: 64, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["fingerprint"], name: "index_solid_errors_on_fingerprint", unique: true
t.index ["resolved_at"], name: "index_solid_errors_on_resolved_at"
end

create_table "solid_errors_occurrences", force: :cascade do |t|
t.bigint "error_id", null: false
t.text "backtrace"
t.json "context"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["error_id"], name: "index_solid_errors_occurrences_on_error_id"
end

add_foreign_key "solid_errors_occurrences", "solid_errors", column: "error_id"
end
Binary file removed test/dummy/db/test.sqlite3
Binary file not shown.
56 changes: 56 additions & 0 deletions test/models/solid_errors/cleaner_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
require "test_helper"

class SolidErrors::CleanerTest < ActiveSupport::TestCase
setup do
assert_nil SolidErrors.destroy_after
end

test "not destroy if destroy_after is not set" do
simulate_99_old_exceptions(:resolved)

assert_difference -> { SolidErrors::Error.count }, +1 do
assert_difference -> { SolidErrors::Occurrence.count }, +1 do
Rails.error.report(dummy_exception)
end
end
end

test "destroy old occurrences every 100 insertions if destroy_after is set" do
set_destroy_after
simulate_99_old_exceptions(:resolved)
Rails.error.report(dummy_exception)

assert_equal 1, SolidErrors::Error.count
assert_equal 1, SolidErrors::Occurrence.count
end

test "not destroy if errors are unresolved" do
set_destroy_after
simulate_99_old_exceptions(:unresolved)

assert_difference -> { SolidErrors::Error.count }, +1 do
assert_difference -> { SolidErrors::Occurrence.count }, +1 do
assert_empty SolidErrors::Error.resolved
Rails.error.report(dummy_exception)
end
end
end

private

def simulate_99_old_exceptions(status)
Rails.error.report(dummy_exception("argh"))
SolidErrors::Error.update_all(resolved_at: Time.current) if status == :resolved
SolidErrors::Occurrence.last.update!(id: 99, created_at: 1.day.ago)
end

def set_destroy_after
SolidErrors.stubs(destroy_after: 1.day)
end

def dummy_exception(message = "oof")
exception = StandardError.new(message)
exception.set_backtrace(caller)
exception
end
end
File renamed without changes.
2 changes: 1 addition & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@
require "rails/test_help"
require "solid_errors"

require "minitest/autorun"
require "mocha/minitest"