log_sense 1.4.1 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.org +15 -0
- data/Gemfile.lock +4 -4
- data/README.org +20 -7
- data/Rakefile +17 -3
- data/exe/log_sense +13 -3
- data/ip_locations/dbip-country-lite.sqlite3 +0 -0
- data/lib/log_sense/apache_data_cruncher.rb +17 -19
- data/lib/log_sense/apache_log_line_parser.rb +1 -1
- data/lib/log_sense/apache_log_parser.rb +1 -1
- data/lib/log_sense/emitter.rb +72 -15
- data/lib/log_sense/ip_locator.rb +26 -19
- data/lib/log_sense/options_parser.rb +5 -0
- data/lib/log_sense/rails_data_cruncher.rb +3 -0
- data/lib/log_sense/templates/_navigation.html.erb +21 -0
- data/lib/log_sense/templates/_output_table.txt.erb +3 -2
- data/lib/log_sense/templates/_summary.html.erb +1 -1
- data/lib/log_sense/templates/_summary.txt.erb +1 -1
- data/lib/log_sense/templates/_warning.txt.erb +1 -0
- data/lib/log_sense/templates/apache.html.erb +14 -134
- data/lib/log_sense/templates/apache.txt.erb +2 -15
- data/lib/log_sense/templates/rails.html.erb +13 -37
- data/lib/log_sense/templates/rails.txt.erb +5 -6
- data/lib/log_sense/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '0128717b2ba709bc5dfbb7b755762a757c575ad4307159910cc894aaa3b88f42'
|
4
|
+
data.tar.gz: e05054b8eee79a439f5b077e60bc0d95a3e7706c550853333d7d631c458abf91
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9d9e3dc495f7479292ae96d1bf6298f531258cae74df47ff705aea9613880b0d50aa6a19328f70685484ad1c606bd12a8fb7c87632fe5c9cbefefe4893d9bb4d
|
7
|
+
data.tar.gz: b417049bcc119ed82ab4c33d007e15804ba85b2485308ca28448fba512814fc5e8b8b215310bfb5c8108fcdc87292322eefc6826b18718fcbc7fced29eea77cb
|
data/CHANGELOG.org
CHANGED
@@ -2,6 +2,21 @@
|
|
2
2
|
#+AUTHOR: Adolfo Villafiorita
|
3
3
|
#+STARTUP: showall
|
4
4
|
|
5
|
+
* 1.5.0
|
6
|
+
|
7
|
+
- [User] Present Unique Visits / day as integer
|
8
|
+
- [User] Added Country and Streaks report for rails
|
9
|
+
- [User] Changed Streak report in Apache
|
10
|
+
|
11
|
+
- [Gem] Updated DBIP
|
12
|
+
- [Gem] Updated Bundle
|
13
|
+
|
14
|
+
- [Code] Refactored all reports, so that they are specified
|
15
|
+
in the same way
|
16
|
+
- [Code] Refactor warning message in textual reports
|
17
|
+
- [Code] Build HTML menu for report specification
|
18
|
+
- [Code] Various refactoring passes on the code
|
19
|
+
|
5
20
|
* 1.4.1
|
6
21
|
|
7
22
|
- [User] New textual report for Apache
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
log_sense (1.
|
4
|
+
log_sense (1.4.2)
|
5
5
|
browser
|
6
6
|
ipaddr
|
7
7
|
iso_country_codes
|
@@ -13,9 +13,9 @@ GEM
|
|
13
13
|
specs:
|
14
14
|
browser (5.3.1)
|
15
15
|
byebug (11.1.3)
|
16
|
-
ipaddr (1.2.
|
16
|
+
ipaddr (1.2.4)
|
17
17
|
iso_country_codes (0.7.8)
|
18
|
-
minitest (5.
|
18
|
+
minitest (5.15.0)
|
19
19
|
rake (12.3.3)
|
20
20
|
sqlite3 (1.4.2)
|
21
21
|
terminal-table (3.0.2)
|
@@ -32,4 +32,4 @@ DEPENDENCIES
|
|
32
32
|
rake (~> 12.0)
|
33
33
|
|
34
34
|
BUNDLED WITH
|
35
|
-
2.
|
35
|
+
2.3.3
|
data/README.org
CHANGED
@@ -19,8 +19,6 @@ LogSense reports the following data:
|
|
19
19
|
- OS, browsers, and devices
|
20
20
|
- IP Country location, thanks to the DPIP lite country DB
|
21
21
|
- Streaks: resources accessed by a given IP over time
|
22
|
-
- Potential attacks: access to resources which are not meant to be
|
23
|
-
served by a web server serving static websites
|
24
22
|
- Performance of Rails requests
|
25
23
|
|
26
24
|
Filters from the command line allow to analyze specific periods and
|
@@ -33,6 +31,18 @@ And, of course, the compulsory screenshot:
|
|
33
31
|
#+ATTR_HTML: :width 80%
|
34
32
|
[[file:./apache-screenshot.png]]
|
35
33
|
|
34
|
+
|
35
|
+
* An important word of warning
|
36
|
+
|
37
|
+
[[https://owasp.org/www-community/attacks/Log_Injection][Log poisoning]] is a technique whereby attackers send requests with invalidated
|
38
|
+
user input to forge log entries or inject malicious content into the logs.
|
39
|
+
|
40
|
+
log_sense sanitizes entries of HTML reports, to try and protect from log
|
41
|
+
poisoning. *Log entries and URLs in SQLite3, however, are not sanitized*:
|
42
|
+
they are stored and read from the log. This is not, in general, an issue,
|
43
|
+
unless you use the data from SQLite in environments in which URLs can be
|
44
|
+
opened or code executed.
|
45
|
+
|
36
46
|
* Motivation
|
37
47
|
|
38
48
|
LogSense moves along the lines of tools such as [[https://goaccess.io/][GoAccess]] (which
|
@@ -54,6 +64,7 @@ generated files are then made available on a private area on the web.
|
|
54
64
|
gem install log_sense
|
55
65
|
#+end_src
|
56
66
|
|
67
|
+
|
57
68
|
* Usage
|
58
69
|
|
59
70
|
#+begin_src bash :results raw output :wrap example
|
@@ -73,10 +84,11 @@ generated files are then made available on a private area on the web.
|
|
73
84
|
-w, --width=WIDTH Maximum width of URL and description columns in text reports
|
74
85
|
-c, --crawlers=POLICY Decide what to do with crawlers (applies to Apache Logs)
|
75
86
|
-n, --no-selfpolls Ignore self poll entries (requests from ::1; applies to Apache Logs)
|
87
|
+
--verbose Inform about progress (prints to STDERR)
|
76
88
|
-v, --version Prints version information
|
77
89
|
-h, --help Prints this help
|
78
90
|
|
79
|
-
This is version 1.
|
91
|
+
This is version 1.5.0
|
80
92
|
|
81
93
|
Output formats
|
82
94
|
rails parsing can produce the following outputs:
|
@@ -96,6 +108,7 @@ log_sense -f apache -i access.log -t txt > access-data.txt
|
|
96
108
|
log_sense -f rails -i production.log -t html -o performance.txt
|
97
109
|
#+end_example
|
98
110
|
|
111
|
+
|
99
112
|
* Change Log
|
100
113
|
|
101
114
|
See the [[file:CHANGELOG.org][CHANGELOG]] file.
|
@@ -110,8 +123,8 @@ Concerning the outputs:
|
|
110
123
|
- HTML reports use [[https://get.foundation/][Zurb Foundation]], [[https://www.datatables.net/][Data Tables]], and [[https://vega.github.io/vega-lite/][Vega Light]], which
|
111
124
|
are all downloaded from a CDN
|
112
125
|
- The textual format is compatible with [[https://orgmode.org/][Org Mode]] and can be further
|
113
|
-
processed to any format [[https://orgmode.org/][Org Mode]] can be exported to
|
114
|
-
and PDF
|
126
|
+
processed to any format [[https://orgmode.org/][Org Mode]] can be exported to, including HTML
|
127
|
+
and PDF, with the word of warning in the section above.
|
115
128
|
|
116
129
|
* Author and Contributors
|
117
130
|
|
@@ -119,8 +132,8 @@ Concerning the outputs:
|
|
119
132
|
|
120
133
|
* Known Bugs
|
121
134
|
|
122
|
-
No known bugs; an unknown number of unknown bugs.
|
123
|
-
|
135
|
+
No known bugs; an unknown number of unknown bugs. (See the open issues for
|
136
|
+
the known bugs.)
|
124
137
|
|
125
138
|
* License
|
126
139
|
|
data/Rakefile
CHANGED
@@ -9,7 +9,21 @@ end
|
|
9
9
|
require_relative './lib/log_sense/ip_locator.rb'
|
10
10
|
|
11
11
|
desc "Convert Geolocation DB to sqlite"
|
12
|
-
task :dbip_to_sqlite3, [:
|
13
|
-
filename = args[:
|
14
|
-
|
12
|
+
task :dbip_to_sqlite3, [:year_month] do |tasks, args|
|
13
|
+
filename = "./ip_locations/dbip-country-lite-#{args[:year_month]}.csv"
|
14
|
+
|
15
|
+
if !File.exist? filename
|
16
|
+
puts "Error. Could not find: #{filename}"
|
17
|
+
puts
|
18
|
+
puts 'I see the following files:'
|
19
|
+
puts Dir.glob("ip_locations/dbip-country-lite*").map { |x| "- #{x}\n" }
|
20
|
+
puts ''
|
21
|
+
puts '1. Download (if necessary) a more recent version from: https://db-ip.com/db/download/ip-to-country-lite'
|
22
|
+
puts '2. Save downloaded file to ip_locations/'
|
23
|
+
puts '3. Relaunch with YYYY-MM'
|
24
|
+
|
25
|
+
exit
|
26
|
+
else
|
27
|
+
LogSense::IpLocator::dbip_to_sqlite filename
|
28
|
+
end
|
15
29
|
end
|
data/exe/log_sense
CHANGED
@@ -12,7 +12,7 @@ require 'log_sense.rb'
|
|
12
12
|
@output_file = @options[:output_file]
|
13
13
|
|
14
14
|
if ARGV.map { |x| File.exist?(x) }.include?(false)
|
15
|
-
|
15
|
+
$stderr.puts "Error: input file(s) '#{ARGV.reject { |x| File.exist(x) }.join(', ')}' do not exist"
|
16
16
|
exit 1
|
17
17
|
end
|
18
18
|
@input_files = ARGV.empty? ? [$stdin] : ARGV.map { |x| File.open(x, 'r') }
|
@@ -31,35 +31,45 @@ when 'rails'
|
|
31
31
|
parser_klass = LogSense::RailsLogParser
|
32
32
|
cruncher_klass = LogSense::RailsDataCruncher
|
33
33
|
else
|
34
|
-
|
34
|
+
$stderr.puts "Error: input format #{@options[:input_format]} not understood."
|
35
35
|
exit 1
|
36
36
|
end
|
37
37
|
|
38
|
+
$stderr.puts "Parsing input files..." if @options[:verbose]
|
38
39
|
@db = parser_klass.parse @input_files
|
39
40
|
|
40
41
|
if @options[:output_format] == 'sqlite'
|
42
|
+
$stderr.puts "Saving to SQLite3..." if @options[:verbose]
|
41
43
|
ddb = SQLite3::Database.new(@output_file || 'db.sqlite3')
|
42
44
|
b = SQLite3::Backup.new(ddb, 'main', @db, 'main')
|
43
45
|
b.step(-1) #=> DONE
|
44
46
|
b.finish
|
45
47
|
else
|
48
|
+
$stderr.puts "Aggregating data..." if @options[:verbose]
|
46
49
|
@data = cruncher_klass.crunch @db, @options
|
50
|
+
|
51
|
+
$stderr.puts "Geolocating..." if @options[:verbose]
|
47
52
|
@data = LogSense::IpLocator.geolocate @data
|
48
53
|
|
54
|
+
$stderr.puts "Grouping by country..." if @options[:verbose]
|
55
|
+
country_col = @data[:ips][0].size - 1
|
56
|
+
@data[:countries] = @data[:ips].group_by { |x| x[country_col] }
|
57
|
+
|
49
58
|
@ended_at = Time.now
|
50
59
|
@duration = @ended_at - @started_at
|
51
60
|
|
52
61
|
@data = @data.merge({
|
53
62
|
command: @command_line,
|
63
|
+
filenames: ARGV,
|
54
64
|
log_files: @input_files,
|
55
65
|
started_at: @started_at,
|
56
66
|
ended_at: @ended_at,
|
57
67
|
duration: @duration,
|
58
68
|
width: @options[:width]
|
59
69
|
})
|
60
|
-
|
61
70
|
#
|
62
71
|
# Emit Output
|
63
72
|
#
|
73
|
+
$stderr.puts "Emitting..." if @options[:verbose]
|
64
74
|
puts LogSense::Emitter.emit @data, @options
|
65
75
|
end
|
Binary file
|
@@ -17,15 +17,15 @@ module LogSense
|
|
17
17
|
@total_days = 0
|
18
18
|
@total_days = (@last_day - @first_day).to_i if @first_day && @last_day
|
19
19
|
|
20
|
-
@source_files = db.execute
|
20
|
+
@source_files = db.execute 'SELECT distinct(source_file) from LogLine'
|
21
21
|
|
22
|
-
@log_size = db.execute
|
22
|
+
@log_size = db.execute 'SELECT count(datetime) from LogLine'
|
23
23
|
@log_size = @log_size[0][0]
|
24
24
|
|
25
25
|
@selfpolls_size = db.execute "SELECT count(datetime) from LogLine where ip == '::1'"
|
26
26
|
@selfpolls_size = @selfpolls_size[0][0]
|
27
27
|
|
28
|
-
@crawlers_size = db.execute
|
28
|
+
@crawlers_size = db.execute 'SELECT count(datetime) from LogLine where bot == 1'
|
29
29
|
@crawlers_size = @crawlers_size[0][0]
|
30
30
|
|
31
31
|
@first_day_requested = options[:from_date]
|
@@ -35,7 +35,7 @@ module LogSense
|
|
35
35
|
@last_day_in_analysis = date_intersect options[:to_date], @last_day, :min
|
36
36
|
|
37
37
|
@total_days_in_analysis = 0
|
38
|
-
if @first_day_in_analysis
|
38
|
+
if @first_day_in_analysis && @last_day_in_analysis
|
39
39
|
@total_days_in_analysis = (@last_day_in_analysis - @first_day_in_analysis).to_i
|
40
40
|
end
|
41
41
|
|
@@ -45,24 +45,24 @@ module LogSense
|
|
45
45
|
filter = [
|
46
46
|
(options[:from_date] ? "date(datetime) >= '#{options[:from_date]}'" : nil),
|
47
47
|
(options[:to_date] ? "date(datetime) <= '#{options[:to_date]}'" : nil),
|
48
|
-
(options[:only_crawlers] ?
|
49
|
-
(options[:ignore_crawlers] ?
|
48
|
+
(options[:only_crawlers] ? 'bot == 1' : nil),
|
49
|
+
(options[:ignore_crawlers] ? 'bot == 0' : nil),
|
50
50
|
(options[:no_selfpolls] ? "ip != '::1'" : nil),
|
51
|
-
|
51
|
+
'true'
|
52
52
|
].compact.join " and "
|
53
53
|
|
54
54
|
mega = 1024 * 1024
|
55
55
|
giga = mega * 1024
|
56
56
|
tera = giga * 1024
|
57
|
-
|
57
|
+
|
58
58
|
# in alternative to sum(size)
|
59
59
|
human_readable_size = <<-EOS
|
60
|
-
CASE
|
60
|
+
CASE
|
61
61
|
WHEN sum(size) < 1024 THEN sum(size) || ' B'
|
62
62
|
WHEN sum(size) >= 1024 AND sum(size) < (#{mega}) THEN ROUND((CAST(sum(size) AS REAL) / 1024), 2) || ' KB'
|
63
63
|
WHEN sum(size) >= (#{mega}) AND sum(size) < (#{giga}) THEN ROUND((CAST(sum(size) AS REAL) / (#{mega})), 2) || ' MB'
|
64
64
|
WHEN sum(size) >= (#{giga}) AND sum(size) < (#{tera}) THEN ROUND((CAST(sum(size) AS REAL) / (#{giga})), 2) || ' GB'
|
65
|
-
WHEN sum(size) >= (#{tera}) THEN ROUND((CAST(sum(size) AS REAL) / (#{tera})), 2) || ' TB'
|
65
|
+
WHEN sum(size) >= (#{tera}) THEN ROUND((CAST(sum(size) AS REAL) / (#{tera})), 2) || ' TB'
|
66
66
|
END AS size
|
67
67
|
EOS
|
68
68
|
|
@@ -117,20 +117,19 @@ module LogSense
|
|
117
117
|
|
118
118
|
@ips = db.execute "SELECT ip, count(ip), count(distinct(unique_visitor)), #{human_readable_size} from LogLine where #{filter} group by ip order by count(ip) desc limit #{options[:limit]}"
|
119
119
|
|
120
|
-
@streaks = db.execute
|
120
|
+
@streaks = db.execute 'SELECT ip, substr(datetime, 1, 10), path from LogLine order by ip, datetime'
|
121
121
|
data = {}
|
122
122
|
|
123
|
-
|
124
|
-
var_as_symbol = variable.to_s[1
|
125
|
-
data[var_as_symbol] =
|
123
|
+
instance_variables.each do |variable|
|
124
|
+
var_as_symbol = variable.to_s[1..].to_sym
|
125
|
+
data[var_as_symbol] = instance_variable_get(variable)
|
126
126
|
end
|
127
|
+
|
127
128
|
data
|
128
129
|
end
|
129
130
|
|
130
|
-
|
131
|
-
|
132
|
-
def self.date_intersect date1, date2, method
|
133
|
-
if date1 and date2
|
131
|
+
def self.date_intersect(date1, date2, method)
|
132
|
+
if date1 && date2
|
134
133
|
[date1, date2].send(method)
|
135
134
|
elsif date1
|
136
135
|
date1
|
@@ -140,4 +139,3 @@ module LogSense
|
|
140
139
|
end
|
141
140
|
end
|
142
141
|
end
|
143
|
-
|
data/lib/log_sense/emitter.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
+
# coding: utf-8
|
1
2
|
require 'terminal-table'
|
2
3
|
require 'json'
|
3
4
|
require 'erb'
|
4
5
|
require 'ostruct'
|
5
|
-
|
6
6
|
module LogSense
|
7
7
|
#
|
8
8
|
# Emit Data
|
@@ -31,9 +31,7 @@ module LogSense
|
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
|
35
|
-
|
36
|
-
def self.render(template, vars)
|
34
|
+
def self.render(template, vars = {})
|
37
35
|
@template = File.join(File.dirname(__FILE__), 'templates', "_#{template}")
|
38
36
|
erb_template = File.read @template
|
39
37
|
ERB.new(erb_template).result(OpenStruct.new(vars).instance_eval { binding })
|
@@ -42,11 +40,11 @@ module LogSense
|
|
42
40
|
def self.escape_javascript(string)
|
43
41
|
js_escape_map = {
|
44
42
|
'<' => '<',
|
45
|
-
'</' => '<
|
46
|
-
'\\' => '\\\\',
|
43
|
+
'</' => '</',
|
47
44
|
'\r\n' => '\\r\\n',
|
48
45
|
'\n' => '\\n',
|
49
46
|
'\r' => '\\r',
|
47
|
+
'\\' => ' \\\\',
|
50
48
|
'"' => ' \\"',
|
51
49
|
"'" => " \\'",
|
52
50
|
'`' => ' \\`',
|
@@ -73,7 +71,9 @@ module LogSense
|
|
73
71
|
# - width width to set
|
74
72
|
def self.shorten(data, heading, width)
|
75
73
|
# indexes of columns which have to be shortened
|
76
|
-
|
74
|
+
keywords = %w[URL Referers Description Path]
|
75
|
+
to_shorten = keywords.map { |x| heading.index x }.compact
|
76
|
+
|
77
77
|
if width.nil? || to_shorten.empty? || data[0].nil?
|
78
78
|
data
|
79
79
|
else
|
@@ -96,7 +96,7 @@ module LogSense
|
|
96
96
|
# column_alignment: specification of column alignments (works for txt reports)
|
97
97
|
# vega_spec: specifications for Vega output
|
98
98
|
# datatable_options: specific options for datatable
|
99
|
-
def self.apache_report_specification(data)
|
99
|
+
def self.apache_report_specification(data = {})
|
100
100
|
[
|
101
101
|
{ title: 'Daily Distribution',
|
102
102
|
header: %w[Day DOW Hits Visits Size],
|
@@ -326,6 +326,19 @@ module LogSense
|
|
326
326
|
column_alignment: %i[left right right right left],
|
327
327
|
rows: data[:ips]
|
328
328
|
},
|
329
|
+
{
|
330
|
+
title: 'Countries',
|
331
|
+
header: %w[Country Hits Visits IPs],
|
332
|
+
column_alignment: %i[left right right left],
|
333
|
+
rows: data[:countries]&.map do |k, v|
|
334
|
+
[
|
335
|
+
k,
|
336
|
+
v.map { |x| x[1] }.inject(&:+),
|
337
|
+
v.map { |x| x[2] }.inject(&:+),
|
338
|
+
v.map { |x| x[0] }.join(' ')
|
339
|
+
]
|
340
|
+
end
|
341
|
+
},
|
329
342
|
{
|
330
343
|
title: 'Referers',
|
331
344
|
header: %w[Referers Hits Visits Size],
|
@@ -333,10 +346,27 @@ module LogSense
|
|
333
346
|
rows: data[:referers],
|
334
347
|
col: 'small-12 cell'
|
335
348
|
},
|
349
|
+
{
|
350
|
+
title: 'Streaks',
|
351
|
+
report: :html,
|
352
|
+
header: ['IP', 'Date', 'Total HTML', 'Total Other', 'HTML', 'Other'],
|
353
|
+
column_alignment: %i[left left right right left left],
|
354
|
+
rows: data[:streaks]&.group_by { |x| [x[0], x[1]] }&.map do |k, v|
|
355
|
+
[
|
356
|
+
k[0],
|
357
|
+
k[1],
|
358
|
+
v.map { |x| x[2] }.compact.select { |x| x.match(/\.html?$/) }.size,
|
359
|
+
v.map { |x| x[2] }.compact.reject { |x| x.match(/\.html?$/) }.size,
|
360
|
+
v.map { |x| x[2] }.compact.select { |x| x.match(/\.html?$/) }.join(' ■ '),
|
361
|
+
v.map { |x| x[2] }.compact.reject { |x| x.match(/\.html?$/) }.join(' ■ ')
|
362
|
+
]
|
363
|
+
end,
|
364
|
+
col: 'small-12 cell'
|
365
|
+
}
|
336
366
|
]
|
337
367
|
end
|
338
368
|
|
339
|
-
def self.rails_report_specification(data)
|
369
|
+
def self.rails_report_specification(data = {})
|
340
370
|
[
|
341
371
|
{
|
342
372
|
title: "Daily Distribution",
|
@@ -469,27 +499,54 @@ module LogSense
|
|
469
499
|
header: %w[Date IP URL Description Log ID],
|
470
500
|
column_alignment: %i[left left left left left],
|
471
501
|
rows: data[:fatal],
|
472
|
-
col:
|
502
|
+
col: 'small-12 cell'
|
473
503
|
},
|
474
504
|
{
|
475
|
-
title:
|
505
|
+
title: 'Internal Server Errors',
|
476
506
|
header: %w[Date Status IP URL Description Log ID],
|
477
507
|
column_alignment: %i[left left left left left left],
|
478
508
|
rows: data[:internal_server_error],
|
479
|
-
col:
|
509
|
+
col: 'small-12 cell'
|
480
510
|
},
|
481
511
|
{
|
482
|
-
title:
|
512
|
+
title: 'Errors',
|
483
513
|
header: %w[Log ID Context Description Count],
|
484
514
|
column_alignment: %i[left left left left],
|
485
515
|
rows: data[:error],
|
486
|
-
col:
|
516
|
+
col: 'small-12 cell'
|
487
517
|
},
|
488
518
|
{
|
489
|
-
title:
|
519
|
+
title: 'IPs',
|
490
520
|
header: %w[IPs Hits Country],
|
491
521
|
column_alignment: %i[left right left],
|
492
522
|
rows: data[:ips]
|
523
|
+
},
|
524
|
+
{
|
525
|
+
title: 'Countries',
|
526
|
+
header: %w[Country Hits IPs],
|
527
|
+
column_alignment: %i[left right left],
|
528
|
+
rows: data[:countries]&.map do |k, v|
|
529
|
+
[
|
530
|
+
k,
|
531
|
+
v.map { |x| x[1] }.inject(&:+),
|
532
|
+
v.map { |x| x[0] }.join(' ■ ')
|
533
|
+
]
|
534
|
+
end
|
535
|
+
},
|
536
|
+
{
|
537
|
+
title: 'Streaks',
|
538
|
+
report: :html,
|
539
|
+
header: %w[IP Date Total Resources],
|
540
|
+
column_alignment: %i[left left right right left left],
|
541
|
+
rows: data[:streaks]&.group_by { |x| [x[0], x[1]] }&.map do |k, v|
|
542
|
+
[
|
543
|
+
k[0],
|
544
|
+
k[1],
|
545
|
+
v.size,
|
546
|
+
v.map { |x| x[2] }.join(' ■ ')
|
547
|
+
]
|
548
|
+
end,
|
549
|
+
col: 'small-12 cell'
|
493
550
|
}
|
494
551
|
]
|
495
552
|
end
|
data/lib/log_sense/ip_locator.rb
CHANGED
@@ -4,19 +4,22 @@ require 'ipaddr'
|
|
4
4
|
require 'iso_country_codes'
|
5
5
|
|
6
6
|
module LogSense
|
7
|
+
#
|
8
|
+
# Populate table of IP Locations from dbip-country-lite
|
9
|
+
#
|
7
10
|
module IpLocator
|
8
|
-
DB_FILE = File.join(File.dirname(__FILE__),
|
11
|
+
DB_FILE = File.join(File.dirname(__FILE__), '..', '..', 'ip_locations', 'dbip-country-lite.sqlite3')
|
9
12
|
|
10
|
-
def self.dbip_to_sqlite
|
11
|
-
db = SQLite3::Database.new
|
12
|
-
db.execute
|
13
|
+
def self.dbip_to_sqlite(db_location)
|
14
|
+
db = SQLite3::Database.new ':memory:'
|
15
|
+
db.execute 'CREATE TABLE ip_location (
|
13
16
|
from_ip_n INTEGER,
|
14
17
|
from_ip TEXT,
|
15
18
|
to_ip TEXT,
|
16
19
|
country_code TEXT
|
17
|
-
)
|
20
|
+
)'
|
18
21
|
|
19
|
-
ins = db.prepare
|
22
|
+
ins = db.prepare 'INSERT INTO ip_location(from_ip_n, from_ip, to_ip, country_code) values (?, ?, ?, ?)'
|
20
23
|
CSV.foreach(db_location) do |row|
|
21
24
|
ip = IPAddr.new row[0]
|
22
25
|
ins.execute(ip.to_i, row[0], row[1], row[2])
|
@@ -33,29 +36,33 @@ module LogSense
|
|
33
36
|
SQLite3::Database.new DB_FILE
|
34
37
|
end
|
35
38
|
|
36
|
-
def self.locate_ip
|
37
|
-
return
|
39
|
+
def self.locate_ip(ip, db)
|
40
|
+
return unless ip
|
38
41
|
|
39
|
-
|
40
|
-
res = db.execute "SELECT * FROM ip_location where from_ip_n <= #{ip_n} order by from_ip_n desc limit 1"
|
42
|
+
query = db.prepare 'SELECT * FROM ip_location where from_ip_n <= ? order by from_ip_n desc limit 1'
|
41
43
|
begin
|
42
|
-
|
43
|
-
|
44
|
-
|
44
|
+
ip_n = IPAddr.new(ip).to_i
|
45
|
+
result_set = query.execute ip_n
|
46
|
+
country_code = result_set.map { |x| x[3] }[0]
|
47
|
+
IsoCountryCodes.find(country_code).name
|
48
|
+
rescue IPAddr::InvalidAddressError
|
49
|
+
'INVALID IP'
|
50
|
+
rescue IsoCountryCodes::UnknownCodeError
|
51
|
+
country_code
|
45
52
|
end
|
46
53
|
end
|
47
54
|
|
48
55
|
#
|
49
56
|
# add country code to data[:ips]
|
50
57
|
#
|
51
|
-
def self.geolocate
|
52
|
-
@location_db = IpLocator
|
53
|
-
|
54
|
-
|
55
|
-
|
58
|
+
def self.geolocate(data)
|
59
|
+
@location_db = IpLocator.load_db
|
60
|
+
|
61
|
+
data[:ips].each do |line|
|
62
|
+
country_code = IpLocator.locate_ip line[0], @location_db
|
63
|
+
line << country_code
|
56
64
|
end
|
57
65
|
data
|
58
66
|
end
|
59
|
-
|
60
67
|
end
|
61
68
|
end
|
@@ -59,6 +59,10 @@ module LogSense
|
|
59
59
|
args[:no_selfpoll] = true
|
60
60
|
end
|
61
61
|
|
62
|
+
opts.on('--verbose', 'Inform about progress (prints to STDERR)') do
|
63
|
+
args[:verbose] = true
|
64
|
+
end
|
65
|
+
|
62
66
|
opts.on('-v', '--version', 'Prints version information') do
|
63
67
|
puts "log_sense version #{LogSense::VERSION}"
|
64
68
|
puts 'Copyright (C) 2021 Shair.Tech'
|
@@ -96,6 +100,7 @@ module LogSense
|
|
96
100
|
args[:ignore_crawlers] ||= false
|
97
101
|
args[:only_crawlers] ||= false
|
98
102
|
args[:no_selfpoll] ||= false
|
103
|
+
args[:verbose] ||= false
|
99
104
|
|
100
105
|
args
|
101
106
|
end
|
@@ -104,6 +104,9 @@ module LogSense
|
|
104
104
|
|
105
105
|
@ips = db.execute "SELECT ip, count(ip) from Event where #{filter} group by ip order by count(ip) desc limit #{options[:limit]}"
|
106
106
|
|
107
|
+
@streaks = db.execute 'SELECT ip, substr(started_at, 1, 10), url from Event order by ip, started_at'
|
108
|
+
data = {}
|
109
|
+
|
107
110
|
@performance = db.execute "SELECT distinct(controller), count(controller), printf(\"%.2f\", min(duration_total_ms)), printf(\"%.2f\", avg(duration_total_ms)), printf(\"%.2f\", max(duration_total_ms)) from Event group by controller order by controller"
|
108
111
|
|
109
112
|
@fatal = db.execute ("SELECT strftime(\"%Y-%m-%d %H:%M\", started_at), ip, url, error.description, event.log_id FROM Event JOIN Error ON event.log_id == error.log_id WHERE exit_status == 'F'") || [[]]
|
@@ -0,0 +1,21 @@
|
|
1
|
+
<nav>
|
2
|
+
<h2>Navigation</h2>
|
3
|
+
<ul class="no-bullet">
|
4
|
+
<% (["Summary", "Log Structure"] +
|
5
|
+
menus +
|
6
|
+
["Command Invocation", "Performance"]).each do |item| %>
|
7
|
+
<li class="nav-item">
|
8
|
+
<a href="#<%= Emitter::slugify item %>" data-close>
|
9
|
+
<%= item %>
|
10
|
+
</a>
|
11
|
+
</li>
|
12
|
+
<% end %>
|
13
|
+
</ul>
|
14
|
+
|
15
|
+
<p>
|
16
|
+
Generated by
|
17
|
+
<a href="https://github.com/avillafiorita/log_sense">LogSense</a> <br />
|
18
|
+
on <%= DateTime.now.strftime("%Y-%m-%d %H:%M") %>.<br />
|
19
|
+
<a href='https://db-ip.com'>IP Geolocation by DB-IP</a>
|
20
|
+
</p>
|
21
|
+
</nav>
|
@@ -6,8 +6,9 @@ shortened = Emitter::shorten(report[:rows], report[:header], data[:width])
|
|
6
6
|
table = Terminal::Table.new headings: report[:header], rows: shortened
|
7
7
|
table.style = { border_i: "|" }
|
8
8
|
columns = report[:header].size - 1
|
9
|
-
(0..columns).map
|
10
|
-
|
9
|
+
(0..columns).map do |i|
|
10
|
+
table.align_column(i, report[:column_alignment][i] || :left)
|
11
|
+
end
|
11
12
|
# return it
|
12
13
|
table
|
13
14
|
%>
|
@@ -18,7 +18,7 @@
|
|
18
18
|
</li>
|
19
19
|
<li class="stats-list-negative">
|
20
20
|
<% days = data[:last_day_in_analysis] - data[:first_day_in_analysis] %>
|
21
|
-
<%= days > 0 ? "
|
21
|
+
<%= days > 0 ? "%d" % (data[:total_unique_visits] / days) : "N/A" %>
|
22
22
|
<span class="stats-list-label">Unique Visits / Day</span>
|
23
23
|
</li>
|
24
24
|
<li class="stats-list-negative">
|
@@ -5,7 +5,7 @@ table = Terminal::Table.new rows: [
|
|
5
5
|
["Days", data[:total_days_in_analysis]],
|
6
6
|
["Hits", data[:total_hits]],
|
7
7
|
["Unique Visits", data[:total_unique_visits]],
|
8
|
-
["Unique Visits / Day", data[:total_days_in_analysis] > 0 ? "
|
8
|
+
["Unique Visits / Day", data[:total_days_in_analysis] > 0 ? "%d" % (data[:total_unique_visits] / data[:total_days_in_analysis]) : "N/A"],
|
9
9
|
["Hits/Unique Visitor", data[:total_unique_visits] != 0 ? data[:total_hits] / data[:total_unique_visits] : "N/A"]
|
10
10
|
]
|
11
11
|
table.style = { border_i: "|" }
|
@@ -0,0 +1 @@
|
|
1
|
+
>>>> URLs IN THIS FILE ARE NOT SANITIZED AND MIGHT BE UNSAFE TO OPEN <<<<
|
@@ -1,7 +1,9 @@
|
|
1
1
|
<!doctype html>
|
2
2
|
<html class="no-js" lang="en">
|
3
3
|
<head>
|
4
|
-
<title
|
4
|
+
<title>
|
5
|
+
<%= options[:title] || "Log Sense: #{data[:filenames].empty? ? "stdin" : data[:filenames].join(", ")}" %>
|
6
|
+
</title>
|
5
7
|
|
6
8
|
<meta charset="utf-8" />
|
7
9
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
@@ -150,42 +152,11 @@
|
|
150
152
|
<body>
|
151
153
|
<div class="off-canvas-wrapper">
|
152
154
|
<div class="off-canvas position-left" id="offCanvas" data-off-canvas>
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
<% [ "Summary",
|
157
|
-
"Log Structure",
|
158
|
-
"Daily Distribution",
|
159
|
-
"Time Distribution",
|
160
|
-
"20_ and 30_ on HTML pages",
|
161
|
-
"20_ and 30_ on other resources",
|
162
|
-
"40_ and 50_ on HTML pages",
|
163
|
-
"40_ and 50_ on other Resources",
|
164
|
-
"Statuses",
|
165
|
-
"Daily Statuses",
|
166
|
-
"Browsers",
|
167
|
-
"Platforms",
|
168
|
-
"Referers",
|
169
|
-
"IPs",
|
170
|
-
"Geolocation",
|
171
|
-
"Streaks",
|
172
|
-
"Command Invocation",
|
173
|
-
"Performance"
|
174
|
-
].each do |item| %>
|
175
|
-
<li class="nav-item">
|
176
|
-
<a href="#<%= item.downcase.gsub(' ', '-') %>" data-close><%= item %></a>
|
177
|
-
</li>
|
178
|
-
<% end %>
|
179
|
-
</ul>
|
180
|
-
|
181
|
-
<p>
|
182
|
-
Generated by
|
183
|
-
<a href="https://github.com/avillafiorita/log_sense">LogSense</a> <br />
|
184
|
-
on <%= DateTime.now.strftime("%Y-%m-%d %H:%M") %>.<br />
|
185
|
-
<a href='https://db-ip.com'>IP Geolocation by DB-IP</a>
|
186
|
-
</p>
|
187
|
-
</nav>
|
155
|
+
<%= render "navigation.html.erb",
|
156
|
+
menus: Emitter::apache_report_specification.map { |x| x[:title] } +
|
157
|
+
["Streaks", "Command Invocation", "Performance"] %>
|
188
158
|
</div>
|
159
|
+
|
189
160
|
<div class="off-canvas-content grid-container grid-x fluid" data-off-canvas-content>
|
190
161
|
<div data-sticky-container>
|
191
162
|
<div class="sticky" data-sticky data-margin-top="0">
|
@@ -196,14 +167,14 @@
|
|
196
167
|
</div>
|
197
168
|
|
198
169
|
<section class="main-section">
|
199
|
-
<h1><%= options[:title] || "Log Sense
|
170
|
+
<h1><%= options[:title] || "Log Sense Apache Log Report" %></h1>
|
200
171
|
|
201
|
-
<p><b>Input File:</b> <%=
|
172
|
+
<p><b>Input File(s):</b> <%= data[:filenames].empty? ? "stdin" : data[:filenames].join(", ") %></p>
|
202
173
|
|
203
174
|
<div class="grid-x grid-margin-x">
|
204
175
|
<article class="card small-12 large-6 cell">
|
205
176
|
<div class="card-divider">
|
206
|
-
<h2 id="
|
177
|
+
<h2 id="<%= Emitter::slugify "Summary" %>">Summary</h2>
|
207
178
|
</div>
|
208
179
|
<div class="card-section">
|
209
180
|
<%= render "summary.html.erb", data: data %>
|
@@ -212,7 +183,7 @@
|
|
212
183
|
|
213
184
|
<article class="card cell small-12 large-6">
|
214
185
|
<div class="card-divider">
|
215
|
-
<h2 id="
|
186
|
+
<h2 id="<%= Emitter::slugify "Summary" %>">Log Structure</h2>
|
216
187
|
</div>
|
217
188
|
<div class="card-section">
|
218
189
|
<%= render "log_structure.html.erb", data: data %>
|
@@ -224,7 +195,7 @@
|
|
224
195
|
<% @reports.each_with_index do |report, index| %>
|
225
196
|
<article class="card cell <%= report[:col] || "small-12 large-6" %>" >
|
226
197
|
<div class="card-divider">
|
227
|
-
<h2 id="<%= report[:title]
|
198
|
+
<h2 id="<%= Emitter::slugify report[:title] %>">
|
228
199
|
<%= report[:title] %>
|
229
200
|
</h2>
|
230
201
|
</div>
|
@@ -252,101 +223,10 @@
|
|
252
223
|
<% end %>
|
253
224
|
</div>
|
254
225
|
|
255
|
-
<article class="card">
|
256
|
-
<div class="card-divider">
|
257
|
-
<h2 id="geolocation">Geolocation</h2>
|
258
|
-
</div>
|
259
|
-
<div class="card-section">
|
260
|
-
<table id="geolocation-table" class="table unstriped">
|
261
|
-
<thead>
|
262
|
-
<tr>
|
263
|
-
<th>Country Code</th>
|
264
|
-
<th>Total Hits</th>
|
265
|
-
<th>Total Visits</th>
|
266
|
-
<th>IPs</th>
|
267
|
-
</tr>
|
268
|
-
</thead>
|
269
|
-
<tbody>
|
270
|
-
<%# IP, Hits, Visits Size, Country%>
|
271
|
-
<% data[:ips].group_by { |x| x[4] }.each do |k, v| %>
|
272
|
-
<tr>
|
273
|
-
<td class="country"><%= k %></td>
|
274
|
-
<td class="total-hits"><%= v.map { |x| x[1] }.inject(&:+) %></td>
|
275
|
-
<td class="total-visits"><%= v.map { |x| x[2] }.inject(&:+) %></td>
|
276
|
-
<td class="ips">
|
277
|
-
<%= v.map { |x| "<a href=\"https://whatismyipaddress.com/ip/#{x[0]}\">#{x[0]}</a>" }.join(", ") %>
|
278
|
-
</td>
|
279
|
-
</tr>
|
280
|
-
<% end %>
|
281
|
-
</tbody>
|
282
|
-
</table>
|
283
|
-
</div>
|
284
|
-
</article>
|
285
|
-
|
286
|
-
<article class="card">
|
287
|
-
<div class="card-divider">
|
288
|
-
<h2 id="streaks">Streaks</h2>
|
289
|
-
</div>
|
290
|
-
<div class="card-section">
|
291
|
-
<table id="streaks-table" class="table data-table streaks">
|
292
|
-
<thead>
|
293
|
-
<tr>
|
294
|
-
<th>IP</th>
|
295
|
-
<th>
|
296
|
-
<div class="grid-x grid-margin-x">
|
297
|
-
<div class="small-2 cell">
|
298
|
-
Day
|
299
|
-
</div>
|
300
|
-
<div class="small-10 cell">
|
301
|
-
Resources
|
302
|
-
</div>
|
303
|
-
</div>
|
304
|
-
</th>
|
305
|
-
</tr>
|
306
|
-
</thead>
|
307
|
-
<tbody>
|
308
|
-
<% data[:streaks].group_by(&:first).each do |ip, date_urls| %>
|
309
|
-
<tr>
|
310
|
-
<td class="ip">
|
311
|
-
<a href="https://whatismyipaddress.com/ip/<%= ip %>"><%= ip %></a>
|
312
|
-
</td>
|
313
|
-
<td class="streaks">
|
314
|
-
<div class="grid-x grid-margin-x">
|
315
|
-
<% date_urls.group_by { |x| x[1] }.each do |date, urls| %>
|
316
|
-
<div class="small-12 medium-1 cell">
|
317
|
-
<span class="date"><%= date %></span>
|
318
|
-
</div>
|
319
|
-
<div class="small-12 medium-5 cell">
|
320
|
-
<span class="res-title">HTML:</span>
|
321
|
-
<% unique_with_count = urls.map { |x| x[2] }.compact.group_by{|e| e}.map{|k, v| [k, v.length]} %>
|
322
|
-
<ul class="no-bullet">
|
323
|
-
<% unique_with_count.select { |x| x[0].match /.*\.html?/ }.each do |url| %>
|
324
|
-
<li>[<%= url[1] %>] <%= Emitter::escape_javascript url[0] %></li>
|
325
|
-
<% end %>
|
326
|
-
</ul>
|
327
|
-
</div>
|
328
|
-
<div class=" small-12 medium-5 cell">
|
329
|
-
<span class="res-title">Other Resources:</span>
|
330
|
-
<ul class="no-bullet">
|
331
|
-
<% unique_with_count.select { |x| x[0] and ! x[0].match /.*\.html?/ }.each do |url| %>
|
332
|
-
<li>[<%= url[1] %>] <%= Emitter::escape_javascript url[0] %></li>
|
333
|
-
<% end %>
|
334
|
-
</ul>
|
335
|
-
</div>
|
336
|
-
<% end %>
|
337
|
-
</div>
|
338
|
-
</td>
|
339
|
-
</tr>
|
340
|
-
<% end %>
|
341
|
-
</tbody>
|
342
|
-
</table>
|
343
|
-
</div>
|
344
|
-
</article>
|
345
|
-
|
346
226
|
<div class="grid-x grid-margin-x">
|
347
227
|
<div class="cell small-12 large-6">
|
348
228
|
<article>
|
349
|
-
<h2 id="
|
229
|
+
<h2 id="<%= Emitter::slugify "Command Invocation" %>">Command Invocation</h2>
|
350
230
|
|
351
231
|
<%= render "command_invocation.html.erb", data: data, options: options %>
|
352
232
|
</article>
|
@@ -354,7 +234,7 @@
|
|
354
234
|
|
355
235
|
<div class="small-12 large-6 cell">
|
356
236
|
<article>
|
357
|
-
<h2 id="
|
237
|
+
<h2 id="<%= Emitter::slugify "Performance" %>">Performance</h2>
|
358
238
|
|
359
239
|
<%= render "performance.html.erb", data: data %>
|
360
240
|
</article>
|
@@ -1,30 +1,17 @@
|
|
1
1
|
* Apache Log Analysis
|
2
2
|
|
3
|
-
|
4
|
-
>>>> (USE THE HTML EXPORT FUNCTION INSTEAD) <<<<
|
3
|
+
<%= render "warning.txt.erb" %>
|
5
4
|
|
6
5
|
** Summary
|
7
6
|
|
8
7
|
<%= render "summary.txt.erb", data: data %>
|
9
8
|
|
10
|
-
<% @reports.each do |report| %>
|
9
|
+
<% @reports.reject { |x| x[:report] == :html }.each do |report| %>
|
11
10
|
** <%= report[:title] %>
|
12
11
|
|
13
12
|
<%= render "output_table.txt.erb", report: report, data: data %>
|
14
13
|
<% end %>
|
15
14
|
|
16
|
-
** Geolocation
|
17
|
-
|
18
|
-
<%=
|
19
|
-
ips = data[:ips].group_by { |x| x[4] }.map { |k, v|
|
20
|
-
[k, v.map { |x| x[1] }.inject(&:+), v.map { |x| x[2] }.inject(&:+) ]
|
21
|
-
}
|
22
|
-
table = Terminal::Table.new headings: ["Country", "Hits", "Total Visits"], rows: ips
|
23
|
-
table.style = { border_i: "|" }
|
24
|
-
(1..2).map { |i| table.align_column(i, :right) }
|
25
|
-
table
|
26
|
-
%>
|
27
|
-
|
28
15
|
** Command Invocation
|
29
16
|
|
30
17
|
<%= render 'command_invocation.txt.erb', data: data %>
|
@@ -1,7 +1,9 @@
|
|
1
1
|
<!doctype html>
|
2
2
|
<html class="no-js" lang="en">
|
3
3
|
<head>
|
4
|
-
<title
|
4
|
+
<title>
|
5
|
+
<%= options[:title] || "Log Sense: #{data[:filenames].empty? ? "stdin" : data[:filenames].join(", ")}" %>
|
6
|
+
</title>
|
5
7
|
|
6
8
|
<meta charset="utf-8" />
|
7
9
|
<meta http-equiv="x-ua-compatible" content="ie=edge">
|
@@ -150,36 +152,8 @@
|
|
150
152
|
<body>
|
151
153
|
<div class="off-canvas-wrapper">
|
152
154
|
<div class="off-canvas position-left" id="offCanvas" data-off-canvas>
|
153
|
-
|
154
|
-
|
155
|
-
<ul class="no-bullet">
|
156
|
-
<% [
|
157
|
-
"Summary",
|
158
|
-
"Log Structure",
|
159
|
-
"Daily Distribution",
|
160
|
-
"Time Distribution",
|
161
|
-
"Statuses",
|
162
|
-
"Rails Performance",
|
163
|
-
"Fatal Events",
|
164
|
-
"Internal Server Errrors",
|
165
|
-
"Errors",
|
166
|
-
"IPs",
|
167
|
-
"Command Invocation",
|
168
|
-
"Performance"
|
169
|
-
].each do |item| %>
|
170
|
-
<li class="nav-item">
|
171
|
-
<a href="#<%= item.downcase.gsub(' ', '-') %>" data-close><%= item %></a>
|
172
|
-
</li>
|
173
|
-
<% end %>
|
174
|
-
</ul>
|
175
|
-
|
176
|
-
<p>
|
177
|
-
Generated by
|
178
|
-
<a href="https://github.com/avillafiorita/log_sense">LogSense</a> <br />
|
179
|
-
on <%= DateTime.now.strftime("%Y-%m-%d %H:%M") %>.<br />
|
180
|
-
<a href='https://db-ip.com'>IP Geolocation by DB-IP</a>
|
181
|
-
</p>
|
182
|
-
</nav>
|
155
|
+
<%= render "navigation.html.erb",
|
156
|
+
menus: Emitter::rails_report_specification.map { |x| x[:title] } %>
|
183
157
|
</div>
|
184
158
|
<div class="off-canvas-content grid-container grid-x fluid" data-off-canvas-content>
|
185
159
|
<div data-sticky-container>
|
@@ -191,14 +165,14 @@
|
|
191
165
|
</div>
|
192
166
|
|
193
167
|
<section class="main-section">
|
194
|
-
<h1><%= options[:title] || "Log Sense
|
168
|
+
<h1><%= options[:title] || "Log Sense Rails Log Report" %></h1>
|
195
169
|
|
196
|
-
<p><b>Input File:</b> <%=
|
170
|
+
<p><b>Input File(s):</b> <%= data[:filenames].empty? ? "stdin" : data[:filenames].join(", ") %></p>
|
197
171
|
|
198
172
|
<div class="grid-x grid-margin-x">
|
199
173
|
<article class="card small-12 large-6 cell">
|
200
174
|
<div class="card-divider">
|
201
|
-
<h2 id="
|
175
|
+
<h2 id="<%= Emitter::slugify "Summary" %>">Summary</h2>
|
202
176
|
</div>
|
203
177
|
<div class="card-section">
|
204
178
|
<%= render "summary.html.erb", data: data %>
|
@@ -207,7 +181,7 @@
|
|
207
181
|
|
208
182
|
<article class="card cell small-12 large-6">
|
209
183
|
<div class="card-divider">
|
210
|
-
<h2 id="
|
184
|
+
<h2 id="<%= Emitter::slugify "Log Structure" %>">Log Structure</h2>
|
211
185
|
</div>
|
212
186
|
<div class="card-section">
|
213
187
|
<%= render "log_structure.html.erb", data: data %>
|
@@ -249,7 +223,9 @@
|
|
249
223
|
<div class="grid-x grid-margin-x">
|
250
224
|
<div class="cell small-12 large-6">
|
251
225
|
<article>
|
252
|
-
<h2 id="
|
226
|
+
<h2 id="<%= Emitter::slugify "Command Invocation" %>">
|
227
|
+
Command Invocation
|
228
|
+
</h2>
|
253
229
|
|
254
230
|
<%= render "command_invocation.html.erb", data: data, options: options %>
|
255
231
|
</article>
|
@@ -257,7 +233,7 @@
|
|
257
233
|
|
258
234
|
<div class="small-12 large-6 cell">
|
259
235
|
<article>
|
260
|
-
<h2 id="
|
236
|
+
<h2 id="<%= Emitter::slugify "Performance" %>"> Performance</h2>
|
261
237
|
|
262
238
|
<%= render "performance.html.erb", data: data %>
|
263
239
|
</article>
|
@@ -1,13 +1,12 @@
|
|
1
1
|
* Rails Log Analysis
|
2
2
|
|
3
|
-
|
4
|
-
>>>> (USE THE HTML EXPORT FUNCTION INSTEAD) <<<<
|
3
|
+
<%= render "warning.txt.erb" %>
|
5
4
|
|
6
5
|
** Summary
|
7
6
|
|
8
7
|
<%= render "summary.txt.erb", data: data %>
|
9
8
|
|
10
|
-
<% @reports.each do |report| %>
|
9
|
+
<% @reports.reject { |x| x[:report] == :html }.each do |report| %>
|
11
10
|
** <%= report[:title] %>
|
12
11
|
|
13
12
|
<%= render "output_table.txt.erb", report: report, data: data %>
|
@@ -15,9 +14,9 @@
|
|
15
14
|
|
16
15
|
** Command Invocation
|
17
16
|
|
18
|
-
<%= render
|
17
|
+
<%= render 'command_invocation.txt.erb', data: data %>
|
19
18
|
|
20
|
-
**
|
19
|
+
** Performance
|
21
20
|
|
22
|
-
<%= render
|
21
|
+
<%= render 'performance.txt.erb', data: data %>
|
23
22
|
|
data/lib/log_sense/version.rb
CHANGED
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: 1.
|
4
|
+
version: 1.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adolfo Fibrillation
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-03-
|
11
|
+
date: 2022-03-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: browser
|
@@ -141,6 +141,7 @@ files:
|
|
141
141
|
- lib/log_sense/templates/_command_invocation.html.erb
|
142
142
|
- lib/log_sense/templates/_command_invocation.txt.erb
|
143
143
|
- lib/log_sense/templates/_log_structure.html.erb
|
144
|
+
- lib/log_sense/templates/_navigation.html.erb
|
144
145
|
- lib/log_sense/templates/_output_table.html.erb
|
145
146
|
- lib/log_sense/templates/_output_table.txt.erb
|
146
147
|
- lib/log_sense/templates/_performance.html.erb
|
@@ -148,6 +149,7 @@ files:
|
|
148
149
|
- lib/log_sense/templates/_report_data.html.erb
|
149
150
|
- lib/log_sense/templates/_summary.html.erb
|
150
151
|
- lib/log_sense/templates/_summary.txt.erb
|
152
|
+
- lib/log_sense/templates/_warning.txt.erb
|
151
153
|
- lib/log_sense/templates/apache.html.erb
|
152
154
|
- lib/log_sense/templates/apache.txt.erb
|
153
155
|
- lib/log_sense/templates/rails.html.erb
|