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 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
@@ -0,0 +1,11 @@
1
+
2
+ module Cardio
3
+ class Logger
4
+ def self.with_logging method, opts, &block
5
+ return block.call unless Performance.enabled_method? method
6
+ Performance.with_timer(method, opts) do
7
+ block.call
8
+ end
9
+ end
10
+ end
11
+ 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('&#47;','_').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('/','&#47;') ]
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: []