hot_catch 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []