card-mod-logger 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 +7 -0
- data/README.md +64 -0
- data/lib/cardio/logger/performance/big_brother.rb +88 -0
- data/lib/cardio/logger/performance/category_log.rb +80 -0
- data/lib/cardio/logger/performance/entry.rb +129 -0
- data/lib/cardio/logger/performance/html_formatter.rb +108 -0
- data/lib/cardio/logger/performance/text_formatter.rb +37 -0
- data/lib/cardio/logger/performance.rb +285 -0
- data/lib/cardio/logger/request.rb +40 -0
- data/lib/cardio/logger.rb +11 -0
- data/set/all/logger.rb +55 -0
- data/set/right/performance_log.rb +70 -0
- metadata +70 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 793c6146f566d8d475adf699b82d26b431b86863582496d4d6aa210f783ca9fc
|
4
|
+
data.tar.gz: eafa632be5a94ac5bb412a5a4718627312ec0078b93b0196e2cea8653b398505
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ddbfd0418cd943d08251886fa7d4b1762cfdde179c6d6c32859fcc68e2d9e35d14228774e7a8938482b074e5c57f2e065f89b16ef46ecd0c9dd661b3154a39a4
|
7
|
+
data.tar.gz: e2220d262c28e4b5bab39e60e73d9d9e8113cf63b8147eed6d6c48ec5667535bb3117194b77c4fb7654e35632f5f025a2b0a59ea7c79dc8070a734c54138f274
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
<!--
|
2
|
+
# @title README - mod: logger
|
3
|
+
-->
|
4
|
+
|
5
|
+
# Logger mod (experimental)
|
6
|
+
|
7
|
+
This experimental mod has code in support of decko-friendly performance and request
|
8
|
+
logging. It cannot boast the sophistication or support of modern logging tools, but
|
9
|
+
there is enough clever code here to keep it around and hope that it some day may be
|
10
|
+
useful, if for nothing else than for building richer integration with more established
|
11
|
+
logging tools.
|
12
|
+
|
13
|
+
## Performance logger
|
14
|
+
|
15
|
+
To enable logging add a performance_logger hash to your configuration.
|
16
|
+
|
17
|
+
Example:
|
18
|
+
|
19
|
+
```
|
20
|
+
config.performance_logger = {
|
21
|
+
min_time: 100, # show only method calls that are slower than 100ms
|
22
|
+
max_depth: 3, # show nested method calls only up to depth 3
|
23
|
+
details: true, # show method arguments and sql
|
24
|
+
methods: [:event, :search, :fetch, :view], # choose methods to log
|
25
|
+
log_level: :info
|
26
|
+
}
|
27
|
+
```
|
28
|
+
|
29
|
+
If you give :methods a hash you can log arbitrary methods. The syntax is as follows:
|
30
|
+
class => method type => method name => log options
|
31
|
+
|
32
|
+
```
|
33
|
+
Example:
|
34
|
+
Card => {
|
35
|
+
instance: [:fetch, :search],
|
36
|
+
singleton: { fetch: { :title => 'Card.fetch' } },
|
37
|
+
all: {
|
38
|
+
fetch{
|
39
|
+
message: 2, # use second argument passed to fetch
|
40
|
+
details: :to_s, # use return value of to_s in method context
|
41
|
+
title: proc { |method_context| method_context.name }
|
42
|
+
},
|
43
|
+
},
|
44
|
+
}
|
45
|
+
```
|
46
|
+
|
47
|
+
`class`, `method type` and `log options` are optional.
|
48
|
+
|
49
|
+
Default values are `Card`, `:all`, and
|
50
|
+
```
|
51
|
+
{ title: method name, message: first argument, details: remaining arguments }
|
52
|
+
```
|
53
|
+
|
54
|
+
For example `[:fetch]` is equivalent to
|
55
|
+
```
|
56
|
+
Card => { all: { fetch: { message: 1, details: 1..-1 } }
|
57
|
+
```
|
58
|
+
|
59
|
+
## Request Logger
|
60
|
+
|
61
|
+
To use the request logger, set `config.paths[request_log]` to the path you
|
62
|
+
want to log to, eg 'log/request.log'.
|
63
|
+
|
64
|
+
It should then begin producing a csv output.
|
@@ -0,0 +1,88 @@
|
|
1
|
+
module Cardio
|
2
|
+
class Logger
|
3
|
+
class Performance
|
4
|
+
module BigBrother
|
5
|
+
def watch_method method_name, method_type=:all, options={}
|
6
|
+
Performance.enable_method method_name
|
7
|
+
if !SPECIAL_METHODS.include? method_name
|
8
|
+
if method_type == :all || method_type == :singleton
|
9
|
+
add_singleton_logging method_name, options
|
10
|
+
end
|
11
|
+
if method_type == :all || method_type == :instance
|
12
|
+
add_instance_logging method_name, options
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def watch_instance_method *names
|
18
|
+
names.each do |name|
|
19
|
+
watch_method name, :instance
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def watch_singleton_method *names
|
24
|
+
names.each do |name|
|
25
|
+
watch_method name, :singleton
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def watch_all_instance_methods
|
30
|
+
watch_instance_method *instance_methods
|
31
|
+
end
|
32
|
+
|
33
|
+
def watch_all_singleton_methods
|
34
|
+
fragile_methods = [:default_scope, :default_scopes, :default_scopes=] # if I touch these methods ActiveRecord breaks
|
35
|
+
watch_singleton_method *(singleton_methods - fragile_methods)
|
36
|
+
end
|
37
|
+
|
38
|
+
def watch_all_methods
|
39
|
+
watch_all_instance_methods
|
40
|
+
watch_all_singleton_methods
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def add_singleton_logging method_name, options
|
46
|
+
return unless singleton_class.method_defined? method_name
|
47
|
+
m = method(method_name)
|
48
|
+
add_logging method_name, :define_singleton_method, options do |bind_object, args, &block|
|
49
|
+
m.call(*args, &block)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_instance_logging method_name, options
|
54
|
+
return unless method_defined? method_name
|
55
|
+
m = instance_method(method_name)
|
56
|
+
add_logging method_name, :define_method, options do |bind_object, args, &block|
|
57
|
+
m.bind(bind_object).(*args, &block)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def add_logging method_name, define_method, options, &bind_block
|
62
|
+
send(define_method, method_name) do |*args, &block|
|
63
|
+
#puts "#{method name } logged"
|
64
|
+
Rails.logger.error "#{method_name} logged"
|
65
|
+
log_args = {}
|
66
|
+
options.each do |key,value|
|
67
|
+
log_args[key] = case value
|
68
|
+
when Integer then args[value-1]
|
69
|
+
when Range then args[value]
|
70
|
+
when Symbol then eval(value.to_s)
|
71
|
+
when Proc then value.call(self)
|
72
|
+
else value
|
73
|
+
end
|
74
|
+
end
|
75
|
+
Performance.with_timer(method_name, log_args) do
|
76
|
+
bind_block.call(self, args, &block)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def log_options_variable_name method_name, define_method
|
82
|
+
"@_#{self.class.name}_#{method_name.hash.to_s.sub(/^-/,'_')}_#{define_method}_logging_options".to_sym
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
@@ -0,0 +1,80 @@
|
|
1
|
+
module Cardio
|
2
|
+
class Logger
|
3
|
+
class Performance
|
4
|
+
class CategoryLog
|
5
|
+
HIERARCHY = {
|
6
|
+
'SQL' => 0,
|
7
|
+
'rule' => 1,
|
8
|
+
'fetch' => 2,
|
9
|
+
'content' => 3,
|
10
|
+
'format' => 4
|
11
|
+
}
|
12
|
+
|
13
|
+
def initialize category=nil
|
14
|
+
@time_per_category = Hash.new { |h, key| h[key] = 0 }
|
15
|
+
@start_time = {}
|
16
|
+
@stack = []
|
17
|
+
if category
|
18
|
+
start category
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def start category
|
23
|
+
if active_category
|
24
|
+
if hierarchy(category) < hierarchy(active_category)
|
25
|
+
pause active_category
|
26
|
+
else
|
27
|
+
return
|
28
|
+
end
|
29
|
+
end
|
30
|
+
@start_time[category] = Time.now
|
31
|
+
@stack << category
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
def stop category
|
36
|
+
if active_category == category
|
37
|
+
save_duration category
|
38
|
+
@stack.pop
|
39
|
+
if active_category
|
40
|
+
continue active_category
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def duration category
|
46
|
+
@time_per_category[category]
|
47
|
+
end
|
48
|
+
|
49
|
+
def each_pair
|
50
|
+
cats = (['SQL', 'rule', 'fetch', 'content', 'format'] & @time_per_category.keys) | @time_per_category.keys
|
51
|
+
cats.each do |key|
|
52
|
+
yield(key, @time_per_category[key])
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def active_category
|
59
|
+
@stack.last
|
60
|
+
end
|
61
|
+
|
62
|
+
def pause category
|
63
|
+
save_duration category
|
64
|
+
end
|
65
|
+
|
66
|
+
def continue category
|
67
|
+
@start_time[category] = Time.now
|
68
|
+
end
|
69
|
+
|
70
|
+
def hierarchy category
|
71
|
+
HIERARCHY[category] || -1
|
72
|
+
end
|
73
|
+
|
74
|
+
def save_duration category
|
75
|
+
@time_per_category[category] += (Time.now - @start_time[category]) * 1000
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Cardio
|
2
|
+
class Logger
|
3
|
+
class Performance
|
4
|
+
class Entry
|
5
|
+
attr_accessor :level, :valid, :context, :parent, :children_cnt, :duration, :children
|
6
|
+
attr_reader :message, :category, :category_duration, :details
|
7
|
+
|
8
|
+
def initialize( parent, level, args )
|
9
|
+
@start = Time.new
|
10
|
+
start_category_timer
|
11
|
+
@message = "#{ args[:title] || args[:method] || '' }"
|
12
|
+
@message += ": #{ args[:message] }" if args[:message]
|
13
|
+
@details = args[:details]
|
14
|
+
@context = args[:context]
|
15
|
+
@category = args[:category]
|
16
|
+
|
17
|
+
@level = level
|
18
|
+
@duration = nil
|
19
|
+
@valid = true
|
20
|
+
@parent = parent
|
21
|
+
@children_cnt = 0
|
22
|
+
@children = []
|
23
|
+
if @parent
|
24
|
+
@parent.add_children self
|
25
|
+
#@sibling_nr = @parent.children_cnt
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_children child=false
|
30
|
+
@children_cnt += 1
|
31
|
+
@children << child if child
|
32
|
+
end
|
33
|
+
|
34
|
+
def delete_children child=false
|
35
|
+
@children_cnt -= 1
|
36
|
+
@children.delete child if child
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
def has_younger_siblings?
|
41
|
+
@parent && @parent.children_cnt > 0 #@sibling_nr
|
42
|
+
end
|
43
|
+
|
44
|
+
def start_category_timer
|
45
|
+
@category_duration = 0
|
46
|
+
@category_start = Time.now
|
47
|
+
end
|
48
|
+
|
49
|
+
def pause_category_timer
|
50
|
+
save_category_duration
|
51
|
+
end
|
52
|
+
|
53
|
+
def save_category_duration
|
54
|
+
if @category
|
55
|
+
@category_duration += (Time.now - @category_start) * 1000
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def continue_category_timer
|
60
|
+
if @category
|
61
|
+
@category_start = Time.now
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def save_duration
|
66
|
+
save_category_duration
|
67
|
+
@duration = (Time.now - @start) * 1000
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete
|
71
|
+
@valid = false
|
72
|
+
@parent.delete_children(self) if @parent
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
# deletes the children counts in order to print the tree;
|
77
|
+
# must be called in the right order
|
78
|
+
#
|
79
|
+
# More robuts but more expensive approach: use @sibling_nr instead of counting @children_cnt down,
|
80
|
+
# but @sibling_nr has to be updated for all siblings of an entry if the entry gets deleted due to
|
81
|
+
# min_time or max_depth restrictions in the config, so we have to save all children relations for that
|
82
|
+
def to_s!
|
83
|
+
@to_s ||= begin
|
84
|
+
msg = indent
|
85
|
+
msg += "(%d.2ms) " % @duration if @duration
|
86
|
+
msg += @message if @message
|
87
|
+
|
88
|
+
if @details
|
89
|
+
msg += ", " + @details.to_s.gsub( "\n", "\n#{ indent(false) }#{' '* TAB_SIZE}" )
|
90
|
+
end
|
91
|
+
@parent.delete_children if @parent
|
92
|
+
msg
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
def indent link=true
|
99
|
+
@indent ||= begin
|
100
|
+
if @level == 0
|
101
|
+
"\n"
|
102
|
+
else
|
103
|
+
res = ' '
|
104
|
+
res += (1..level-1).inject('') do |msg, index|
|
105
|
+
if younger_siblings[index]
|
106
|
+
msg << '|' + ' ' * (TAB_SIZE-1)
|
107
|
+
else
|
108
|
+
msg << ' ' * TAB_SIZE
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
res += link ? '|--' : ' '
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def younger_siblings
|
118
|
+
res = []
|
119
|
+
next_parent = self
|
120
|
+
while (next_parent)
|
121
|
+
res << next_parent.has_younger_siblings?
|
122
|
+
next_parent = next_parent.parent
|
123
|
+
end
|
124
|
+
res.reverse
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
module Cardio
|
2
|
+
class Logger
|
3
|
+
class Performance
|
4
|
+
class HtmlFormatter
|
5
|
+
def initialize performance_logger
|
6
|
+
@log = performance_logger.log
|
7
|
+
@category_log = performance_logger.category_log
|
8
|
+
end
|
9
|
+
|
10
|
+
def output
|
11
|
+
@output ||=
|
12
|
+
begin
|
13
|
+
list =
|
14
|
+
@log.inject([]) do |tree, entry|
|
15
|
+
if !entry.parent && entry.message != 'fetch: performance_log'
|
16
|
+
tree << entry
|
17
|
+
end
|
18
|
+
tree
|
19
|
+
end
|
20
|
+
list_to_accordion list
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def list_to_accordion list
|
27
|
+
list.map do |entry|
|
28
|
+
if entry.children && entry.children.present?
|
29
|
+
accordion_entry entry
|
30
|
+
else
|
31
|
+
simple_entry entry
|
32
|
+
end
|
33
|
+
end.join "\n"
|
34
|
+
end
|
35
|
+
|
36
|
+
|
37
|
+
def duration_badge duration
|
38
|
+
if duration
|
39
|
+
<<-HTML
|
40
|
+
<span class='badge #{"badge-danger" if duration > 100} open-slow-items'> #{"%d.2ms" % duration}
|
41
|
+
</span>
|
42
|
+
HTML
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def panel_heading entry, collapse_id
|
48
|
+
<<-HTML
|
49
|
+
<h4 class="panel-title">
|
50
|
+
<a data-toggle="collapse" data-parent="#accordion-#{collapse_id}" href="##{collapse_id}" aria-expanded="true" aria-controls="#{collapse_id}" class="show-fast-items">
|
51
|
+
<span title='#{entry.details}'>
|
52
|
+
#{entry.message}
|
53
|
+
</span>
|
54
|
+
</a>
|
55
|
+
#{ duration_badge entry.duration}
|
56
|
+
</h4>
|
57
|
+
#{ extra_info entry }
|
58
|
+
HTML
|
59
|
+
end
|
60
|
+
|
61
|
+
def extra_info entry
|
62
|
+
if entry == @log.first
|
63
|
+
cat_sum = ''
|
64
|
+
@category_log.each_pair do |category, time|
|
65
|
+
cat_sum << "<strong>%s:</strong> %d.2ms " % [category, time]
|
66
|
+
end
|
67
|
+
<<-HTML
|
68
|
+
#{cat_sum}
|
69
|
+
<span class="float-right">
|
70
|
+
<a class="toggle-fast-items">hide < 100ms</a>
|
71
|
+
</span>
|
72
|
+
HTML
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def simple_entry entry
|
77
|
+
<<-HTML
|
78
|
+
<li class='list-group-item #{ entry.duration > 100 ? 'panel-danger' : 'duration-ok'}'>
|
79
|
+
<span title='#{entry.details}'>
|
80
|
+
#{ entry.message }
|
81
|
+
</span>
|
82
|
+
#{ duration_badge entry.duration}
|
83
|
+
</li>
|
84
|
+
HTML
|
85
|
+
end
|
86
|
+
|
87
|
+
def accordion_entry entry
|
88
|
+
panel_body = list_to_accordion entry.children
|
89
|
+
collapse_id = entry.hash.to_s
|
90
|
+
%{
|
91
|
+
<div class="panel-group" id="accordion-#{collapse_id}" role="tablist" aria-multiselectable="true">
|
92
|
+
<div class="panel panel-default #{ entry.duration > 100 ? 'panel-danger' : 'duration-ok'}">
|
93
|
+
<div class="panel-heading" role="tab" id="heading-#{collapse_id}">
|
94
|
+
#{ panel_heading entry, collapse_id }
|
95
|
+
</div>
|
96
|
+
<div id="#{collapse_id}" class="panel-collapse collapse" role="tabpanel" aria-labelledby="heading-#{collapse_id}">
|
97
|
+
<div class="panel-body">
|
98
|
+
#{ panel_body }
|
99
|
+
</div>
|
100
|
+
</div>
|
101
|
+
</div>
|
102
|
+
</div>
|
103
|
+
}
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Cardio
|
2
|
+
class Logger
|
3
|
+
class Performance
|
4
|
+
class TextFormatter
|
5
|
+
def initialize performance_logger
|
6
|
+
@log = performance_logger.log
|
7
|
+
@category_log = performance_logger.category_log
|
8
|
+
end
|
9
|
+
|
10
|
+
def output
|
11
|
+
@output ||= "#{details}\n#{category_summary}\n"
|
12
|
+
end
|
13
|
+
|
14
|
+
def details
|
15
|
+
@details ||=
|
16
|
+
@log.select { |entry| entry.valid }.map do |entry|
|
17
|
+
entry.to_s!
|
18
|
+
end.join "\n"
|
19
|
+
end
|
20
|
+
|
21
|
+
def category_summary
|
22
|
+
@category_summary ||=
|
23
|
+
begin
|
24
|
+
total = 0
|
25
|
+
output = ''
|
26
|
+
@category_log.each_pair do |category, time|
|
27
|
+
total += time
|
28
|
+
output << "%s: %d.2ms\n" % [category, time]
|
29
|
+
end
|
30
|
+
output << "total: %d.2ms" % total
|
31
|
+
output
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,285 @@
|
|
1
|
+
module Cardio
|
2
|
+
class Logger
|
3
|
+
class Performance
|
4
|
+
DEFAULT_CLASS = Card
|
5
|
+
DEFAULT_METHOD_TYPE = :all
|
6
|
+
DEFAULT_LOG_LEVEL = :info
|
7
|
+
DEFAULT_METHOD_OPTIONS = {
|
8
|
+
:title => :method_name,
|
9
|
+
:message => 1,
|
10
|
+
:details => 1..-1,
|
11
|
+
:context => nil
|
12
|
+
}
|
13
|
+
|
14
|
+
SPECIAL_METHODS = [:search, :view, :event, :rule, :execute] # these methods have already a Decko.with_logging block
|
15
|
+
# we don't have to monkey patch them, only turn the logging on with adding the symbol to the methods hash
|
16
|
+
|
17
|
+
TAB_SIZE = 3
|
18
|
+
cattr_reader :log, :category_log
|
19
|
+
|
20
|
+
@@log = []
|
21
|
+
@@category_log = CategoryLog.new
|
22
|
+
@@context_entries = []
|
23
|
+
@@active_entries = []
|
24
|
+
@@current_level = 0
|
25
|
+
|
26
|
+
class << self
|
27
|
+
def default_methods_config
|
28
|
+
[:execute, :rule, :fetch, :view]
|
29
|
+
# {
|
30
|
+
# # ActiveRecord => {
|
31
|
+
# # :instance => {
|
32
|
+
# # :save => { :category => 'SQL'},
|
33
|
+
# # :take => { :category => 'SQL'},
|
34
|
+
# # :_create_record => { :category => 'SQL'},
|
35
|
+
# # :_update_record => { :category => 'SQL'}
|
36
|
+
# # },
|
37
|
+
# # :singleton=> {
|
38
|
+
# # :delete => { :category => 'SQL'},
|
39
|
+
# # :find => { :category => 'SQL'},
|
40
|
+
# # :update => { :category => 'SQL'},
|
41
|
+
# # :create => { :category => 'SQL'},
|
42
|
+
# # :where => { :category => 'SQL'}
|
43
|
+
# # }
|
44
|
+
# # },
|
45
|
+
# Card => {
|
46
|
+
# :instance => {
|
47
|
+
# :rule_card => { :category => 'rule' }
|
48
|
+
# # :'update!' => { :category => 'SQL'},
|
49
|
+
# # :update => { :category => 'SQL'},
|
50
|
+
# # :save => { :category => 'SQL'},
|
51
|
+
# # :'save!' => { :category => 'SQL'},
|
52
|
+
# # :delete => { :category => 'SQL'},
|
53
|
+
# # :'delete!' => { :category => 'SQL'}
|
54
|
+
# },
|
55
|
+
# :singleton => {
|
56
|
+
# :fetch => { :category => 'fetch' },
|
57
|
+
# :view => { :category => 'content'},
|
58
|
+
# # :delete => { :category => 'SQL'},
|
59
|
+
# # :find => { :category => 'SQL'},
|
60
|
+
# # :update => { :category => 'SQL'},
|
61
|
+
# # :create => { :category => 'SQL'},
|
62
|
+
# :where => { :category => 'SQL'},
|
63
|
+
# :execute => { :category => 'SQL'}
|
64
|
+
# # :take => { :category => 'Take'}
|
65
|
+
# }
|
66
|
+
# },
|
67
|
+
# # Card::Query => {
|
68
|
+
# # :instance => {
|
69
|
+
# # :run_sql => { :category => 'SQL'},
|
70
|
+
# #
|
71
|
+
# # }
|
72
|
+
# # }
|
73
|
+
# }
|
74
|
+
end
|
75
|
+
|
76
|
+
def load_config args
|
77
|
+
args = params_to_config args
|
78
|
+
@details = args[:details] || false
|
79
|
+
@max_depth = args[:max_depth] || false
|
80
|
+
@min_time = args[:min_time] || false
|
81
|
+
@log_level = args[:log_level] || DEFAULT_LOG_LEVEL
|
82
|
+
@output = args[:output] || :text
|
83
|
+
@output_card = args[:output_card] || '*all'
|
84
|
+
@enabled_methods = ::Set.new
|
85
|
+
if args[:methods] == :default
|
86
|
+
args[:methods] = default_methods_config
|
87
|
+
end
|
88
|
+
prepare_methods_for_logging args[:methods] if args[:methods]
|
89
|
+
end
|
90
|
+
|
91
|
+
def start args = {}
|
92
|
+
@@current_level = 0
|
93
|
+
@@log = []
|
94
|
+
@@context_entries = []
|
95
|
+
@@active_entries = []
|
96
|
+
@@first_entry = new_entry(args)
|
97
|
+
@@category_log = CategoryLog.new args[:category]
|
98
|
+
end
|
99
|
+
|
100
|
+
def stop
|
101
|
+
finish_all_context_entries
|
102
|
+
if @@first_entry
|
103
|
+
@@first_entry.save_duration
|
104
|
+
finish_entry @@first_entry
|
105
|
+
end
|
106
|
+
print_log
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
def with_timer method, args, &block
|
111
|
+
if args[:context]
|
112
|
+
update_context args[:context]
|
113
|
+
end
|
114
|
+
|
115
|
+
timer = new_entry args.merge(:method => method)
|
116
|
+
begin
|
117
|
+
result = block.call
|
118
|
+
ensure
|
119
|
+
timer.save_duration
|
120
|
+
finish_entry timer
|
121
|
+
finish_context
|
122
|
+
end
|
123
|
+
result
|
124
|
+
end
|
125
|
+
|
126
|
+
def enable_method method_name
|
127
|
+
@enabled_methods ||= ::Set.new
|
128
|
+
@enabled_methods << method_name
|
129
|
+
end
|
130
|
+
|
131
|
+
def enabled_method? method_name
|
132
|
+
@enabled_methods && @enabled_methods.include?(method_name)
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def update_context new_context
|
138
|
+
# if the current context was created by an entry on the same level
|
139
|
+
# then finish it if it's a different context
|
140
|
+
if (current_context = @@context_entries.last) &&
|
141
|
+
current_context.level + 1 == @@current_level && # the
|
142
|
+
new_context != current_context.context
|
143
|
+
finish_entry @@context_entries.pop
|
144
|
+
end
|
145
|
+
|
146
|
+
# start new context if it's different from the current context
|
147
|
+
if @@context_entries.empty? || new_context != @@context_entries.last.context
|
148
|
+
@@context_entries << new_entry(:title => 'process', :message => new_context, :context => new_context)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def finish_context
|
153
|
+
# finish all deeper nested contexts
|
154
|
+
while @@context_entries.last && @@context_entries.last.level >= @@current_level
|
155
|
+
finish_entry @@context_entries.pop
|
156
|
+
end
|
157
|
+
# we don't know whether the next entry will belong to the same context or will start a new one
|
158
|
+
# so we save the time
|
159
|
+
@@context_entries.last.save_duration if @@context_entries.last
|
160
|
+
end
|
161
|
+
|
162
|
+
def print_log
|
163
|
+
if @output == :card && @output_card
|
164
|
+
html_log = HtmlFormatter.new(self).output
|
165
|
+
card = @output_card.fetch :performance_log, new: { type: :pointer }
|
166
|
+
card.add_log_entry @@log.first.message, html_log
|
167
|
+
elsif @output == :html
|
168
|
+
HtmlFormatter.new(self).output
|
169
|
+
else
|
170
|
+
text_log = TextFormatter.new(self).output
|
171
|
+
Rails.logger.send @log_level, text_log
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
|
176
|
+
def new_entry args
|
177
|
+
args.delete(:details) unless @details
|
178
|
+
level = @@current_level
|
179
|
+
|
180
|
+
last_entry = @@active_entries.last
|
181
|
+
parent =
|
182
|
+
if last_entry
|
183
|
+
last_entry.level == level ? last_entry.parent : last_entry
|
184
|
+
end
|
185
|
+
|
186
|
+
if args[:category]
|
187
|
+
@@category_log.start args[:category]
|
188
|
+
end
|
189
|
+
|
190
|
+
@@log << Entry.new(parent, level, args)
|
191
|
+
@@current_level += 1
|
192
|
+
@@active_entries << @@log.last
|
193
|
+
|
194
|
+
@@log.last
|
195
|
+
end
|
196
|
+
|
197
|
+
def finish_entry entry
|
198
|
+
if entry.category
|
199
|
+
@@category_log.stop entry.category
|
200
|
+
end
|
201
|
+
if (@max_depth && entry.level > @max_depth) || (@min_time && entry.duration < @min_time)
|
202
|
+
entry.delete
|
203
|
+
end
|
204
|
+
@@active_entries.pop
|
205
|
+
@@current_level -= 1
|
206
|
+
end
|
207
|
+
|
208
|
+
def finish_all_context_entries
|
209
|
+
while (entry = @@context_entries.pop) do
|
210
|
+
entry.save_duration unless entry.duration
|
211
|
+
finish_entry entry
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def prepare_methods_for_logging args
|
216
|
+
classes = hashify_and_verify_keys(args, DEFAULT_CLASS) do |key|
|
217
|
+
key.kind_of?(Class) || key.kind_of?(Module)
|
218
|
+
end
|
219
|
+
|
220
|
+
classes.each do |klass, method_types|
|
221
|
+
klass.extend BigBrother # add watch methods
|
222
|
+
|
223
|
+
method_types = hashify_and_verify_keys(method_types, DEFAULT_METHOD_TYPE) do |key|
|
224
|
+
[:all, :instance, :singleton].include? key
|
225
|
+
end
|
226
|
+
|
227
|
+
method_types.each do |method_type, methods|
|
228
|
+
methods = hashify_and_verify_keys methods
|
229
|
+
methods.each do |method_name, options|
|
230
|
+
klass.watch_method method_name, method_type, DEFAULT_METHOD_OPTIONS.merge(options)
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def hashify_and_verify_keys args, default_key = nil
|
238
|
+
if default_key
|
239
|
+
case args
|
240
|
+
when Symbol
|
241
|
+
{ default_key => [args] }
|
242
|
+
when Array
|
243
|
+
{ default_key => args }
|
244
|
+
when Hash
|
245
|
+
if block_given?
|
246
|
+
args.keys.select {|key| !(yield(key))}.each do |key|
|
247
|
+
args[default_key] = { key => args[key] }
|
248
|
+
args.delete key
|
249
|
+
end
|
250
|
+
end
|
251
|
+
args
|
252
|
+
end
|
253
|
+
else
|
254
|
+
case args
|
255
|
+
when Symbol
|
256
|
+
{ args => {} }
|
257
|
+
when Array
|
258
|
+
args.inject({}) do |h, key|
|
259
|
+
h[key] = {}
|
260
|
+
h
|
261
|
+
end
|
262
|
+
else
|
263
|
+
args
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
def params_to_config args
|
269
|
+
args[:details] = args[:details] == 'true' ? true : false
|
270
|
+
args[:max_depth] &&= args[:max_depth].to_i
|
271
|
+
args[:min_time] &&= args[:min_time].to_i
|
272
|
+
args[:output] &&= args[:output].to_sym
|
273
|
+
if args[:methods]
|
274
|
+
if args[:methods].kind_of?(String) && args[:methods].match(/^\[.+\]$/)
|
275
|
+
args[:methods] = JSON.parse(args[:methods]).map(&:to_sym)
|
276
|
+
elsif args[:methods].kind_of?(Array)
|
277
|
+
args[:methods].map!(&:to_sym)
|
278
|
+
end
|
279
|
+
end
|
280
|
+
args
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'csv'
|
2
|
+
|
3
|
+
module Cardio
|
4
|
+
class Logger
|
5
|
+
class Request
|
6
|
+
class << self
|
7
|
+
def path
|
8
|
+
path = Card.paths['request_log']&.first || File.dirname(Card.paths['log'].first)
|
9
|
+
filename = "#{Date.today}_#{Rails.env}.csv"
|
10
|
+
File.join path, filename
|
11
|
+
end
|
12
|
+
|
13
|
+
def write_log_entry controller
|
14
|
+
env = controller.env
|
15
|
+
return if env["REQUEST_URI"] =~ %r{^/files?/}
|
16
|
+
|
17
|
+
controller.instance_eval do
|
18
|
+
log = []
|
19
|
+
log << (Card::Env.ajax? ? "YES" : "NO")
|
20
|
+
log << env["REMOTE_ADDR"]
|
21
|
+
log << Card::Auth.current_id
|
22
|
+
log << card.name
|
23
|
+
log << action_name
|
24
|
+
log << params['view'] || (s = params['success'] and s['view'])
|
25
|
+
log << env["REQUEST_METHOD"]
|
26
|
+
log << status
|
27
|
+
log << env["REQUEST_URI"]
|
28
|
+
log << DateTime.now.to_s
|
29
|
+
log << env['HTTP_ACCEPT_LANGUAGE'].to_s.scan(/^[a-z]{2}/).first
|
30
|
+
log << env["HTTP_REFERER"]
|
31
|
+
|
32
|
+
File.open(Request.path, "a") do |f|
|
33
|
+
f.write CSV.generate_line(log)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/set/all/logger.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
event :start_performance_logger_on_change, before: :act,
|
2
|
+
when: :performance_log? do
|
3
|
+
start_performance_logger
|
4
|
+
@handle_logger = true
|
5
|
+
end
|
6
|
+
|
7
|
+
event :stop_performance_logger_on_change, after: :act,
|
8
|
+
when: :performance_log? do
|
9
|
+
stop_performance_logger
|
10
|
+
@handle_logger = false
|
11
|
+
end
|
12
|
+
|
13
|
+
event :start_performance_logger_on_read, before: :show_page, on: :read,
|
14
|
+
when: :performance_log? do
|
15
|
+
start_performance_logger unless @handle_logger
|
16
|
+
end
|
17
|
+
|
18
|
+
event :stop_performance_logger_on_read, after: :show_page, on: :read,
|
19
|
+
when: :performance_log? do
|
20
|
+
stop_performance_logger unless @handle_logger
|
21
|
+
end
|
22
|
+
|
23
|
+
event :request_logger, after: :show_page, when: :request_logger? do
|
24
|
+
Cardio::Logger::Request.write_log_entry Env[:controller]
|
25
|
+
end
|
26
|
+
|
27
|
+
def request_logger?
|
28
|
+
Card.config.request_logger
|
29
|
+
end
|
30
|
+
|
31
|
+
def start_performance_logger
|
32
|
+
if Env.params[:performance_log]
|
33
|
+
Cardio::Logger::Performance.load_config Env.params[:performance_log]
|
34
|
+
end
|
35
|
+
if (request = Env[:controller]&.request)
|
36
|
+
method = request.env["REQUEST_METHOD"]
|
37
|
+
path = request.env["PATH_INFO"]
|
38
|
+
else
|
39
|
+
method = "no request"
|
40
|
+
path = "no path"
|
41
|
+
end
|
42
|
+
Cardio::Logger::Performance.start method: method, message: path, category: "format"
|
43
|
+
end
|
44
|
+
|
45
|
+
def stop_performance_logger
|
46
|
+
Cardio::Logger::Performance.stop
|
47
|
+
return unless Env.params[:perfomance_log]
|
48
|
+
Cardio::Logger::Performance.load_config(Card.config.performance_logger || {})
|
49
|
+
end
|
50
|
+
|
51
|
+
def performance_log?
|
52
|
+
Card::Env.params[:performance_log] || Card.config.performance_logger
|
53
|
+
end
|
54
|
+
|
55
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
def log_dir
|
2
|
+
dir = File.join File.dirname(Decko.paths['log'].existent.first), 'performance'
|
3
|
+
Dir.mkdir dir unless Dir.exists? dir
|
4
|
+
dir
|
5
|
+
end
|
6
|
+
|
7
|
+
def log_path item
|
8
|
+
File.join log_dir, "#{item.gsub('/','_').gsub(/[^0-9A-Za-z.\-]/, '_')}.log"
|
9
|
+
end
|
10
|
+
|
11
|
+
def csv_path
|
12
|
+
File.join log_dir, "#{cardname.safe_key}.csv"
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_log_entry request, html_log
|
16
|
+
time = DateTime.now.utc.strftime "%Y%m%d%H%M%S"
|
17
|
+
item_name = "%s+%s %s" % [name, time, request.gsub('/','/') ]
|
18
|
+
if include_item? item_name
|
19
|
+
item_name += 'a'
|
20
|
+
while include_item? item_name
|
21
|
+
item_name.next!
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
Card::Auth.as_bot do
|
26
|
+
File.open(log_path(item_name), 'w') {|f| f.puts html_log}
|
27
|
+
add_item! item_name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def add_csv_entry page, wbench_data, runs
|
32
|
+
if !File.exists? csv_path
|
33
|
+
File.open(csv_path, 'w') { |f| f.puts "page, render time, dom loading time, connection time, date"}
|
34
|
+
end
|
35
|
+
browser = wbench_data.browser
|
36
|
+
runs.times do |i|
|
37
|
+
csv_data = [
|
38
|
+
page,
|
39
|
+
browser['responseEnd'][i] - browser['requestStart'][i],
|
40
|
+
browser['domComplete'][i] - browser['domLoading'][i], # domLoadingTime
|
41
|
+
browser['requestStart'][i], # domLoadingStart
|
42
|
+
DateTime.now.utc.inspect
|
43
|
+
]
|
44
|
+
csv_line = CSV.generate_line(csv_data)
|
45
|
+
File.open(csv_path, 'a') { |f| f.puts csv_line }
|
46
|
+
end
|
47
|
+
|
48
|
+
if left != Card[:all]
|
49
|
+
all = Card.fetch "#{Card[:all].name}+#{Card[:performance_log].name}", :new=>{}
|
50
|
+
all.add_csv_entry page, wbench_data, runs
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
format :html do
|
55
|
+
view :core do
|
56
|
+
wagn_data =
|
57
|
+
card.item_names.map do |item|
|
58
|
+
path = card.log_path item
|
59
|
+
if File.exists? path
|
60
|
+
File.read(path)
|
61
|
+
end
|
62
|
+
end.compact.join "\n"
|
63
|
+
browser_data = CSV.parse(File.read(card.csv_path))
|
64
|
+
output [
|
65
|
+
table(browser_data, header: true),
|
66
|
+
wagn_data
|
67
|
+
]
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: card-mod-logger
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Philipp Kühl
|
8
|
+
- Ethan McCutchen
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2021-10-26 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: card
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0'
|
28
|
+
description: customizable logging of decko request and performance meta data
|
29
|
+
email:
|
30
|
+
- info@decko.org
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- README.md
|
36
|
+
- lib/cardio/logger.rb
|
37
|
+
- lib/cardio/logger/performance.rb
|
38
|
+
- lib/cardio/logger/performance/big_brother.rb
|
39
|
+
- lib/cardio/logger/performance/category_log.rb
|
40
|
+
- lib/cardio/logger/performance/entry.rb
|
41
|
+
- lib/cardio/logger/performance/html_formatter.rb
|
42
|
+
- lib/cardio/logger/performance/text_formatter.rb
|
43
|
+
- lib/cardio/logger/request.rb
|
44
|
+
- set/all/logger.rb
|
45
|
+
- set/right/performance_log.rb
|
46
|
+
homepage: http://decko.org
|
47
|
+
licenses:
|
48
|
+
- GPL-3.0
|
49
|
+
metadata:
|
50
|
+
card-mod: logger
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '2.5'
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubygems_version: 3.2.28
|
67
|
+
signing_key:
|
68
|
+
specification_version: 4
|
69
|
+
summary: request and performance logger for decko
|
70
|
+
test_files: []
|