hot_catch 0.1.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
+ SHA1:
3
+ metadata.gz: ce4c89223725b3c189db6f67ccca14d5d3f2f92a
4
+ data.tar.gz: 6cb563238d8ee4e61b38900ada6e32909328f81a
5
+ SHA512:
6
+ metadata.gz: a821681cac3f21fd3b43f1486a523e9adbe368af0b4b2abc6b0e7c0de6460d8ac269ad8cdc63e319fd9027d400102a0f644c82ae1ff2cae5e453a9b5aa51f62e
7
+ data.tar.gz: 2172336cfc52925ce9b3d1171d0d47024051350471d3fb5c4bd187df44226d205089177dd0e38eabefe012b89dee501addf7ba3b91409faedf2f20368c9873c5
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2017 Davhot
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.
data/README.md ADDED
@@ -0,0 +1,27 @@
1
+ # HotCatch
2
+ Данный гем позволяет изменить логирование в базу данных.
3
+ В дальнейшем будет отправлять логи главному приложению.
4
+
5
+ ## Использование
6
+ Логи можно посмотреть в консоли:
7
+ ```ruby
8
+ > HotCatchLog.all
9
+ > HotCatchLog.first.log_data
10
+ ```
11
+ В данной реализации логи разделены по статусам запросов (поле `status HotCatchLog`).
12
+ Кроме того одинаковые ошибки собираются в одну, при этом увеличивается счётчик в моделе (поле `count_log HotCatchLog`).
13
+
14
+ ## Установка
15
+ 1. Добавить гем в `Gemfile`
16
+ ```ruby
17
+ gem 'hot_catch'
18
+ ```
19
+ 2. `$ bundle install`
20
+ 3. Запустить генератор установки: `$ rails generate hot_catch:install`
21
+ Для удаления: `$ rails generate hot_catch:uninstall`
22
+ 4. `$ rails db:migrate`
23
+
24
+ Это всё! Теперь все логи формируются в базе данных.
25
+
26
+ ## License
27
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,34 @@
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 = 'HotCatch'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+
18
+
19
+
20
+
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task default: :test
@@ -0,0 +1,57 @@
1
+ # rails generate testgem:install
2
+
3
+ require 'rails/generators'
4
+
5
+ module HotCatch
6
+ class InstallGenerator < Rails::Generators::Base
7
+ # Включение всех файлов из указанной папки относительно __FILE__ (то есть текущего файла)
8
+ source_root File.expand_path('../../../hot_catch', __FILE__)
9
+ # include Rails::Generators::Migration
10
+
11
+ def add_model_hot_catch
12
+ create_file "db/migrate/20170729154223_create_hot_catch_log.rb" do
13
+ <<RUBY
14
+ class CreateHotCatchLog < ActiveRecord::Migration[5.0]
15
+ def change
16
+ create_table :hot_catch_logs do |t|
17
+ t.string :log_data, null: false
18
+ t.integer :count_log, default: 1, null: false
19
+ t.string :status, null: false
20
+
21
+ t.timestamps
22
+ end
23
+ end
24
+ end
25
+ RUBY
26
+ end
27
+
28
+ create_file "app/models/application_record.rb" do
29
+ "class ApplicationRecord < ActiveRecord::Base
30
+ self.abstract_class = true
31
+ end"
32
+ end unless File.exist? "app/models/application_record.rb"
33
+
34
+
35
+ copy_file "hot_catch_log.rb", "app/models/hot_catch_log.rb"
36
+ end
37
+
38
+
39
+ def add_rack_logger
40
+ copy_file "hot_catch_logger.rb", "config/hot_catch_logger.rb"
41
+ end
42
+
43
+ def change_files
44
+ insert_into_file "config/application.rb", "\nrequire_relative 'hot_catch_logger'\n", :before => "module ExampleApp"
45
+
46
+ application "config.middleware.insert_before Rails::Rack::Logger, Rails::Rack::HotCatchLogger\n
47
+ config.middleware.delete Rails::Rack::Logger"
48
+
49
+ insert_string = "\n require 'hot_catch/custom_log_subscribers.rb'
50
+ config.logger = ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new('hot_catch_buf_file'))\n"
51
+
52
+ insert_into_file "config/environments/development.rb", insert_string, :after => "Rails.application.configure do"
53
+ insert_into_file "config/environments/production.rb", insert_string, :after => "Rails.application.configure do"
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,28 @@
1
+ # rails generate testgem:install
2
+
3
+ require 'rails/generators'
4
+
5
+ module HotCatch
6
+ class UninstallGenerator < Rails::Generators::Base
7
+ # include Rails::Generators::Migration
8
+
9
+ def remove_model_hot_catch
10
+ remove_file "db/migrate/20170729154223_create_hot_catch_log.rb"
11
+ remove_file "app/models/hot_catch_log.rb"
12
+ end
13
+
14
+ def remove_rack_logger
15
+ remove_file "config/hot_catch_logger.rb"
16
+ end
17
+
18
+ def unchange_files
19
+ gsub_file "config/application.rb", /require_relative 'hot_catch_logger'/, ""
20
+ gsub_file "config/application.rb", /config.*Logger/, ""
21
+
22
+ gsub_file "config/environments/development.rb", /.*\s.*hot_catch_buf_file'\)\)/, ""
23
+ gsub_file "config/environments/production.rb", /.*\s.*hot_catch_buf_file'\)\)/, ""
24
+ end
25
+
26
+
27
+ end
28
+ end
data/lib/hot_catch.rb ADDED
@@ -0,0 +1,3 @@
1
+ module HotCatch
2
+ require 'hot_catch/custom_log_subscribers.rb'
3
+ end
@@ -0,0 +1,67 @@
1
+ require 'active_record/log_subscriber'
2
+
3
+ class CustomActiveRecordLogSubscriber < ActiveRecord::LogSubscriber
4
+ # Убрана подсветка и не логируется таблица hot_catch_buf_file
5
+ def sql(event)
6
+ self.class.runtime += event.duration
7
+ return unless logger.debug?
8
+
9
+ payload = event.payload
10
+
11
+ return if IGNORE_PAYLOAD_NAMES.include?(payload[:name])
12
+
13
+ name = "#{payload[:name]} (#{event.duration.round(1)}ms)"
14
+ sql = payload[:sql]
15
+ binds = nil
16
+
17
+ unless (payload[:binds] || []).empty?
18
+ casted_params = type_casted_binds(payload[:binds], payload[:type_casted_binds])
19
+ binds = " " + payload[:binds].zip(casted_params).map { |attr, value|
20
+ render_bind(attr, value)
21
+ }.inspect
22
+ end
23
+
24
+ # name = colorize_payload_name(name, payload[:name])
25
+ # sql = color(sql, sql_color(sql), true)
26
+
27
+ debug " #{name} #{sql}#{binds}" unless payload[:sql].to_s =~ /hot_catch_logs|COMMIT|BEGIN/
28
+ end
29
+ end
30
+
31
+ # Отписка от уведомлений ActiveRecord::LogSubscriber
32
+ notifier = ActiveSupport::Notifications.notifier
33
+ subscribers = notifier.listeners_for("sql.active_record")
34
+ subscribers.each {|s| ActiveSupport::Notifications.unsubscribe s }
35
+
36
+ # Подписка на уведомления CustomActiveRecordLogSubscriber
37
+ CustomActiveRecordLogSubscriber.attach_to :active_record
38
+ # ====================================================================================
39
+ require 'action_controller/log_subscriber'
40
+
41
+ class CustomActionControllerLogSubscriber < ActionController::LogSubscriber
42
+ # Статус записывается в лог файл hot_catch_buf_file
43
+ def process_action(event)
44
+ payload = event.payload
45
+ additions = ActionController::Base.log_process_action(payload)
46
+
47
+ status = payload[:status]
48
+ if status.nil? && payload[:exception].present?
49
+ exception_class_name = payload[:exception].first
50
+ status = ActionDispatch::ExceptionWrapper.status_code_for_exception(exception_class_name)
51
+ end
52
+
53
+ File.open('hot_catch_buf_file', 'a'){ |file| file.puts "!!!#{status}!!!" }
54
+
55
+ info do
56
+
57
+ message = "Completed #{status} #{Rack::Utils::HTTP_STATUS_CODES[status]} in #{event.duration.round}ms"
58
+ message << " (#{additions.join(" | ")})" unless additions.blank?
59
+ message
60
+ end
61
+ end
62
+ end
63
+
64
+ subscribers = notifier.listeners_for("process_action.action_controller")
65
+ subscribers.each {|s| ActiveSupport::Notifications.unsubscribe s }
66
+
67
+ CustomActionControllerLogSubscriber.attach_to :action_controller
@@ -0,0 +1,60 @@
1
+ class HotCatchLog < ApplicationRecord
2
+
3
+ validates :log_data, :status, presence: true
4
+ validates :count_log, presence: true, numericality: {only_integer: true, greater_than: 0}
5
+
6
+ STATUSES = ["STATUS", "SUCCESS", "REDIRECTION", "CLIENT_ERROR", "SERVER_ERROR", "WARNING"]
7
+
8
+ # Проверка лога на уникальность
9
+ def self.process_data_log(str)
10
+ str, status = get_status(str)
11
+
12
+ # Если статус не найден, то возможна ошибка в контроллере
13
+ status = STATUSES[3] if !status && str =~ /ActionController/
14
+
15
+ str2 = strip_str(str)
16
+ opt = true
17
+
18
+ HotCatchLog.where(status: status).each do |cur_log|
19
+ if strip_str(cur_log.log_data) == str2
20
+ cur_log.count_log += 1
21
+ cur_log.save
22
+ opt = false
23
+ return
24
+ end
25
+ end
26
+
27
+ if opt && str.strip.present? && status.present?
28
+ HotCatchLog.create(log_data: format_log(str), status: status)
29
+ end
30
+
31
+ end
32
+
33
+ private
34
+
35
+ # Метод для сравнения логов
36
+ def self.strip_str(str)
37
+ str = str.dup
38
+ str.gsub!(/\d+(\.\d+)?ms/, "") # За сколько выполнилось действие
39
+ str.gsub!(/\d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}\s\+\d{4}/, "") # дата
40
+ str.gsub!(/#<.*:0x[\d|\w]+>/, "") # Объект (например #<StaticPagesController:0x007f37791686d8>)
41
+ format_log(str)
42
+ end
43
+
44
+ # Помимо сравнения логов, нужен для форматирования лога и записи в бд
45
+ def self.format_log(str)
46
+ str = str.split("\n").select{|x| x.present?}.join("\n") # убирает повторяющиеся переводы строки
47
+ str.strip! # лишние символы пропусков в начале и конце
48
+ str
49
+ end
50
+
51
+ # Возвращает статус и лог без статуса
52
+ def self.get_status(str)
53
+ status = str.match(/!!!(\d{3})!!!/)
54
+ status = status[1] if status
55
+ str = str.gsub(/!!!(\d{3})!!!\s/, "")
56
+ status = STATUSES[status[0].to_i - 1] if status.present?
57
+ [str, status]
58
+ end
59
+
60
+ end
@@ -0,0 +1,114 @@
1
+ require 'active_support/core_ext/time/conversions'
2
+ require 'active_support/core_ext/object/blank'
3
+ require 'active_support/log_subscriber'
4
+ require 'action_dispatch/http/request'
5
+ require 'rack/body_proxy'
6
+ # require 'rack/utils'
7
+
8
+ require_relative '../app/models/application_record.rb'
9
+ require_relative '../app/models/hot_catch_log.rb'
10
+
11
+ module Rails
12
+ module Rack
13
+ # Sets log tags, logs the request, calls the app, and flushes the logs.
14
+ #
15
+ # Log tags (+taggers+) can be an Array containing: methods that the +request+
16
+ # object responds to, objects that respond to +to_s+ or Proc objects that accept
17
+ # an instance of the +request+ object.
18
+ class HotCatchLogger < ActiveSupport::LogSubscriber
19
+ def initialize(app, taggers = nil)
20
+ @app = app
21
+ @taggers = taggers || []
22
+ end
23
+
24
+ def call(env)
25
+ request = ActionDispatch::Request.new(env)
26
+
27
+ if logger.respond_to?(:tagged)
28
+ logger.tagged(compute_tags(request)) { call_app(request, env) }
29
+ else
30
+ call_app(request, env)
31
+ end
32
+ end
33
+
34
+ protected
35
+
36
+ def call_app(request, env)
37
+ instrumenter = ActiveSupport::Notifications.instrumenter
38
+ instrumenter.start 'request.action_dispatch', request: request
39
+
40
+ # Флаг начала запроса
41
+ logger << "\nBEGIN\n"
42
+
43
+ logger.info { started_request_message(request) }
44
+ resp = @app.call(env)
45
+ resp[2] = ::Rack::BodyProxy.new(resp[2]) { finish(request) }
46
+ resp
47
+ rescue Exception
48
+ finish(request)
49
+ raise
50
+ ensure
51
+ ActiveSupport::LogSubscriber.flush_all!
52
+ end
53
+
54
+ # Started GET "/session/new" for 127.0.0.1 at 2012-09-26 14:51:42 -0700
55
+ def started_request_message(request)
56
+ 'Started %s "%s" for %s at %s' % [
57
+ request.request_method,
58
+ request.filtered_path,
59
+ request.ip,
60
+ Time.now.to_default_s ]
61
+ end
62
+
63
+ def compute_tags(request)
64
+ @taggers.collect do |tag|
65
+ case tag
66
+ when Proc
67
+ tag.call(request)
68
+ when Symbol
69
+ request.send(tag)
70
+ else
71
+ tag
72
+ end
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def finish(request)
79
+ # Если быстро обновлять страницу, то конец лога не успевает записаться.
80
+ # Для этого записываем последний лог в новый файл и с новым запросом считываем
81
+ # его и записанный конец лога из изначального лог-файла.
82
+
83
+ # Считали данные из основного лог-файла
84
+ logs = ""
85
+ File.open('hot_catch_buf_file'){ |file| logs = file.read}
86
+ File.open('hot_catch_buf_file', 'w'){ |file| file.print "" }
87
+ logs = logs.split("BEGIN").delete_if{|x| !x.present? }
88
+
89
+ # Считали данные из дополнительного лог-файла и записали начало последнего лога
90
+ add_log = ""
91
+ File.open('hot_catch_buf_file2'){ |file| add_log = file.read} if File.exist?('hot_catch_buf_file2')
92
+ File.open('hot_catch_buf_file2', 'w'){ |file| file.print logs[-1] }
93
+ logs.delete_at(-1)
94
+
95
+ # Собираем начало лога и конец
96
+ add_log = add_log + (logs[0]).to_s
97
+ HotCatchLog.process_data_log(add_log) unless add_log.strip =~ /assets/
98
+
99
+ logs.delete_at(0)
100
+ # Обрабатываем остальные логи, если есть
101
+ logs.each {|log| HotCatchLog.process_data_log(log) unless log.strip =~ /assets/}
102
+
103
+
104
+
105
+ instrumenter = ActiveSupport::Notifications.instrumenter
106
+ instrumenter.finish 'request.action_dispatch', request: request
107
+ end
108
+
109
+ def logger
110
+ Rails.logger
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,3 @@
1
+ module HotCatch
2
+ VERSION = '0.1.1'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :hot_catch do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hot_catch
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Davhot
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-08-09 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Description of Testgem.
14
+ email:
15
+ - david.home@mail.ru
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - MIT-LICENSE
21
+ - README.md
22
+ - Rakefile
23
+ - lib/generators/hot_catch/install_generator.rb
24
+ - lib/generators/hot_catch/uninstall_generator.rb
25
+ - lib/hot_catch.rb
26
+ - lib/hot_catch/custom_log_subscribers.rb
27
+ - lib/hot_catch/hot_catch_log.rb
28
+ - lib/hot_catch/hot_catch_logger.rb
29
+ - lib/hot_catch/version.rb
30
+ - lib/tasks/hot_catch_tasks.rake
31
+ homepage: https://myhomepage.com
32
+ licenses:
33
+ - MIT
34
+ metadata: {}
35
+ post_install_message:
36
+ rdoc_options: []
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ required_rubygems_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ requirements: []
50
+ rubyforge_project:
51
+ rubygems_version: 2.5.1
52
+ signing_key:
53
+ specification_version: 4
54
+ summary: Summary of Testgem.
55
+ test_files: []