plog 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +7 -0
- data/LICENSE +20 -0
- data/README.rdoc +148 -0
- data/Rakefile +56 -0
- data/VERSION +1 -0
- data/bin/plog +22 -0
- data/lib/cli.rb +119 -0
- data/lib/completed_line.rb +109 -0
- data/lib/log_file.rb +52 -0
- data/lib/object_file.rb +165 -0
- data/lib/plog.rb +13 -0
- data/lib/url.rb +36 -0
- data/plog.gemspec +70 -0
- data/test/completed_line_test.rb +156 -0
- data/test/data/example.log +10 -0
- data/test/log_file_test.rb +39 -0
- data/test/object_file_test.rb +115 -0
- data/test/plog_test.rb +8 -0
- data/test/test_helper.rb +10 -0
- data/test/url_test.rb +28 -0
- metadata +90 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Kazuyoshi Tlacaelel
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
= Plog
|
2
|
+
|
3
|
+
Ruby on Rails production log statistics generator.
|
4
|
+
|
5
|
+
by Kazuyoshi Tlacaelel.
|
6
|
+
|
7
|
+
$ plog my_directory_with_production_logs/
|
8
|
+
$ ls plog/
|
9
|
+
objects statistics.txt
|
10
|
+
|
11
|
+
|
12
|
+
== Copyright
|
13
|
+
|
14
|
+
Copyright (c) 2009 Kazuyoshi Tlacaelel. See LICENSE for details.
|
15
|
+
|
16
|
+
== Comming soon!
|
17
|
+
|
18
|
+
comming soon to a terminal near you!
|
19
|
+
|
20
|
+
* sorting:
|
21
|
+
* more statistics
|
22
|
+
(hits, number of hits per response type like 200, 302 etc)
|
23
|
+
|
24
|
+
May be... but don't expect any of these to become a reality
|
25
|
+
I love programming and tons of ideas flow in my head with posibilities
|
26
|
+
but I do not have the time! :(
|
27
|
+
|
28
|
+
* Different export drivers?
|
29
|
+
(should be another plugin)
|
30
|
+
|
31
|
+
* cli options abstracted into classes so they can be aggregated with ease
|
32
|
+
(this might be cool)
|
33
|
+
|
34
|
+
* google charts integration
|
35
|
+
(is too much for this api)
|
36
|
+
(may be as a different plugin)
|
37
|
+
|
38
|
+
== Dependancies:
|
39
|
+
|
40
|
+
* FileUtils
|
41
|
+
* MD5
|
42
|
+
* File
|
43
|
+
* Dir
|
44
|
+
* URI
|
45
|
+
* rubygems 1.3.3
|
46
|
+
|
47
|
+
== Features:
|
48
|
+
|
49
|
+
Compiles statistics in a easy to read manner
|
50
|
+
|
51
|
+
|
52
|
+
Hits Time Avg-Time DbTime Avg-DB View Avg-View Url
|
53
|
+
93 18057 0.194 5775 0.062 10437 0.112 /users/[0-9]
|
54
|
+
12 277 0.023 193 0.016 0 0 /user_themes/update
|
55
|
+
1 290 0.29 107 0.107 175 0.175 /posts
|
56
|
+
3 99 0.033 57 0.019 0 0 /comments
|
57
|
+
82 11922 0.145 5969 0.072 4979 0.06 /user/[0-9]/comments
|
58
|
+
1868 1301006 0.696 623246 0.333 491474 0.263 /user/[0-9]/posts?
|
59
|
+
1 72 0.072 39 0.039 0 0 /confirm_destroy_account/[0-9]?
|
60
|
+
797 971882 1.219 414829 0.52 423130 0.53 /user/[0-9]/articles/friends?
|
61
|
+
122 20830 0.17 7081 0.058 10291 0.084 /user/[0-9]/topics?
|
62
|
+
|
63
|
+
== Improvized twicks:
|
64
|
+
|
65
|
+
You can improvize some quick sorting using the command line like:
|
66
|
+
|
67
|
+
cat plog/statistics.txt | head -n1 > some_file.txt
|
68
|
+
cat plog/statistics.txt | sort -nr >> some_file.txt
|
69
|
+
|
70
|
+
This will create a new file with sorted results by hits for you!
|
71
|
+
|
72
|
+
Hits Time Avg-Time DbTime Avg-DB View Avg-View Url
|
73
|
+
1868 1301006 0.696 623246 0.333 491474 0.263 /user/[0-9]/posts?
|
74
|
+
797 971882 1.219 414829 0.52 423130 0.53 /user/[0-9]/articles/friends?
|
75
|
+
122 20830 0.17 7081 0.058 10291 0.084 /user/[0-9]/topics?
|
76
|
+
93 18057 0.194 5775 0.062 10437 0.112 /users/[0-9]
|
77
|
+
82 11922 0.145 5969 0.072 4979 0.06 /user/[0-9]/comments
|
78
|
+
12 277 0.023 193 0.016 0 0 /user_themes/update
|
79
|
+
3 99 0.033 57 0.019 0 0 /comments
|
80
|
+
1 72 0.072 39 0.039 0 0 /confirm_destroy_account/[0-9]?
|
81
|
+
1 290 0.29 107 0.107 175 0.175 /posts
|
82
|
+
|
83
|
+
== Installation
|
84
|
+
|
85
|
+
$ gem sources -a http://gems.github.com
|
86
|
+
$ gem install ktlacaelel-plog
|
87
|
+
|
88
|
+
== Known bugs:
|
89
|
+
none, so far..
|
90
|
+
|
91
|
+
== License:
|
92
|
+
MIT. See the "LICENSE" file for details.
|
93
|
+
|
94
|
+
== Participate
|
95
|
+
|
96
|
+
* Send me a brief message
|
97
|
+
* Fork the project.
|
98
|
+
* Add tests for it. This is important so I don't break it in a
|
99
|
+
future version unintentionally.
|
100
|
+
* Make your feature addition or bug fix.
|
101
|
+
* Commit, do not mess with rakefile, version, or history.
|
102
|
+
(if you want to have your own version, that is fine but
|
103
|
+
bump version in a commit by itself I can ignore when I pull)
|
104
|
+
* Send me a pull request. Bonus points for topic branches.
|
105
|
+
|
106
|
+
== Output:
|
107
|
+
|
108
|
+
All output is packed in a plog directory, from wherever you run
|
109
|
+
the "plog" executable.
|
110
|
+
|
111
|
+
Inside this directory you'll find a directory called objects.
|
112
|
+
wich contains many small files called "object-files"
|
113
|
+
this files contain all the parsed statistics of you log files.
|
114
|
+
|
115
|
+
The information is scattered and can be reused for future compilations
|
116
|
+
meaning that new compilations data will merge with the existing one.
|
117
|
+
|
118
|
+
all data in these "object-files" is read and summarized into a
|
119
|
+
statistics.txt file found inside of the plog directory.
|
120
|
+
|
121
|
+
== Tests:
|
122
|
+
|
123
|
+
* 70% tested, with shoulda
|
124
|
+
* all core areas of the api are tested, and tests pass!
|
125
|
+
* only the cli part of the api has no tests, but will be implemented soon
|
126
|
+
|
127
|
+
== Usage:
|
128
|
+
|
129
|
+
The "plog" ( production log ) executable receives one option.
|
130
|
+
This must be a directory containing one or more production logs.
|
131
|
+
Only logs in the first level will be parsed.
|
132
|
+
Recursive reading is not allowed
|
133
|
+
|
134
|
+
$ plog directory_with_logs/
|
135
|
+
|
136
|
+
== Works on:
|
137
|
+
|
138
|
+
* OSX Darwin utopia.local 9.8.0 Darwin Kernel Version 9.8.0: Wed Jul 15 16:55:01 PDT 2009; root:xnu-1228.15.4~1/RELEASE_I386 i386
|
139
|
+
* UBUNTU Linux utopia.local 2.6.28-11-server #42-Ubuntu SMP Fri Apr 17 02:45:36 UTC 2009 x86_64 GNU/Linux
|
140
|
+
|
141
|
+
* OSX ruby 1.8.6 (2008-08-11 patchlevel 287) [universal-darwin9.0]
|
142
|
+
* UBUNTU ruby 1.8.7 (2009-06-12 patchlevel 174) [x86_64-linux]
|
143
|
+
|
144
|
+
* rubygems 1.3.4
|
145
|
+
* rubygems 1.3.3
|
146
|
+
|
147
|
+
== The end.
|
148
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "plog"
|
8
|
+
gem.summary = %Q{Plog - Ruby on Rails production log statistic generator. by Kazuyoshi Tlacaelel}
|
9
|
+
gem.description = %Q{Plog - Ruby on Rails production log statistic generator. by Kazuyoshi Tlacaelel}
|
10
|
+
gem.email = "kazu.dev@gmail.com"
|
11
|
+
gem.homepage = "http://github.com/ktlacaelel/plog"
|
12
|
+
gem.authors = ["Kazuyoshi Tlacaelel"]
|
13
|
+
gem.add_development_dependency "thoughtbot-shoulda"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
rescue LoadError
|
17
|
+
puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'rake/testtask'
|
21
|
+
Rake::TestTask.new(:test) do |test|
|
22
|
+
test.libs << 'lib' << 'test'
|
23
|
+
test.pattern = 'test/**/*_test.rb'
|
24
|
+
test.verbose = true
|
25
|
+
end
|
26
|
+
|
27
|
+
begin
|
28
|
+
require 'rcov/rcovtask'
|
29
|
+
Rcov::RcovTask.new do |test|
|
30
|
+
test.libs << 'test'
|
31
|
+
test.pattern = 'test/**/*_test.rb'
|
32
|
+
test.verbose = true
|
33
|
+
end
|
34
|
+
rescue LoadError
|
35
|
+
task :rcov do
|
36
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
task :test => :check_dependencies
|
41
|
+
|
42
|
+
task :default => :test
|
43
|
+
|
44
|
+
require 'rake/rdoctask'
|
45
|
+
Rake::RDocTask.new do |rdoc|
|
46
|
+
if File.exist?('VERSION')
|
47
|
+
version = File.read('VERSION')
|
48
|
+
else
|
49
|
+
version = ""
|
50
|
+
end
|
51
|
+
|
52
|
+
rdoc.rdoc_dir = 'rdoc'
|
53
|
+
rdoc.title = "plog #{version}"
|
54
|
+
rdoc.rdoc_files.include('README*')
|
55
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
56
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.4.0
|
data/bin/plog
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
#!/home/jognote/ruby/bin/ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'plog'
|
5
|
+
|
6
|
+
banner = '
|
7
|
+
ERROR!
|
8
|
+
No logs given, I have nothing to do!
|
9
|
+
|
10
|
+
HELP?
|
11
|
+
The "plog" ( production log ) executable receives one option.
|
12
|
+
This must be a directory containing one or more production logs.
|
13
|
+
Only logs in the first level will be parsed.
|
14
|
+
Recursive reading is not allowed
|
15
|
+
|
16
|
+
USAGE:
|
17
|
+
$ plog directory_with_logs/
|
18
|
+
|
19
|
+
'
|
20
|
+
|
21
|
+
abort banner if ARGV.size != 1
|
22
|
+
Plog::Cli.new(ARGV[0], 'plog').run!
|
data/lib/cli.rb
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
module Plog
|
2
|
+
|
3
|
+
class Cli
|
4
|
+
|
5
|
+
attr_reader :source_directory, :log_files
|
6
|
+
|
7
|
+
STATS_FILE = 'statistics.txt'
|
8
|
+
|
9
|
+
# ==========================================================================
|
10
|
+
# CLIENT INTERFACE
|
11
|
+
# ==========================================================================
|
12
|
+
|
13
|
+
def initialize(source_directory, target_directory)
|
14
|
+
@log_files = []
|
15
|
+
@source_directory = source_directory
|
16
|
+
@target_directory = target_directory
|
17
|
+
check_directory_consistency!
|
18
|
+
preload_log_files!
|
19
|
+
end
|
20
|
+
|
21
|
+
def run!
|
22
|
+
stdout count_of_logs_banner(@log_files.size)
|
23
|
+
stdout notice_banner
|
24
|
+
create_object_files
|
25
|
+
destroy_old_log_file
|
26
|
+
append_headers_to_log_file
|
27
|
+
parse_object_files
|
28
|
+
end
|
29
|
+
|
30
|
+
# ==========================================================================
|
31
|
+
# INTERNAL INTERFACE
|
32
|
+
# ==========================================================================
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def trim(string)
|
37
|
+
return '' unless string.is_a? String
|
38
|
+
string.gsub(/^\s+/, '').gsub(/\s+$/, '')
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_object_files
|
42
|
+
@log_files.each do |log_file|
|
43
|
+
stdout parsing_log_file_banner(log_file.name)
|
44
|
+
log_file.parse_completed_lines!
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def parse_object_files
|
49
|
+
Dir.glob(object_file_pattern).each do |object_file|
|
50
|
+
file = File.new(statistic_file_path, 'a+')
|
51
|
+
file.puts trim(ObjectFile.new(object_file).export)
|
52
|
+
file.close
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def object_file_pattern
|
57
|
+
File.join(@target_directory, 'objects') + '/*'
|
58
|
+
end
|
59
|
+
|
60
|
+
def statistic_file_path
|
61
|
+
File.join(@target_directory, STATS_FILE)
|
62
|
+
end
|
63
|
+
|
64
|
+
def destroy_old_log_file
|
65
|
+
FileUtils.touch statistic_file_path
|
66
|
+
FileUtils.rm statistic_file_path
|
67
|
+
end
|
68
|
+
|
69
|
+
def append_headers_to_log_file
|
70
|
+
file = File.new(statistic_file_path, 'a+')
|
71
|
+
file.puts trim(ObjectFile.formated_headers)
|
72
|
+
file.close
|
73
|
+
end
|
74
|
+
|
75
|
+
def check_directory_consistency!
|
76
|
+
abort directory_not_found_banner unless File.exist? @source_directory
|
77
|
+
end
|
78
|
+
|
79
|
+
def preload_log_files!
|
80
|
+
Dir.glob(File.join(@source_directory, '*.log')).each do |log_file|
|
81
|
+
@log_files << LogFile.new(log_file, @target_directory)
|
82
|
+
stdout loading_log_file_banner(log_file)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def stdout(string)
|
87
|
+
puts ' ---> ' + string
|
88
|
+
end
|
89
|
+
|
90
|
+
# ==========================================================================
|
91
|
+
# BANNERS
|
92
|
+
# ==========================================================================
|
93
|
+
|
94
|
+
def notice_banner
|
95
|
+
'Parsing logs...
|
96
|
+
This may take a long-while, go get yourself a coffee!
|
97
|
+
While I hanlde this stuff for you.
|
98
|
+
'
|
99
|
+
end
|
100
|
+
|
101
|
+
def count_of_logs_banner(size)
|
102
|
+
'Count of loaded logs : %s ' % size
|
103
|
+
end
|
104
|
+
|
105
|
+
def parsing_log_file_banner(file)
|
106
|
+
'Parsing log located at : %s ' % file
|
107
|
+
end
|
108
|
+
|
109
|
+
def loading_log_file_banner(file)
|
110
|
+
'Initializing log file: %s' % file
|
111
|
+
end
|
112
|
+
|
113
|
+
def directory_not_found_banner
|
114
|
+
'No such dir: %s' % @source_directory
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
@@ -0,0 +1,109 @@
|
|
1
|
+
module Plog
|
2
|
+
|
3
|
+
class CompletedLine
|
4
|
+
|
5
|
+
TOTAL_TIME_REGEX = /^(Completed in )(\d+)ms/ # \2
|
6
|
+
VIEW_TIME_REGEX = /([^\(]+)([^V]+)(View: )(\d+)(.*)/ # \4
|
7
|
+
DB_TIME_REGEX = /([^\(]+)([^D]+)(DB: )(\d+)(.*)/ # \4
|
8
|
+
URL_REGEX = /([^\[]+)(\[)([^\]]+)(.*)/
|
9
|
+
STATUS_REGEX = /(\s)(\d+)(\s)(\w+)(\s)(\[)(.*)/ # \2, \4
|
10
|
+
COMPLETED_TIME_REGEX = /^Completed/
|
11
|
+
|
12
|
+
def self.url
|
13
|
+
URL.new(@url)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.valid?
|
17
|
+
completed_line?
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.completed_line?
|
21
|
+
return false unless @line.is_a? String
|
22
|
+
return false unless COMPLETED_TIME_REGEX =~ @line
|
23
|
+
return false unless DB_TIME_REGEX =~ @line
|
24
|
+
return false if @line.size < 10
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.read!(string_line)
|
29
|
+
@line = string_line
|
30
|
+
validate
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.db_time
|
34
|
+
@db_time
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.view_time
|
38
|
+
@view_time
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.total_time
|
42
|
+
@total_time
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.status_string
|
46
|
+
@status_string
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.status_number
|
50
|
+
@status_number
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.to_csv
|
54
|
+
[total_time, view_time, db_time, url.simplify].join(',')
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.merge(view, db)
|
58
|
+
@view_time += view.to_i
|
59
|
+
@db_time += db.to_i
|
60
|
+
@total_time = (@view_time + @db_time)
|
61
|
+
end
|
62
|
+
|
63
|
+
protected
|
64
|
+
|
65
|
+
def self.validate
|
66
|
+
return unless completed_line?
|
67
|
+
fragmentize!
|
68
|
+
extract_time!
|
69
|
+
extract_url!
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.fragmentize!
|
73
|
+
@first_fragment, @second_fragment = @line.split('|')
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.extract_time!
|
77
|
+
@total_time = extract_total_time
|
78
|
+
@view_time = extract_view_time
|
79
|
+
@db_time = extract_db_time
|
80
|
+
@status_number, @status_string = extract_status_number_and_string
|
81
|
+
nil
|
82
|
+
end
|
83
|
+
|
84
|
+
def self.extract_total_time
|
85
|
+
return 0 unless TOTAL_TIME_REGEX =~ @first_fragment
|
86
|
+
@first_fragment.gsub(TOTAL_TIME_REGEX, '\2').to_i
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.extract_db_time
|
90
|
+
return 0 unless DB_TIME_REGEX =~ @first_fragment
|
91
|
+
@first_fragment.gsub(DB_TIME_REGEX, '\4').to_i
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.extract_view_time
|
95
|
+
return 0 unless VIEW_TIME_REGEX =~ @first_fragment
|
96
|
+
@first_fragment.gsub(VIEW_TIME_REGEX, '\4').to_i
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.extract_status_number_and_string
|
100
|
+
@second_fragment.gsub(STATUS_REGEX, '\2,\4').split(',')
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.extract_url!
|
104
|
+
@url = @second_fragment.gsub(URL_REGEX, '\3')
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
data/lib/log_file.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
module Plog
|
2
|
+
|
3
|
+
class LogFile < File
|
4
|
+
|
5
|
+
attr_accessor :name
|
6
|
+
|
7
|
+
def initialize(file, dump_dir)
|
8
|
+
@dump_dir = dump_dir
|
9
|
+
check_dump_dir_consistency!
|
10
|
+
super(file, 'r')
|
11
|
+
@name = file
|
12
|
+
end
|
13
|
+
|
14
|
+
def check_dump_dir_consistency!
|
15
|
+
unless File.exist? objects_recipient_path
|
16
|
+
FileUtils.mkdir_p objects_recipient_path
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def validate(file)
|
21
|
+
abort 'File not found: ' + file.inspect unless File.exist? file
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_completed_lines!
|
25
|
+
each_line do |line|
|
26
|
+
CompletedLine.read! line
|
27
|
+
parse_completed_line if CompletedLine.valid?
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def parse_completed_line
|
32
|
+
of = ObjectFile.new(object_path, 'w+')
|
33
|
+
of.simplified_url = CompletedLine.url.simplify
|
34
|
+
of.append_total_time CompletedLine.total_time
|
35
|
+
of.append_db_time CompletedLine.db_time
|
36
|
+
of.append_view_time CompletedLine.view_time
|
37
|
+
of.append_hits 1
|
38
|
+
of.save_changes!
|
39
|
+
of.close
|
40
|
+
end
|
41
|
+
|
42
|
+
def object_path
|
43
|
+
File.join(objects_recipient_path, CompletedLine.url.hashify)
|
44
|
+
end
|
45
|
+
|
46
|
+
def objects_recipient_path
|
47
|
+
@objects_recipient_path ||= File.join(@dump_dir, 'objects')
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/lib/object_file.rb
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
module Plog
|
2
|
+
|
3
|
+
class ObjectFile < File
|
4
|
+
|
5
|
+
DEFAULT_VALUES = {
|
6
|
+
:@total_hits => 0,
|
7
|
+
:@total_time => 0,
|
8
|
+
:@view_time => 0,
|
9
|
+
:@db_time => 0,
|
10
|
+
:@simplified_url => 'default'
|
11
|
+
}
|
12
|
+
|
13
|
+
KEY_ORDER = [
|
14
|
+
:@total_hits,
|
15
|
+
:@total_time,
|
16
|
+
:@view_time,
|
17
|
+
:@db_time,
|
18
|
+
:@simplified_url
|
19
|
+
]
|
20
|
+
|
21
|
+
attr_accessor :simplified_url
|
22
|
+
attr_reader :view_time, :total_time, :db_time, :total_hits
|
23
|
+
|
24
|
+
def initialize(*args)
|
25
|
+
raw_data(args.first)
|
26
|
+
super(*args)
|
27
|
+
setup!
|
28
|
+
end
|
29
|
+
|
30
|
+
def setup!
|
31
|
+
unpack_default_values if ObjectFile.zero? path
|
32
|
+
load_changes!
|
33
|
+
end
|
34
|
+
|
35
|
+
def unpack_default_values
|
36
|
+
DEFAULT_VALUES.each do |k, v|
|
37
|
+
instance_variable_set k, v
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def append_view_time(value)
|
42
|
+
@view_time += value.to_i
|
43
|
+
end
|
44
|
+
|
45
|
+
def append_db_time(value)
|
46
|
+
@db_time += value.to_i
|
47
|
+
end
|
48
|
+
|
49
|
+
def append_total_time(value)
|
50
|
+
@total_time += value
|
51
|
+
end
|
52
|
+
|
53
|
+
def append_hits(value)
|
54
|
+
@total_hits += value
|
55
|
+
end
|
56
|
+
|
57
|
+
def save_changes!
|
58
|
+
puts serialize_changes
|
59
|
+
end
|
60
|
+
|
61
|
+
def serialize_changes
|
62
|
+
KEY_ORDER.collect { |key| instance_variable_get key }.join(',')
|
63
|
+
end
|
64
|
+
|
65
|
+
def load_changes!
|
66
|
+
return if @raw_data == ''
|
67
|
+
@total_hits, @total_time, @view_time, @db_time, @simplified_url = @raw_data.split(',')
|
68
|
+
intify!
|
69
|
+
end
|
70
|
+
|
71
|
+
def intify!
|
72
|
+
@total_hits = @total_hits.to_i
|
73
|
+
@total_time = @total_time.to_i
|
74
|
+
@db_time = @db_time.to_i
|
75
|
+
@view_time = @view_time.to_i
|
76
|
+
end
|
77
|
+
|
78
|
+
def raw_data(some_string)
|
79
|
+
if File.exist? some_string
|
80
|
+
@raw_data = File.new(some_string).read
|
81
|
+
else
|
82
|
+
@raw_data = ''
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def export_settings
|
87
|
+
[
|
88
|
+
[@total_hits, [:left, 10]],
|
89
|
+
[total_time_in_secs, [:left, 10]],
|
90
|
+
[avg_total_time, [:left, 10]],
|
91
|
+
[db_time_in_secs, [:left, 10]],
|
92
|
+
[avg_db_time, [:left, 10]],
|
93
|
+
[view_time_in_secs, [:left, 10]],
|
94
|
+
[avg_view_time, [:left, 10]],
|
95
|
+
[simplified_url, [:left, 40]],
|
96
|
+
]
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.export_header_settings
|
100
|
+
[
|
101
|
+
['Hits', [:left, 10]],
|
102
|
+
['Time', [:left, 10]],
|
103
|
+
['Avg-Time', [:left, 10]],
|
104
|
+
['DbTime', [:left, 10]],
|
105
|
+
['Avg-DB', [:left, 10]],
|
106
|
+
['View', [:left, 10]],
|
107
|
+
['Avg-View', [:left, 10]],
|
108
|
+
['Url', [:left, 40]],
|
109
|
+
]
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.formated_headers
|
113
|
+
export_header_settings.collect do |value, setting|
|
114
|
+
if setting.first == :left
|
115
|
+
value.to_s.ljust(setting[1])
|
116
|
+
else
|
117
|
+
value.to_s.rjust(setting[1])
|
118
|
+
end
|
119
|
+
end.join('') + "\n"
|
120
|
+
end
|
121
|
+
|
122
|
+
def export_headers
|
123
|
+
export_header_settings.collect { |header| header.ljust(20) }.join('')
|
124
|
+
end
|
125
|
+
|
126
|
+
def export
|
127
|
+
export_settings.collect do |value, setting|
|
128
|
+
if setting.first == :left
|
129
|
+
value.to_s.ljust(setting[1])
|
130
|
+
else
|
131
|
+
value.to_s.rjust(setting[1])
|
132
|
+
end
|
133
|
+
end.join('')
|
134
|
+
end
|
135
|
+
|
136
|
+
def avg_total_time
|
137
|
+
return 0 if @total_time == 0
|
138
|
+
(@total_time / @total_hits / 1000.0)
|
139
|
+
end
|
140
|
+
|
141
|
+
def avg_view_time
|
142
|
+
return 0 if @view_time == 0
|
143
|
+
(@view_time / @total_hits / 1000.0)
|
144
|
+
end
|
145
|
+
|
146
|
+
def avg_db_time
|
147
|
+
return 0 if @db_time == 0
|
148
|
+
(@db_time / @total_hits / 1000.0)
|
149
|
+
end
|
150
|
+
|
151
|
+
def total_time_in_secs
|
152
|
+
@total_time / 1000.0
|
153
|
+
end
|
154
|
+
|
155
|
+
def db_time_in_secs
|
156
|
+
@db_time / 1000.0
|
157
|
+
end
|
158
|
+
|
159
|
+
def view_time_in_secs
|
160
|
+
@view_time / 1000.0
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
data/lib/plog.rb
ADDED
data/lib/url.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
module Plog
|
2
|
+
|
3
|
+
class URL
|
4
|
+
|
5
|
+
def initialize(url)
|
6
|
+
@url = url
|
7
|
+
@obj = URI.parse URI.escape(url)
|
8
|
+
end
|
9
|
+
|
10
|
+
def to_s
|
11
|
+
@url
|
12
|
+
end
|
13
|
+
|
14
|
+
def simplify
|
15
|
+
simplify_url = @url.dup
|
16
|
+
simplifiers.each { |k, v| simplify_url.gsub!(k, v) if k =~ simplify_url }
|
17
|
+
simplify_url
|
18
|
+
end
|
19
|
+
|
20
|
+
def hashify
|
21
|
+
::MD5.hexdigest simplify
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def simplifiers
|
27
|
+
{
|
28
|
+
/\?.*/ => '?',
|
29
|
+
/\d+/ => '[0-9]',
|
30
|
+
/http:..[^\/]+/ => ''
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
data/plog.gemspec
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec`
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{plog}
|
8
|
+
s.version = "0.2.4"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Kazuyoshi Tlacaelel"]
|
12
|
+
s.date = %q{2009-09-09}
|
13
|
+
s.default_executable = %q{plog}
|
14
|
+
s.description = %q{Plog - Ruby on Rails production log statistic generator. by Kazuyoshi Tlacaelel}
|
15
|
+
s.email = %q{kazu.dev@gmail.com}
|
16
|
+
s.executables = ["plog"]
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE",
|
19
|
+
"README.rdoc"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".document",
|
23
|
+
".gitignore",
|
24
|
+
"LICENSE",
|
25
|
+
"README.rdoc",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"bin/plog",
|
29
|
+
"lib/cli.rb",
|
30
|
+
"lib/completed_line.rb",
|
31
|
+
"lib/log_file.rb",
|
32
|
+
"lib/object_file.rb",
|
33
|
+
"lib/plog.rb",
|
34
|
+
"lib/url.rb",
|
35
|
+
"plog.gemspec",
|
36
|
+
"test/completed_line_test.rb",
|
37
|
+
"test/data/example.log",
|
38
|
+
"test/log_file_test.rb",
|
39
|
+
"test/object_file_test.rb",
|
40
|
+
"test/plog_test.rb",
|
41
|
+
"test/test_helper.rb",
|
42
|
+
"test/url_test.rb"
|
43
|
+
]
|
44
|
+
s.homepage = %q{http://github.com/ktlacaelel/plog}
|
45
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
46
|
+
s.require_paths = ["lib"]
|
47
|
+
s.rubygems_version = %q{1.3.3}
|
48
|
+
s.summary = %q{Plog - Ruby on Rails production log statistic generator. by Kazuyoshi Tlacaelel}
|
49
|
+
s.test_files = [
|
50
|
+
"test/completed_line_test.rb",
|
51
|
+
"test/log_file_test.rb",
|
52
|
+
"test/object_file_test.rb",
|
53
|
+
"test/plog_test.rb",
|
54
|
+
"test/test_helper.rb",
|
55
|
+
"test/url_test.rb"
|
56
|
+
]
|
57
|
+
|
58
|
+
if s.respond_to? :specification_version then
|
59
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
60
|
+
s.specification_version = 3
|
61
|
+
|
62
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
63
|
+
s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
64
|
+
else
|
65
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
66
|
+
end
|
67
|
+
else
|
68
|
+
s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class CompletedLineTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@valid_first_fragment = 'Completed in 5ms (View: 4, DB: 1) '
|
7
|
+
@valid_line = 'Completed in 5ms (View: 4, DB: 1) | 200 OK [http://www.example.com/users/12972]'
|
8
|
+
@valid_line_no_view = 'Completed in 5ms (DB: 5) | 200 OK [http://www.example.com/users/12972]'
|
9
|
+
@valid_second_fragment = ' 200 OK [http://www.example.com/users/12972]'
|
10
|
+
|
11
|
+
@valid_db_time = 1
|
12
|
+
@valid_view_time = 4
|
13
|
+
@valid_total_time = 5
|
14
|
+
|
15
|
+
@valid_db_time_no_view = 5
|
16
|
+
@valid_view_time_no_view = 0
|
17
|
+
@valid_total_time_no_view = 5
|
18
|
+
|
19
|
+
@valid_csv_line = '5,4,1,/users/[0-9]'
|
20
|
+
@valid_status_string = 'OK'
|
21
|
+
@valid_status_number = '200'
|
22
|
+
end
|
23
|
+
|
24
|
+
# ============================================================================
|
25
|
+
# READ
|
26
|
+
# ============================================================================
|
27
|
+
|
28
|
+
should 'read a valid completed line (standard)' do
|
29
|
+
Plog::CompletedLine.read! @valid_line
|
30
|
+
assert_equal @valid_view_time, Plog::CompletedLine.view_time
|
31
|
+
assert_equal @valid_db_time, Plog::CompletedLine.db_time
|
32
|
+
assert_equal @valid_total_time, Plog::CompletedLine.total_time
|
33
|
+
end
|
34
|
+
|
35
|
+
should 'read valid completed line (without view time)' do
|
36
|
+
Plog::CompletedLine.read! @valid_line_no_view
|
37
|
+
assert_equal 5, Plog::CompletedLine.total_time
|
38
|
+
assert_equal 0, Plog::CompletedLine.view_time
|
39
|
+
assert_equal 5, Plog::CompletedLine.db_time
|
40
|
+
end
|
41
|
+
|
42
|
+
# ============================================================================
|
43
|
+
# RECALCULATION
|
44
|
+
# ============================================================================
|
45
|
+
|
46
|
+
should 'recalculate acurately!' do
|
47
|
+
Plog::CompletedLine.read! @valid_line
|
48
|
+
Plog::CompletedLine.merge(1, 1)
|
49
|
+
assert_equal (@valid_view_time + 1), Plog::CompletedLine.view_time
|
50
|
+
assert_equal (@valid_db_time + 1), Plog::CompletedLine.db_time
|
51
|
+
assert_equal (@valid_total_time + 2), Plog::CompletedLine.total_time
|
52
|
+
end
|
53
|
+
|
54
|
+
# ============================================================================
|
55
|
+
# FRAGMENTATION
|
56
|
+
# ============================================================================
|
57
|
+
|
58
|
+
should 'fragmentize correctly' do
|
59
|
+
Plog::CompletedLine.read! @valid_line
|
60
|
+
first, second = Plog::CompletedLine.fragmentize!
|
61
|
+
assert_equal @valid_first_fragment, first
|
62
|
+
assert_equal @valid_second_fragment, second
|
63
|
+
end
|
64
|
+
|
65
|
+
# ============================================================================
|
66
|
+
# TIME RETRIVAL ( when view & db are *both* present )
|
67
|
+
# ============================================================================
|
68
|
+
|
69
|
+
should 'retrive correct total time (view + db)' do
|
70
|
+
Plog::CompletedLine.read! @valid_line
|
71
|
+
assert_equal @valid_total_time, Plog::CompletedLine.total_time
|
72
|
+
end
|
73
|
+
|
74
|
+
should 'retrive correct view time (view + db)' do
|
75
|
+
Plog::CompletedLine.read! @valid_line
|
76
|
+
assert_equal @valid_view_time, Plog::CompletedLine.view_time
|
77
|
+
end
|
78
|
+
|
79
|
+
should 'retrive correct db time (view + db)' do
|
80
|
+
Plog::CompletedLine.read! @valid_line
|
81
|
+
assert_equal @valid_db_time, Plog::CompletedLine.db_time
|
82
|
+
end
|
83
|
+
|
84
|
+
# ============================================================================
|
85
|
+
# TIME RETRIVAL ( when only db time is present )
|
86
|
+
# ============================================================================
|
87
|
+
|
88
|
+
should 'retrive correct total time (only db)' do
|
89
|
+
Plog::CompletedLine.read! @valid_line_no_view
|
90
|
+
assert_equal @valid_total_time_no_view, Plog::CompletedLine.total_time
|
91
|
+
end
|
92
|
+
|
93
|
+
should 'retrive correct view time (only db)' do
|
94
|
+
Plog::CompletedLine.read! @valid_line_no_view
|
95
|
+
assert_equal @valid_view_time_no_view, Plog::CompletedLine.view_time
|
96
|
+
end
|
97
|
+
|
98
|
+
should 'retrive correct db time (only db)' do
|
99
|
+
Plog::CompletedLine.read! @valid_line_no_view
|
100
|
+
assert_equal @valid_db_time_no_view, Plog::CompletedLine.db_time
|
101
|
+
end
|
102
|
+
|
103
|
+
# ============================================================================
|
104
|
+
# CSV COMPILATION
|
105
|
+
# ============================================================================
|
106
|
+
|
107
|
+
should 'export to csv' do
|
108
|
+
Plog::CompletedLine.read! @valid_line
|
109
|
+
assert_equal @valid_csv_line, Plog::CompletedLine.to_csv
|
110
|
+
end
|
111
|
+
|
112
|
+
# ============================================================================
|
113
|
+
# COMPLETED LINE DETECTION
|
114
|
+
# ============================================================================
|
115
|
+
|
116
|
+
should 'ignore *NON* completed lines' do
|
117
|
+
Plog::CompletedLine.read! 'completed'
|
118
|
+
assert_equal false, Plog::CompletedLine.completed_line?
|
119
|
+
|
120
|
+
Plog::CompletedLine.read! 'Completed'
|
121
|
+
assert_equal false, Plog::CompletedLine.completed_line?
|
122
|
+
|
123
|
+
Plog::CompletedLine.read! 'Completed Completed Completed Completed'
|
124
|
+
assert_equal false, Plog::CompletedLine.completed_line?
|
125
|
+
|
126
|
+
Plog::CompletedLine.read! 'aalsdjflasdjflsa dflksjdflkasjdf lfCompleted'
|
127
|
+
assert_equal false, Plog::CompletedLine.completed_line?
|
128
|
+
|
129
|
+
Plog::CompletedLine.read! nil
|
130
|
+
assert_equal false, Plog::CompletedLine.completed_line?
|
131
|
+
|
132
|
+
Plog::CompletedLine.read! false
|
133
|
+
assert_equal false, Plog::CompletedLine.completed_line?
|
134
|
+
|
135
|
+
Plog::CompletedLine.read! 0
|
136
|
+
assert_equal false, Plog::CompletedLine.completed_line?
|
137
|
+
|
138
|
+
Plog::CompletedLine.read! ''
|
139
|
+
assert_equal false, Plog::CompletedLine.completed_line?
|
140
|
+
end
|
141
|
+
|
142
|
+
# ============================================================================
|
143
|
+
# STATUS EXTRACTION
|
144
|
+
# ============================================================================
|
145
|
+
|
146
|
+
should 'extract status string' do
|
147
|
+
Plog::CompletedLine.read! @valid_line
|
148
|
+
assert_equal @valid_status_string, Plog::CompletedLine.status_string
|
149
|
+
end
|
150
|
+
|
151
|
+
should 'extract status number' do
|
152
|
+
Plog::CompletedLine.read! @valid_line
|
153
|
+
assert_equal @valid_status_number, Plog::CompletedLine.status_number
|
154
|
+
end
|
155
|
+
|
156
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
|
2
|
+
Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
|
3
|
+
Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
|
4
|
+
Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
|
5
|
+
Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
|
6
|
+
Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
|
7
|
+
Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
|
8
|
+
Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
|
9
|
+
Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
|
10
|
+
Completed in 7ms (View: 4, DB: 3) | 200 OK [http://www.example.com/users/12972]
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class LogFileTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@log_file = 'test/data/example.log'
|
7
|
+
@dump_dir = 'plog'
|
8
|
+
@file = Plog::LogFile.new(@log_file, @dump_dir)
|
9
|
+
@object_hash = 'a1e0e00d04e82bdf0f1ac151de03591c'
|
10
|
+
@subdir = 'plog/objects'
|
11
|
+
@object_path = @subdir + '/' + @object_hash
|
12
|
+
@final_csv_result = '10,70,40,30,/users/[0-9]'
|
13
|
+
@default_object_path = 'plog/objects/2a5565416b0c92c6c5081342322bf945'
|
14
|
+
end
|
15
|
+
|
16
|
+
def teardown
|
17
|
+
@file.close
|
18
|
+
end
|
19
|
+
|
20
|
+
should 'generate an appropriate object_path' do
|
21
|
+
assert_equal @default_object_path, @file.object_path
|
22
|
+
assert_equal @subdir, @file.objects_recipient_path
|
23
|
+
end
|
24
|
+
|
25
|
+
def whipe_out_objects_dir
|
26
|
+
Dir.glob('./plog/objects/*').each do |file|
|
27
|
+
FileUtils.rm_r(file, :force => true)
|
28
|
+
end
|
29
|
+
FileUtils.rmdir './plog/objects'
|
30
|
+
end
|
31
|
+
|
32
|
+
should 'parse data appropriately' do
|
33
|
+
whipe_out_objects_dir
|
34
|
+
@file = Plog::LogFile.new(@log_file, @dump_dir)
|
35
|
+
@file.parse_completed_lines!
|
36
|
+
assert_equal @final_csv_result, File.new(@object_path).read.chomp
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class ObjectFileTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@testing_filename = 'kazu.test'
|
7
|
+
@file = Plog::ObjectFile.new(@testing_filename, 'w+')
|
8
|
+
@default_serialized_data = '0,0,0,0,default'
|
9
|
+
|
10
|
+
@changed_hits = 1
|
11
|
+
@changed_total_time = 2
|
12
|
+
@changed_view_time = 3
|
13
|
+
@changed_db_time = 4
|
14
|
+
@changed_simplified_url = 'http://example.com'
|
15
|
+
@changed_serialized_data = '1,2,3,4,http://example.com'
|
16
|
+
@changed_serialized_data_twice = '2,4,6,8,http://example.com'
|
17
|
+
|
18
|
+
@export_data = '1 0.002 0.002 0.004 0.004 0.003 0.003 http://example.com '
|
19
|
+
end
|
20
|
+
|
21
|
+
def teardown
|
22
|
+
@file.close unless @skip_close
|
23
|
+
end
|
24
|
+
|
25
|
+
should 'load default values on initialization' do
|
26
|
+
assert_equal 'default', @file.simplified_url
|
27
|
+
assert_equal 0, @file.view_time
|
28
|
+
assert_equal 0, @file.db_time
|
29
|
+
assert_equal 0, @file.total_time
|
30
|
+
assert_equal 0, @file.total_hits
|
31
|
+
end
|
32
|
+
|
33
|
+
should 'serialize changes correctly' do
|
34
|
+
assert_equal @default_serialized_data, @file.serialize_changes
|
35
|
+
end
|
36
|
+
|
37
|
+
# ============================================================================
|
38
|
+
# DATA APPENDING
|
39
|
+
# ============================================================================
|
40
|
+
|
41
|
+
should 'increment view time correctly' do
|
42
|
+
@file.append_view_time 500
|
43
|
+
assert_equal 500, @file.view_time
|
44
|
+
@file.append_view_time 500
|
45
|
+
assert_equal 1000, @file.view_time
|
46
|
+
end
|
47
|
+
|
48
|
+
should 'increment db time correctly' do
|
49
|
+
@file.append_db_time 500
|
50
|
+
assert_equal 500, @file.db_time
|
51
|
+
@file.append_db_time 500
|
52
|
+
assert_equal 1000, @file.db_time
|
53
|
+
end
|
54
|
+
|
55
|
+
should 'increment total time correctly' do
|
56
|
+
@file.append_total_time 500
|
57
|
+
assert_equal 500, @file.total_time
|
58
|
+
@file.append_total_time 500
|
59
|
+
assert_equal 1000, @file.total_time
|
60
|
+
end
|
61
|
+
|
62
|
+
should 'increment hits count correctly' do
|
63
|
+
@file.append_hits 500
|
64
|
+
assert_equal 500, @file.total_hits
|
65
|
+
@file.append_hits 500
|
66
|
+
assert_equal 1000, @file.total_hits
|
67
|
+
end
|
68
|
+
|
69
|
+
# ============================================================================
|
70
|
+
# DATA STORAGE
|
71
|
+
# ============================================================================
|
72
|
+
|
73
|
+
should 'store default data correctly if nothing is given' do
|
74
|
+
@file.save_changes!
|
75
|
+
@file.close
|
76
|
+
@skip_close = true
|
77
|
+
stored_data = File.new(@testing_filename).read.chomp
|
78
|
+
assert_equal @default_serialized_data, stored_data
|
79
|
+
end
|
80
|
+
|
81
|
+
def alter_data
|
82
|
+
@file.append_hits @changed_hits
|
83
|
+
@file.append_db_time @changed_db_time
|
84
|
+
@file.append_view_time @changed_view_time
|
85
|
+
@file.append_total_time @changed_total_time
|
86
|
+
@file.simplified_url = @changed_simplified_url
|
87
|
+
end
|
88
|
+
|
89
|
+
# ============================================================================
|
90
|
+
# DATA LOADING
|
91
|
+
# ============================================================================
|
92
|
+
|
93
|
+
should 'store altered data & retrive correctly' do
|
94
|
+
alter_data
|
95
|
+
@file.save_changes!
|
96
|
+
@file.close
|
97
|
+
@skip_close = true
|
98
|
+
|
99
|
+
@file = Plog::ObjectFile.new(@testing_filename, 'w+')
|
100
|
+
assert_equal @changed_total_time, @file.total_time, 'total'
|
101
|
+
assert_equal @changed_view_time, @file.view_time, 'view'
|
102
|
+
assert_equal @changed_db_time, @file.db_time, 'db'
|
103
|
+
assert_equal @changed_hits, @file.total_hits, 'hits'
|
104
|
+
end
|
105
|
+
|
106
|
+
# ============================================================================
|
107
|
+
# DATA EXPORTS
|
108
|
+
# ============================================================================
|
109
|
+
|
110
|
+
should 'export data correctly' do
|
111
|
+
alter_data
|
112
|
+
assert_equal @export_data, @file.export
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
data/test/plog_test.rb
ADDED
data/test/test_helper.rb
ADDED
data/test/url_test.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class URLTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
@valid_line = 'Completed in 5ms (View: 4, DB: 1) | 200 OK [http://www.example.com/users/12972]'
|
7
|
+
@valid_url = 'http://www.example.com/users/12972'
|
8
|
+
@url = Plog::URL.new(@valid_url)
|
9
|
+
end
|
10
|
+
|
11
|
+
should 'return a valid url object when instantiated' do
|
12
|
+
assert_equal @valid_url, @url.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
should 'simplify correctly' do
|
16
|
+
assert_equal '/users/[0-9]', @url.simplify
|
17
|
+
end
|
18
|
+
|
19
|
+
should 'simplify removing arguments' do
|
20
|
+
@url = Plog::URL.new('http://www.example.com/users/1234?key1=val1&key2=val2')
|
21
|
+
assert_equal '/users/[0-9]?', @url.simplify
|
22
|
+
end
|
23
|
+
|
24
|
+
should 'hashify correctly' do
|
25
|
+
assert_equal '2a5565416b0c92c6c5081342322bf945', @url.hashify
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: plog
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kazuyoshi Tlacaelel
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-09-09 00:00:00 +09:00
|
13
|
+
default_executable: plog
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: thoughtbot-shoulda
|
17
|
+
type: :development
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: "0"
|
24
|
+
version:
|
25
|
+
description: Plog - Ruby on Rails production log statistic generator. by Kazuyoshi Tlacaelel
|
26
|
+
email: kazu.dev@gmail.com
|
27
|
+
executables:
|
28
|
+
- plog
|
29
|
+
extensions: []
|
30
|
+
|
31
|
+
extra_rdoc_files:
|
32
|
+
- LICENSE
|
33
|
+
- README.rdoc
|
34
|
+
files:
|
35
|
+
- .document
|
36
|
+
- .gitignore
|
37
|
+
- LICENSE
|
38
|
+
- README.rdoc
|
39
|
+
- Rakefile
|
40
|
+
- VERSION
|
41
|
+
- bin/plog
|
42
|
+
- lib/cli.rb
|
43
|
+
- lib/completed_line.rb
|
44
|
+
- lib/log_file.rb
|
45
|
+
- lib/object_file.rb
|
46
|
+
- lib/plog.rb
|
47
|
+
- lib/url.rb
|
48
|
+
- plog.gemspec
|
49
|
+
- test/completed_line_test.rb
|
50
|
+
- test/data/example.log
|
51
|
+
- test/log_file_test.rb
|
52
|
+
- test/object_file_test.rb
|
53
|
+
- test/plog_test.rb
|
54
|
+
- test/test_helper.rb
|
55
|
+
- test/url_test.rb
|
56
|
+
has_rdoc: true
|
57
|
+
homepage: http://github.com/ktlacaelel/plog
|
58
|
+
licenses: []
|
59
|
+
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options:
|
62
|
+
- --charset=UTF-8
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: "0"
|
70
|
+
version:
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: "0"
|
76
|
+
version:
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.3.5
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: Plog - Ruby on Rails production log statistic generator. by Kazuyoshi Tlacaelel
|
84
|
+
test_files:
|
85
|
+
- test/completed_line_test.rb
|
86
|
+
- test/log_file_test.rb
|
87
|
+
- test/object_file_test.rb
|
88
|
+
- test/plog_test.rb
|
89
|
+
- test/test_helper.rb
|
90
|
+
- test/url_test.rb
|