duffy_log 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: df1ca47085a6b83d1df4421f75264659cfd0aaa2dea7228685a1ff1cc553a8cb
4
+ data.tar.gz: 1c99dc9adfe6f145e79f94d6028bc26d634523b4878a41eccee3de3dda39e863
5
+ SHA512:
6
+ metadata.gz: b7f8448737deff737c64222318278e694c904d91bc4fa2cd2da23005ea08c0e82784d231a5f43603dfc05e3caafc9f7f2ed92c15feee1beb3cc7c6cec1050f1b
7
+ data.tar.gz: b09cfff025a1b672b9a4e1e53c28bb4425a335096cf24efdaff929c61dede12c25625ad3cccb446bd8c5ca1c5bd80fedcb6046597710efd9a553e8ced2d3f845
@@ -0,0 +1,20 @@
1
+ Copyright 2020 Jacob Duffy
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,23 @@
1
+ # DuffyLog
2
+ Module to log rake tasks, expensive ETL steps etc.
3
+ Able to capture and report errors.
4
+
5
+ ## Usage
6
+ Adds a single model `ProcessLog` which has to be used around the code you want to track.
7
+
8
+ TODO: ADD EXAMPLES
9
+
10
+ ## Installation
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'duffy_log'
15
+ ```
16
+
17
+ And then execute:
18
+ ```bash
19
+ $ bundle
20
+ ```
21
+
22
+ ## License
23
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,27 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'DuffyLog'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ # Set RSpec to run as the default rake task.
25
+ require 'rspec/core/rake_task'
26
+ RSpec::Core::RakeTask.new(:spec)
27
+ task default: :spec
@@ -0,0 +1,62 @@
1
+ class ProcessLogsController < ApplicationController
2
+ before_action :set_process_log, only: [:show, :edit, :update, :destroy]
3
+
4
+ # GET /process_logs
5
+ def index
6
+ @process_logs = ProcessLog.all
7
+
8
+ @process_logs = @process_logs.where(key: params[:key]) if ProcessLog.pluck(:key).uniq.include?(params[:key])
9
+ @process_logs = @process_logs.where(status: params[:status]) if ProcessLog.pluck(:status).uniq.include?(params[:status])
10
+
11
+ end
12
+
13
+ # GET /process_logs/1
14
+ def show
15
+ end
16
+
17
+ # GET /process_logs/new
18
+ def new
19
+ @process_log = ProcessLog.new
20
+ end
21
+
22
+ # GET /process_logs/1/edit
23
+ def edit
24
+ end
25
+
26
+ # POST /process_logs
27
+ def create
28
+ @process_log = ProcessLog.new(process_log_params)
29
+
30
+ if @process_log.save
31
+ redirect_to @process_log, notice: 'Process log was successfully created.'
32
+ else
33
+ render :new
34
+ end
35
+ end
36
+
37
+ # PATCH/PUT /process_logs/1
38
+ def update
39
+ if @process_log.update(process_log_params)
40
+ redirect_to @process_log, notice: 'Process log was successfully updated.'
41
+ else
42
+ render :edit
43
+ end
44
+ end
45
+
46
+ # DELETE /process_logs/1
47
+ def destroy
48
+ @process_log.destroy
49
+ redirect_to process_logs_url, notice: 'Process log was successfully destroyed.'
50
+ end
51
+
52
+ private
53
+ # Use callbacks to share common setup or constraints between actions.
54
+ def set_process_log
55
+ @process_log = ProcessLog.find(params[:id])
56
+ end
57
+
58
+ # Only allow a trusted parameter "white list" through.
59
+ def process_log_params
60
+ params.require(:process_log).permit(:key, :status, :start_time, :end_time, :elapsed, :average_elapsed, :comment, :backtrace)
61
+ end
62
+ end
@@ -0,0 +1,91 @@
1
+ class ProcessLog < ApplicationRecord
2
+
3
+ validates :key, presence: true
4
+ validates :start_time, presence: true
5
+ validates :status, presence: true
6
+
7
+ before_validation { self.start_time ||= Time.now }
8
+ before_validation { self.status ||= "Started" }
9
+ before_save { self.elapsed = end_time && end_time - start_time }
10
+ before_save { self.average_elapsed = siblings.completed.sorted.limit(10).average(:elapsed) }
11
+
12
+
13
+ # Scopes
14
+ scope :completed, -> { where.not(elapsed: nil) }
15
+ scope :sorted, -> { order(start_time: :desc) } # Newest first
16
+
17
+
18
+ # Methods
19
+
20
+ def completed?
21
+ !!elapsed
22
+ end
23
+
24
+ def elapsed
25
+ super || [(Time.now - start_time).to_i, 86400].min
26
+ end
27
+
28
+ # Fail the Process Log.
29
+ # Pass in nothing for no comment or backtrace
30
+ # Pass in the Exception itself to automatically extract.
31
+ # Pass in comment and/or backtrace to set/overwrite.
32
+ def fail!(exception = nil, comment: nil, backtrace: nil)
33
+ self.status = "Fail"
34
+ self.end_time = Time.now
35
+
36
+ unless exception.blank?
37
+ self.comment = exception.to_s
38
+ self.backtrace = exception.backtrace.join("\n")
39
+ end
40
+
41
+ # Manually passed named arguments overwrite exception if that was also provided.
42
+ self.comment = comment unless comment.blank?
43
+ self.comment ||= 'Fail'
44
+ self.backtrace = Array(backtrace).join("\n") unless backtrace.blank?
45
+
46
+ save
47
+ end
48
+
49
+ def fail?
50
+ status == "Fail"
51
+ end
52
+
53
+ def local_end
54
+ end_time&.localtime&.strftime("%F %-l:%M%P")
55
+ end
56
+
57
+ def local_start
58
+ start_time&.localtime&.strftime("%F %-l:%M%P")
59
+ end
60
+
61
+ def progress
62
+ success? ? 100 : [99, (100 * elapsed / average_elapsed).to_i].min rescue "?"
63
+ end
64
+
65
+ def siblings
66
+ ProcessLog.where(key: key)
67
+ end
68
+
69
+ def started?
70
+ status == "Started"
71
+ end
72
+
73
+ def status
74
+ elapsed >= 86400 ? "Fail" : super
75
+ end
76
+
77
+ def success!
78
+ self.status = "Success" unless status == "Fail"
79
+ self.end_time = Time.now
80
+ save!
81
+ end
82
+
83
+ def success?
84
+ status == "Success"
85
+ end
86
+
87
+ def to_s
88
+ key
89
+ end
90
+
91
+ end
@@ -0,0 +1,57 @@
1
+ <%= form_with(model: process_log, local: true) do |form| %>
2
+ <% if process_log.errors.any? %>
3
+ <div id="error_explanation">
4
+ <h2><%= pluralize(process_log.errors.count, "error") %> prohibited this process_log from being saved:</h2>
5
+
6
+ <ul>
7
+ <% process_log.errors.full_messages.each do |message| %>
8
+ <li><%= message %></li>
9
+ <% end %>
10
+ </ul>
11
+ </div>
12
+ <% end %>
13
+
14
+ <div class="field">
15
+ <%= form.label :key %>
16
+ <%= form.text_field :key %>
17
+ </div>
18
+
19
+ <div class="field">
20
+ <%= form.label :status %>
21
+ <%= form.text_field :status %>
22
+ </div>
23
+
24
+ <div class="field">
25
+ <%= form.label :start_time %>
26
+ <%= form.datetime_select :start_time %>
27
+ </div>
28
+
29
+ <div class="field">
30
+ <%= form.label :end_time %>
31
+ <%= form.datetime_select :end_time %>
32
+ </div>
33
+
34
+ <div class="field">
35
+ <%= form.label :elapsed %>
36
+ <%= form.number_field :elapsed %>
37
+ </div>
38
+
39
+ <div class="field">
40
+ <%= form.label :average_elapsed %>
41
+ <%= form.number_field :average_elapsed %>
42
+ </div>
43
+
44
+ <div class="field">
45
+ <%= form.label :comment %>
46
+ <%= form.text_area :comment %>
47
+ </div>
48
+
49
+ <div class="field">
50
+ <%= form.label :backtrace %>
51
+ <%= form.text_area :backtrace %>
52
+ </div>
53
+
54
+ <div class="actions">
55
+ <%= form.submit %>
56
+ </div>
57
+ <% end %>
@@ -0,0 +1,6 @@
1
+ <h1>Editing Process Log</h1>
2
+
3
+ <%= render 'form', process_log: @process_log %>
4
+
5
+ <%= link_to 'Show', @process_log %> |
6
+ <%= link_to 'Back', process_logs_path %>
@@ -0,0 +1,30 @@
1
+ <h1><%= link_to 'Process Logs', process_logs_path %></h1>
2
+
3
+ <table>
4
+ <thead>
5
+ <tr>
6
+ <th>Key</th>
7
+ <th>Status</th>
8
+ <th>Start</th>
9
+ <th>End</th>
10
+ <th>Elapsed</th>
11
+ <th>Average</th>
12
+ <th>Error</th>
13
+ </tr>
14
+ </thead>
15
+
16
+ <tbody>
17
+ <% @process_logs.each do |process_log| %>
18
+ <tr>
19
+ <td><%= link_to process_log.key, params.permit(:key, :status).merge(key: process_log.key) %></td>
20
+ <td><%= link_to process_log.status, params.permit(:key, :status).merge(status: process_log.status) %></td>
21
+ <td><%= process_log.local_start %></td>
22
+ <td><%= process_log.local_end %></td>
23
+ <td><%= distance_of_time_in_words(process_log.elapsed) rescue nil %></td>
24
+ <td><%= distance_of_time_in_words(process_log.average_elapsed) rescue nil %></td>
25
+ <td><%= link_to_if process_log.comment, process_log.comment, process_log %></td>
26
+ </tr>
27
+ <% end %>
28
+ </tbody>
29
+ </table>
30
+
@@ -0,0 +1,5 @@
1
+ <h1>New Process Log</h1>
2
+
3
+ <%= render 'form', process_log: @process_log %>
4
+
5
+ <%= link_to 'Back', process_logs_path %>
@@ -0,0 +1,42 @@
1
+ <h1><%= @process_log %> - <%= @process_log.local_start %></h1>
2
+
3
+ <table>
4
+ <tbody>
5
+ <tr>
6
+ <th style="width:160px;">status</th>
7
+ <td class="<%= 'alert' if @process_log.fail? %>"><%= @process_log.status %></td>
8
+ </tr>
9
+ <tr>
10
+ <th>start time</th>
11
+ <td><%= @process_log.local_start %></td>
12
+ </tr>
13
+ <tr>
14
+ <th>end time</th>
15
+ <td><%= @process_log.local_end %></td>
16
+ </tr>
17
+ <tr>
18
+ <th>elapsed</th>
19
+ <td><%= distance_of_time_in_words(@process_log.elapsed) rescue nil %></td>
20
+ </tr>
21
+ <tr>
22
+ <th nowrap>average elapsed</th>
23
+ <td><%= distance_of_time_in_words(@process_log.average_elapsed) rescue nil %></td>
24
+ </tr>
25
+ <tr>
26
+ <th>key</th>
27
+ <td><%= @process_log.key %></td>
28
+ </tr>
29
+ <tr>
30
+ <th style="vertical-align: top;">comment</th>
31
+ <td style="font-size: 0.9em;"><pre style='margin: 0;'><%= sanitize h(@process_log.comment) %></pre></td>
32
+ </tr>
33
+ <tr>
34
+ <th style="vertical-align: top;">backtrace</th>
35
+ <td style="max-width: 0; overflow: hidden;
36
+ text-overflow: ellipsis;
37
+ white-space: nowrap;
38
+ font-size: 0.9em;
39
+ "><pre style='margin: 0;'><%= sanitize h(@process_log.backtrace.to_s.gsub("`", "'")).nl2br %></pre></td>
40
+ </tr>
41
+ </tbody>
42
+ </table>
@@ -0,0 +1,3 @@
1
+ Rails.application.routes.draw do
2
+ resources :process_logs
3
+ end
@@ -0,0 +1,16 @@
1
+ class CreateProcessLogs < ActiveRecord::Migration[6.0]
2
+ def change
3
+ create_table :process_logs do |t|
4
+ t.string :key
5
+ t.string :status
6
+ t.datetime :start_time
7
+ t.datetime :end_time
8
+ t.integer :elapsed
9
+ t.integer :average_elapsed
10
+ t.text :comment
11
+ t.text :backtrace
12
+
13
+ t.timestamps
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ require "duffy_log/engine"
2
+
3
+ module DuffyLog
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,30 @@
1
+ module DuffyLog
2
+ class Engine < ::Rails::Engine
3
+
4
+
5
+ # Append DFM Auth's migrations to the host application
6
+ # http://pivotallabs.com/leave-your-migrations-in-your-rails-engines/
7
+ initializer :append_migrations do |app|
8
+ unless app.root.to_s.match root.to_s
9
+ config.paths["db/migrate"].expanded.each do |expanded_path|
10
+ app.config.paths["db/migrate"] << expanded_path
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ # Wrapper for task
18
+ # * Automatically creates a ProcessLog
19
+ # * Captures any errors with stack trace
20
+ def logged_task(*args, &block)
21
+ task *args do |task|
22
+ log = ProcessLog.create(key: task.to_s)
23
+ begin
24
+ yield
25
+ log.success!
26
+ rescue StandardError => e
27
+ log.fail!(e)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ module DuffyLog
2
+ VERSION = '1.0.0'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :duffy_log do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,143 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: duffy_log
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jacob Duffy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-05-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: amazing_print
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
+ - !ruby/object:Gem::Dependency
42
+ name: factory_bot_rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec-rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: sqlite3
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description:
98
+ email:
99
+ - duffy.jp@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - MIT-LICENSE
105
+ - README.md
106
+ - Rakefile
107
+ - app/controllers/process_logs_controller.rb
108
+ - app/models/process_log.rb
109
+ - app/views/process_logs/_form.html.erb
110
+ - app/views/process_logs/edit.html.erb
111
+ - app/views/process_logs/index.html.erb
112
+ - app/views/process_logs/new.html.erb
113
+ - app/views/process_logs/show.html.erb
114
+ - config/routes.rb
115
+ - db/migrate/20200515191840_create_process_logs.rb
116
+ - lib/duffy_log.rb
117
+ - lib/duffy_log/engine.rb
118
+ - lib/duffy_log/version.rb
119
+ - lib/tasks/duffy_log_tasks.rake
120
+ homepage: https://github.com/duffyjp/duffy_log
121
+ licenses:
122
+ - MIT
123
+ metadata: {}
124
+ post_install_message:
125
+ rdoc_options: []
126
+ require_paths:
127
+ - lib
128
+ required_ruby_version: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ required_rubygems_version: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: '0'
138
+ requirements: []
139
+ rubygems_version: 3.1.2
140
+ signing_key:
141
+ specification_version: 4
142
+ summary: Log long running tasks with status.
143
+ test_files: []