log_sense 1.5.2 → 1.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.org +31 -0
- data/Gemfile.lock +6 -4
- data/README.org +108 -34
- data/Rakefile +30 -10
- data/exe/log_sense +110 -39
- data/ip_locations/dbip-country-lite.sqlite3 +0 -0
- data/lib/log_sense/aggregator.rb +191 -0
- data/lib/log_sense/apache_aggregator.rb +122 -0
- data/lib/log_sense/apache_log_line_parser.rb +23 -21
- data/lib/log_sense/apache_log_parser.rb +15 -12
- data/lib/log_sense/apache_report_shaper.rb +309 -0
- data/lib/log_sense/emitter.rb +55 -553
- data/lib/log_sense/ip_locator.rb +35 -18
- data/lib/log_sense/options_checker.rb +24 -0
- data/lib/log_sense/options_parser.rb +81 -51
- data/lib/log_sense/rails_aggregator.rb +69 -0
- data/lib/log_sense/rails_log_parser.rb +82 -68
- data/lib/log_sense/rails_report_shaper.rb +183 -0
- data/lib/log_sense/report_shaper.rb +105 -0
- data/lib/log_sense/templates/_cdn_links.html.erb +11 -0
- data/lib/log_sense/templates/_command_invocation.html.erb +4 -0
- data/lib/log_sense/templates/_log_structure.html.erb +7 -1
- data/lib/log_sense/templates/_output_table.html.erb +6 -2
- data/lib/log_sense/templates/_rails.css.erb +7 -0
- data/lib/log_sense/templates/_summary.html.erb +9 -7
- data/lib/log_sense/templates/_summary.txt.erb +2 -2
- data/lib/log_sense/templates/{rails.html.erb → report_html.erb} +19 -37
- data/lib/log_sense/templates/{apache.txt.erb → report_txt.erb} +1 -1
- data/lib/log_sense/version.rb +1 -1
- data/lib/log_sense.rb +19 -9
- data/log_sense.gemspec +1 -1
- data/screenshots/rails-screenshot.png +0 -0
- metadata +18 -12
- data/lib/log_sense/apache_data_cruncher.rb +0 -147
- data/lib/log_sense/rails_data_cruncher.rb +0 -141
- data/lib/log_sense/templates/apache.html.erb +0 -115
- data/lib/log_sense/templates/rails.txt.erb +0 -22
- /data/{apache-screenshot.png → screenshots/apache-screenshot.png} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0a915af429fe2492f17bc767c86767e38d37a674af6206fb3b2c0a76e4cad620
|
4
|
+
data.tar.gz: bf4cc3a1e438b752c9c99fee5f91c85abd38de95dac947c2382e2f9825b51467
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5612ef5474aa397132527588d289d9d1eba4f8954b92553e32fd5b856f2bd5441ba09d89d1bf12a0f220c602dcd2e73de2d6e360b6177b162d57adc2726442c9
|
7
|
+
data.tar.gz: c3b546cc177a3364b1b513f4274c1521aa1669bb17b142eb453ba73251f1e3458e7b9d1703b692ef275a474821ce7375e387155f63e3e7d5d7c9c42e1c50a150
|
data/CHANGELOG.org
CHANGED
@@ -2,6 +2,37 @@
|
|
2
2
|
#+AUTHOR: Adolfo Villafiorita
|
3
3
|
#+STARTUP: showall
|
4
4
|
|
5
|
+
* 1.6.1
|
6
|
+
|
7
|
+
- Country DB now stores country name.
|
8
|
+
|
9
|
+
* 1.6.0
|
10
|
+
|
11
|
+
- [User] New output format =ufw= generates directives to blacklist IPs
|
12
|
+
requesting URLs matching a pattern. For users of the Uncomplicated
|
13
|
+
Firewall.
|
14
|
+
- [User] new option =--no-geo= skips geolocation, which is terribly
|
15
|
+
costly in the current implementation.
|
16
|
+
- [User] Updated DB-IP country file to Dec 2022 version.
|
17
|
+
- [User] Changed name of SQLite output format to sqlite3
|
18
|
+
- [User] It is now possible to start analysis from a sqlite3 DB
|
19
|
+
generated by log_sense, breaking parsing and generation in two
|
20
|
+
steps.
|
21
|
+
- [User] Check for correctness of I/O formats before launching
|
22
|
+
analysis
|
23
|
+
- [User] Streak report has been renames Session. Limited the number
|
24
|
+
of URLs shown in each session, to avoid buffer?/memory overflows
|
25
|
+
when an IP requests a massive amount of URLs.
|
26
|
+
- [User] Added an IP-per-hour visits report.
|
27
|
+
- [Code] A rather extensive refactoring of the source code to
|
28
|
+
remove code duplications and improve code structure.
|
29
|
+
- [Code] Rubocop-ped various files
|
30
|
+
- [Code] Added text renderer to DataTable, which sanitizes input and
|
31
|
+
further reduces risks of XSS and log poisoning attacks
|
32
|
+
- [Code] CDN links have been ported into the Emitter module and used
|
33
|
+
in the Embedded Ruby Templates (erbs). This simplifies version
|
34
|
+
updates of Javascript libraries used in reports.
|
35
|
+
|
5
36
|
* 1.5.2
|
6
37
|
|
7
38
|
- [User] Updated DB-IP country file.
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
log_sense (1.5.
|
4
|
+
log_sense (1.5.3)
|
5
5
|
browser
|
6
6
|
ipaddr
|
7
7
|
iso_country_codes
|
@@ -16,18 +16,20 @@ GEM
|
|
16
16
|
irb (>= 1.3.6)
|
17
17
|
reline (>= 0.3.1)
|
18
18
|
io-console (0.5.11)
|
19
|
-
ipaddr (1.2.
|
19
|
+
ipaddr (1.2.5)
|
20
20
|
irb (1.4.1)
|
21
21
|
reline (>= 0.3.0)
|
22
22
|
iso_country_codes (0.7.8)
|
23
|
+
mini_portile2 (2.8.0)
|
23
24
|
minitest (5.15.0)
|
24
25
|
rake (12.3.3)
|
25
26
|
reline (0.3.1)
|
26
27
|
io-console (~> 0.5)
|
27
|
-
sqlite3 (1.
|
28
|
+
sqlite3 (1.5.4)
|
29
|
+
mini_portile2 (~> 2.8.0)
|
28
30
|
terminal-table (3.0.2)
|
29
31
|
unicode-display_width (>= 1.1.1, < 3)
|
30
|
-
unicode-display_width (2.
|
32
|
+
unicode-display_width (2.3.0)
|
31
33
|
|
32
34
|
PLATFORMS
|
33
35
|
ruby
|
data/README.org
CHANGED
@@ -9,7 +9,7 @@ Rails logs. Written in Ruby, it runs from the command line, it is
|
|
9
9
|
fast, and it can be installed on any system with a relatively recent
|
10
10
|
version of Ruby. We tested on Ruby 2.6.9, Ruby 3.0.x and later.
|
11
11
|
|
12
|
-
LogSense reports the following data:
|
12
|
+
When generating reports, LogSense reports the following data:
|
13
13
|
|
14
14
|
- Visitors, hits, unique visitors, bandwidth used
|
15
15
|
- Most accessed HTML pages
|
@@ -22,18 +22,49 @@ LogSense reports the following data:
|
|
22
22
|
- IP Country location, thanks to the DP-IP lite country DB
|
23
23
|
- Streaks: resources accessed by a given IP over time
|
24
24
|
- Performance of Rails requests
|
25
|
+
|
26
|
+
A special output format =ufw= generates rules for the [[https://launchpad.net/ufw][Uncomplicated
|
27
|
+
Firewall]] to blacklist IPs requesting URLs matching a specific pattern.
|
25
28
|
|
26
29
|
Filters from the command line allow to analyze specific periods and
|
27
30
|
distinguish traffic generated by self polls and crawlers.
|
28
31
|
|
29
|
-
LogSense generates HTML, txt, and SQLite outputs.
|
32
|
+
LogSense generates HTML, txt, ufw, and SQLite outputs.
|
30
33
|
|
31
|
-
|
34
|
+
** Apache Report Structure
|
32
35
|
|
33
36
|
#+ATTR_HTML: :width 80%
|
34
|
-
[[file:./apache-screenshot.png]]
|
37
|
+
[[file:./screenshots/apache-screenshot.png]]
|
38
|
+
|
39
|
+
|
40
|
+
** Rails Report Structure
|
41
|
+
|
42
|
+
#+ATTR_HTML: :width 80%
|
43
|
+
[[file:./screenshots/rails-screenshot.png]]
|
44
|
+
|
45
|
+
|
46
|
+
** UFW Report
|
35
47
|
|
48
|
+
The output format =ufw= generates directives for Uncomplicated
|
49
|
+
Firewall blacklisting IPs requesting URLs matching a given pattern.
|
36
50
|
|
51
|
+
We use it to blacklist IPs requesting WordPress login pages on our
|
52
|
+
websites... since we don't use WordPress for our websites.
|
53
|
+
|
54
|
+
*Example*
|
55
|
+
|
56
|
+
#+begin_src
|
57
|
+
$ log_sense -f apache -t ufw -i apache.log
|
58
|
+
# /users/sign_in/xmlrpc.php?rsd
|
59
|
+
ufw deny from 20.212.3.206
|
60
|
+
|
61
|
+
# /wp-login.php /wordpress/wp-login.php /blog/wp-login.php /wp/wp-login.php
|
62
|
+
ufw deny from 185.255.134.18
|
63
|
+
|
64
|
+
...
|
65
|
+
#+end_src
|
66
|
+
|
67
|
+
|
37
68
|
* An important word of warning
|
38
69
|
|
39
70
|
[[https://owasp.org/www-community/attacks/Log_Injection][Log poisoning]] is a technique whereby attackers send requests with invalidated
|
@@ -48,9 +79,10 @@ opened or code executed.
|
|
48
79
|
* Motivation
|
49
80
|
|
50
81
|
LogSense moves along the lines of tools such as [[https://goaccess.io/][GoAccess]] (which
|
51
|
-
strongly inspired the development of Log Sense) and [[https://umami.is/][Umami]],
|
52
|
-
*privacy* and *data-ownership*: the data generated by
|
53
|
-
stored on your computer and owned by you (like it should
|
82
|
+
strongly inspired the development of Log Sense) and [[https://umami.is/][Umami]], both
|
83
|
+
focusing on *privacy* and *data-ownership*: the data generated by
|
84
|
+
LogSense is stored on your computer and owned by you (like it should
|
85
|
+
be)[fn:1].
|
54
86
|
|
55
87
|
LogSense is also inspired by *static websites generators*: statistics
|
56
88
|
are generated from the command line and accessed as static HTML files.
|
@@ -76,33 +108,30 @@ generated files are then made available on a private area on the web.
|
|
76
108
|
#+RESULTS:
|
77
109
|
#+begin_example
|
78
110
|
Usage: log_sense [options] [logfile ...]
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
- sqlite
|
104
|
-
- html
|
105
|
-
- txt
|
111
|
+
--title=TITLE Title to use in the report
|
112
|
+
-f, --input-format=FORMAT Input format (either rails or apache)
|
113
|
+
-i, --input-files=file,file, Input files (can also be passed directly)
|
114
|
+
-t, --output-format=FORMAT Output format: html, org, txt, sqlite.
|
115
|
+
-o, --output-file=OUTPUT_FILE Output file
|
116
|
+
-b, --begin=DATE Consider entries after or on DATE
|
117
|
+
-e, --end=DATE Consider entries before or on DATE
|
118
|
+
-l, --limit=N Limit to the N most requested resources (defaults to 100)
|
119
|
+
-w, --width=WIDTH Maximum width of long columns in textual reports
|
120
|
+
-r, --rows=ROWS Maximum number of rows for columns with multiple entries in textual reports
|
121
|
+
-p, --pattern=PATTERN Pattern to use with ufw report to decide IP to blacklist
|
122
|
+
-c, --crawlers=POLICY Decide what to do with crawlers (applies to Apache Logs)
|
123
|
+
--no-selfpolls Ignore self poll entries (requests from ::1; applies to Apache Logs)
|
124
|
+
-n, --no-geog Do not geolocate entries
|
125
|
+
--verbose Inform about progress (output to STDERR)
|
126
|
+
-v, --version Prints version information
|
127
|
+
-h, --help Prints this help
|
128
|
+
|
129
|
+
This is version 1.6.0
|
130
|
+
|
131
|
+
Output formats:
|
132
|
+
|
133
|
+
- rails: txt, html, sqlite3, ufw
|
134
|
+
- apache: txt, html, sqlite3, ufw
|
106
135
|
#+end_example
|
107
136
|
|
108
137
|
Examples:
|
@@ -112,6 +141,51 @@ log_sense -f apache -i access.log -t txt > access-data.txt
|
|
112
141
|
log_sense -f rails -i production.log -t html -o performance.html
|
113
142
|
#+end_example
|
114
143
|
|
144
|
+
* Code Structure
|
145
|
+
|
146
|
+
The code implements a pipeline, with the following steps:
|
147
|
+
|
148
|
+
1. *Parser:* parses a log to a SQLite3 database. The database
|
149
|
+
contains a table with a list of events, and, in the case of Rails
|
150
|
+
report, a table with the errors.
|
151
|
+
2. *Aggregator:* takes as input a SQLite DB and aggregates data,
|
152
|
+
typically performing "group by", which are simpler to generate in
|
153
|
+
Ruby, rather than in SQL. The module outputs a Hash, with
|
154
|
+
different reporting data.
|
155
|
+
3. *GeoLocator:* add country information to all the reporting data
|
156
|
+
which has an IP as one the fields.
|
157
|
+
4. *Shaper:* makes (geolocated) aggregated data (e.g. Hashes and
|
158
|
+
such), into Array of Arrays, simplifying the structure of the code
|
159
|
+
building the reports.
|
160
|
+
5. *Emitter* generates reports from shaped data using ERB.
|
161
|
+
|
162
|
+
The architecture and the structure of the code is far from being nice,
|
163
|
+
for historical reason and for a bunch of small differences existing
|
164
|
+
between the input and the outputs to be generated. This usually ends
|
165
|
+
up with modifications to the code that have to be replicated in
|
166
|
+
different parts of the code and in interferences.
|
167
|
+
|
168
|
+
Among the points I would like to address:
|
169
|
+
|
170
|
+
- The execution pipeline in the main script has a few exceptions to
|
171
|
+
manage SQLite reading/dumping and ufw report. A linear structure
|
172
|
+
would be a lot nicer.
|
173
|
+
- Two different classes are defined for steps 1, 2, and 4, to manage,
|
174
|
+
respectively, Apache and Rails logs. These classes inherit from a
|
175
|
+
common ancestor (e.g. ApacheParser and RailsParser both inherit from
|
176
|
+
Parser), but there is still too little code shared. A nicer
|
177
|
+
approach would be that of identifying a common DB structure and
|
178
|
+
unify the pipeline up to (or including) the generation of
|
179
|
+
reports. There are a bunch of small different things to highlight in
|
180
|
+
reports, which still make this difficult. For instance, the country
|
181
|
+
report for Apache reports size of TX data, which is not available
|
182
|
+
for Rail reports.
|
183
|
+
- Geolocation could become a lot more efficient if performed in
|
184
|
+
SQLite, rather than in Ruby
|
185
|
+
- The distinction between Aggregation, Shaping, and Emission is a too
|
186
|
+
fine-grained and it would be nice to be able to cleanly remove one
|
187
|
+
of the steps.
|
188
|
+
|
115
189
|
|
116
190
|
* Change Log
|
117
191
|
|
data/Rakefile
CHANGED
@@ -9,18 +9,38 @@ end
|
|
9
9
|
require_relative './lib/log_sense/ip_locator.rb'
|
10
10
|
|
11
11
|
desc "Convert Geolocation DB to sqlite"
|
12
|
-
task :
|
13
|
-
|
12
|
+
task :dbip, [:filename] do |tasks, args|
|
13
|
+
filename_or_yyyy_mm = args[:filename]
|
14
|
+
|
15
|
+
filename = if /\d{4}-\d{2}/.match(filename_or_yyyy_mm)
|
16
|
+
"ip_locations/dbip-country-lite-#{filename_or_yyyy_mm}.csv"
|
17
|
+
else
|
18
|
+
filename_or_yyyy_mm
|
19
|
+
end
|
20
|
+
|
21
|
+
# if the filename has a .gz extension or a gzipped version of the file
|
22
|
+
# exists, gunzip it
|
23
|
+
if File.extname(filename) == ".gz" || File.exist?("#{filename}.gz")
|
24
|
+
system "gunzip #{filename}.gz"
|
25
|
+
end
|
14
26
|
|
15
27
|
if !File.exist? filename
|
16
|
-
puts
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
28
|
+
puts <<-EOS
|
29
|
+
Error. Could not find: #{filename}
|
30
|
+
|
31
|
+
I see the following files:
|
32
|
+
|
33
|
+
#{Dir.glob("ip_locations/dbip-country-lite*").map { |x| "- #{x}" }.join("\n")}
|
34
|
+
|
35
|
+
1. Download (if necessary) a more recent version from: https://db-ip.com/db/download/ip-to-country-lite
|
36
|
+
2. Save downloaded file to ip_locations/
|
37
|
+
3. Relaunch with YYYY-MM (will build: dbip-country-lite-YYYY-MM.csv)
|
38
|
+
or with filename.
|
39
|
+
|
40
|
+
Remark. If the filename has the extension .gz or if the
|
41
|
+
filename does not exist, but a file with the same name and .gz extension
|
42
|
+
exists, it is gunzipped first
|
43
|
+
EOS
|
24
44
|
|
25
45
|
exit
|
26
46
|
else
|
data/exe/log_sense
CHANGED
@@ -1,82 +1,153 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
3
|
+
require "log_sense"
|
4
|
+
require "sqlite3"
|
4
5
|
|
5
6
|
#
|
6
7
|
# Parse Command Line Arguments
|
7
8
|
#
|
8
9
|
|
9
10
|
# this better be here... OptionsParser consumes ARGV
|
10
|
-
@command_line = ARGV.join(
|
11
|
-
@options
|
12
|
-
@
|
11
|
+
@command_line = ARGV.join(" ")
|
12
|
+
@options = LogSense::OptionsParser.parse ARGV
|
13
|
+
@input_filenames = @options[:input_filenames] + ARGV
|
14
|
+
@output_filename = @options[:output_filename]
|
13
15
|
|
14
16
|
#
|
15
|
-
#
|
16
|
-
#
|
17
|
+
# Check correctness of input data.
|
18
|
+
#
|
19
|
+
|
20
|
+
#
|
21
|
+
# Check input files
|
17
22
|
#
|
18
|
-
@input_filenames = @options[:input_filenames] + ARGV
|
19
23
|
@non_existing = @input_filenames.reject { |x| File.exist?(x) }
|
20
24
|
|
21
|
-
|
22
|
-
|
25
|
+
if @non_existing.any?
|
26
|
+
warn "Error: some input file(s) \"#{@non_existing.join(", ")}\" do not exist"
|
27
|
+
exit 1
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Special condition: sqlite3 requires a single file as input
|
32
|
+
#
|
33
|
+
if @input_filenames.size > 0 &&
|
34
|
+
File.extname(@input_filenames.first) == "sqlite3" &&
|
35
|
+
@input_filenames.size > 1
|
36
|
+
warn "Error: you can pass only one sqlite3 file as input"
|
37
|
+
exit 1
|
38
|
+
end
|
39
|
+
|
40
|
+
#
|
41
|
+
# Supported input/output chains
|
42
|
+
#
|
43
|
+
iformat = @options[:input_format]
|
44
|
+
oformat = @options[:output_format]
|
45
|
+
|
46
|
+
if !LogSense::OptionsChecker::compatible?(iformat, oformat)
|
47
|
+
warn "Error: don't know how to make #{iformat} into #{oformat}."
|
48
|
+
warn "Possible transformation chains:"
|
49
|
+
warn LogSense::OptionsChecker.chains_to_s
|
23
50
|
exit 1
|
24
51
|
end
|
25
|
-
@input_files = @input_filenames.empty? ? [$stdin] : @input_filenames.map { |x| File.open(x, 'r') }
|
26
52
|
|
27
53
|
#
|
28
|
-
#
|
54
|
+
# Do the work
|
29
55
|
#
|
30
56
|
|
31
57
|
@started_at = Time.now
|
32
58
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
when 'rails'
|
38
|
-
parser_klass = LogSense::RailsLogParser
|
39
|
-
cruncher_klass = LogSense::RailsDataCruncher
|
59
|
+
if @input_filenames.size > 0 &&
|
60
|
+
File.extname(@input_filenames.first) == ".sqlite3"
|
61
|
+
warn "Reading SQLite3 DB ..." if @options[:verbose]
|
62
|
+
@db = SQLite3::Database.open @input_filenames.first
|
40
63
|
else
|
41
|
-
|
42
|
-
|
64
|
+
warn "Parsing ..." if @options[:verbose]
|
65
|
+
@input_files = if @input_filenames.empty?
|
66
|
+
[$stdin]
|
67
|
+
else
|
68
|
+
@input_filenames.map { |fname| File.open(fname, "r") }
|
69
|
+
end
|
70
|
+
class_name = "LogSense::#{@options[:input_format].capitalize}LogParser"
|
71
|
+
parser_class = Object.const_get class_name
|
72
|
+
parser = parser_class.new
|
73
|
+
@db = parser.parse @input_files
|
43
74
|
end
|
44
75
|
|
45
|
-
|
46
|
-
|
76
|
+
if @options[:output_format] == "sqlite3"
|
77
|
+
warn "Saving SQLite3 DB ..." if @options[:verbose]
|
47
78
|
|
48
|
-
|
49
|
-
|
50
|
-
ddb = SQLite3::Database.new(@output_file || 'db.sqlite3')
|
51
|
-
b = SQLite3::Backup.new(ddb, 'main', @db, 'main')
|
79
|
+
ddb = SQLite3::Database.new(@output_filename || "db.sqlite3")
|
80
|
+
b = SQLite3::Backup.new(ddb, "main", @db, "main")
|
52
81
|
b.step(-1) #=> DONE
|
53
82
|
b.finish
|
83
|
+
|
84
|
+
exit 0
|
85
|
+
elsif @options[:output_format] == "ufw"
|
86
|
+
pattern = @options[:pattern] || "php"
|
87
|
+
|
88
|
+
if @options[:input_format] == "rails"
|
89
|
+
query = "select distinct event.ip,event.url
|
90
|
+
from error join event
|
91
|
+
where event.log_id = error.log_id and
|
92
|
+
event.url like '%#{pattern}%'"
|
93
|
+
else
|
94
|
+
query = "select distinct ip,path from logline
|
95
|
+
where path like '%#{pattern}%'"
|
96
|
+
end
|
97
|
+
|
98
|
+
ips = @db.execute query
|
99
|
+
ips_and_urls = ips.group_by { |x| x[0] }.transform_values { |x|
|
100
|
+
x.map { |y| y[1..-1] }.flatten
|
101
|
+
}
|
102
|
+
ips_and_urls.each do |ip, urls|
|
103
|
+
puts "# #{urls[0..10].uniq.join(' ')}"
|
104
|
+
puts "ufw deny from #{ip}"
|
105
|
+
puts
|
106
|
+
end
|
107
|
+
|
108
|
+
exit 0
|
54
109
|
else
|
55
|
-
|
56
|
-
|
110
|
+
warn "Aggregating data ..." if @options[:verbose]
|
111
|
+
class_name = "LogSense::#{@options[:input_format].capitalize}Aggregator"
|
112
|
+
aggr_class = Object.const_get class_name
|
113
|
+
aggr = aggr_class.new(@db, @options)
|
114
|
+
@data = aggr.aggregate
|
57
115
|
|
58
|
-
|
59
|
-
|
116
|
+
if @options[:geolocation]
|
117
|
+
warn "Geolocating ..." if @options[:verbose]
|
118
|
+
@data = LogSense::IpLocator.geolocate @data
|
60
119
|
|
61
|
-
|
62
|
-
|
63
|
-
|
120
|
+
warn "Grouping IPs by country ..." if @options[:verbose]
|
121
|
+
country_col = @data[:ips][0].size - 1
|
122
|
+
@data[:countries] = @data[:ips].group_by { |x| x[country_col] }
|
123
|
+
else
|
124
|
+
@data[:countries] = {}
|
125
|
+
end
|
64
126
|
|
65
127
|
@ended_at = Time.now
|
66
128
|
@duration = @ended_at - @started_at
|
67
129
|
|
68
130
|
@data = @data.merge({
|
69
131
|
command: @command_line,
|
70
|
-
filenames:
|
132
|
+
filenames: @input_filenames,
|
71
133
|
log_files: @input_files,
|
72
134
|
started_at: @started_at,
|
73
135
|
ended_at: @ended_at,
|
74
136
|
duration: @duration,
|
75
137
|
width: @options[:width]
|
76
138
|
})
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
139
|
+
|
140
|
+
if @options[:verbose]
|
141
|
+
warn "I have the following keys in data: "
|
142
|
+
warn @data.keys.sort.map { |key| "#{key}: #{@data[key].class}" }.join("\n")
|
143
|
+
end
|
144
|
+
|
145
|
+
warn "Shaping data for output ..." if @options[:verbose]
|
146
|
+
class_name = "LogSense::#{@options[:input_format].capitalize}ReportShaper"
|
147
|
+
shaper_class = Object.const_get class_name
|
148
|
+
shaper = shaper_class.new
|
149
|
+
@reports = shaper.shape @data
|
150
|
+
|
151
|
+
warn "Emitting..." if @options[:verbose]
|
152
|
+
puts LogSense::Emitter.emit @reports, @data, @options
|
82
153
|
end
|
Binary file
|