tracer_client 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.idea/.name +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +63 -0
- data/Rakefile +1 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/lib/tracer_client.rb +5 -0
- data/lib/tracer_client/changes.rb +177 -0
- data/lib/tracer_client/client.rb +122 -0
- data/lib/tracer_client/errors/api_error.rb +2 -0
- data/lib/tracer_client/errors/api_error_alert.rb +2 -0
- data/lib/tracer_client/errors/api_error_log.rb +2 -0
- data/lib/tracer_client/errors/api_request_failure.rb +15 -0
- data/lib/tracer_client/errors/api_request_failure_alert.rb +2 -0
- data/lib/tracer_client/errors/api_request_failure_log.rb +2 -0
- data/lib/tracer_client/errors/application_error.rb +28 -0
- data/lib/tracer_client/errors/logic_error.rb +2 -0
- data/lib/tracer_client/errors/logic_error_alert.rb +2 -0
- data/lib/tracer_client/errors/logic_error_log.rb +2 -0
- data/lib/tracer_client/log.rb +153 -0
- data/lib/tracer_client/server.rb +36 -0
- data/lib/tracer_client/version.rb +3 -0
- data/tracer_client.gemspec +28 -0
- metadata +139 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 824a991b553db0aefd11d0f2d83dafffd9435b28
|
4
|
+
data.tar.gz: 64248db71c319412b85f8be5e32187c7fe40530e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2475d3fa342a85d8564869043e85df849fe4cf9f35e8ab96e5b08813293a30e11e6da473ea866eb623c93df8cdadc25d7806ce265a42a8075cc3193c6d25ed26
|
7
|
+
data.tar.gz: a718a7a74831aee0f0d6aa13fa1d5d7851f1be9cf91cf91ab09c15a45c5a64e7696c56810fdecd4c06587e3756c7c85b4c116a6ba84f87f5e18e201121fa397d
|
data/.gitignore
ADDED
data/.idea/.name
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
tracer_client
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2015 Serge Galanin
|
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,63 @@
|
|
1
|
+
# TracerClient
|
2
|
+
|
3
|
+
Library for logging errors and ActiveRecord object changes to Tracer.
|
4
|
+
|
5
|
+
Tracer is a web application for accumulating, showing and analyzing of a vital application information.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'tracer_client'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install tracer_client
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
Methods for debugging:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
Log.debug('Debug message') # log simple message
|
29
|
+
Log.debug({a: 2, b: 5}) # log hash values
|
30
|
+
Log.debug(any_object) # log any object
|
31
|
+
```
|
32
|
+
|
33
|
+
Log some info with severity level. Each method requires message text and log tags and accepts optional data.
|
34
|
+
```ruby
|
35
|
+
Log.info('Order created', 'order create', id: id)
|
36
|
+
Log.warn('Order shipping too long!', 'order shipping', id: id)
|
37
|
+
Log.error('Order was not paid', 'order payment', id: id)
|
38
|
+
```
|
39
|
+
|
40
|
+
Log catched exception
|
41
|
+
```ruby
|
42
|
+
begin
|
43
|
+
raise 'Some exception'
|
44
|
+
rescue => e
|
45
|
+
Log.exception(e, 'Unable to send request', 'request', request_data: data)
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
## Development
|
50
|
+
|
51
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
52
|
+
|
53
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
54
|
+
|
55
|
+
## Contributing
|
56
|
+
|
57
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/galanin/tracer_client.
|
58
|
+
|
59
|
+
|
60
|
+
## License
|
61
|
+
|
62
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
63
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "tracer_client"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
module Tracer
|
2
|
+
module Changes
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
|
10
|
+
def log_record_changes(options = {})
|
11
|
+
send :include, InstanceMethods
|
12
|
+
|
13
|
+
class_attribute :changes_logging_options
|
14
|
+
self.changes_logging_options = options.dup
|
15
|
+
|
16
|
+
%i(ignore skip only).each do |k|
|
17
|
+
changes_logging_options[k] =
|
18
|
+
[changes_logging_options[k]].flatten.compact.map { |attr| attr.is_a?(Hash) ? attr.stringify_keys : attr.to_s }
|
19
|
+
end
|
20
|
+
|
21
|
+
options_on = Array(options[:on]) # so that a single symbol can be passed in without wrapping it in an `Array`
|
22
|
+
|
23
|
+
after_create :log_create, :if => :log_changes? if options_on.empty? || options_on.include?(:create)
|
24
|
+
|
25
|
+
if options_on.empty? || options_on.include?(:update)
|
26
|
+
before_update :log_update, :if => :log_changes?
|
27
|
+
end
|
28
|
+
|
29
|
+
after_destroy :log_destroy, :if => :log_changes? if options_on.empty? || options_on.include?(:destroy)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
module InstanceMethods
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
|
40
|
+
def log_create
|
41
|
+
Tracer::Client.log_changes(
|
42
|
+
item_id: id,
|
43
|
+
item_type: self.class.base_class.name,
|
44
|
+
event: 'create',
|
45
|
+
changes: changes_for_tracing,
|
46
|
+
)
|
47
|
+
rescue Exception => e
|
48
|
+
Log.exception_with_alert(e, 'Ошибка регистрации создания', 'log_changes create',
|
49
|
+
item_id: id,
|
50
|
+
item_type: self.class.base_class.name)
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
def log_update
|
55
|
+
if changed_notably?
|
56
|
+
Tracer::Client.log_changes(
|
57
|
+
item_id: id,
|
58
|
+
item_type: self.class.base_class.name,
|
59
|
+
event: 'update',
|
60
|
+
object: object_attrs_for_tracing(item_before_change),
|
61
|
+
changes: changes_for_tracing,
|
62
|
+
)
|
63
|
+
end
|
64
|
+
rescue Exception => e
|
65
|
+
Log.exception_with_alert(e, 'Ошибка регистрации изменения', 'log_changes update',
|
66
|
+
item_id: id,
|
67
|
+
item_type: self.class.base_class.name)
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def log_destroy
|
72
|
+
if persisted?
|
73
|
+
Tracer::Client.log_changes(
|
74
|
+
item_id: id,
|
75
|
+
item_type: self.class.base_class.name,
|
76
|
+
event: 'destroy',
|
77
|
+
object: object_attrs_for_tracing(item_before_change),
|
78
|
+
)
|
79
|
+
end
|
80
|
+
rescue Exception => e
|
81
|
+
Log.exception_with_alert(e, 'Ошибка регистрации удаления', 'log_changes destroy',
|
82
|
+
item_id: id,
|
83
|
+
item_type: self.class.base_class.name)
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def object_attrs_for_tracing(object)
|
88
|
+
object_attrs = object.attributes.except(*changes_logging_options[:skip]).with_indifferent_access
|
89
|
+
|
90
|
+
unwrap_serialized_attributes(object_attrs) do |object_attrs, values, attr|
|
91
|
+
object_attrs[attr] = values[attr] if values.key?(attr)
|
92
|
+
end
|
93
|
+
|
94
|
+
object_attrs.as_json
|
95
|
+
end
|
96
|
+
|
97
|
+
|
98
|
+
def changes_for_tracing
|
99
|
+
changed = self.changes.delete_if do |key, value|
|
100
|
+
!notably_changed.include?(key)
|
101
|
+
end
|
102
|
+
|
103
|
+
unwrap_serialized_attributes(changed) do |changed, (a, b), attr|
|
104
|
+
if (a.key?(attr) || b.key?(attr)) && a[attr] != b[attr]
|
105
|
+
changed[attr] = [a[attr], b[attr]]
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
changed.as_json
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
# attrs должны быть либо с ключами-символами, либо HashWithIndifferentAccess
|
114
|
+
def unwrap_serialized_attributes(attrs)
|
115
|
+
stored_attrs = self.class.stored_attributes
|
116
|
+
serialized = attrs.extract!(*stored_attrs.keys)
|
117
|
+
|
118
|
+
serialized.each do |store_attr, value|
|
119
|
+
stored_attrs[store_attr.to_sym].each do |attr|
|
120
|
+
yield(attrs, value, attr)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
|
126
|
+
def item_before_change
|
127
|
+
previous = self.dup
|
128
|
+
# `dup` clears timestamps so we add them back.
|
129
|
+
all_timestamp_attributes.each do |column|
|
130
|
+
previous[column] = send(column) if self.class.column_names.include?(column.to_s) and not send(column).nil?
|
131
|
+
end
|
132
|
+
previous.tap do |prev|
|
133
|
+
prev.id = id # `dup` clears the `id` so we add that back
|
134
|
+
changed_attributes.select { |k,v| self.class.column_names.include?(k) }.each { |attr, before| prev[attr] = before }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
def changed_notably?
|
140
|
+
notably_changed.any?
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
def notably_changed
|
145
|
+
only = self.changes_logging_options[:only].dup
|
146
|
+
# remove Hash arguments and then evaluate whether the attributes (the keys of the hash) should also get pushed into the collection
|
147
|
+
only.delete_if do |obj|
|
148
|
+
obj.is_a?(Hash) && obj.each { |attr, condition| only << attr if condition.respond_to?(:call) && condition.call(self) }
|
149
|
+
end
|
150
|
+
only.empty? ? changed_and_not_ignored : (changed_and_not_ignored & only)
|
151
|
+
end
|
152
|
+
|
153
|
+
def changed_and_not_ignored
|
154
|
+
ignore = self.changes_logging_options[:ignore].dup
|
155
|
+
# remove Hash arguments and then evaluate whether the attributes (the keys of the hash) should also get pushed into the collection
|
156
|
+
ignore.delete_if do |obj|
|
157
|
+
obj.is_a?(Hash) && obj.each { |attr, condition| ignore << attr if condition.respond_to?(:call) && condition.call(self) }
|
158
|
+
end
|
159
|
+
skip = self.changes_logging_options[:skip]
|
160
|
+
changed - ignore - skip
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
|
166
|
+
def log_changes?
|
167
|
+
if_condition = self.changes_logging_options[:if]
|
168
|
+
unless_condition = self.changes_logging_options[:unless]
|
169
|
+
(if_condition.blank? || if_condition.call(self)) && !unless_condition.try(:call, self)
|
170
|
+
end
|
171
|
+
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
ActiveSupport.on_load(:active_record) do
|
176
|
+
include Tracer::Changes
|
177
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
module Tracer
|
2
|
+
module Client
|
3
|
+
|
4
|
+
class << self
|
5
|
+
|
6
|
+
def start_request(current_user, request, params, session)
|
7
|
+
Thread.current[:tracer_current_user] = current_user
|
8
|
+
Thread.current[:tracer_request] = request
|
9
|
+
Thread.current[:tracer_params] = get_params(params)
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def end_request
|
14
|
+
Thread.current[:tracer_current_user] = nil
|
15
|
+
Thread.current[:tracer_request] = nil
|
16
|
+
Thread.current[:tracer_params] = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def log(notice)
|
21
|
+
LogJob.new.async.perform(notice.merge(request_log_data))
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
def log_changes(changes)
|
26
|
+
LogChangesJob.new.async.perform(changes.merge(request_changes_data))
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
|
33
|
+
def get_params(params)
|
34
|
+
@parameter_filter ||= ActionDispatch::Http::ParameterFilter.new Rails.application.config.filter_parameters
|
35
|
+
@parameter_filter.filter(params.except(:utf8, :authenticity_token, :_method)).symbolize_keys
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def request_data
|
40
|
+
data = {context: {}}
|
41
|
+
|
42
|
+
request = Thread.current[:tracer_request]
|
43
|
+
if request
|
44
|
+
data[:context].merge!(
|
45
|
+
url: request.original_url,
|
46
|
+
referer: request.headers['Referer'],
|
47
|
+
user_agent: request.headers['User-Agent'],
|
48
|
+
ip: request.remote_ip,
|
49
|
+
method: request.request_method,
|
50
|
+
request_id: request.uuid,
|
51
|
+
headers: {
|
52
|
+
accept: request.headers['Accept'],
|
53
|
+
}
|
54
|
+
)
|
55
|
+
end
|
56
|
+
|
57
|
+
cookies = Thread.current[:cookies]
|
58
|
+
if cookies && cookies[:administrator_id]
|
59
|
+
data[:context][:cookies] = {
|
60
|
+
administrator_id: cookies.signed[:administrator_id]
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
current_user = Thread.current[:tracer_current_user]
|
65
|
+
if current_user
|
66
|
+
data[:context].merge!(
|
67
|
+
userType: current_user[:type],
|
68
|
+
userId: current_user[:id],
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
params = Thread.current[:tracer_params]
|
73
|
+
if params
|
74
|
+
data[:context].merge!(
|
75
|
+
component: params[:controller],
|
76
|
+
action: params[:action],
|
77
|
+
)
|
78
|
+
data[:params] = params.except(:controller, :action)
|
79
|
+
end
|
80
|
+
|
81
|
+
data[:context].merge!(
|
82
|
+
environment: Rails.env,
|
83
|
+
rootDirectory: Rails.root,
|
84
|
+
)
|
85
|
+
|
86
|
+
data
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
def request_log_data
|
91
|
+
request_data
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
def request_changes_data
|
96
|
+
request_data
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
|
105
|
+
class LogJob
|
106
|
+
include SuckerPunch::Job
|
107
|
+
|
108
|
+
def perform(notice)
|
109
|
+
Tracer::Server.log(notice)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
class LogChangesJob
|
114
|
+
include SuckerPunch::Job
|
115
|
+
|
116
|
+
def perform(changes)
|
117
|
+
Tracer::Server.log_changes(changes)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class ApiRequestFailure < ApiError
|
2
|
+
|
3
|
+
def initialize(subject, tags, response, data = {})
|
4
|
+
super(subject, tags, data.merge({
|
5
|
+
api_response: {
|
6
|
+
status: response.code + ' ' + response.message,
|
7
|
+
type: response.content_type,
|
8
|
+
length: response.content_length,
|
9
|
+
body: response.body.force_encoding('UTF-8'),
|
10
|
+
|
11
|
+
},
|
12
|
+
}))
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
class ApplicationError < StandardError
|
2
|
+
|
3
|
+
attr_reader :tags, :data
|
4
|
+
|
5
|
+
|
6
|
+
def initialize(subject, tags = '', data = {})
|
7
|
+
super(subject)
|
8
|
+
@tags = tags
|
9
|
+
@data = data
|
10
|
+
|
11
|
+
puts "#{'-'*10}\n#{@data}\n#{'-'*10}" if Rails.env.development?
|
12
|
+
|
13
|
+
Log.on_raise(self, caller(2))
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
# для логирования при бросании и в дефолтном обработчике
|
18
|
+
def with_log?
|
19
|
+
self.class.name.end_with?('Log', 'Alert')
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
# для логирования при бросании и в дефолтном обработчике
|
24
|
+
def with_alert?
|
25
|
+
self.class.name.end_with?('Alert')
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'config'
|
2
|
+
|
3
|
+
module Log
|
4
|
+
|
5
|
+
# error - ошибки
|
6
|
+
# crit - исключения
|
7
|
+
# emerg - неперехваченные исключения
|
8
|
+
SEVERITIES = %i(debug info notice warn error crit alert emerg)
|
9
|
+
FACILITIES = %i(request lib auth user product order line_item delivery odkl email direct_mail page exception)
|
10
|
+
|
11
|
+
ROBOT_UA_FRAGMENTS = /AhrefsBot|bingbot|DotBot|Googlebot|Mail.RU_Bot|MJ12bot|msnbot|SputnikBot|updown_tester|Web-Monitoring|WebMasterAid|YaDirectFetcher|YandexBot/
|
12
|
+
|
13
|
+
|
14
|
+
def self.start_request(current_user, request, params, session, do_log_request)
|
15
|
+
Tracer::Client.start_request(current_user, request, params, session)
|
16
|
+
|
17
|
+
info('HTTP запрос', 'http_request') if do_log_request
|
18
|
+
|
19
|
+
Thread.current[:request_tags] = %w(robot) if request.headers['User-Agent'] =~ ROBOT_UA_FRAGMENTS
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.end_request
|
23
|
+
Thread.current[:request_tags] = nil
|
24
|
+
Tracer::Client.end_request
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
# Можно вызывать в сокращённой форме:
|
29
|
+
# Log.debug('Сообщение')
|
30
|
+
# Log.debug({a: 2, b: 5})
|
31
|
+
# Log.debug(any_object)
|
32
|
+
def self.debug(subject = '', tags = '', data = {})
|
33
|
+
if Hash === subject
|
34
|
+
# передан только хэш с данными
|
35
|
+
message(:debug, subject.inspect[0...40], '', subject, caller)
|
36
|
+
elsif String === subject
|
37
|
+
# передан только subject
|
38
|
+
message(:debug, subject, tags, data, caller)
|
39
|
+
else
|
40
|
+
# переданы некие другие данные
|
41
|
+
message(:debug, subject.inspect[0...40], '', {debug: subject}, caller)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
%i(info warn error).each do |base_method|
|
47
|
+
instance_eval <<-CODE, __FILE__, __LINE__ + 1
|
48
|
+
def #{base_method}(subject, tags = '', data = {})
|
49
|
+
message(:#{base_method}, subject, tags, data, caller)
|
50
|
+
end
|
51
|
+
|
52
|
+
def #{base_method}_with_alert(subject, tags = '', data = {})
|
53
|
+
message(:#{base_method}, subject, tags, data.merge(with_alert: true), caller)
|
54
|
+
end
|
55
|
+
CODE
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def self.exception(exception, subject, tags = '', data = {})
|
60
|
+
exception_message(exception, :crit, subject, tags, data, exception.backtrace)
|
61
|
+
exception
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def self.exception_with_alert(exception, subject, tags = '', data = {})
|
66
|
+
exception(exception, subject, tags, data.merge(with_alert: true))
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def self.on_raise(exception, backtrace)
|
71
|
+
if exception.with_log?
|
72
|
+
exception_message(exception, :warn, '', 'raise', {with_alert: exception.with_alert?}, backtrace)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
|
77
|
+
def self.unhandled(exception)
|
78
|
+
if exception.with_log? && !exception.logged?
|
79
|
+
exception_message(exception, :emerg, '', 'unhandled', {with_alert: exception.with_alert?}, exception.backtrace)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
|
87
|
+
def self.message(severity, subject, tags, data, backtrace)
|
88
|
+
return unless log?(severity)
|
89
|
+
|
90
|
+
tags = (Thread.current[:request_tags] || []) + tags.split(' ')
|
91
|
+
|
92
|
+
data = {data: data} unless Hash === data
|
93
|
+
|
94
|
+
return if data.key?(:exception) && data[:exception][:class] == 'ActiveRecord::RecordNotFound' && tags == %w(robot)
|
95
|
+
|
96
|
+
error_hash = {
|
97
|
+
severity: severity,
|
98
|
+
tags: tags,
|
99
|
+
message: subject,
|
100
|
+
backtrace: get_backtrace(backtrace).first(10)
|
101
|
+
}
|
102
|
+
|
103
|
+
if data.key?(:exception)
|
104
|
+
error_hash[:exception_message] = data[:exception][:message].strip
|
105
|
+
error_hash[:type] = data[:exception][:class]
|
106
|
+
end
|
107
|
+
|
108
|
+
Tracer::Client.log(
|
109
|
+
errors: [error_hash],
|
110
|
+
data: data.except(:exception, :with_alert),
|
111
|
+
with_alert: data[:with_alert],
|
112
|
+
)
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
def self.exception_message(exception, severity, subject, tags, data, backtrace)
|
117
|
+
tags = (tags + ' ' + exception.tags).strip if exception.respond_to?(:tags)
|
118
|
+
data.merge!(exception.data.except(:with_alert)) if exception.respond_to?(:data)
|
119
|
+
data.merge!(exception: {
|
120
|
+
class: exception.class.name,
|
121
|
+
message: exception.message
|
122
|
+
})
|
123
|
+
|
124
|
+
message(severity, subject, tags, data, backtrace)
|
125
|
+
exception.mark_logged
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
def self.log?(severity)
|
130
|
+
Settings.log.severity_level.nil? || enabled_severity?(severity, Settings.log.severity_level)
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.enabled_severity?(message_severity, configured_severity)
|
134
|
+
message_index = SEVERITIES.index(message_severity)
|
135
|
+
configured_index = SEVERITIES.index(configured_severity.to_sym)
|
136
|
+
message_index.nil? || configured_index.nil? || message_index >= configured_index
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.get_backtrace(backtrace)
|
140
|
+
locations = Rails.backtrace_cleaner.clean(backtrace || [])
|
141
|
+
|
142
|
+
locations.map do |location|
|
143
|
+
if location =~ /\A(.*):(\d+):\s*(?:in `(.*)')\z/
|
144
|
+
{
|
145
|
+
file: $~[1],
|
146
|
+
line: $~[2],
|
147
|
+
function: $~[3],
|
148
|
+
}
|
149
|
+
end
|
150
|
+
end.compact
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'config'
|
2
|
+
|
3
|
+
module Tracer
|
4
|
+
module Server
|
5
|
+
|
6
|
+
def self.log(notice_hash)
|
7
|
+
send("http://#{Settings.tracer.host}:#{Settings.tracer.port}/api/v3/projects/#{Settings.tracer.project}/notices?api_key=#{Settings.tracer.api_key}", notice_hash)
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
def self.log_changes(changes_hash)
|
12
|
+
Rails.logger.debug "Log change: #{changes_hash.inspect}"
|
13
|
+
send("http://#{Settings.tracer.host}:#{Settings.tracer.port}/api/v3/projects/#{Settings.tracer.project}/changes?api_key=#{Settings.tracer.api_key}", changes_hash)
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
|
20
|
+
def self.send(url, data)
|
21
|
+
uri = URI.parse(url)
|
22
|
+
|
23
|
+
Net::HTTP.start(uri.host, uri.port, read_timeout: 20) do |http|
|
24
|
+
http.request_post(uri.path + '?' + uri.query, JSON.dump(data), 'Content-Type' => 'text/xml; charset=utf-8') do |response|
|
25
|
+
return true if Net::HTTPNoContent === response
|
26
|
+
Rails.logger.error "Tracer request url: #{url}"
|
27
|
+
Rails.logger.error "Tracer response #{response.inspect}: #{response.body.inspect}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
rescue Exception => e
|
31
|
+
Rails.logger.error "Tracer request url: #{url}"
|
32
|
+
Rails.logger.error "Tracer exception #{e.inspect}: #{e.message}"
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'tracer_client/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'tracer_client'
|
8
|
+
spec.version = TracerClient::VERSION
|
9
|
+
spec.authors = ['Serge Galanin']
|
10
|
+
spec.email = ['s.galanin@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = %q{Tracer API}
|
13
|
+
spec.description = %q{Log errors and objects changes to Tracer.}
|
14
|
+
spec.homepage = "https://github.com/galanin/tracer_client"
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
18
|
+
spec.bindir = 'exe'
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_dependency 'config', '~> 1.0'
|
23
|
+
spec.add_dependency 'rails', '~> 4.2'
|
24
|
+
spec.add_dependency 'sucker_punch', '~> 1.5'
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.10'
|
27
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tracer_client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Serge Galanin
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-28 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: config
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: sucker_punch
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.5'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.5'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.10'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.10'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '10.0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '10.0'
|
83
|
+
description: Log errors and objects changes to Tracer.
|
84
|
+
email:
|
85
|
+
- s.galanin@gmail.com
|
86
|
+
executables: []
|
87
|
+
extensions: []
|
88
|
+
extra_rdoc_files: []
|
89
|
+
files:
|
90
|
+
- ".gitignore"
|
91
|
+
- ".idea/.name"
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE.txt
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- bin/console
|
97
|
+
- bin/setup
|
98
|
+
- lib/tracer_client.rb
|
99
|
+
- lib/tracer_client/changes.rb
|
100
|
+
- lib/tracer_client/client.rb
|
101
|
+
- lib/tracer_client/errors/api_error.rb
|
102
|
+
- lib/tracer_client/errors/api_error_alert.rb
|
103
|
+
- lib/tracer_client/errors/api_error_log.rb
|
104
|
+
- lib/tracer_client/errors/api_request_failure.rb
|
105
|
+
- lib/tracer_client/errors/api_request_failure_alert.rb
|
106
|
+
- lib/tracer_client/errors/api_request_failure_log.rb
|
107
|
+
- lib/tracer_client/errors/application_error.rb
|
108
|
+
- lib/tracer_client/errors/logic_error.rb
|
109
|
+
- lib/tracer_client/errors/logic_error_alert.rb
|
110
|
+
- lib/tracer_client/errors/logic_error_log.rb
|
111
|
+
- lib/tracer_client/log.rb
|
112
|
+
- lib/tracer_client/server.rb
|
113
|
+
- lib/tracer_client/version.rb
|
114
|
+
- tracer_client.gemspec
|
115
|
+
homepage: https://github.com/galanin/tracer_client
|
116
|
+
licenses:
|
117
|
+
- MIT
|
118
|
+
metadata: {}
|
119
|
+
post_install_message:
|
120
|
+
rdoc_options: []
|
121
|
+
require_paths:
|
122
|
+
- lib
|
123
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - ">="
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0'
|
128
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
version: '0'
|
133
|
+
requirements: []
|
134
|
+
rubyforge_project:
|
135
|
+
rubygems_version: 2.4.8
|
136
|
+
signing_key:
|
137
|
+
specification_version: 4
|
138
|
+
summary: Tracer API
|
139
|
+
test_files: []
|