rails_log_parser 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: a7d5317b56ac5adc2cd38dae3ec083ab684960a1ce622821140c8b1d7ecfe33c
4
+ data.tar.gz: 12f6efea87dfc891e31eadadbb1e6a5993b1c5b8c5fee0ea648246415b55c167
5
+ SHA512:
6
+ metadata.gz: 2a995df0bc41c2e81ef0b0cbd283592a24a17f030f1b02fb868d50d1b79f0982643187e9013fd9cddb2539dc9fe7a82a1e290198a5214e1373c735c63b9d305a
7
+ data.tar.gz: f999faf6b4905bd5ee0191d0a6402e456928836bb296970407c00bf91635e575fb147fa88b064de272882871a301da467134bc64399c6868c9045b2cfb711025
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in rails_log_parser.gemspec
6
+ gemspec
7
+
8
+ gem 'rake', '~> 12.0'
9
+ gem 'rspec', '~> 3.0'
data/Gemfile.lock ADDED
@@ -0,0 +1,50 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rails_log_parser (0.0.1)
5
+ enumerize (~> 2.4)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ activesupport (6.1.4.1)
11
+ concurrent-ruby (~> 1.0, >= 1.0.2)
12
+ i18n (>= 1.6, < 2)
13
+ minitest (>= 5.1)
14
+ tzinfo (~> 2.0)
15
+ zeitwerk (~> 2.3)
16
+ concurrent-ruby (1.1.9)
17
+ diff-lcs (1.4.4)
18
+ enumerize (2.4.0)
19
+ activesupport (>= 3.2)
20
+ i18n (1.8.11)
21
+ concurrent-ruby (~> 1.0)
22
+ minitest (5.14.4)
23
+ rake (12.3.3)
24
+ rspec (3.10.0)
25
+ rspec-core (~> 3.10.0)
26
+ rspec-expectations (~> 3.10.0)
27
+ rspec-mocks (~> 3.10.0)
28
+ rspec-core (3.10.1)
29
+ rspec-support (~> 3.10.0)
30
+ rspec-expectations (3.10.1)
31
+ diff-lcs (>= 1.2.0, < 2.0)
32
+ rspec-support (~> 3.10.0)
33
+ rspec-mocks (3.10.2)
34
+ diff-lcs (>= 1.2.0, < 2.0)
35
+ rspec-support (~> 3.10.0)
36
+ rspec-support (3.10.3)
37
+ tzinfo (2.0.4)
38
+ concurrent-ruby (~> 1.0)
39
+ zeitwerk (2.5.1)
40
+
41
+ PLATFORMS
42
+ ruby
43
+
44
+ DEPENDENCIES
45
+ rails_log_parser!
46
+ rake (~> 12.0)
47
+ rspec (~> 3.0)
48
+
49
+ BUNDLED WITH
50
+ 2.1.4
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Georg Limbach
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,42 @@
1
+ # RailsLogParser
2
+
3
+ This gem helps you to quick analyse your server logs without external monitor servers. Simple applications with a low number of requests cause only a low number of errors. So you can call a cronjob to analyse your logs periodly. If the summary is not empty your server will email you the result automaticly.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'rails_log_parser'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle install
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install rails_log_parser
20
+
21
+ ## Usage
22
+
23
+ Sets your server log path:
24
+
25
+ ```ruby
26
+ RailsLogParser::Parser.log_path = Rails.root.join('log/production.log')
27
+ ```
28
+
29
+ Call the rake tasks in cronjobs:
30
+
31
+ ```
32
+ 0,20,40 * * * * rake rails_log_parser:parse[22]' # summary of the last 22 minutes
33
+ ```
34
+
35
+ ## Contributing
36
+
37
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Lichtbit/rails_log_parser.
38
+
39
+
40
+ ## License
41
+
42
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+ require 'time'
3
+ require 'securerandom'
4
+
5
+ class RailsLogParser::Action
6
+ class << self
7
+ attr_accessor :last
8
+ end
9
+
10
+ SEVERITIES = %i[debug info warn error fatal].freeze
11
+ KNOWN_EXCEPTIONS = {
12
+ 'ActiveRecord::RecordNotFound' => :fatal,
13
+ 'ActionController::RoutingError' => :fatal,
14
+ "Can't verify CSRF token authenticity." => :warn,
15
+ }.freeze
16
+
17
+ extend Enumerize
18
+ enumerize :severity, in: SEVERITIES, predicates: true
19
+ enumerize :type, in: %i[request without_request delayed_job active_job], predicates: true
20
+
21
+ def initialize(type, id)
22
+ self.type = type
23
+ @id = id
24
+ @messages = []
25
+ @stacktrace = []
26
+ self.class.last = self
27
+ end
28
+
29
+ def severity=(value)
30
+ value = value.downcase.to_sym
31
+ return unless severity.nil? || SEVERITIES.index(severity.to_sym) < SEVERITIES.index(value)
32
+
33
+ super(value)
34
+ @headline = nil
35
+ end
36
+
37
+ def known_exception?
38
+ @messages.any? do |message|
39
+ KNOWN_EXCEPTIONS.any? { |e, s| message.include?(e) && severity == s }
40
+ end
41
+ end
42
+
43
+ def headline
44
+ @headline.presence || @messages.first
45
+ end
46
+
47
+ def datetime=(value)
48
+ @datetime ||= Time.parse(value)
49
+ end
50
+
51
+ def after?(datetime)
52
+ @datetime > datetime
53
+ end
54
+
55
+ def add_message(value)
56
+ @messages.push(value)
57
+ @headline = value if @headline.nil?
58
+ end
59
+
60
+ def add_stacktrace(value)
61
+ @stacktrace.push(value)
62
+ end
63
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RailsLogParser::Line
4
+ attr_reader :parser, :line
5
+
6
+ def initialize(parser, line)
7
+ @parser = parser
8
+ @line = line.strip
9
+
10
+ parse
11
+ end
12
+
13
+ def parse
14
+ return if line.empty?
15
+
16
+ # empty log line
17
+ match = line.match(/
18
+ \A(?<severity_id>[DIWEFU]),\s # I,
19
+ \[(?<datetime>[^\]]+)\s\#(?<pid>\d+)\]\s+ # [2021-11-26T13:11:01.255168 #10158]
20
+ (?<severity_label>[A-Z]+)\s+--\s+[^:]*:\s+ # INFO -- :
21
+ \[(?<id>[a-f0-9\-]{36})\]\s* # [b42b65ab-7985-4bc2-a5b5-1fb23e6ad940]
22
+ \z/x)
23
+ return if match
24
+
25
+ # normal log line
26
+ match = line.match(/
27
+ \A(?<severity_id>[DIWEFU]),\s # I,
28
+ \[(?<datetime>[^\]]+)\s\#(?<pid>\d+)\]\s+ # [2021-11-26T13:11:01.255168 #10158]
29
+ (?<severity_label>[A-Z]+)\s+--\s+[^:]*:\s+ # INFO -- :
30
+ \[(?<id>[a-f0-9\-]{36})\]\s # [b42b65ab-7985-4bc2-a5b5-1fb23e6ad940]
31
+ (?<message>.*) # Processing by Public::Controller
32
+ \z/x)
33
+ if match
34
+ parser.request(match)
35
+ return
36
+ end
37
+
38
+ match = line.match(/
39
+ \A(?<severity_id>[DIWEFU]),\s # I,
40
+ \[(?<datetime>[^\]]+)\s\#(?<pid>\d+)\]\s+ # [2021-11-26T13:11:01.255168 #10158]
41
+ (?<severity_label>[A-Z]+)\s+--\s+[^:]*:\s+ # INFO -- :
42
+ \[ActiveJob\] # [ActiveJob]
43
+ (?:\s\[[^\]]+\])? # [ActionMailer::Parameterized::DeliveryJob]
44
+ \s\[(?<id>[^\]]+)\]\s # [09f4c08a-b92e-42e3-9046-7effcf87aa2f]
45
+ (?<message>.*) # Performing ActionMailer::Parameterized::DeliveryJob
46
+ \z/x)
47
+ if match
48
+ parser.active_job(match)
49
+ return
50
+ end
51
+
52
+ match = line.match(/
53
+ \A(?<severity_id>[DIWEFU]),\s # I,
54
+ \[(?<datetime>[^\]]+)\s\#(?<pid>\d+)\]\s+ # [2021-11-26T13:11:01.255168 #10158]
55
+ (?<severity_label>[A-Z]+)\s+--\s+[^:]*:\s # INFO -- :
56
+ (?<datetime2>[^\s]+)\s # 2021-11-26T13:51:30+0000:
57
+ \[Worker[^\]]+\]\s # [Worker(delayed_job host:production pid:10411)]
58
+ (?<message>.*) # Job GeoLocationSearch
59
+ (?<id>\(id=\d+\)\s\([^)]+\)) # (id=148781) (queue=default)
60
+ (?<message2>.*) # RUNNING
61
+ \z/x)
62
+ if match
63
+ parser.delayed_job(match)
64
+ return
65
+ end
66
+
67
+ match = line.match(/
68
+ \A(?<severity_id>[DIWEFU]),\s # I,
69
+ \[(?<datetime>[^\]]+)\s\#(?<pid>\d+)\]\s+ # [2021-11-26T13:11:01.255168 #10158]
70
+ (?<severity_label>[A-Z]+)\s+--\s+[^:]*:\s # INFO -- :
71
+ (?<datetime2>[^\s]+)\s # 2021-11-26T13:51:30+0000:
72
+ \[Worker[^\]]+\]\s # [Worker(delayed_job host:production pid:10411)]
73
+ (?:\d+\sjobs\sprocessed)|(?:Starting\sjob) # 19 jobs processed
74
+ (?:.*) # at 2.8816
75
+ \z/x)
76
+ return if match
77
+
78
+ # Warning or Message without request
79
+ match = line.match(/
80
+ \A(?<severity_id>[DIWEFU]),\s # I,
81
+ \[(?<datetime>[^\]]+)\s\#(?<pid>\d+)\]\s+ # [2021-11-26T13:11:01.255168 #10158]
82
+ (?<severity_label>[A-Z]+)\s+--\s+[^:]*:\s # INFO -- :
83
+ (?<message>.*) # Creating scope :for_tipster. Overwriting existing...
84
+ \z/x)
85
+ if match
86
+ parser.without_request(match)
87
+ return
88
+ end
89
+
90
+ match = line.match(/
91
+ \A\[(?<id>[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12})\]\s # [UUID]
92
+ (?<message>.*)
93
+ \z/x)
94
+ if match
95
+ parser.add_message(match)
96
+ return
97
+ end
98
+
99
+ if parser.last_action&.error?
100
+ parser.last_action.add_stacktrace(line)
101
+ return
102
+ end
103
+
104
+ parser.not_parseable_lines.push(line)
105
+ end
106
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RailsLogParser::Parser
4
+ class << self
5
+ attr_writer :log_path
6
+
7
+ def log_path
8
+ @log_path || ENV['LOG_PATH']
9
+ end
10
+
11
+ def from_file(path)
12
+ parser = new
13
+ File.open(path, 'r') do |handle|
14
+ while (line = handle.gets)
15
+ parser.puts(line)
16
+ end
17
+ end
18
+ parser
19
+ end
20
+ end
21
+
22
+ attr_reader :not_parseable_lines
23
+
24
+ def initialize
25
+ @actions = {}
26
+ @not_parseable_lines = []
27
+ end
28
+
29
+ def summary(last_minutes: nil)
30
+ relevant = actions
31
+ if last_minutes.present?
32
+ from = last_minutes.to_i.minutes.ago
33
+ relevant = relevant.select { |a| a.after?(from) }
34
+ end
35
+ @summary_output = []
36
+ if @not_parseable_lines.present?
37
+ @summary_output.push('Not parseable lines:')
38
+ @summary_output += @not_parseable_lines.map { |line| " #{line}" }
39
+ @summary_output.push("\n\n")
40
+ end
41
+
42
+ %i[warn error fatal].each do |severity|
43
+ selected = relevant.select { |a| a.public_send("#{severity}?") }.reject(&:known_exception?)
44
+ next if selected.blank?
45
+
46
+ @summary_output.push("#{selected.count} lines with #{severity}:")
47
+ @summary_output += selected.map(&:headline).map { |line| " #{line}" }
48
+ @summary_output.push("\n\n")
49
+ end
50
+
51
+ @summary_output.join("\n")
52
+ end
53
+
54
+ def actions
55
+ @actions.values
56
+ end
57
+
58
+ def puts(line)
59
+ RailsLogParser::Line.new(self, line.encode('UTF-8', invalid: :replace))
60
+ end
61
+
62
+ def action(type, params)
63
+ @actions[params['id']] ||= RailsLogParser::Action.new(type, params['id'])
64
+ @actions[params['id']].severity = params['severity_label']
65
+ @actions[params['id']].datetime = params['datetime']
66
+ @actions[params['id']].add_message(params['message'])
67
+ end
68
+
69
+ def request(params)
70
+ action(:request, params)
71
+ end
72
+
73
+ def without_request(params)
74
+ params = params.named_captures
75
+ params['id'] = SecureRandom.uuid
76
+ action(:without_request, params)
77
+ end
78
+
79
+ def active_job(params)
80
+ action(:active_job, params)
81
+ end
82
+
83
+ def delayed_job(params)
84
+ action(:delayed_job, params)
85
+ end
86
+
87
+ def add_message(params)
88
+ @actions[params['id']] ||= RailsLogParser::Action.new(type, params['id'])
89
+ @actions[params['id']].add_message(params['message'])
90
+ end
91
+
92
+ def last_action
93
+ RailsLogParser::Action.last
94
+ end
95
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_log_parser'
4
+ require 'rails'
5
+
6
+ class RailsLogParser::Railtie < Rails::Railtie
7
+ rake_tasks do
8
+ load 'rails_log_parser/tasks.rb'
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ namespace :rails_log_parser do
4
+ desc 'notify about found problems in production.log'
5
+ task :parse, [:from_minutes] do |_t, args|
6
+ print RailsLogParser::Parser.from_file(RailsLogParser::Parser.log_path).summary(last_minutes: args[:from_minutes])
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'enumerize'
4
+
5
+ module RailsLogParser
6
+ end
7
+
8
+ require_relative 'rails_log_parser/parser'
9
+ require_relative 'rails_log_parser/action'
10
+ require_relative 'rails_log_parser/line'
11
+
12
+ require 'rails_log_parser/railtie' if defined?(Rails::Railtie)
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = 'rails_log_parser'
5
+ spec.version = '0.0.1'
6
+ spec.authors = ['Georg Limbach']
7
+ spec.email = ['georg.limbach@lichtbit.com']
8
+
9
+ spec.summary = 'Simple and fast analysing of default rails logs'
10
+ spec.description = 'If you want to get an email with errors and fatal log lines you can use this gem.'
11
+ spec.homepage = 'https://github.com/Lichtbit/rails_log_parser'
12
+ spec.license = 'MIT'
13
+ spec.required_ruby_version = Gem::Requirement.new('>= 2.3.0')
14
+
15
+ spec.metadata['homepage_uri'] = spec.homepage
16
+ spec.metadata['source_code_uri'] = 'https://github.com/Lichtbit/rails_log_parser'
17
+
18
+ # Specify which files should be added to the gem when it is released.
19
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
20
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
21
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
22
+ end
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_dependency 'enumerize', '~> 2.4'
26
+ spec.add_development_dependency 'rspec'
27
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rails_log_parser
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Georg Limbach
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-11-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: enumerize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
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'
41
+ description: If you want to get an email with errors and fatal log lines you can use
42
+ this gem.
43
+ email:
44
+ - georg.limbach@lichtbit.com
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - ".gitignore"
50
+ - ".rspec"
51
+ - Gemfile
52
+ - Gemfile.lock
53
+ - LICENSE.txt
54
+ - README.md
55
+ - Rakefile
56
+ - lib/rails_log_parser.rb
57
+ - lib/rails_log_parser/action.rb
58
+ - lib/rails_log_parser/line.rb
59
+ - lib/rails_log_parser/parser.rb
60
+ - lib/rails_log_parser/railtie.rb
61
+ - lib/rails_log_parser/tasks.rb
62
+ - rails_log_parser.gemspec
63
+ homepage: https://github.com/Lichtbit/rails_log_parser
64
+ licenses:
65
+ - MIT
66
+ metadata:
67
+ homepage_uri: https://github.com/Lichtbit/rails_log_parser
68
+ source_code_uri: https://github.com/Lichtbit/rails_log_parser
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 2.3.0
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubygems_version: 3.1.4
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Simple and fast analysing of default rails logs
88
+ test_files: []