card-mod-logger 0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ 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: []