solid_errors 0.2.17 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 335f924071f33fa526763f11b5c6512824cfbc2b3f66e5d71a732c497a7fbaf0
4
- data.tar.gz: 8300d7c0f0db903ca91edcb821ac476cd9ed786dcbf92814530ec6d826d6d75c
3
+ metadata.gz: 4287723f04052a3d778c3d867c85d18891d0fef3a0fc88bdb8bc68051b809c75
4
+ data.tar.gz: f8c7880279550637834e8d6ddbfbf70473ad7c849c8ffc67aa100ff03f8261a5
5
5
  SHA512:
6
- metadata.gz: dd452b7c2ca0b0673ef5924d0e7aae0764cae8fdbcaf14d0f5bb913238d23b1bfab4c16570687d90bfb091a45881ead9b6940777e849d21acb0806ccf14b99df
7
- data.tar.gz: b30cad238e2afb5a4522d3c9dc6266e9276199389966b5a84b5a1d5db8123d8d41f62d7610946e7b98b91d1cd702e7420c8cf8df1ff29eaa0f95ec5aaa83cb10
6
+ metadata.gz: 03ec744d7c5c169aba544ad9b6c5c7669720a1a4d09ea8c1d9c4947e2ff29fb2139e5684929dd6a929dff7162752e0b9654dcb38afb6ba9da5b72e1d254e0543
7
+ data.tar.gz: 3ca9e7d6a603903c18f1338e0efa7c800935ecf136bbee650862e9c425fb9d82b0c861304a93a2c06d8d9950fb56ee1905b9b626e3f50f462f6f2db8978af0e9
data/README.md CHANGED
@@ -1,28 +1,125 @@
1
- # SolidErrors
1
+ # Solid Errors
2
+
3
+ <p>
4
+ <a href="https://rubygems.org/gems/solid_errors">
5
+ <img alt="GEM Version" src="https://img.shields.io/gem/v/solid_errors?color=168AFE&include_prereleases&logo=ruby&logoColor=FE1616">
6
+ </a>
7
+ <a href="https://rubygems.org/gems/solid_errors">
8
+ <img alt="GEM Downloads" src="https://img.shields.io/gem/dt/solid_errors?color=168AFE&logo=ruby&logoColor=FE1616">
9
+ </a>
10
+ <a href="https://github.com/testdouble/standard">
11
+ <img alt="Ruby Style" src="https://img.shields.io/badge/style-standard-168AFE?logo=ruby&logoColor=FE1616" />
12
+ </a>
13
+ <a href="https://github.com/fractaledmind/solid_errors/actions/workflows/main.yml">
14
+ <img alt="Tests" src="https://github.com/fractaledmind/solid_errors/actions/workflows/main.yml/badge.svg" />
15
+ </a>
16
+ <a href="https://github.com/sponsors/fractaledmind">
17
+ <img alt="Sponsors" src="https://img.shields.io/github/sponsors/fractaledmind?color=eb4aaa&logo=GitHub%20Sponsors" />
18
+ </a>
19
+ <a href="https://ruby.social/@fractaledmind">
20
+ <img alt="Ruby.Social Follow" src="https://img.shields.io/mastodon/follow/109291299520066427?domain=https%3A%2F%2Fruby.social&label=%40fractaledmind&style=social">
21
+ </a>
22
+ <a href="https://twitter.com/fractaledmind">
23
+ <img alt="Twitter Follow" src="https://img.shields.io/twitter/url?label=%40fractaledmind&style=social&url=https%3A%2F%2Ftwitter.com%2Ffractaledmind">
24
+ </a>
25
+ </p>
26
+
27
+
28
+ Solid Errors is a DB-based, app-internal exception tracker for Rails applications, designed with simplicity and performance in mind. It uses the new [Rails error reporting API](https://guides.rubyonrails.org/error_reporting.html) to store uncaught exceptions in the database, and provides a simple UI for viewing and managing exceptions.
2
29
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/solid_errors`. To experiment with that code, run `bin/console` for an interactive prompt.
30
+ ## Installation
4
31
 
5
- TODO: Delete this and the text above, and describe your gem
32
+ Install the gem and add to the application's Gemfile by executing:
6
33
 
7
- ## Installation
34
+ $ bundle add solid_errors
35
+
36
+ If bundler is not being used to manage dependencies, install the gem by executing:
37
+
38
+ $ gem install solid_errors
39
+
40
+ After installing the gem, run the installer:
41
+
42
+ $ rails generate solid_errors:install
43
+
44
+ This will copy the required migration over to your app.
45
+
46
+ Then mount the engine in your config/routes.rb file
47
+
48
+ mount SolidErrors::Engine, at: "/solid_errors"
49
+
50
+ > [!NOTE]
51
+ > Be sure to [secure the dashboard](#authentication) in production.
52
+
53
+ ## Usage
54
+
55
+ All exceptions are recorded automatically. No additional code required.
56
+
57
+ Please consult the [official guides](https://guides.rubyonrails.org/error_reporting.html) for an introduction to the error reporting API.
58
+
59
+ ### Configuration
60
+
61
+ You can configure Solid Errors via the Rails configuration object, under the `solid_errors` key. Currently, only 3 configuration options are available:
62
+
63
+ * `connects_to` - The database configuration to use for the Solid Errors database. See [Database Configuration](#database-configuration) for more information.
64
+ * `username` - The username to use for HTTP authentication. See [Authentication](#authentication) for more information.
65
+ * `password` - The password to use for HTTP authentication. See [Authentication](#authentication) for more information.
66
+
67
+ #### Database Configuration
68
+
69
+ `config.solid_errors.connects_to` takes a custom database configuration hash that will be used in the abstract `SolidErrors::Record` Active Record model. This is required to use a different database than the main app. For example:
8
70
 
9
- Add this line to your application's Gemfile:
71
+ ```ruby
72
+ # Use a single separate DB for Solid Errors
73
+ config.solid_errors.connects_to = { database: { writing: :solid_errors, reading: :solid_errors } }
74
+ ```
75
+
76
+ or
10
77
 
11
78
  ```ruby
12
- gem 'solid_errors'
79
+ # Use a separate primary/replica pair for Solid Errors
80
+ config.solid_errors.connects_to = { database: { writing: :solid_errors_primary, reading: :solid_errors_replica } }
13
81
  ```
14
82
 
15
- And then execute:
83
+ #### Authentication
16
84
 
17
- $ bundle install
85
+ Solid Errors does not restrict access out of the box. You must secure the dashboard yourself. However, it does provide basic HTTP authentication that can be used with basic authentication or Devise. All you need to do is setup a username and password.
18
86
 
19
- Or install it yourself as:
87
+ There are two ways to setup a username and password. First, you can use the `SOLIDERRORS_USERNAME` and `SOLIDERRORS_PASSWORD` environment variables:
20
88
 
21
- $ gem install solid_errors
89
+ ```ruby
90
+ ENV["SOLIDERRORS_USERNAME"] = "frodo"
91
+ ENV["SOLIDERRORS_PASSWORD"] = "ikeptmysecrets"
92
+ ```
22
93
 
23
- ## Usage
94
+ Second, you can set the `SolidErrors.username` and `SolidErrors.password` variables in an initializer:
95
+
96
+ ```ruby
97
+ # Set authentication credentials for Solid Errors
98
+ config.solid_errors.username = Rails.application.credentials.solid_errors.username
99
+ config.solid_errors.password = Rails.application.credentials.solid_errors.password
100
+ ```
101
+
102
+ Either way, if you have set a username and password, Solid Errors will use basic HTTP authentication. If you have not set a username and password, Solid Errors will not require any authentication to view the dashboard.
103
+
104
+ If you use Devise for authenctication in your app, you can also restrict access to the dashboard by using their `authenticate` contraint in your routes file:
105
+
106
+ ```ruby
107
+ authenticate :user, -> (user) { user.admin? } do
108
+ mount SolidErrors::Engine, at: "/solid_errors"
109
+ end
110
+ ```
111
+
112
+ ### Examples
113
+
114
+ There are only two screens in the dashboard.
115
+
116
+ * the index view of all unresolved errors:
117
+
118
+ ![image description](images/index-screenshot.png)
119
+
120
+ * and the show view of a particular error:
24
121
 
25
- TODO: Write usage instructions here
122
+ ![image description](images/show-screenshot.png)
26
123
 
27
124
  ## Development
28
125
 
@@ -32,7 +129,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
32
129
 
33
130
  ## Contributing
34
131
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/solid_errors. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/solid_errors/blob/main/CODE_OF_CONDUCT.md).
132
+ Bug reports and pull requests are welcome on GitHub at https://github.com/fractaledmind/solid_errors. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/fractaledmind/solid_errors/blob/main/CODE_OF_CONDUCT.md).
36
133
 
37
134
  ## License
38
135
 
@@ -40,4 +137,4 @@ The gem is available as open source under the terms of the [MIT License](https:/
40
137
 
41
138
  ## Code of Conduct
42
139
 
43
- Everyone interacting in the SolidErrors project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/solid_errors/blob/main/CODE_OF_CONDUCT.md).
140
+ Everyone interacting in the SolidErrors project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/fractaledmind/solid_errors/blob/main/CODE_OF_CONDUCT.md).
@@ -1,4 +1,7 @@
1
1
  module SolidErrors
2
2
  class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+
5
+ http_basic_authenticate_with name: SolidErrors.username, password: SolidErrors.password if SolidErrors.password
3
6
  end
4
7
  end
@@ -1,6 +1,6 @@
1
1
  module SolidErrors
2
2
  class ErrorsController < ApplicationController
3
- before_action :set_error, only: %i[ show update ]
3
+ before_action :set_error, only: %i[show update]
4
4
 
5
5
  # GET /errors
6
6
  def index
@@ -8,10 +8,10 @@ module SolidErrors
8
8
  occurrences_table = Occurrence.arel_table
9
9
 
10
10
  @errors = Error.unresolved
11
- .joins(:occurrences)
12
- .select(errors_table[Arel.star], occurrences_table[:created_at].maximum.as('recent_occurrence'))
13
- .group(errors_table[:id])
14
- .order(recent_occurrence: :desc)
11
+ .joins(:occurrences)
12
+ .select(errors_table[Arel.star], occurrences_table[:created_at].maximum.as("recent_occurrence"))
13
+ .group(errors_table[:id])
14
+ .order(recent_occurrence: :desc)
15
15
  end
16
16
 
17
17
  # GET /errors/1
@@ -25,13 +25,14 @@ module SolidErrors
25
25
  end
26
26
 
27
27
  private
28
- # Only allow a list of trusted parameters through.
29
- def error_params
30
- params.require(:error).permit(:resolved_at)
31
- end
32
28
 
33
- def set_error
34
- @error = Error.find(params[:id])
35
- end
29
+ # Only allow a list of trusted parameters through.
30
+ def error_params
31
+ params.require(:error).permit(:resolved_at)
32
+ end
33
+
34
+ def set_error
35
+ @error = Error.find(params[:id])
36
+ end
36
37
  end
37
38
  end
@@ -10,7 +10,7 @@ module SolidErrors
10
10
  BacktraceLine.parse(unparsed_line.to_s, opts)
11
11
  end.compact
12
12
 
13
- instance = new(lines)
13
+ new(lines)
14
14
  end
15
15
 
16
16
  def initialize(lines)
@@ -22,9 +22,9 @@ module SolidErrors
22
22
  #
23
23
  # Returns array containing backtrace lines.
24
24
  def to_ary
25
- lines.take(1000).map { |l| { :number => l.filtered_number, :file => l.filtered_file, :method => l.filtered_method, :source => l.source } }
25
+ lines.take(1000).map { |l| {number: l.filtered_number, file: l.filtered_file, method: l.filtered_method, source: l.source} }
26
26
  end
27
- alias :to_a :to_ary
27
+ alias_method :to_a, :to_ary
28
28
 
29
29
  # JSON support.
30
30
  #
@@ -2,14 +2,14 @@ module SolidErrors
2
2
  class BacktraceLine
3
3
  # Backtrace line regexp (optionally allowing leading X: for windows support).
4
4
  INPUT_FORMAT = %r{^((?:[a-zA-Z]:)?[^:]+):(\d+)(?::in `([^']+)')?$}.freeze
5
- STRING_EMPTY = ''.freeze
6
- GEM_ROOT = '[GEM_ROOT]'.freeze
7
- PROJECT_ROOT = '[PROJECT_ROOT]'.freeze
5
+ STRING_EMPTY = "".freeze
6
+ GEM_ROOT = "[GEM_ROOT]".freeze
7
+ PROJECT_ROOT = "[PROJECT_ROOT]".freeze
8
8
  PROJECT_ROOT_CACHE = {}
9
9
  GEM_ROOT_CACHE = {}
10
10
  RELATIVE_ROOT = Regexp.new('^\.\/').freeze
11
11
  RAILS_ROOT = ::Rails.root.to_s.dup.freeze
12
- ROOT_REGEXP = Regexp.new("^#{ Regexp.escape(RAILS_ROOT) }").freeze
12
+ ROOT_REGEXP = Regexp.new("^#{Regexp.escape(RAILS_ROOT)}").freeze
13
13
  BACKTRACE_FILTERS = [
14
14
  lambda { |line|
15
15
  return line unless defined?(Gem)
@@ -47,21 +47,19 @@ module SolidErrors
47
47
  file, number, method = match[1], match[2], match[3]
48
48
  filtered_args = [fmatch[1], fmatch[2], fmatch[3]]
49
49
  new(file, number, method, *filtered_args, opts.fetch(:source_radius, 2))
50
- else
51
- nil
52
50
  end
53
51
  end
54
52
 
55
53
  def initialize(file, number, method, filtered_file = file,
56
- filtered_number = number, filtered_method = method,
57
- source_radius = 2)
58
- self.filtered_file = filtered_file
54
+ filtered_number = number, filtered_method = method,
55
+ source_radius = 2)
56
+ self.filtered_file = filtered_file
59
57
  self.filtered_number = filtered_number
60
58
  self.filtered_method = filtered_method
61
- self.file = file
62
- self.number = number
63
- self.method = method
64
- self.source_radius = source_radius
59
+ self.file = file
60
+ self.number = number
61
+ self.method = method
62
+ self.source_radius = source_radius
65
63
  end
66
64
 
67
65
  # Reconstructs the line in a readable fashion.
@@ -74,7 +72,7 @@ module SolidErrors
74
72
  end
75
73
 
76
74
  def inspect
77
- "<Line:#{to_s}>"
75
+ "<Line:#{self}>"
78
76
  end
79
77
 
80
78
  # Determines if this line is part of the application trace or not.
@@ -105,8 +103,11 @@ module SolidErrors
105
103
 
106
104
  l = 0
107
105
  File.open(file) do |f|
108
- start.times { f.gets ; l += 1 }
109
- return Hash[duration.times.map { (line = f.gets) ? [(l += 1), line] : nil }.compact]
106
+ start.times {
107
+ f.gets
108
+ l += 1
109
+ }
110
+ return duration.times.map { (line = f.gets) ? [(l += 1), line] : nil }.compact.to_h
110
111
  end
111
112
  end
112
113
  end
@@ -27,7 +27,7 @@ module SolidErrors
27
27
  end
28
28
 
29
29
  def badge_classes
30
- "px-2 inline-flex text-[.75em] font-semibold rounded-md #{SEVERITY_TO_BADGE_CLASSES[severity.to_sym]}"
30
+ "px-2 inline-flex text-sm font-semibold rounded-md #{SEVERITY_TO_BADGE_CLASSES[severity.to_sym]}"
31
31
  end
32
32
  end
33
33
  end
@@ -14,8 +14,8 @@ module SolidErrors
14
14
 
15
15
  private
16
16
 
17
- def parse_backtrace(backtrace)
18
- Backtrace.parse(backtrace)
19
- end
17
+ def parse_backtrace(backtrace)
18
+ Backtrace.parse(backtrace)
19
+ end
20
20
  end
21
21
  end
@@ -4,7 +4,7 @@ module SolidErrors
4
4
  class Record < ActiveRecord::Base
5
5
  self.abstract_class = true
6
6
 
7
- connects_to **SolidErrors.connects_to if SolidErrors.connects_to
7
+ connects_to(**SolidErrors.connects_to) if SolidErrors.connects_to
8
8
  end
9
9
  end
10
10
 
@@ -95,8 +95,8 @@
95
95
  */
96
96
 
97
97
  abbr:where([title]) {
98
- -webkit-text-decoration: underline dotted;
99
- text-decoration:underline dotted
98
+ text-decoration-line: underline;
99
+ text-decoration-style: dotted;
100
100
  }
101
101
 
102
102
  /*
@@ -578,12 +578,6 @@
578
578
  }
579
579
  }
580
580
 
581
- .bi {
582
- fill: currentColor;
583
- stroke-width: 0.5;
584
- stroke: currentColor;
585
- }
586
-
587
581
  .sr-only{
588
582
  position: absolute;
589
583
  width: 1px;
@@ -596,14 +590,26 @@
596
590
  border-width: 0
597
591
  }
598
592
 
599
- .static{
600
- position: static
593
+ .fixed{
594
+ position: fixed
601
595
  }
602
596
 
603
597
  .relative{
604
598
  position: relative
605
599
  }
606
600
 
601
+ .left-0{
602
+ left: 0px
603
+ }
604
+
605
+ .right-0{
606
+ right: 0px
607
+ }
608
+
609
+ .top-0{
610
+ top: 0px
611
+ }
612
+
607
613
  .-mx-2{
608
614
  margin-left: -0.5rem;
609
615
  margin-right: -0.5rem
@@ -618,10 +624,6 @@
618
624
  margin-bottom: 0.75rem
619
625
  }
620
626
 
621
- .mb-36{
622
- margin-bottom: 9rem
623
- }
624
-
625
627
  .ml-6{
626
628
  margin-left: 1.5rem
627
629
  }
@@ -634,37 +636,8 @@
634
636
  margin-top: 1rem
635
637
  }
636
638
 
637
- .min-h-full{
638
- min-height: 100%
639
- }
640
-
641
- .select-none{
642
- user-select: none
643
- }
644
-
645
- .overflow-auto{
646
- overflow: auto
647
- }
648
-
649
- .rounded-b-lg{
650
- border-bottom-right-radius: 0.5rem;
651
- border-bottom-left-radius: 0.5rem
652
- }
653
-
654
- .leading-normal{
655
- line-height: 1.5
656
- }
657
-
658
- .mt-4{
659
- margin-top: 1rem
660
- }
661
-
662
- .\!block{
663
- display: block !important
664
- }
665
-
666
- .block{
667
- display: block
639
+ .inline-block{
640
+ display: inline-block
668
641
  }
669
642
 
670
643
  .flex{
@@ -675,10 +648,6 @@
675
648
  display: inline-flex
676
649
  }
677
650
 
678
- .flex-col{
679
- flex-direction: column
680
- }
681
-
682
651
  .table{
683
652
  display: table
684
653
  }
@@ -687,12 +656,8 @@
687
656
  display: grid
688
657
  }
689
658
 
690
- .hidden{
691
- display: none
692
- }
693
-
694
- .w-full{
695
- width: 100%
659
+ .min-h-full{
660
+ min-height: 100%
696
661
  }
697
662
 
698
663
  .min-w-full{
@@ -719,10 +684,18 @@
719
684
  grid-template-columns: repeat(2, minmax(0, 1fr))
720
685
  }
721
686
 
687
+ .flex-col{
688
+ flex-direction: column
689
+ }
690
+
722
691
  .flex-wrap{
723
692
  flex-wrap: wrap
724
693
  }
725
694
 
695
+ .items-start{
696
+ align-items: flex-start
697
+ }
698
+
726
699
  .items-center{
727
700
  align-items: center
728
701
  }
@@ -780,10 +753,18 @@
780
753
  border-color: rgb(209 213 219 / var(--tw-divide-opacity))
781
754
  }
782
755
 
756
+ .overflow-auto{
757
+ overflow: auto
758
+ }
759
+
783
760
  .whitespace-nowrap{
784
761
  white-space: nowrap
785
762
  }
786
763
 
764
+ .whitespace-pre-wrap{
765
+ white-space: pre-wrap
766
+ }
767
+
787
768
  .rounded{
788
769
  border-radius: 0.25rem
789
770
  }
@@ -792,8 +773,9 @@
792
773
  border-radius: 0.5rem
793
774
  }
794
775
 
795
- .rounded-md{
796
- border-radius: 0.375rem
776
+ .rounded-b-lg{
777
+ border-bottom-right-radius: 0.5rem;
778
+ border-bottom-left-radius: 0.5rem
797
779
  }
798
780
 
799
781
  .border{
@@ -804,14 +786,14 @@
804
786
  border-bottom-width: 1px
805
787
  }
806
788
 
807
- .border-gray-300{
789
+ .border-blue-500{
808
790
  --tw-border-opacity: 1;
809
- border-color: rgb(209 213 219 / var(--tw-border-opacity))
791
+ border-color: rgb(59 130 246 / var(--tw-border-opacity))
810
792
  }
811
793
 
812
- .border-blue-500{
794
+ .border-gray-300{
813
795
  --tw-border-opacity: 1;
814
- border-color: rgb(59 130 246 / var(--tw-border-opacity))
796
+ border-color: rgb(209 213 219 / var(--tw-border-opacity))
815
797
  }
816
798
 
817
799
  .bg-gray-100{
@@ -819,18 +801,14 @@
819
801
  background-color: rgb(243 244 246 / var(--tw-bg-opacity))
820
802
  }
821
803
 
822
- .bg-gray-200{
804
+ .bg-green-50{
823
805
  --tw-bg-opacity: 1;
824
- background-color: rgb(229 231 235 / var(--tw-bg-opacity))
806
+ background-color: rgb(240 253 244 / var(--tw-bg-opacity))
825
807
  }
826
808
 
827
- .bg-transparent{
828
- background-color: transparent
829
- }
830
-
831
- .bg-white{
809
+ .bg-red-50{
832
810
  --tw-bg-opacity: 1;
833
- background-color: rgb(255 255 255 / var(--tw-bg-opacity))
811
+ background-color: rgb(254 242 242 / var(--tw-bg-opacity))
834
812
  }
835
813
 
836
814
  .bg-slate-800{
@@ -838,34 +816,17 @@
838
816
  background-color: rgb(30 41 59 / var(--tw-bg-opacity))
839
817
  }
840
818
 
841
- .bg-blue-100{
842
- --tw-bg-opacity: 1;
843
- background-color: rgb(219 234 254 / var(--tw-bg-opacity))
844
- }
845
-
846
- .bg-red-100{
847
- --tw-bg-opacity: 1;
848
- background-color: rgb(254 226 226 / var(--tw-bg-opacity))
819
+ .bg-transparent{
820
+ background-color: transparent
849
821
  }
850
822
 
851
- .bg-yellow-100{
823
+ .bg-white{
852
824
  --tw-bg-opacity: 1;
853
- background-color: rgb(254 249 195 / var(--tw-bg-opacity))
854
- }
855
-
856
- .text-blue-800{
857
- --tw-text-opacity: 1;
858
- color: rgb(30 64 175 / var(--tw-text-opacity))
859
- }
860
-
861
- .text-red-800{
862
- --tw-text-opacity: 1;
863
- color: rgb(153 27 27 / var(--tw-text-opacity))
825
+ background-color: rgb(255 255 255 / var(--tw-bg-opacity))
864
826
  }
865
827
 
866
- .text-yellow-800{
867
- --tw-text-opacity: 1;
868
- color: rgb(133 77 14 / var(--tw-text-opacity))
828
+ .p-2{
829
+ padding: 0.5rem
869
830
  }
870
831
 
871
832
  .p-4{
@@ -877,10 +838,6 @@
877
838
  padding-right: 0px
878
839
  }
879
840
 
880
- .p-2{
881
- padding: 0.5rem
882
- }
883
-
884
841
  .px-2{
885
842
  padding-left: 0.5rem;
886
843
  padding-right: 0.5rem
@@ -901,6 +858,11 @@
901
858
  padding-bottom: 0.25rem
902
859
  }
903
860
 
861
+ .py-2{
862
+ padding-top: 0.5rem;
863
+ padding-bottom: 0.5rem
864
+ }
865
+
904
866
  .py-3{
905
867
  padding-top: 0.75rem;
906
868
  padding-bottom: 0.75rem
@@ -940,10 +902,18 @@
940
902
  padding-top: 1.75rem
941
903
  }
942
904
 
905
+ .rounded-md{
906
+ border-radius: 0.375rem
907
+ }
908
+
943
909
  .text-left{
944
910
  text-align: left
945
911
  }
946
912
 
913
+ .text-center{
914
+ text-align: center
915
+ }
916
+
947
917
  .text-right{
948
918
  text-align: right
949
919
  }
@@ -957,10 +927,6 @@
957
927
  line-height: 2rem
958
928
  }
959
929
 
960
- .text-\[\.75em\]{
961
- font-size: .75em
962
- }
963
-
964
930
  .text-base{
965
931
  font-size: 1rem;
966
932
  line-height: 1.5rem
@@ -983,9 +949,23 @@
983
949
  font-weight: 600
984
950
  }
985
951
 
986
- .text-white{
987
- --tw-text-opacity: 1;
988
- color: rgb(255 255 255 / var(--tw-text-opacity))
952
+ .leading-normal{
953
+ line-height: 1.5
954
+ }
955
+
956
+ .bg-blue-100{
957
+ --tw-bg-opacity: 1;
958
+ background-color: rgb(219 234 254 / var(--tw-bg-opacity))
959
+ }
960
+
961
+ .bg-red-100{
962
+ --tw-bg-opacity: 1;
963
+ background-color: rgb(254 226 226 / var(--tw-bg-opacity))
964
+ }
965
+
966
+ .bg-yellow-100{
967
+ --tw-bg-opacity: 1;
968
+ background-color: rgb(254 249 195 / var(--tw-bg-opacity))
989
969
  }
990
970
 
991
971
  .text-blue-400{
@@ -1008,11 +988,6 @@
1008
988
  color: rgb(75 85 99 / var(--tw-text-opacity))
1009
989
  }
1010
990
 
1011
- .text-gray-800{
1012
- --tw-text-opacity: 1;
1013
- color: rgb(31 41 55 / var(--tw-text-opacity))
1014
- }
1015
-
1016
991
  .text-gray-900{
1017
992
  --tw-text-opacity: 1;
1018
993
  color: rgb(17 24 39 / var(--tw-text-opacity))
@@ -1023,10 +998,45 @@
1023
998
  color: rgb(34 197 94 / var(--tw-text-opacity))
1024
999
  }
1025
1000
 
1001
+ .text-red-500{
1002
+ --tw-text-opacity: 1;
1003
+ color: rgb(239 68 68 / var(--tw-text-opacity))
1004
+ }
1005
+
1006
+ .text-blue-800{
1007
+ --tw-text-opacity: 1;
1008
+ color: rgb(30 64 175 / var(--tw-text-opacity))
1009
+ }
1010
+
1011
+ .text-red-800{
1012
+ --tw-text-opacity: 1;
1013
+ color: rgb(153 27 27 / var(--tw-text-opacity))
1014
+ }
1015
+
1016
+ .text-yellow-800{
1017
+ --tw-text-opacity: 1;
1018
+ color: rgb(133 77 14 / var(--tw-text-opacity))
1019
+ }
1020
+
1021
+ .text-white{
1022
+ --tw-text-opacity: 1;
1023
+ color: rgb(255 255 255 / var(--tw-text-opacity))
1024
+ }
1025
+
1026
1026
  .underline{
1027
1027
  text-decoration-line: underline
1028
1028
  }
1029
1029
 
1030
+ .transition-opacity{
1031
+ transition-property: opacity;
1032
+ transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
1033
+ transition-duration: 150ms
1034
+ }
1035
+
1036
+ .opacity-0{
1037
+ opacity: 0
1038
+ }
1039
+
1030
1040
  .even\:bg-gray-50:nth-child(even){
1031
1041
  --tw-bg-opacity: 1;
1032
1042
  background-color: rgb(249 250 251 / var(--tw-bg-opacity))
@@ -1043,14 +1053,14 @@
1043
1053
  box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000)
1044
1054
  }
1045
1055
 
1046
- .hover\:ring-gray-200:hover{
1056
+ .hover\:ring-blue-200:hover{
1047
1057
  --tw-ring-opacity: 1;
1048
- --tw-ring-color: rgb(229 231 235 / var(--tw-ring-opacity))
1058
+ --tw-ring-color: rgb(191 219 254 / var(--tw-ring-opacity))
1049
1059
  }
1050
1060
 
1051
- .hover\:ring-blue-200:hover{
1061
+ .hover\:ring-gray-200:hover{
1052
1062
  --tw-ring-opacity: 1;
1053
- --tw-ring-color: rgb(191 219 254 / var(--tw-ring-opacity))
1063
+ --tw-ring-color: rgb(229 231 235 / var(--tw-ring-opacity))
1054
1064
  }
1055
1065
 
1056
1066
  @media (min-width: 640px){
@@ -1101,21 +1111,20 @@
1101
1111
  <% end %>
1102
1112
  </div>
1103
1113
 
1104
- <script>
1105
- var element = document.querySelector('[data-controller="fade"]');
1106
- function fadeOut(el) {
1107
- var opacity = 1; // Initial opacity
1108
- var interval = setInterval(function() {
1109
- if (opacity > 0) {
1110
- opacity -= 0.1;
1111
- el.style.opacity = opacity;
1112
- } else {
1113
- clearInterval(interval); // Stop the interval when opacity reaches 0
1114
- el.style.display = 'none'; // Hide the element
1115
- }
1116
- }, 50);
1117
- }
1118
- fadeOut(element);
1114
+ <script nonce="<%= content_security_policy_nonce %>">
1115
+ function fadeOut(element) {
1116
+ element.classList.add('transition-opacity')
1117
+ setTimeout(
1118
+ () => {
1119
+ element.classList.add('opacity-0')
1120
+ element.remove()
1121
+ },
1122
+ 2000
1123
+ )
1124
+ }
1125
+ document.querySelectorAll('[data-controller="fade"]').forEach(element => {
1126
+ fadeOut(element);
1127
+ });
1119
1128
  </script>
1120
1129
  </body>
1121
1130
  </html>
@@ -8,7 +8,7 @@
8
8
  from
9
9
  <em><code><%= error.source %></code></em>
10
10
  </div>
11
- <pre class="ml-6 mt-4"><%= error.message %></pre>
11
+ <pre class="whitespace-pre-wrap ml-6 mt-4"><%= error.message %></pre>
12
12
  </td>
13
13
  <td scope="col" class="whitespace-nowrap px-3 py-4 pt-7 text-gray-500 text-right">
14
14
  <%= error.occurrences.size %>
@@ -99,7 +99,8 @@
99
99
  <%= render "solid_errors/errors/actions", error: @error %>
100
100
  <% end %>
101
101
 
102
- <hr class="mt-4 pb-4">
102
+ <br>
103
+ <br>
103
104
 
104
105
  <%= render "solid_errors/occurrences/collection",
105
106
  occurrences: @error.occurrences,
@@ -10,12 +10,12 @@
10
10
  <div class="">
11
11
  <dl class="ml-6">
12
12
  <% occurrence.context&.each do |key, value| %>
13
- <div class="flex items-center justify-between flex-wrap gap-x-2">
13
+ <div class="flex items-center justify-start flex-wrap gap-x-2">
14
14
  <dt class="font-bold">
15
15
  <%= SolidErrors::Occurrence.human_attribute_name(key) %>
16
16
  </dt>
17
17
  <dd class="">
18
- <%= value %>
18
+ <code><%= value %></code>
19
19
  </dd>
20
20
  </div>
21
21
  <% end %>
data/config/routes.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  SolidErrors::Engine.routes.draw do
2
- get "/" => "errors#index", as: :root
2
+ get "/" => "errors#index", :as => :root
3
3
 
4
4
  resources :errors, only: [:index, :show, :update], path: ""
5
5
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'rails/generators'
4
- require 'rails/generators/active_record'
3
+ require "rails/generators"
4
+ require "rails/generators/active_record"
5
5
 
6
6
  module SolidErrors
7
7
  #
@@ -13,14 +13,14 @@ module SolidErrors
13
13
 
14
14
  source_root File.expand_path("templates", __dir__)
15
15
 
16
- class_option :database, type: :string, aliases: %i(--db), desc: "The database for your migration. By default, the current environment's primary database is used."
16
+ class_option :database, type: :string, aliases: %i[--db], desc: "The database for your migration. By default, the current environment's primary database is used."
17
17
  class_option :skip_migrations, type: :boolean, default: nil, desc: "Skip migrations"
18
18
 
19
19
  # Generates monolithic migration file that contains all database changes.
20
20
  def create_migration_file
21
21
  return if options[:skip_migrations]
22
22
 
23
- migration_template 'create_solid_errors_tables.rb.erb', File.join(db_migrate_path, "create_solid_errors_tables.rb")
23
+ migration_template "create_solid_errors_tables.rb.erb", File.join(db_migrate_path, "create_solid_errors_tables.rb")
24
24
  end
25
25
 
26
26
  private
@@ -11,11 +11,11 @@ class CreateSolidErrorsTables < ActiveRecord::Migration<%= migration_version %>
11
11
 
12
12
  t.timestamps
13
13
 
14
- t.index [:exception_class, :message, :severity, :source], unique: true
14
+ t.index [:exception_class, :message, :severity, :source], unique: true, name: "solid_error_uniqueness_index"
15
15
  end
16
16
 
17
17
  create_table :solid_errors_occurrences do |t|
18
- t.belongs_to :error, null: false, foreign_key: true
18
+ t.belongs_to :solid_error, null: false, foreign_key: true
19
19
  t.text :backtrace
20
20
  t.json :context
21
21
 
@@ -1,11 +1,11 @@
1
1
  module SolidErrors
2
2
  # adapted from: https://github.com/honeybadger-io/honeybadger-ruby/blob/master/lib/honeybadger/util/sanitizer.rb
3
3
  class Sanitizer
4
- BASIC_OBJECT = '#<BasicObject>'.freeze
5
- DEPTH = '[DEPTH]'.freeze
6
- RAISED = '[RAISED]'.freeze
7
- RECURSION = '[RECURSION]'.freeze
8
- TRUNCATED = '[TRUNCATED]'.freeze
4
+ BASIC_OBJECT = "#<BasicObject>".freeze
5
+ DEPTH = "[DEPTH]".freeze
6
+ RAISED = "[RAISED]".freeze
7
+ RECURSION = "[RECURSION]".freeze
8
+ TRUNCATED = "[TRUNCATED]".freeze
9
9
  MAX_STRING_SIZE = 65536
10
10
 
11
11
  def self.sanitize(data)
@@ -21,7 +21,7 @@ module SolidErrors
21
21
  return BASIC_OBJECT if basic_object?(data)
22
22
 
23
23
  if recursive?(data)
24
- return RECURSION if stack && stack.include?(data.object_id)
24
+ return RECURSION if stack&.include?(data.object_id)
25
25
 
26
26
  stack = stack ? stack.dup : Set.new
27
27
  stack << data.object_id
@@ -33,8 +33,8 @@ module SolidErrors
33
33
 
34
34
  new_hash = {}
35
35
  data.each do |key, value|
36
- key = key.kind_of?(Symbol) ? key : sanitize(key, depth+1, stack)
37
- value = sanitize(value, depth+1, stack)
36
+ key = key.is_a?(Symbol) ? key : sanitize(key, depth + 1, stack)
37
+ value = sanitize(value, depth + 1, stack)
38
38
  new_hash[key] = value
39
39
  end
40
40
  new_hash
@@ -42,7 +42,7 @@ module SolidErrors
42
42
  return DEPTH if depth >= max_depth
43
43
 
44
44
  data.to_a.map do |value|
45
- sanitize(value, depth+1, stack)
45
+ sanitize(value, depth + 1, stack)
46
46
  end
47
47
  when Numeric, TrueClass, FalseClass, NilClass
48
48
  data
@@ -64,28 +64,29 @@ module SolidErrors
64
64
  end
65
65
 
66
66
  private
67
- attr_reader :max_depth
68
-
69
- def basic_object?(object)
70
- object.respond_to?(:to_s)
71
- false
72
- rescue
73
- # BasicObject doesn't respond to `#respond_to?`.
74
- true
75
- end
76
67
 
77
- def recursive?(data)
78
- data.is_a?(Hash) || data.is_a?(Array) || data.is_a?(Set)
79
- end
68
+ attr_reader :max_depth
80
69
 
81
- def sanitize_string(string)
82
- string.gsub!(/#<(.*?):0x.*?>/, '#<\1>') # remove object_id
83
- return string unless string.respond_to?(:size) && string.size > MAX_STRING_SIZE
84
- string[0...MAX_STRING_SIZE] + TRUNCATED
85
- end
70
+ def basic_object?(object)
71
+ object.respond_to?(:to_s)
72
+ false
73
+ rescue
74
+ # BasicObject doesn't respond to `#respond_to?`.
75
+ true
76
+ end
86
77
 
87
- def inspected?(string)
88
- String(string) =~ /#<.*>/
89
- end
78
+ def recursive?(data)
79
+ data.is_a?(Hash) || data.is_a?(Array) || data.is_a?(Set)
80
+ end
81
+
82
+ def sanitize_string(string)
83
+ string.gsub!(/#<(.*?):0x.*?>/, '#<\1>') # remove object_id
84
+ return string unless string.respond_to?(:size) && string.size > MAX_STRING_SIZE
85
+ string[0...MAX_STRING_SIZE] + TRUNCATED
86
+ end
87
+
88
+ def inspected?(string)
89
+ String(string) =~ /#<.*>/
90
+ end
90
91
  end
91
92
  end
@@ -1,26 +1,25 @@
1
1
  module SolidErrors
2
2
  class Subscriber
3
- IGNORED_ERRORS = ['ActionController::RoutingError',
4
- 'AbstractController::ActionNotFound',
5
- 'ActionController::MethodNotAllowed',
6
- 'ActionController::UnknownHttpMethod',
7
- 'ActionController::NotImplemented',
8
- 'ActionController::UnknownFormat',
9
- 'ActionController::InvalidAuthenticityToken',
10
- 'ActionController::InvalidCrossOriginRequest',
11
- 'ActionDispatch::Http::Parameters::ParseError',
12
- 'ActionController::BadRequest',
13
- 'ActionController::ParameterMissing',
14
- 'ActiveRecord::RecordNotFound',
15
- 'ActionController::UnknownAction',
16
- 'ActionDispatch::Http::MimeNegotiation::InvalidType',
17
- 'Rack::QueryParser::ParameterTypeError',
18
- 'Rack::QueryParser::InvalidParameterError',
19
- 'CGI::Session::CookieStore::TamperedWithCookie',
20
- 'Mongoid::Errors::DocumentNotFound',
21
- 'Sinatra::NotFound',
22
- 'Sidekiq::JobRetry::Skip'].map(&:freeze).freeze
23
-
3
+ IGNORED_ERRORS = ["ActionController::RoutingError",
4
+ "AbstractController::ActionNotFound",
5
+ "ActionController::MethodNotAllowed",
6
+ "ActionController::UnknownHttpMethod",
7
+ "ActionController::NotImplemented",
8
+ "ActionController::UnknownFormat",
9
+ "ActionController::InvalidAuthenticityToken",
10
+ "ActionController::InvalidCrossOriginRequest",
11
+ "ActionDispatch::Http::Parameters::ParseError",
12
+ "ActionController::BadRequest",
13
+ "ActionController::ParameterMissing",
14
+ "ActiveRecord::RecordNotFound",
15
+ "ActionController::UnknownAction",
16
+ "ActionDispatch::Http::MimeNegotiation::InvalidType",
17
+ "Rack::QueryParser::ParameterTypeError",
18
+ "Rack::QueryParser::InvalidParameterError",
19
+ "CGI::Session::CookieStore::TamperedWithCookie",
20
+ "Mongoid::Errors::DocumentNotFound",
21
+ "Sinatra::NotFound",
22
+ "Sidekiq::JobRetry::Skip"].map(&:freeze).freeze
24
23
 
25
24
  def report(error, handled:, severity:, context:, source: nil)
26
25
  return if ignore_by_class?(error.class.name)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidErrors
4
- VERSION = "0.2.17"
4
+ VERSION = "0.3.1"
5
5
  end
data/lib/solid_errors.rb CHANGED
@@ -7,4 +7,20 @@ require_relative "solid_errors/engine"
7
7
 
8
8
  module SolidErrors
9
9
  mattr_accessor :connects_to
10
+ mattr_accessor :username
11
+ mattr_accessor :password
12
+
13
+ class << self
14
+ # use method instead of attr_accessor to ensure
15
+ # this works if variable set after SolidErrors is loaded
16
+ def username
17
+ @username ||= ENV["SOLIDERRORS_USERNAME"]
18
+ end
19
+
20
+ # use method instead of attr_accessor to ensure
21
+ # this works if variable set after SolidErrors is loaded
22
+ def password
23
+ @password ||= ENV["SOLIDERRORS_PASSWORD"]
24
+ end
25
+ end
10
26
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solid_errors
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.17
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephen Margheim
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-15 00:00:00.000000000 Z
11
+ date: 2024-01-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '7.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  description:
28
42
  email:
29
43
  - stephen.margheim@gmail.com