log_sense 1.5.2 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.org +27 -0
  3. data/Gemfile.lock +6 -4
  4. data/README.org +108 -34
  5. data/Rakefile +6 -6
  6. data/exe/log_sense +110 -39
  7. data/ip_locations/dbip-country-lite.sqlite3 +0 -0
  8. data/lib/log_sense/aggregator.rb +191 -0
  9. data/lib/log_sense/apache_aggregator.rb +122 -0
  10. data/lib/log_sense/apache_log_line_parser.rb +23 -21
  11. data/lib/log_sense/apache_log_parser.rb +15 -12
  12. data/lib/log_sense/apache_report_shaper.rb +309 -0
  13. data/lib/log_sense/emitter.rb +55 -553
  14. data/lib/log_sense/ip_locator.rb +24 -12
  15. data/lib/log_sense/options_checker.rb +24 -0
  16. data/lib/log_sense/options_parser.rb +81 -51
  17. data/lib/log_sense/rails_aggregator.rb +69 -0
  18. data/lib/log_sense/rails_log_parser.rb +82 -68
  19. data/lib/log_sense/rails_report_shaper.rb +183 -0
  20. data/lib/log_sense/report_shaper.rb +105 -0
  21. data/lib/log_sense/templates/_cdn_links.html.erb +11 -0
  22. data/lib/log_sense/templates/_command_invocation.html.erb +4 -0
  23. data/lib/log_sense/templates/_log_structure.html.erb +7 -1
  24. data/lib/log_sense/templates/_output_table.html.erb +6 -2
  25. data/lib/log_sense/templates/_rails.css.erb +7 -0
  26. data/lib/log_sense/templates/_summary.html.erb +9 -7
  27. data/lib/log_sense/templates/_summary.txt.erb +2 -2
  28. data/lib/log_sense/templates/{rails.html.erb → report_html.erb} +19 -37
  29. data/lib/log_sense/templates/{apache.txt.erb → report_txt.erb} +1 -1
  30. data/lib/log_sense/version.rb +1 -1
  31. data/lib/log_sense.rb +19 -9
  32. data/log_sense.gemspec +1 -1
  33. data/{apache-screenshot.png → screenshots/apache-screenshot.png} +0 -0
  34. data/screenshots/rails-screenshot.png +0 -0
  35. metadata +17 -11
  36. data/lib/log_sense/apache_data_cruncher.rb +0 -147
  37. data/lib/log_sense/rails_data_cruncher.rb +0 -141
  38. data/lib/log_sense/templates/apache.html.erb +0 -115
  39. data/lib/log_sense/templates/rails.txt.erb +0 -22
@@ -0,0 +1,183 @@
1
+ module LogSense
2
+ class RailsReportShaper < ReportShaper
3
+ def shape(data)
4
+ [
5
+ {
6
+ title: "Daily Distribution",
7
+ header: %w[Day DOW Hits],
8
+ column_alignment: %i[left left right],
9
+ rows: data[:daily_distribution],
10
+ vega_spec: {
11
+ "encoding": {
12
+ "x": {"field": "Day", "type": "temporal"},
13
+ "y": {"field": "Hits", "type": "quantitative"}
14
+ },
15
+ "layer": [
16
+ {
17
+ "mark": {
18
+ "type": "line",
19
+ "point": {
20
+ "filled": false,
21
+ "fill": "white"
22
+ }
23
+ }
24
+ },
25
+ {
26
+ "mark": {
27
+ "type": "text",
28
+ "align": "left",
29
+ "baseline": "middle",
30
+ "dx": 5
31
+ },
32
+ "encoding": {
33
+ "text": {"field": "Hits", "type": "quantitative"}
34
+ }
35
+ }
36
+ ]
37
+ }
38
+ },
39
+ {
40
+ title: "Time Distribution",
41
+ header: %w[Hour Hits],
42
+ column_alignment: %i[left right],
43
+ rows: data[:time_distribution],
44
+ vega_spec: {
45
+ "layer": [
46
+ {
47
+ "mark": "bar",
48
+ },
49
+ {
50
+ "mark": {
51
+ "type": "text",
52
+ "align": "middle",
53
+ "baseline": "top",
54
+ "dx": -10,
55
+ "yOffset": -15
56
+ },
57
+ "encoding": {
58
+ "text": {"field": "Hits", "type": "quantitative"}
59
+ }
60
+ }
61
+ ],
62
+ "encoding": {
63
+ "x": {"field": "Hour", "type": "nominal"},
64
+ "y": {"field": "Hits", "type": "quantitative"}
65
+ }
66
+ }
67
+ },
68
+ {
69
+ title: "Statuses",
70
+ header: %w[Status Count],
71
+ column_alignment: %i[left right],
72
+ rows: data[:statuses],
73
+ vega_spec: {
74
+ "layer": [
75
+ {
76
+ "mark": "bar"
77
+ },
78
+ {
79
+ "mark": {
80
+ "type": "text",
81
+ "align": "left",
82
+ "baseline": "top",
83
+ "dx": -10,
84
+ "yOffset": -20
85
+ },
86
+ "encoding": {
87
+ "text": {"field": "Count", "type": "quantitative"}
88
+ }
89
+ }
90
+ ],
91
+ "encoding": {
92
+ "x": {"field": "Status", "type": "nominal"},
93
+ "y": {"field": "Count", "type": "quantitative"}
94
+ }
95
+ }
96
+ },
97
+ {
98
+ title: "Rails Performance",
99
+ header: %w[Controller Hits Min Avg Max],
100
+ column_alignment: %i[left right right right right],
101
+ rows: data[:performance],
102
+ vega_spec: {
103
+ "layer": [
104
+ {
105
+ "mark": { "type": "point",
106
+ "name": "data_points"
107
+ }
108
+ },
109
+ {
110
+ "mark": { "name": "label",
111
+ "type": "text",
112
+ "align": "left",
113
+ "baseline": "middle",
114
+ "dx": 5,
115
+ "yOffset": 0
116
+ },
117
+ "encoding": { "text": {"field": "Controller"},
118
+ "fontSize": {"value": 8}
119
+ },
120
+ },
121
+ ],
122
+ "encoding": { "x": { "field": "Avg",
123
+ "type": "quantitative"
124
+ },
125
+ "y": { "field": "Hits",
126
+ "type": "quantitative"
127
+ }
128
+ },
129
+ }
130
+ },
131
+ {
132
+ title: "Fatal Events",
133
+ header: %w[Date IP URL Description Log ID],
134
+ column_alignment: %i[left left left left left],
135
+ rows: data[:fatal],
136
+ col: "small-12 cell"
137
+ },
138
+ {
139
+ title: "Internal Server Errors",
140
+ header: %w[Date Status IP URL Description Log ID],
141
+ column_alignment: %i[left left left left left left],
142
+ rows: data[:internal_server_error],
143
+ col: "small-12 cell"
144
+ },
145
+ {
146
+ title: "Errors",
147
+ header: %w[Log ID Context Description Count],
148
+ column_alignment: %i[left left left left],
149
+ rows: data[:error],
150
+ col: "small-12 cell"
151
+ },
152
+ {
153
+ title: "IPs",
154
+ header: %w[IPs Hits Country],
155
+ column_alignment: %i[left right left],
156
+ rows: data[:ips]
157
+ },
158
+ {
159
+ title: "Countries",
160
+ header: ["Country", "Hits", "IPs", "IP List"],
161
+ column_alignment: %i[left right left],
162
+ rows: countries_table(data[:countries])
163
+ },
164
+ ip_per_hour_report_spec(ips_per_hour(data[:ips_per_hour])),
165
+ session_report_spec(ips_detailed(data[:ips_per_day_detailed]))
166
+ ]
167
+ end
168
+
169
+ private
170
+
171
+ # { country => [[ip, visit, country], ...]
172
+ def countries_table(data)
173
+ data&.map { |k, v|
174
+ [
175
+ k,
176
+ v.map { |x| x[1] }.inject(&:+),
177
+ v.map { |x| x[0] }.uniq.size,
178
+ v.map { |x| x[0] }.join(WORDS_SEPARATOR)
179
+ ]
180
+ }&.sort { |x, y| x[0] <=> y[0] }
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,105 @@
1
+ module LogSense
2
+ class ReportShaper
3
+ WORDS_SEPARATOR = ' · '
4
+
5
+ # return { [ip,day] => { [ hits, list of urls ] } }
6
+ def ips_detailed(ips_per_day)
7
+ hash = pivot(ips_per_day, 0, 1, lambda { |array| array.map { |x| x[2] } })
8
+ array = []
9
+ hash.keys.map do |ip|
10
+ array += hash[ip].keys.map do |date|
11
+ [
12
+ ip,
13
+ hash[ip].keys.size,
14
+ date,
15
+ hash[ip][date].size,
16
+ hash[ip][date].uniq.size,
17
+ hash[ip][date].uniq.size < 100 ? hash[ip][date].uniq.join(WORDS_SEPARATOR) : "[too many]"
18
+ ]
19
+ end
20
+ end
21
+ array
22
+ end
23
+
24
+ def ips_per_hour(ips_per_hour)
25
+ hash = pivot(ips_per_hour, 0, 1)
26
+ hash.keys.map do |ip|
27
+ [
28
+ ip,
29
+ (0..23).map { |hour| hash[ip][hour.to_s]&.to_i }
30
+ ].flatten
31
+ end
32
+ end
33
+
34
+ # Shape an array of arrays into a hash of hashes (row, cols)
35
+ # Params:
36
+ # +input_data+:: array of array
37
+ # +row_key+:: index of the inner array or lambda, used to group data
38
+ # by rows
39
+ # +col_key+:: index of the inner array or lambda, will be used
40
+ # to group cols
41
+ # +cell+:: lambda to get the value of the cells to put in the hashes.
42
+ # Takes and array as input (that is, an array of all the
43
+ # elements resulting from grouping)
44
+ #
45
+ # Example
46
+ #
47
+ # input_array: [[IP, HOUR, VISITS], [...]],
48
+ # row_key: 0
49
+ # col_key: 1
50
+ # cell: lambda { |x| x[2] }
51
+ #
52
+ # Will output:
53
+ #
54
+ # { IP => { HOUR => VISITS, HOUR => VISITS }, ... }
55
+ #
56
+ def pivot(input_data,
57
+ row_key,
58
+ col_key,
59
+ cell_maker = lambda { |x| x[0].last })
60
+ # here we build:
61
+ # "95.108.213.66"=>{12=>1, 18=>2, 19=>1, 20=>1, 5=>3, 6=>2, 7=>2, 3=>1},
62
+ #
63
+ # first transform: IP => [ HOUR => [[IP,HOUR,T], [IP,HOUR,T]] ]
64
+ # second transform: [IP,HOUR,T] -> T
65
+ input_data.group_by { |entry|
66
+ if row_key.class == Integer
67
+ entry[row_key]
68
+ else
69
+ row_key.call(entry)
70
+ end
71
+ }.transform_values { |rows|
72
+ rows.group_by { |cols|
73
+ if col_key.class == Integer
74
+ cols[col_key]
75
+ else
76
+ col_key.call(cols)
77
+ end
78
+ }.transform_values { |array|
79
+ cell_maker.call(array)
80
+ }
81
+ }
82
+ end
83
+
84
+ def session_report_spec(data)
85
+ {
86
+ title: "Sessions",
87
+ report: :html,
88
+ header: ["IP", "Days", "Date", "Visits", "Distinct URL", "URL List"],
89
+ column_alignment: %i[left left right right right right],
90
+ rows: data,
91
+ col: "small-12 cell"
92
+ }
93
+ end
94
+
95
+ def ip_per_hour_report_spec(data)
96
+ {
97
+ title: "IP per hour",
98
+ header: ["IP"] + (0..23).map { |hour| hour.to_s },
99
+ column_alignment: [:left] + [:right] * 24,
100
+ rows: data,
101
+ col: "small-12 cell"
102
+ }
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,11 @@
1
+ <link rel="preconnect" href="https://fonts.googleapis.com">
2
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
3
+ <link href="https://fonts.googleapis.com/css2?family=Fira+Sans:wght@300;700&display=swap" rel="stylesheet">
4
+
5
+ <% LogSense::Emitter::CDN_CSS.each do |link| %>
6
+ <link rel="stylesheet" type="text/css" href="<%= link %>">
7
+ <% end %>
8
+
9
+ <% LogSense::Emitter::CDN_JS.each do |link| %>
10
+ <script type="text/javascript" src="<%= link %>"></script>
11
+ <% end %>
@@ -19,6 +19,10 @@
19
19
  <th>No selfpoll</th>
20
20
  <td><code><%= options[:no_selfpoll] %></code></td>
21
21
  </tr>
22
+ <tr>
23
+ <th>Geolocation</th>
24
+ <td><code><%= options[:geolocation] %></code></td>
25
+ </tr>
22
26
  <tr>
23
27
  <th>Filter by date</th>
24
28
  <td>
@@ -11,17 +11,23 @@
11
11
  <%= data[:total_days] %>
12
12
  <span class="stats-list-label">Days in Log</span>
13
13
  </li>
14
+ <% if data[:log_size] %>
14
15
  <li class="stats-list-negative">
15
16
  <%= data[:log_size] %>
16
17
  <span class="stats-list-label">Total Entries</span>
17
18
  </li>
19
+ <% end %>
20
+ <% if data[:selfpolls_size] %>
18
21
  <li class="stats-list-negative">
19
22
  <%= data[:selfpolls_size] %>
20
23
  <span class="stats-list-label">Self Polls</span>
21
24
  </li>
22
- <li class="stats-list-negative">
25
+ <% end %>
26
+ <% if data[:crawlers] %>
27
+ <li class="stats-list-negative">
23
28
  <td><%= data[:crawlers_size] %></td>
24
29
  <span class="stats-list-label">Crawlers</span>
30
+ <% end %>
25
31
  </li>
26
32
  </ul>
27
33
 
@@ -54,7 +54,7 @@
54
54
  data: 'IP',
55
55
  className: '<%= Emitter::slugify("IP") %>',
56
56
  render: function(data, type, row) {
57
- // If display or filter data is requested, format the date
57
+ // If display or filter data is requested, format the data
58
58
  if ( type === 'display' || type === 'filter' ) {
59
59
  return "<a target=\"_blank\" href=\"https://db-ip.com/" + data + "\">" + data + "</a>";
60
60
  }
@@ -63,7 +63,11 @@
63
63
  }
64
64
  },
65
65
  <% else -%>
66
- { data: '<%= header %>', className: '<%= Emitter::slugify(header) %>' },
66
+ {
67
+ data: '<%= header %>',
68
+ className: '<%= Emitter::slugify(header) %>',
69
+ render: DataTable.render.text()
70
+ },
67
71
  <% end -%>
68
72
  <% end -%>
69
73
  ]
@@ -0,0 +1,7 @@
1
+ #offCanvas {
2
+ background: #d30001 !important;
3
+ }
4
+ h2 {
5
+ background: #d30001 !important;
6
+ }
7
+
@@ -11,12 +11,14 @@
11
11
  <%= data[:total_days_in_analysis] %>
12
12
  <span class="stats-list-label">Days</span>
13
13
  </li>
14
- <li class="stats-list-positive">
15
- <%= data[:total_size] %>
16
- <span class="stats-list-label">Data Transferred</span>
17
- </li>
14
+ <% if data[:total_size] %>
15
+ <li class="stats-list-positive">
16
+ <%= data[:total_size] %>
17
+ <span class="stats-list-label">Data Transferred</span>
18
+ </li>
19
+ <% end %>
18
20
  <li class="stats-list-negative">
19
- <%= data[:total_hits] %>
21
+ <%= data[:events] %>
20
22
  <span class="stats-list-label">Hits</span>
21
23
  </li>
22
24
  <li class="stats-list-negative">
@@ -24,12 +26,12 @@
24
26
  <span class="stats-list-label">Unique Visits</span>
25
27
  </li>
26
28
  <li class="stats-list-negative">
27
- <% days = data[:last_day_in_analysis] - data[:first_day_in_analysis] %>
29
+ <% days = data[:total_days_in_analysis] %>
28
30
  <%= days > 0 ? "%d" % (data[:total_unique_visits] / days) : "N/A" %>
29
31
  <span class="stats-list-label">Unique Visits / Day</span>
30
32
  </li>
31
33
  <li class="stats-list-negative">
32
- <%= data[:total_unique_visits] != 0 ? data[:total_hits] / data[:total_unique_visits] : "N/A" %>
34
+ <%= data[:total_unique_visits] != 0 ? data[:events] / data[:total_unique_visits] : "N/A" %>
33
35
  <span class="stats-list-label">Hits / Unique Visitor</span>
34
36
  </li>
35
37
  </ul>
@@ -3,10 +3,10 @@ table = Terminal::Table.new rows: [
3
3
  ["From", data[:first_day_in_analysis]],
4
4
  ["To", data[:last_day_in_analysis]],
5
5
  ["Days", data[:total_days_in_analysis]],
6
- ["Hits", data[:total_hits]],
6
+ ["Hits", data[:events]],
7
7
  ["Unique Visits", data[:total_unique_visits]],
8
8
  ["Unique Visits / Day", data[:total_days_in_analysis] > 0 ? "%d" % (data[:total_unique_visits] / data[:total_days_in_analysis]) : "N/A"],
9
- ["Hits/Unique Visitor", data[:total_unique_visits] != 0 ? data[:total_hits] / data[:total_unique_visits] : "N/A"]
9
+ ["Hits/Unique Visitor", data[:total_unique_visits] != 0 ? data[:events] / data[:total_unique_visits] : "N/A"]
10
10
  ]
11
11
  table.style = { border_i: "|" }
12
12
  table
@@ -1,42 +1,22 @@
1
1
  <!doctype html>
2
2
  <html class="no-js" lang="en">
3
3
  <head>
4
- <title>
5
- <%= options[:title] || "Log Sense: #{data[:filenames].empty? ? "stdin" : data[:filenames].join(", ")}" %>
6
- </title>
4
+ <title><%= @report_title %></title>
7
5
 
8
6
  <meta charset="utf-8" />
9
7
  <meta http-equiv="x-ua-compatible" content="ie=edge">
10
8
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
11
9
 
12
10
  <meta name="author" content="Shair.Tech">
13
- <meta name="description" content="Analysis of <%= data[:filenames].join(', ') %>">
11
+ <meta name="description"
12
+ content="Analysis of <%= @data[:filenames].join(', ') %>">
14
13
 
15
- <link rel="preconnect" href="https://fonts.googleapis.com">
16
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
17
- <link href="https://fonts.googleapis.com/css2?family=Fira+Sans:wght@300;700&display=swap" rel="stylesheet">
18
-
19
- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/foundicons/3.0.0/foundation-icons.min.css">
20
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/foundation-sites@6.7.4/dist/css/foundation.min.css">
21
- <link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/zf/dt-1.11.3/datatables.min.css"/>
22
-
23
- <script type="text/javascript" src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
24
- <script type="text/javascript" src="https://cdn.datatables.net/v/zf/dt-1.11.3/datatables.min.js"></script>
25
-
26
- <script src="https://cdn.jsdelivr.net/npm/vega@5.21.0"></script>
27
- <script src="https://cdn.jsdelivr.net/npm/vega-lite@5.2.0"></script>
28
- <script src="https://cdn.jsdelivr.net/npm/vega-embed@6.20.2"></script>
14
+ <%= render "cdn_links.html.erb" %>
29
15
 
30
16
  <style>
31
17
  <%= render "stylesheet.css" %>
32
- </style>
33
- <style>
34
- #offCanvas {
35
- background: #d30001 !important;
36
- }
37
- h2 {
38
- background: #d30001 !important;
39
- }
18
+
19
+ <%= render @format_specific_css %>
40
20
  </style>
41
21
  </head>
42
22
 
@@ -44,7 +24,7 @@
44
24
  <div class="off-canvas-wrapper">
45
25
  <div class="off-canvas position-left" id="offCanvas" data-off-canvas>
46
26
  <%= render "navigation.html.erb",
47
- menus: Emitter::rails_report_specification.map { |x| x[:title] } %>
27
+ menus: @reports.map { |x| x[:title] } %>
48
28
  </div>
49
29
  <div class="off-canvas-content" data-off-canvas-content>
50
30
  <button id="toggle-button" type="button" data-toggle="offCanvas">
@@ -52,24 +32,29 @@
52
32
  </button>
53
33
 
54
34
  <section class="main-section grid-container fluid">
55
- <h1><%= options[:title] || "Log Sense Rails Log Report" %></h1>
35
+ <h1><%= options[:title] || "Log Sense #{@report_title} Log Report" %></h1>
56
36
 
57
- <p><b>Input File(s):</b> <%= data[:filenames].empty? ? "stdin" : data[:filenames].join(", ") %></p>
37
+ <p>
38
+ <b>Input File(s):</b>
39
+ <%= @data[:filenames].empty? ? "stdin" : @data[:filenames].join(", ") %>
40
+ </p>
58
41
 
59
42
  <div class="grid-x grid-padding-x">
60
43
  <article class="small-12 large-6 cell">
61
44
  <h2 id="<%= Emitter::slugify "Summary" %>">Summary</h2>
62
- <%= render "summary.html.erb", data: data %>
45
+ <%= render "summary.html.erb", data: @data %>
63
46
  </article>
64
47
 
65
48
  <article class="small-12 large-6 cell">
66
49
  <h2 id="<%= Emitter::slugify "Summary" %>">Log Structure</h2>
67
- <%= render "log_structure.html.erb", data: data %>
50
+ <%= render "log_structure.html.erb", data: @data %>
68
51
  </article>
69
52
 
70
53
  <% @reports.each_with_index do |report, index| %>
71
54
  <article class="cell <%= report[:col] || "small-12 large-6" %>" >
72
- <h2 id="<%= Emitter::slugify report[:title] %>"><%= report[:title] %></h2>
55
+ <h2 id="<%= Emitter::slugify report[:title] %>">
56
+ <%= report[:title] %>
57
+ </h2>
73
58
 
74
59
  <%= render "report_data.html.erb", report: report, index: index %>
75
60
  <% if report[:vega_spec] %>
@@ -95,21 +80,18 @@
95
80
 
96
81
  <article class="small-12 large-6 cell">
97
82
  <h2 id="<%= Emitter::slugify "Command Invocation" %>">Command Invocation</h2>
98
- <%= render "command_invocation.html.erb", data: data, options: options %>
83
+ <%= render "command_invocation.html.erb", data: @data, options: options %>
99
84
  </article>
100
85
 
101
86
  <article class="small-12 large-6 cell">
102
87
  <h2 id="<%= Emitter::slugify "Performance" %>">Performance</h2>
103
- <%= render "performance.html.erb", data: data %>
88
+ <%= render "performance.html.erb", data: @data %>
104
89
  </article>
105
90
  </div>
106
91
  </section>
107
92
  </div>
108
93
  </div>
109
94
 
110
- <script type="text/javascript" src="js/vendor/what-input.js"></script>
111
- <script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
112
- <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/foundation-sites@6.7.4/dist/js/foundation.min.js" crossorigin="anonymous"></script>
113
95
  <script>
114
96
  $(document).foundation();
115
97
 
@@ -1,4 +1,4 @@
1
- * Apache Log Analysis
1
+ * <%= @report_title %> Log Analysis
2
2
 
3
3
  <%= render "warning.txt.erb" %>
4
4
 
@@ -1,3 +1,3 @@
1
1
  module LogSense
2
- VERSION = "1.5.2"
2
+ VERSION = "1.6.0"
3
3
  end
data/lib/log_sense.rb CHANGED
@@ -1,9 +1,19 @@
1
- require 'log_sense/version'
2
- require 'log_sense/options_parser'
3
- require 'log_sense/apache_log_line_parser'
4
- require 'log_sense/apache_log_parser'
5
- require 'log_sense/apache_data_cruncher'
6
- require 'log_sense/rails_log_parser'
7
- require 'log_sense/rails_data_cruncher'
8
- require 'log_sense/ip_locator'
9
- require 'log_sense/emitter'
1
+ require "log_sense/version"
2
+
3
+ require "log_sense/options_parser"
4
+ require "log_sense/options_checker"
5
+
6
+ require "log_sense/apache_log_parser"
7
+ require "log_sense/rails_log_parser"
8
+
9
+ require "log_sense/aggregator"
10
+ require "log_sense/apache_aggregator"
11
+ require "log_sense/rails_aggregator"
12
+
13
+ require "log_sense/ip_locator"
14
+
15
+ require "log_sense/report_shaper"
16
+ require "log_sense/apache_report_shaper"
17
+ require "log_sense/rails_report_shaper"
18
+
19
+ require "log_sense/emitter"
data/log_sense.gemspec CHANGED
@@ -8,7 +8,7 @@ Gem::Specification.new do |spec|
8
8
 
9
9
  spec.summary = %q{Generate analytics for Apache and Rails log file.}
10
10
  spec.description = %q{Generate analytics in HTML, txt, and SQLite format for Apache and Rails log files.}
11
- spec.homepage = "https://github.com/shair-tech/log_sense/log_sense"
11
+ spec.homepage = "https://github.com/shair-tech/log_sense/"
12
12
  spec.license = "MIT"
13
13
  spec.required_ruby_version = Gem::Requirement.new(">= 2.6.9")
14
14
 
Binary file