log_sense 2.0.0 → 2.2.0

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.
@@ -1,5 +1,7 @@
1
1
  module LogSense
2
2
  class RailsAggregator < Aggregator
3
+ WORDS_SEPARATOR = ' · '
4
+
3
5
  def initialize(db, options = { limit: 900 })
4
6
  @table = "Event"
5
7
  @date_field = "started_at"
@@ -76,6 +78,7 @@ module LogSense
76
78
  sum(iif(platform != 'ios' and platform != 'android' and platform != 'mac' and platform != 'windows' and platform != 'linux', 1, 0)) as Other,
77
79
  count(distinct(id)) as Total
78
80
  from BrowserInfo
81
+ where #{filter}
79
82
  group by controller, method, request_format
80
83
  )
81
84
 
@@ -86,6 +89,7 @@ module LogSense
86
89
  SELECT browser as Browser,
87
90
  count(distinct(id)) as Visits
88
91
  from BrowserInfo
92
+ where #{filter}
89
93
  group by browser
90
94
  )
91
95
 
@@ -93,58 +97,83 @@ module LogSense
93
97
  SELECT platform as Platform,
94
98
  count(distinct(id)) as Visits
95
99
  from BrowserInfo
100
+ where #{filter}
96
101
  group by platform
97
102
  )
98
103
 
99
- @fatal = @db.execute %Q(
104
+ @fatal_plot = @db.execute %(
105
+ SELECT strftime("%Y-%m-%d", started_at) as Day,
106
+ sum(distinct(event.id)) as Errors,
107
+ sum(iif(context LIKE '%ActionController::RoutingError%', 1, 0)) as RoutingErrors,
108
+ sum(iif(context NOT LIKE '%ActionController::RoutingError%', 1, 0)) as OtherErrors
109
+ FROM Event JOIN Error
110
+ ON event.log_id == error.log_id
111
+ WHERE #{filter} and exit_status == 'F'
112
+ GROUP BY strftime("%Y-%m-%d", started_at)
113
+ ).gsub("\n", "") || [[]]
114
+
115
+ @fatal = @db.execute %(
100
116
  SELECT strftime("%Y-%m-%d %H:%M", started_at),
101
117
  ip,
102
118
  url,
103
- error.description,
119
+ context,
120
+ description,
104
121
  event.log_id
105
122
  FROM Event JOIN Error
106
123
  ON event.log_id == error.log_id
107
- WHERE #{filter} and exit_status == 'F').gsub("\n", "") || [[]]
108
-
109
- @fatal_plot = @db.execute %Q(
110
- SELECT strftime("%Y-%m-%d", started_at) as Day,
111
- count(distinct(event.id)) as Errors
112
- FROM Event JOIN Error
113
- ON event.log_id == error.log_id
114
124
  WHERE #{filter} and exit_status == 'F'
115
- GROUP BY strftime("%Y-%m-%d", started_at)).gsub("\n", "") || [[]]
116
-
117
- @internal_server_error = @db.execute %Q(
118
- SELECT strftime("%Y-%m-%d %H:%M", started_at), status, ip, url,
119
- error.description,
120
- event.log_id
121
- FROM Event JOIN Error
122
- ON event.log_id == error.log_id
123
- WHERE #{filter} and substr(status, 1, 1) == '5').gsub("\n", "") || [[]]
124
-
125
- @internal_server_error_plot = @db.execute %Q(
126
- SELECT strftime('%Y-%m-%d', started_at) as Day,
127
- count(distinct(event.id)) as Errors
128
- FROM Event JOIN Error
129
- ON event.log_id == error.log_id
130
- WHERE #{filter} and substr(status, 1, 1) == '5'
131
- GROUP BY strftime('%Y-%m-%d', started_at)).gsub("\n", "") || [[]]
132
-
133
- @error = @db.execute %Q(
125
+ ).gsub("\n", "") || [[]]
126
+
127
+ @fatal_grouped = @db.execute %(
134
128
  SELECT filename,
135
- log_id, description, count(log_id)
129
+ group_concat(log_id, '#{WORDS_SEPARATOR}'),
130
+ context,
131
+ description,
132
+ count(distinct(error.id))
136
133
  FROM Error
137
- WHERE (description NOT LIKE '%No route matches%' and
138
- description NOT LIKE '%Couldn''t find%')
139
- GROUP BY description).gsub("\n", "") || [[]]
134
+ GROUP BY description
135
+ ).gsub("\n", "") || [[]]
140
136
 
141
- @possible_attacks = @db.execute %Q(
142
- SELECT filename,
143
- log_id, description, count(log_id)
144
- FROM Error
145
- WHERE (description LIKE '%No route matches%' or
146
- description LIKE '%Couldn''t find%')
147
- GROUP BY description).gsub("\n", "") || [[]]
137
+ @job_plot = @db.execute %(
138
+ SELECT strftime("%Y-%m-%d", ended_at) as Day,
139
+ sum(iif(exit_status == 'C', 1, 0)) as Completed,
140
+ sum(iif(exit_status == 'E', 1, 0)) as Errors
141
+ FROM Job
142
+ WHERE #{filter}
143
+ GROUP BY strftime("%Y-%m-%d", ended_at)
144
+ ).gsub("\n", "") || [[]]
145
+
146
+ # worker,
147
+ # host,
148
+ # pid,
149
+ @jobs = @db.execute %(
150
+ SELECT strftime("%Y-%m-%d %H:%M", started_at),
151
+ duration_total_ms,
152
+ pid,
153
+ object_id,
154
+ exit_status,
155
+ method,
156
+ arguments,
157
+ error_msg,
158
+ attempt
159
+ FROM Job
160
+ WHERE #{filter}
161
+ ).gsub("\n", "") || [[]]
162
+
163
+ @job_error_grouped = @db.execute %(
164
+ SELECT worker,
165
+ host,
166
+ pid,
167
+ exit_status,
168
+ object_id,
169
+ GROUP_CONCAT(DISTINCT(error_msg)),
170
+ method,
171
+ arguments,
172
+ max(attempt)
173
+ FROM Job
174
+ WHERE #{filter} and exit_status == 'E'
175
+ GROUP BY object_id
176
+ ).gsub("\n", "") || [[]]
148
177
 
149
178
  instance_vars_to_hash
150
179
  end
@@ -48,27 +48,51 @@ module LogSense
48
48
  column_alignment: %i[left right right right right],
49
49
  rows: data[:performance],
50
50
  col: "small-12 cell",
51
- echarts_height: "600px",
51
+ echarts_height: "800px",
52
52
  echarts_spec: "{
53
53
  xAxis: {
54
+ name: 'Hits',
55
+ type: 'value',
56
+ minInterval: 1,
57
+ axisLabel: {
58
+ formatter: function(val) {
59
+ return val.toFixed(0);
60
+ }
61
+ }
54
62
  },
55
63
  yAxis: {
64
+ name: 'Average Time',
65
+ type: 'value',
66
+ axisLabel: {
67
+ formatter: function(val) {
68
+ return val.toFixed(0) + ' ms';
69
+ }
70
+ }
56
71
  },
57
72
  tooltip: {
58
- trigger: 'axis'
73
+ trigger: 'item',
74
+ formatter: function(params) {
75
+ var index = params.dataIndex
76
+ var row = SERIES_DATA[index]
77
+ var controller = row['Controller']
78
+ var hits = Number(params.value[0]).toFixed(0).toLocaleString('en')
79
+ var average = Number(params.value[1]).toFixed(0).toLocaleString('en') + ' ms'
80
+ return `<b>${controller}</b><br/>Hits: ${hits}<br>Average Time: ${average}`;
81
+ }
59
82
  },
60
83
  series: [
61
84
  {
62
- data: SERIES_DATA.map(row => [row['Avg'], row['Hits']]),
85
+ data: SERIES_DATA.map(row => [row['Hits'], row['Avg']]),
63
86
  type: 'scatter',
64
87
  color: '#D30001',
65
88
  label: {
66
89
  show: true,
67
90
  position: 'right',
68
- formatter: function (params) {
91
+ formatter: function(params) {
69
92
  var row = SERIES_DATA[params.dataIndex]
70
- return row['Controller'] +
71
- ': (' + row['Avg'] + ', ' + row['Hits'] + ')';
93
+ return row['Controller'];
94
+ // +
95
+ // ':\\n(' + row['Hits'] + ', ' + row['Avg'] + ')';
72
96
  }
73
97
  },
74
98
  }
@@ -125,8 +149,9 @@ module LogSense
125
149
  },
126
150
  {
127
151
  title: "Fatal Events",
128
- header: %w[Date IP URL Description Log ID],
129
- column_alignment: %i[left left left left left],
152
+ header: %w[Date IP URL Context Description ID],
153
+ column_alignment: %i[left left left left left left],
154
+ column_width: ["10%", "10%", "20%", "10%", "30%", "20%"],
130
155
  rows: data[:fatal],
131
156
  col: "small-12 cell",
132
157
  echarts_extra: "var fatal_plot=#{data[:fatal_plot].to_json}",
@@ -154,7 +179,18 @@ module LogSense
154
179
  },
155
180
  series: [
156
181
  {
157
- data: fatal_plot.filter(row => row[0] != '').map(row => row[1]),
182
+ name: 'Routing Errors',
183
+ data: fatal_plot.filter(row => row[0] != '').map(row => row[2]),
184
+ type: 'bar',
185
+ color: '#D0D0D0',
186
+ label: {
187
+ show: true,
188
+ position: 'top'
189
+ },
190
+ },
191
+ {
192
+ name: 'Other Errors',
193
+ data: fatal_plot.filter(row => row[0] != '').map(row => row[3]),
158
194
  type: 'bar',
159
195
  color: '#D30001',
160
196
  label: {
@@ -166,12 +202,34 @@ module LogSense
166
202
  };"
167
203
  },
168
204
  {
169
- title: "Internal Server Errors",
170
- header: %w[Date Status IP URL Description Log ID],
171
- column_alignment: %i[left left left left left left],
172
- rows: data[:internal_server_error],
205
+ title: "Fatal Events (grouped by type)",
206
+ header: %w[Log ID Context Description Count],
207
+ column_alignment: %i[left left left left right],
208
+ column_width: ["10%", "20%", "10%", "60%", "5%"],
209
+ rows: data[:fatal_grouped],
210
+ col: "small-12 cell"
211
+ },
212
+ browsers(data),
213
+ platforms(data),
214
+ ips(data),
215
+ countries(data),
216
+ ip_per_hour_report_spec(ips_per_hour(data[:ips_per_hour])),
217
+ session_report_spec(ips_detailed(data[:ips_per_day_detailed])),
218
+ {
219
+ title: "Jobs (Completed and Failed)",
220
+ explanation: %(
221
+ This report includes completed and failed jobs, parsing lines
222
+ marked as COMPLETED or ERROR/FAILED.
223
+
224
+ This excludes from the table entries marked as RUNNING and then
225
+ completed with "performed".
226
+ ),
227
+ header: %w[Date Duration PID ID Exit_Status Method Arguments Error_Msg Attempts],
228
+ column_alignment: %i[left left right left left left left left right],
229
+ column_width: ["10%", "5%", "5%", "5%", "5%", "15%", "25%", "25%", "5%"],
230
+ rows: data[:jobs],
173
231
  col: "small-12 cell",
174
- echarts_extra: "var internal_server_error_plot=#{data[:internal_server_error_plot].to_json}",
232
+ echarts_extra: "var fatal_plot=#{data[:job_plot].to_json}",
175
233
  echarts_spec: "{
176
234
  toolbox: {
177
235
  feature: {
@@ -183,7 +241,7 @@ module LogSense
183
241
  },
184
242
  xAxis: {
185
243
  type: 'category',
186
- data: internal_server_error_plot.filter(row => row[0] != '').map(row => row[0]),
244
+ data: fatal_plot.filter(row => row[0] != '').map(row => row[0]),
187
245
  showGrid: true,
188
246
  axisLabel: {
189
247
  rotate: 45 // Rotate the labels
@@ -196,37 +254,36 @@ module LogSense
196
254
  },
197
255
  series: [
198
256
  {
199
- data: internal_server_error_plot.filter(row => row[0] != '').map(row => row[1]),
257
+ name: 'Completed',
258
+ data: fatal_plot.filter(row => row[0] != '').map(row => row[1]),
200
259
  type: 'bar',
201
- color: '#D30001',
260
+ color: '#D0D0D0',
202
261
  label: {
203
262
  show: true,
204
263
  position: 'top'
205
264
  },
206
265
  },
266
+ {
267
+ name: 'Errors',
268
+ data: fatal_plot.filter(row => row[0] != '').map(row => row[2]),
269
+ type: 'bar',
270
+ color: '#D30001',
271
+ label: {
272
+ show: true,
273
+ position: 'top'
274
+ },
275
+ }
207
276
  ]
208
277
  };"
209
278
  },
210
279
  {
211
- title: "Errors",
212
- header: %w[Log ID Description Count],
213
- column_alignment: %i[left left left right],
214
- rows: data[:error],
215
- col: "small-12 cell"
216
- },
217
- {
218
- title: "Potential Attacks",
219
- header: %w[Log ID Description Count],
220
- column_alignment: %i[left left left right],
221
- rows: data[:possible_attacks],
280
+ title: "Job Errors (grouped)",
281
+ header: %w[Worker Host PID ID Error Method Arguments Attempts],
282
+ column_alignment: %i[left left left left left left left right],
283
+ column_width: ["5%", "5%", "5%", "5%", "20%", "30%", "20%", "10%"],
284
+ rows: data[:job_error_grouped],
222
285
  col: "small-12 cell"
223
- },
224
- browsers(data),
225
- platforms(data),
226
- ips(data),
227
- countries(data),
228
- ip_per_hour_report_spec(ips_per_hour(data[:ips_per_hour])),
229
- session_report_spec(ips_detailed(data[:ips_per_day_detailed]))
286
+ }
230
287
  ]
231
288
  end
232
289
  end
@@ -1,5 +1,6 @@
1
1
  module LogSense
2
2
  class ReportShaper
3
+ SESSION_URL_LIMIT = 300
3
4
  WORDS_SEPARATOR = ' · '
4
5
 
5
6
  # return { [ip,day] => { [ hits, list of urls ] } }
@@ -14,7 +15,11 @@ module LogSense
14
15
  date,
15
16
  hash[ip][date].size,
16
17
  hash[ip][date].uniq.size,
17
- hash[ip][date].uniq.size < 100 ? hash[ip][date].uniq.join(WORDS_SEPARATOR) : "[too many]"
18
+ if hash[ip][date].uniq.size < SESSION_URL_LIMIT
19
+ hash[ip][date].uniq.join(WORDS_SEPARATOR)
20
+ else
21
+ "[too many]"
22
+ end
18
23
  ]
19
24
  end
20
25
  end
@@ -277,7 +282,7 @@ module LogSense
277
282
  title: "Sessions",
278
283
  report: :html,
279
284
  header: ["IP", "Days", "Date", "Visits", "Distinct URL", "URL List"],
280
- column_alignment: %i[left left right right right right],
285
+ column_alignment: %i[left left right right right left],
281
286
  rows: data,
282
287
  col: "small-12 cell"
283
288
  }
@@ -288,6 +293,7 @@ module LogSense
288
293
  title: "IP per hour",
289
294
  header: ["IP"] + (0..23).map { |hour| hour.to_s },
290
295
  column_alignment: %i[left] + (%i[right] * 24),
296
+ column_width: ["10%"] + (["3.75%"] * 24),
291
297
  rows: data,
292
298
  col: "small-12 cell"
293
299
  }
@@ -419,7 +425,7 @@ module LogSense
419
425
  max = country_and_hits.map { |x| x[1] }.max
420
426
 
421
427
  country_and_hits.map do |element|
422
- underscored = (element[0] || "").gsub(" ", "_")
428
+ underscored = (element[0] || "").gsub(" ", "_").gsub(/[()]/, "")
423
429
  bin = bin(element[1], max:)
424
430
  <<-EOS
425
431
  /* bin: #{bin} */
@@ -16,15 +16,20 @@
16
16
  pageLength: 25,
17
17
  <%= report[:datatable_options] + "," if report[:datatable_options] %>
18
18
  columns: [
19
- <% report[:header].each do |header| %>
20
- <% if header == "Size" -%>
19
+ <% report[:header].each_with_index do |header, index| %>
21
20
  {
22
- data: 'Size',
23
- className: '<%= Emitter::slugify("Size") %>',
21
+ data: '<%= header %>',
22
+ className: '<%= Emitter::slugify(header) %> <%= Emitter.alignment_class(report, index) %>',
23
+ <% if report[:column_width] %>
24
+ width: '<%= report[:column_width][index] %>',
25
+ <% end %>
26
+
27
+ <%# USE A SPECIFIC RENDERER FOR SOME TYPES OF COLUMNS %>
28
+ <% if header == "Size" -%>
24
29
  render: function(data, type, row) {
25
30
  // If display or filter data is requested, format the date
26
31
  if ( type === 'display' || type === 'filter' ) {
27
- return data;
32
+ return data;
28
33
  }
29
34
  // Otherwise the data type requested (`type`) is type detection or
30
35
  // sorting data, for which we want to use an integer value
@@ -48,11 +53,7 @@
48
53
  }
49
54
  return size * multiplier;
50
55
  }
51
- },
52
- <% elsif header == "IP" -%>
53
- {
54
- data: 'IP',
55
- className: '<%= Emitter::slugify("IP") %>',
56
+ <% elsif header == "IP" -%>
56
57
  render: function(data, type, row) {
57
58
  // If display or filter data is requested, format the data
58
59
  if ( type === 'display' || type === 'filter' ) {
@@ -61,15 +62,11 @@
61
62
  // For any other purpose return data
62
63
  return data;
63
64
  }
64
- },
65
- <% else -%>
66
- {
67
- data: '<%= header %>',
68
- className: '<%= Emitter::slugify(header) %>',
65
+ <% else -%>
69
66
  render: DataTable.render.text()
67
+ <% end -%>
70
68
  },
71
69
  <% end -%>
72
- <% end -%>
73
70
  ]
74
71
  });
75
72
  });
@@ -85,6 +85,12 @@ h2 {
85
85
  background: var(--background-color);
86
86
  }
87
87
 
88
+ table {
89
+ width: 100% !important;
90
+ /* table-layout: fixed !important;*/
91
+ word-wrap: break-word;
92
+ }
93
+
88
94
  table thead, table tbody, table tfoot {
89
95
  border-color: var(--row-border) !important;
90
96
  }
@@ -174,7 +180,16 @@ ul.pagination, li.paginate_button {
174
180
  font-weight: normal;
175
181
  }
176
182
 
177
- .hits, .visits, .size, .count, .s2xx, .s3xx, .so4xx, .total-hits, .total-visits {
183
+ /* datatable resists aligment directives. We make them important */
184
+ .text-left {
185
+ text-align: left !important;
186
+ }
187
+
188
+ .text-center {
189
+ text-align: center !important;
190
+ }
191
+
192
+ .text-right {
178
193
  text-align: right !important;
179
194
  }
180
195
 
@@ -182,3 +197,7 @@ td {
182
197
  vertical-align: top;
183
198
  }
184
199
 
200
+ .url {
201
+ overflow: hidden;
202
+ text-overflow: ellipsis;
203
+ }
@@ -61,6 +61,7 @@
61
61
  <% if report[:rows] %>
62
62
  <%= render "report_data.html.erb", report: report, index: index %>
63
63
  <% end %>
64
+
64
65
  <% if report[:vega_spec] %>
65
66
  <div id="<%= "plot-#{index}" %>" class="plot-canvas">
66
67
  </div>
@@ -78,6 +79,7 @@
78
79
  vegaEmbed('#<%= "plot-#{index}"%>', plot_spec_<%= index %>);
79
80
  </script>
80
81
  <% end %>
82
+
81
83
  <% if report[:echarts_spec] %>
82
84
  <% height = report[:echarts_height] || "400px"%>
83
85
  <div id="<%= "echart-#{index}" %>" style="width: 100%;height: <%= height %>;"></div>
@@ -91,12 +93,14 @@
91
93
 
92
94
  </script>
93
95
  <% end %>
96
+
94
97
  <% if report[:raw_html] %>
95
98
  <% height = report[:raw_html_height] || "400px"%>
96
99
  <div id="raw-html-#{index}" style="width: 100%;height: <%= height %>">
97
100
  <%= report[:raw_html] %>
98
101
  </div>
99
102
  <% end %>
103
+
100
104
  <% if report[:rows] %>
101
105
  <%= render "output_table.html.erb", report: report, index: index %>
102
106
  <% end %>
@@ -312,7 +312,7 @@ THE SOFTWARE.
312
312
  </path>
313
313
  <path d="M974.8 276l1.9 4.1 0.3 3.9 1.9 6.8 1.4 1.4-1 2.5-7.1 1.1-2.5 2.3-3.1 0.6-0.3 4.8-6.4 2.5-2.1 3.2-4.5 1.7-5.4 1-8.9 4.8-0.1 7.6-0.9 0 0.1 3.4-3.4 0.2-1.8 1.5-2.5 0-2-0.9-4.6 0.7-1.9 5-1.8 0.5-2.7 8.1-7.9 6.9-2 8.9-2.4 2.9-0.7 2.3-12.5 0.5-0.1 0 0.3-3 2.2-1.7 1.9-3.4-0.3-2.2 2-4.5 3.2-4.1 1.9-1 1.6-3.7 0.2-3.5 2.1-3.9 3.8-2.4 3.6-6.5 0.1-0.1 2.9-2.5 5.1-0.7 4.4-4.4 2.8-1.7 4.7-5.4-1.2-7.9 2.2-5.6 0.9-3.4 3.6-4.3 5.4-2.9 4.1-2.7 3.7-6.6 1.8-4 3.9 0.1 3.1 2.7 5.1-0.4 5.5 1.4 2.4 0z" id="MA" name="Morocco">
314
314
  </path>
315
- <path d="M1129.4 210.3l-1.3-2.9 0.2-2.7-0.6-2.7-3.4-3.8-2-2.6-1.8-1.8-1.6-0.7 1.1-0.9 3.2-0.6 4 1.9 2 0.3 2.6 1.7-0.1 2.1 2 1 1.1 2.6 2 1.6-0.2 1 1 0.6-1.3 0.5-3-0.2-0.6-0.9-1 0.5 0.6 1.1-1.1 2.1-0.6 2.1-1.2 0.7z" id="MD" name="Moldova">
315
+ <path d="M1129.4 210.3l-1.3-2.9 0.2-2.7-0.6-2.7-3.4-3.8-2-2.6-1.8-1.8-1.6-0.7 1.1-0.9 3.2-0.6 4 1.9 2 0.3 2.6 1.7-0.1 2.1 2 1 1.1 2.6 2 1.6-0.2 1 1 0.6-1.3 0.5-3-0.2-0.6-0.9-1 0.5 0.6 1.1-1.1 2.1-0.6 2.1-1.2 0.7z" id="MD" name="Moldova_Republic_of">
316
316
  </path>
317
317
  <path d="M1267.9 588.9l0.4 7.7 1.3 3-0.7 3.1-1.2 1.8-1.6-3.7-1.2 1.9 0.8 4.7-0.7 2.8-1.7 1.4-0.7 5.5-2.7 7.5-3.4 8.8-4.3 12.2-2.9 8.9-3.1 7.5-4.6 1.5-5.1 2.7-3-1.6-4.2-2.3-1.2-3.4 0-5.7-1.5-5.1-0.2-4.7 1.3-4.6 2.6-1.1 0.2-2.1 2.9-4.9 0.8-4.1-1.1-3-0.8-4.1-0.1-5.9 2.2-3.6 1-4.1 2.8-0.2 3.2-1.3 2.2-1.2 2.4-0.1 3.4-3.6 4.9-4 1.8-3.2-0.6-2.8 2.4 0.8 3.3-4.4 0.3-3.9 2-2.9 1.8 2.8 1.4 2.7 1.2 4.3z" id="MG" name="Madagascar">
318
318
  </path>
@@ -1,3 +1,3 @@
1
1
  module LogSense
2
- VERSION = "2.0.0"
2
+ VERSION = "2.2.0"
3
3
  end
data/todo.org CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  * Todo (2024-08-01)
4
4
 
5
+ ** T Break rails log_parser in subclasses (one per log type)
6
+
7
+ ** T Distinguish between exceptions, errors with no timestamps and errors with timestamps :feature:
8
+ - In rails reports some errors don't have a timestamp, whereas other have.
9
+
10
+ 1. Check whether we can always get an ID of an event
11
+
5
12
  ** T Move geolocation to aggregator, so that we can perform queries more efficiently :refactoring:
6
13
  ** T Filter on dates :feature:
7
14
  ** T Visits/Hits :feature:
@@ -11,9 +18,7 @@
11
18
  ** T error on data
12
19
  - The data reported by echarts on the number of hits of a controller
13
20
  differs from the data shown in the table?
14
- ** T Sidebar foreground color in new apache report
15
- ** T Dark style
16
- ** T Remove dependency from Zurb Foundation (native css grid instead)
21
+ ** T Remove dependency from Zurb Foundation (native css grid instead) :refactoring:
17
22
  ** T refactor report specifications in their own class :refactoring:
18
23
  ** T Add lines not parsed to the report :feature:
19
24
  ** T Using an empty log as input raises an error :feature:
@@ -307,3 +312,22 @@
307
312
  :ARCHIVE_CATEGORY: todo
308
313
  :ARCHIVE_TODO: REJECTED
309
314
  :END:
315
+
316
+ ** D Dark style
317
+ :PROPERTIES:
318
+ :ARCHIVE_TIME: 2024-08-23 Fri 16:25
319
+ :ARCHIVE_FILE: ~/Sources/ruby/log_sense/todo.org
320
+ :ARCHIVE_OLPATH: Todo (2024-08-01)
321
+ :ARCHIVE_CATEGORY: todo
322
+ :ARCHIVE_TODO: D
323
+ :END:
324
+
325
+ ** D Sidebar foreground color in new apache report
326
+ :PROPERTIES:
327
+ :ARCHIVE_TIME: 2024-08-23 Fri 16:25
328
+ :ARCHIVE_FILE: ~/Sources/ruby/log_sense/todo.org
329
+ :ARCHIVE_OLPATH: Todo (2024-08-01)
330
+ :ARCHIVE_CATEGORY: todo
331
+ :ARCHIVE_TODO: D
332
+ :END:
333
+
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: log_sense
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adolfo Villafiorita
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-08-19 00:00:00.000000000 Z
11
+ date: 2024-08-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: browser