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