log_sense 2.3.1 → 2.5.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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/CHANGELOG.org +10 -0
- data/Gemfile.lock +4 -0
- data/README.org +4 -3
- data/lib/log_sense/rails/log_parser.rb +65 -41
- data/lib/log_sense/rails_aggregator.rb +22 -6
- data/lib/log_sense/rails_report_shaper.rb +2 -0
- data/lib/log_sense/report_shaper.rb +23 -0
- data/lib/log_sense/version.rb +1 -1
- data/log_sense.gemspec +15 -3
- metadata +38 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 85b0ebee3c5879005dce7f3e32675a5f0319411eb74f138b2dfd7a4c21d26d43
|
4
|
+
data.tar.gz: 1aec3ca221196cfaf9061495e6767f5136a23a1f9d6f510fb43a28c056c1ca8c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 27389b84f365fdaa02ece44dd70eabb130051ca81d26d17cd573325f8101d21c0dc83fd5c81915babfcd1e8d1f363ef777811f9676b2439e8f6b868013515700
|
7
|
+
data.tar.gz: e52b3e05fb838362c3938aaf20d1f6347a3a4c8025d40963c98eeae4fe4b6c01d9acf225353ce2c872c213b2f113af089de418bc0e424e4b151884f4fd37c03c
|
data/.gitignore
CHANGED
data/CHANGELOG.org
CHANGED
@@ -2,6 +2,16 @@
|
|
2
2
|
#+AUTHOR: Adolfo Villafiorita
|
3
3
|
#+STARTUP: showall
|
4
4
|
|
5
|
+
* 2.5.0
|
6
|
+
|
7
|
+
- Add query reports
|
8
|
+
|
9
|
+
* 2.4.0
|
10
|
+
|
11
|
+
- Updates log_parser to match changes in log lines introduced from Rails 7
|
12
|
+
(or, in other words, fixes a bug which caused log_sense to miss events
|
13
|
+
from logs produced by Rails >= 7 applications)
|
14
|
+
|
5
15
|
* 2.3.1
|
6
16
|
|
7
17
|
- Updates geoip db
|
data/Gemfile.lock
CHANGED
@@ -3,8 +3,10 @@ PATH
|
|
3
3
|
specs:
|
4
4
|
log_sense (2.3.1)
|
5
5
|
browser (~> 6.0.0)
|
6
|
+
csv
|
6
7
|
ipaddr (~> 1.2.0)
|
7
8
|
iso_country_codes (~> 0.7.0)
|
9
|
+
ostruct
|
8
10
|
sqlite3 (~> 2.6.0)
|
9
11
|
terminal-table (~> 4.0.0)
|
10
12
|
|
@@ -18,6 +20,7 @@ GEM
|
|
18
20
|
bundler-audit (0.9.2)
|
19
21
|
bundler (>= 1.2.0, < 3)
|
20
22
|
thor (~> 1.0)
|
23
|
+
csv (3.3.4)
|
21
24
|
date (3.4.1)
|
22
25
|
debug (1.10.0)
|
23
26
|
irb (~> 1.10)
|
@@ -34,6 +37,7 @@ GEM
|
|
34
37
|
lint_roller (1.1.0)
|
35
38
|
mini_portile2 (2.8.9)
|
36
39
|
minitest (5.25.5)
|
40
|
+
ostruct (0.6.1)
|
37
41
|
parallel (1.27.0)
|
38
42
|
parser (3.3.8.0)
|
39
43
|
ast (~> 2.4.1)
|
data/README.org
CHANGED
@@ -4,8 +4,8 @@
|
|
4
4
|
|
5
5
|
* Introduction
|
6
6
|
|
7
|
-
LogSense
|
8
|
-
|
7
|
+
LogSense analyzes Apache and Rails log files, generating reports and statistics
|
8
|
+
about usage and performace.
|
9
9
|
|
10
10
|
Main features:
|
11
11
|
|
@@ -22,10 +22,11 @@ Main features:
|
|
22
22
|
self polls and crawlers.
|
23
23
|
- Reports can be generated in HTML, txt, ufw, and SQLite. HTML reports are
|
24
24
|
responsive and come with dark and light theme.
|
25
|
+
- It works with Rails' vanilla log format (no need to format the log)
|
25
26
|
|
26
27
|
LogSense is Written in Ruby, it runs from the command line, it is fast, and it
|
27
28
|
can be installed on any system with a relatively recent version of Ruby. We
|
28
|
-
|
29
|
+
started using it with Ruby 3.1.4 and we are now at version 3.4.
|
29
30
|
|
30
31
|
It is fast. On a ThinkPad P16, a 277M log file is parsed in 15 seconds,
|
31
32
|
processing, that is, about 7740 events per second; a 569M log file is parsed in
|
@@ -16,50 +16,43 @@ module LogSense
|
|
16
16
|
def parse(streams, options = {})
|
17
17
|
db = SQLite3::Database.new ":memory:"
|
18
18
|
|
19
|
+
event_table = {
|
20
|
+
exit_status: "TEXT",
|
21
|
+
started_at: "TEXT",
|
22
|
+
ended_at: "TEXT",
|
23
|
+
log_id: "TEXT",
|
24
|
+
ip: "TEXT",
|
25
|
+
unique_visitor: "TEXT",
|
26
|
+
url: "TEXT",
|
27
|
+
controller: "TEXT",
|
28
|
+
html_verb: "TEXT",
|
29
|
+
status: "INTEGER",
|
30
|
+
duration_total_ms: "FLOAT",
|
31
|
+
duration_views_ms: "FLOAT",
|
32
|
+
duration_ar_ms: "FLOAT",
|
33
|
+
allocations: "INTEGER",
|
34
|
+
queries: "INTEGER",
|
35
|
+
cached_queries: "INTEGER",
|
36
|
+
gc_duration: "FLOAT",
|
37
|
+
comment: "TEXT",
|
38
|
+
source_file: "TEXT",
|
39
|
+
line_number: "INTEGER"
|
40
|
+
}
|
41
|
+
|
42
|
+
table_declaration = event_table.map { |k, v| "#{k} #{v}" }.join(",")
|
19
43
|
db.execute <<-EOS
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
ended_at TEXT,
|
25
|
-
log_id TEXT,
|
26
|
-
ip TEXT,
|
27
|
-
unique_visitor TEXT,
|
28
|
-
url TEXT,
|
29
|
-
controller TEXT,
|
30
|
-
html_verb TEXT,
|
31
|
-
status INTEGER,
|
32
|
-
duration_total_ms FLOAT,
|
33
|
-
duration_views_ms FLOAT,
|
34
|
-
duration_ar_ms FLOAT,
|
35
|
-
allocations INTEGER,
|
36
|
-
comment TEXT,
|
37
|
-
source_file TEXT,
|
38
|
-
line_number INTEGER
|
39
|
-
)
|
44
|
+
CREATE TABLE IF NOT EXISTS Event(
|
45
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
46
|
+
#{table_declaration}
|
47
|
+
)
|
40
48
|
EOS
|
41
49
|
|
50
|
+
tds = event_table.keys.size
|
42
51
|
ins = db.prepare <<-EOS
|
43
|
-
|
44
|
-
|
45
|
-
started_at,
|
46
|
-
ended_at,
|
47
|
-
log_id,
|
48
|
-
ip,
|
49
|
-
unique_visitor,
|
50
|
-
url,
|
51
|
-
controller,
|
52
|
-
html_verb,
|
53
|
-
status,
|
54
|
-
duration_total_ms,
|
55
|
-
duration_views_ms,
|
56
|
-
duration_ar_ms,
|
57
|
-
allocations,
|
58
|
-
comment,
|
59
|
-
source_file,
|
60
|
-
line_number
|
52
|
+
insert into Event(
|
53
|
+
#{event_table.keys.join(", ")}
|
61
54
|
)
|
62
|
-
values (#{Array.new(
|
55
|
+
values (#{Array.new(tds, "?").join(", ")})
|
63
56
|
EOS
|
64
57
|
|
65
58
|
db.execute <<-EOS
|
@@ -180,9 +173,16 @@ module LogSense
|
|
180
173
|
# LOG_LEVEL, [ZULU_TIMESTAMP #NUMBER] INFO --: [ID] Processing by CONTROLLER as FORMAT
|
181
174
|
# LOG_LEVEL, [ZULU_TIMESTAMP #NUMBER] INFO --: [ID] Parameters: JSON
|
182
175
|
# LOG_LEVEL, [ZULU_TIMESTAMP #NUMBER] INFO --: [ID] Rendered VIEW within LAYOUT (Duration: DURATION | Allocations: ALLOCATIONS)
|
176
|
+
#
|
177
|
+
# For rails_6:
|
178
|
+
#
|
183
179
|
# LOG_LEVEL, [ZULU_TIMESTAMP #NUMBER] INFO --: [ID] Completed STATUS STATUS_STRING in DURATION (Views: DURATION | ActiveRecord: DURATION | Allocations: NUMBER)
|
184
180
|
#
|
185
|
-
#
|
181
|
+
# For rails_7:
|
182
|
+
#
|
183
|
+
# LOG_LEVEL, [ZULU_TIMESTAMP #NUMBER] INFO --: [ID] Completed STATUS STATUS_STRING in DURATION (Views: DURATION | ActiveRecord: DURATION (N queries, M cached) | GC: DURATION)
|
184
|
+
#
|
185
|
+
# and they appear in the order shown above: started, processing, ...
|
186
186
|
#
|
187
187
|
# Different requests might be interleaved, of course
|
188
188
|
#
|
@@ -244,6 +244,9 @@ module LogSense
|
|
244
244
|
event[:duration_views_ms],
|
245
245
|
event[:duration_ar_ms],
|
246
246
|
event[:allocations],
|
247
|
+
event[:queries],
|
248
|
+
event[:cached_queries],
|
249
|
+
event[:gc_duration],
|
247
250
|
event[:comment],
|
248
251
|
filename,
|
249
252
|
line_number
|
@@ -280,6 +283,9 @@ module LogSense
|
|
280
283
|
event[:duration_views_ms],
|
281
284
|
event[:duration_ar_ms],
|
282
285
|
event[:allocations],
|
286
|
+
event[:queries],
|
287
|
+
event[:cached_queries],
|
288
|
+
event[:gc_duration],
|
283
289
|
event[:comment],
|
284
290
|
filename,
|
285
291
|
line_number
|
@@ -434,6 +440,7 @@ module LogSense
|
|
434
440
|
STATUS = '(?<status>[0-9]+)'
|
435
441
|
STATUS_IN_WORDS = '(OK|Unauthorized|Found|Internal Server Error|Bad Request|Method Not Allowed|Request Timeout|Not Implemented|Bad Gateway|Service Unavailable)'
|
436
442
|
MSECS = '[0-9.]+'
|
443
|
+
INT = '[0-9]+'
|
437
444
|
|
438
445
|
# I, [2021-10-19T08:16:34.343858 #10477] INFO -- : [67103c0d-455d-4fe8-951e-87e97628cb66] Started GET "/grow/people/471" for 217.77.80.35 at 2021-10-19 08:16:34 +0000
|
439
446
|
STARTED_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{LOG_ID}\] Started #{VERB} "#{URL}" for #{IP} at/o
|
@@ -455,7 +462,21 @@ module LogSense
|
|
455
462
|
# I, [2021-10-19T08:16:34.712331 #10477] INFO -- : [67103c0d-455d-4fe8-951e-87e97628cb66] Completed 200 OK in 367ms (Views: 216.7ms | ActiveRecord: 141.3ms | Allocations: 168792)
|
456
463
|
# I, [2021-12-09T16:53:52.657727 #2735058] INFO -- : [0064e403-9eb2-439d-8fe1-a334c86f5532] Completed 200 OK in 13ms (Views: 11.1ms | ActiveRecord: 1.2ms)
|
457
464
|
# I, [2021-12-06T14:28:19.736545 #2804090] INFO -- : [34091cb5-3e7b-4042-aaf8-6c6510d3f14c] Completed 500 Internal Server Error in 66ms (ActiveRecord: 8.0ms | Allocations: 24885)
|
458
|
-
|
465
|
+
|
466
|
+
# REGEXP Fragments
|
467
|
+
|
468
|
+
# views is optional
|
469
|
+
# In strings I need to properly escape \ or it will be mis-interpreted
|
470
|
+
VIEWS = "(Views: (?<views>#{MSECS})ms \\| )?"
|
471
|
+
# active records has two formats (according to Rails version)
|
472
|
+
ACTIVE_RECORDS_BASE = "ActiveRecord: (?<arec>#{MSECS})ms"
|
473
|
+
ACTIVE_RECORDS_QUERIES = "( \\((?<queries>#{INT}) queries, (?<cached_queries>#{INT}) cached\\))?"
|
474
|
+
ACTIVE_RECORDS = "#{ACTIVE_RECORDS_BASE}#{ACTIVE_RECORDS_QUERIES}"
|
475
|
+
# older rails versions have ALLOCATIONS, newer have GC
|
476
|
+
ALLOCATIONS = " \\| Allocations: (?<alloc>#{INT})"
|
477
|
+
GC = " \\| GC: (?<gc_duration>#{MSECS})ms"
|
478
|
+
|
479
|
+
COMPLETED_REGEXP = /I, \[#{TIMESTAMP} #[0-9]+\] INFO -- : \[#{LOG_ID}\] Completed #{STATUS} #{STATUS_IN_WORDS} in (?<total>#{MSECS})ms \(#{VIEWS}#{ACTIVE_RECORDS}(#{ALLOCATIONS}|#{GC})?\)/o
|
459
480
|
|
460
481
|
def match_and_process_completed(line)
|
461
482
|
matchdata = (COMPLETED_REGEXP.match line)
|
@@ -470,6 +491,9 @@ module LogSense
|
|
470
491
|
duration_views_ms: matchdata[:views],
|
471
492
|
duration_ar_ms: matchdata[:arec],
|
472
493
|
allocations: matchdata[:alloc],
|
494
|
+
queries: matchdata[:queries],
|
495
|
+
cached_queries: matchdata[:cached_queries],
|
496
|
+
gc_duration: matchdata[:gc_duration],
|
473
497
|
comment: ""
|
474
498
|
}
|
475
499
|
end
|
@@ -81,6 +81,22 @@ module LogSense
|
|
81
81
|
}
|
82
82
|
end
|
83
83
|
|
84
|
+
@queries = @db.execute %Q(
|
85
|
+
SELECT SUM(queries), SUM(cached_queries),
|
86
|
+
ROUND(SUM(queries) / SUM(cached_queries), 2)
|
87
|
+
FROM Event
|
88
|
+
)
|
89
|
+
|
90
|
+
@queries_by_controller = @db.execute %Q(
|
91
|
+
SELECT controller,
|
92
|
+
MIN(queries), MAX(queries), ROUND(AVG(queries), 2),
|
93
|
+
SUM(queries), SUM(cached_queries),
|
94
|
+
ROUND(SUM(cached_queries) / SUM(queries), 2),
|
95
|
+
SUM(gc_duration)
|
96
|
+
FROM Event
|
97
|
+
GROUP BY Event.controller
|
98
|
+
)
|
99
|
+
|
84
100
|
@controller_and_methods_by_device = @db.execute %Q(
|
85
101
|
SELECT controller as Controller,
|
86
102
|
method as Method,
|
@@ -125,7 +141,7 @@ module LogSense
|
|
125
141
|
ON event.log_id == error.log_id
|
126
142
|
WHERE #{filter} and exit_status == 'S:FAILED'
|
127
143
|
GROUP BY strftime("%Y-%m-%d", started_at)
|
128
|
-
)
|
144
|
+
) || [[]]
|
129
145
|
|
130
146
|
@fatal = @db.execute %(
|
131
147
|
SELECT strftime("%Y-%m-%d %H:%M", started_at),
|
@@ -137,7 +153,7 @@ module LogSense
|
|
137
153
|
FROM Event JOIN Error
|
138
154
|
ON event.log_id == error.log_id
|
139
155
|
WHERE #{filter} and exit_status == 'S:FAILED'
|
140
|
-
)
|
156
|
+
) || [[]]
|
141
157
|
|
142
158
|
@fatal_grouped = @db.execute %(
|
143
159
|
SELECT filename,
|
@@ -147,7 +163,7 @@ module LogSense
|
|
147
163
|
count(distinct(error.id))
|
148
164
|
FROM Error
|
149
165
|
GROUP BY description
|
150
|
-
)
|
166
|
+
) || [[]]
|
151
167
|
|
152
168
|
@job_plot = @db.execute %(
|
153
169
|
SELECT strftime("%Y-%m-%d", ended_at) as Day,
|
@@ -156,7 +172,7 @@ module LogSense
|
|
156
172
|
FROM Job
|
157
173
|
WHERE #{filter}
|
158
174
|
GROUP BY strftime("%Y-%m-%d", ended_at)
|
159
|
-
)
|
175
|
+
) || [[]]
|
160
176
|
|
161
177
|
# worker,
|
162
178
|
# host,
|
@@ -173,7 +189,7 @@ module LogSense
|
|
173
189
|
attempt
|
174
190
|
FROM Job
|
175
191
|
WHERE #{filter}
|
176
|
-
)
|
192
|
+
) || [[]]
|
177
193
|
|
178
194
|
@job_error_grouped = @db.execute %(
|
179
195
|
SELECT worker,
|
@@ -188,7 +204,7 @@ module LogSense
|
|
188
204
|
FROM Job
|
189
205
|
WHERE #{filter} and (exit_status == 'S:ERROR' or exit_status == 'S:FAILED')
|
190
206
|
GROUP BY object_id
|
191
|
-
)
|
207
|
+
) || [[]]
|
192
208
|
|
193
209
|
instance_vars_to_hash
|
194
210
|
end
|
@@ -136,6 +136,29 @@ module LogSense
|
|
136
136
|
}
|
137
137
|
end
|
138
138
|
|
139
|
+
def queries(data, colors: [], col: "small-12 cell")
|
140
|
+
{
|
141
|
+
title: "Number of queries",
|
142
|
+
header: %w[Queries Cached Perc_Cached],
|
143
|
+
column_alignment: %i[right right right],
|
144
|
+
rows: data[:queries],
|
145
|
+
col: col,
|
146
|
+
}
|
147
|
+
end
|
148
|
+
|
149
|
+
def queries_by_controller(data, colors: [], col: "small-12 cell")
|
150
|
+
{
|
151
|
+
title: "Queries by Controller",
|
152
|
+
header: ["Controller",
|
153
|
+
"Min queries", "Max queries", "Avg Queries",
|
154
|
+
"Total queries", "Cached queries", "Perc",
|
155
|
+
"Total GC"],
|
156
|
+
column_alignment: %i[left right right right right right right right],
|
157
|
+
rows: data[:queries_by_controller],
|
158
|
+
col: col,
|
159
|
+
}
|
160
|
+
end
|
161
|
+
|
139
162
|
#
|
140
163
|
# Reports shared between rails and apache/nginx
|
141
164
|
#
|
data/lib/log_sense/version.rb
CHANGED
data/log_sense.gemspec
CHANGED
@@ -6,8 +6,18 @@ Gem::Specification.new do |spec|
|
|
6
6
|
spec.authors = ["Adolfo Villafiorita"]
|
7
7
|
spec.email = ["adolfo@shair.tech"]
|
8
8
|
|
9
|
-
spec.summary = %q{
|
10
|
-
spec.description = %q{
|
9
|
+
spec.summary = %q{Analyze Rails and Apache/Nginx log file, generating statistics and reports.}
|
10
|
+
spec.description = %q{log_sense is a command-line command which analyzes Rails and Nginx logs and
|
11
|
+
provides detailed data about accesses, performances, and errors. It is a
|
12
|
+
one stop shop solution for analyzing your Rails application in production.
|
13
|
+
|
14
|
+
Simple to use and working offline, it does not require any modification to
|
15
|
+
your app (e.g., no need to change the log format, no cookies, no JavaScript
|
16
|
+
code, no infrastructure to setup).
|
17
|
+
|
18
|
+
We use it to monitor our applications in production, using a cron job which
|
19
|
+
pulls the logs and calls log_sense to generate HTML reports.
|
20
|
+
}
|
11
21
|
spec.homepage = "https://github.com/shair-tech/log_sense/"
|
12
22
|
spec.license = "MIT"
|
13
23
|
spec.required_ruby_version = Gem::Requirement.new(">= 2.6.9")
|
@@ -26,7 +36,9 @@ Gem::Specification.new do |spec|
|
|
26
36
|
spec.bindir = "exe"
|
27
37
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
28
38
|
spec.require_paths = ["lib"]
|
29
|
-
|
39
|
+
|
40
|
+
spec.add_dependency "ostruct"
|
41
|
+
spec.add_dependency "csv"
|
30
42
|
spec.add_dependency "browser", "~> 6.0.0"
|
31
43
|
spec.add_dependency "ipaddr", "~> 1.2.0"
|
32
44
|
spec.add_dependency "iso_country_codes", "~> 0.7.0"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: log_sense
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Adolfo Villafiorita
|
@@ -9,6 +9,34 @@ bindir: exe
|
|
9
9
|
cert_chain: []
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
11
11
|
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: ostruct
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - ">="
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - ">="
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: csv
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
12
40
|
- !ruby/object:Gem::Dependency
|
13
41
|
name: browser
|
14
42
|
requirement: !ruby/object:Gem::Requirement
|
@@ -135,8 +163,14 @@ dependencies:
|
|
135
163
|
- - ">="
|
136
164
|
- !ruby/object:Gem::Version
|
137
165
|
version: '0'
|
138
|
-
description:
|
139
|
-
|
166
|
+
description: "log_sense is a command-line command which analyzes Rails and Nginx logs
|
167
|
+
and\n provides detailed data about accesses, performances, and errors. It is
|
168
|
+
a\n one stop shop solution for analyzing your Rails application in production.\n\n
|
169
|
+
\ Simple to use and working offline, it does not require any modification to\n
|
170
|
+
\ your app (e.g., no need to change the log format, no cookies, no JavaScript\n
|
171
|
+
\ code, no infrastructure to setup).\n\n We use it to monitor our applications
|
172
|
+
in production, using a cron job which\n pulls the logs and calls log_sense to
|
173
|
+
generate HTML reports.\n "
|
140
174
|
email:
|
141
175
|
- adolfo@shair.tech
|
142
176
|
executables:
|
@@ -218,5 +252,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
218
252
|
requirements: []
|
219
253
|
rubygems_version: 3.6.7
|
220
254
|
specification_version: 4
|
221
|
-
summary:
|
255
|
+
summary: Analyze Rails and Apache/Nginx log file, generating statistics and reports.
|
222
256
|
test_files: []
|