ccdk 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +7 -0
- data/README.markdown +54 -0
- data/bin/cc_count_actions +63 -0
- data/bin/cc_track_user +42 -0
- data/lib/ccdk.rb +19 -0
- data/lib/ccdk/log_file.rb +105 -0
- data/lib/ccdk/tdump.rb +62 -0
- data/lib/ccdk/version.rb +3 -0
- metadata +90 -0
data/Manifest.txt
ADDED
data/README.markdown
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# CCDK
|
2
|
+
|
3
|
+
The Crystal Commerce Debugging Kit
|
4
|
+
|
5
|
+
## Description
|
6
|
+
|
7
|
+
CCDK is a set of tools made to ease development of Rails applications
|
8
|
+
developed at [Crystal Commerce](http://www.crystalcommerce.com). It
|
9
|
+
includes both executable scripts and modules that can be mixed into
|
10
|
+
classes to help debug. The code herein is not designed to be run as
|
11
|
+
part of a production application, just for use while building one.
|
12
|
+
|
13
|
+
## Included Executables
|
14
|
+
|
15
|
+
* `cc_track_user`
|
16
|
+
|
17
|
+
## `cc_track_user`
|
18
|
+
|
19
|
+
`cc_track_user` is a script that will search a Rails log file for a
|
20
|
+
specific IP address and display the actions that the user has
|
21
|
+
performed. This can be helpful in tracking the steps a user took to
|
22
|
+
get their session into a particular state.
|
23
|
+
|
24
|
+
### Usage
|
25
|
+
|
26
|
+
`cc_track_user IP_ADDR FILE [FILE ...]`
|
27
|
+
|
28
|
+
* IP_ADDR is the ip address to track
|
29
|
+
* FILE is a a file (or list of files) to search across
|
30
|
+
|
31
|
+
If any the file names passed to `cc_track_user` end in `.gz`,
|
32
|
+
`cc_track_user` will assume that they have been gzipped when it opens
|
33
|
+
them. This makes it easy to follow a user's actions across several
|
34
|
+
days of logs without having to unzip the file first.
|
35
|
+
|
36
|
+
It is suggested to pass the files to `cc_track_user` from oldest to
|
37
|
+
newest so the output is chronological.
|
38
|
+
|
39
|
+
## Included Modules
|
40
|
+
|
41
|
+
* `Ccdk::TDump`
|
42
|
+
|
43
|
+
## `Ccdk::TDump`
|
44
|
+
|
45
|
+
`TDump` is a module that allows you to display a templated
|
46
|
+
representation of a record using the `#tdump` method. It can be passed
|
47
|
+
a filename, a string template, an erb object, or an IO (really
|
48
|
+
anything that responds to `#read`).
|
49
|
+
|
50
|
+
### Examples
|
51
|
+
|
52
|
+
car.tdump('I have a <%= year %> <%= make %> <%= model %>')
|
53
|
+
|
54
|
+
product.tdump('path/to/product_template.erb')
|
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'ccdk/log_file'
|
3
|
+
|
4
|
+
class ActionCounter
|
5
|
+
attr_reader :ignore_domain, :counts
|
6
|
+
|
7
|
+
def initialize(ignore_domain = false)
|
8
|
+
@counts = {}
|
9
|
+
@ignore_domain = ignore_domain
|
10
|
+
end
|
11
|
+
|
12
|
+
def count(file_name)
|
13
|
+
log = Ccdk::LogFile.open(file_name)
|
14
|
+
|
15
|
+
log.each_block do |blk|
|
16
|
+
a = get_action(blk)
|
17
|
+
|
18
|
+
if a
|
19
|
+
counts[a] ||= 0
|
20
|
+
counts[a] += 1
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def print_results
|
26
|
+
counts.sort{ |a, b| b[1] <=> a[1] }.each do |(action, count)|
|
27
|
+
printf("%6d: %s\n", count, action)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def get_action(block)
|
34
|
+
url = block.match(/\[http(s?):\/\/([^\/?]+(\/[^?]+))(\?.*)?\]/)
|
35
|
+
return nil unless url
|
36
|
+
|
37
|
+
if ignore_domain
|
38
|
+
url[3]
|
39
|
+
else
|
40
|
+
url[2]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
if ARGV.size < 1
|
46
|
+
puts "Usage: cc_count_actions [--ignore-domain] FILE [FILE ...]"
|
47
|
+
exit 1
|
48
|
+
elsif ARGV[0] == "--ignore-domain"
|
49
|
+
counter = ActionCounter.new(true)
|
50
|
+
ARGV.shift
|
51
|
+
else
|
52
|
+
counter = ActionCounter.new
|
53
|
+
end
|
54
|
+
|
55
|
+
ARGV.each do |file_name|
|
56
|
+
counter.count(file_name)
|
57
|
+
end
|
58
|
+
|
59
|
+
begin
|
60
|
+
counter.print_results
|
61
|
+
rescue Errno::EPIPE, Interrupt
|
62
|
+
# Allow the output to be passed to head etc
|
63
|
+
end
|
data/bin/cc_track_user
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'ccdk/log_file'
|
3
|
+
|
4
|
+
class Tracker
|
5
|
+
def initialize(ip_address)
|
6
|
+
@ip_address = ip_address
|
7
|
+
end
|
8
|
+
|
9
|
+
def track(file_name)
|
10
|
+
log = Ccdk::LogFile.open(file_name)
|
11
|
+
|
12
|
+
log.each_block do |blk|
|
13
|
+
display(blk) if blk =~ /for #{@ip_address}/
|
14
|
+
end
|
15
|
+
|
16
|
+
log.close
|
17
|
+
end
|
18
|
+
|
19
|
+
def display(block)
|
20
|
+
timestamp_method = block.match(/at (.+)\) \[(\w+)\]/)
|
21
|
+
timestamp, method = timestamp_method[1], timestamp_method[2]
|
22
|
+
url = block.match(/\[(http(s?):\/\/.+\/.*)\]/)
|
23
|
+
params = block.match(/Parameters: (\{.*\})/)
|
24
|
+
|
25
|
+
if url && timestamp && method && params
|
26
|
+
puts timestamp
|
27
|
+
puts " #{method} #{url[1]}"
|
28
|
+
puts " #{params[1]}"
|
29
|
+
puts
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
if ARGV.size < 2
|
35
|
+
puts "Usage: cc_track_user IP_ADDR FILE [FILE ...]"
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
|
39
|
+
tracker = Tracker.new(ARGV.shift)
|
40
|
+
ARGV.each do |file_name|
|
41
|
+
tracker.track(file_name)
|
42
|
+
end
|
data/lib/ccdk.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module Ccdk
|
2
|
+
MODULES = {
|
3
|
+
:tdump => 'ccdk/tdump'
|
4
|
+
}
|
5
|
+
|
6
|
+
class << self
|
7
|
+
def load_all!
|
8
|
+
MODULES.values.each do |lib|
|
9
|
+
Kernel.require lib
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def load(*sym)
|
14
|
+
sym.each do |sym|
|
15
|
+
Kernel.require MODULES[sym]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'zlib'
|
2
|
+
|
3
|
+
module Ccdk #:nodoc:
|
4
|
+
|
5
|
+
# A class for reading Rails log files. Provides a way to step through the log
|
6
|
+
# by assuming the format of the output conforms to the Rails defaul.
|
7
|
+
class LogFile
|
8
|
+
|
9
|
+
class << self
|
10
|
+
|
11
|
+
# Open a LogFile with the provided name. If the file name ends in '.gz'
|
12
|
+
# assume that the file is gzipped
|
13
|
+
#
|
14
|
+
# ==== Parameters
|
15
|
+
#
|
16
|
+
# * +file_name+ - The name of the log file to open
|
17
|
+
#
|
18
|
+
# ==== Examples
|
19
|
+
# # Open the log file 'log/production.log'
|
20
|
+
# LogFile.open('log/production.log')
|
21
|
+
#
|
22
|
+
# # Open the gzipped log file 'log/old/production.log.1.gz'
|
23
|
+
# LogFile.open('log/old/production.log.1.gz')
|
24
|
+
def open(file_name)
|
25
|
+
if file_name =~ /\.gz$/
|
26
|
+
GzipLogFile.new(file_name)
|
27
|
+
else
|
28
|
+
LogFile.new(file_name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Create a new LogFile
|
34
|
+
#
|
35
|
+
# ==== Parameters
|
36
|
+
#
|
37
|
+
# * +name+ - The name of the file to open
|
38
|
+
def initialize(name)
|
39
|
+
@fh = File.open(name, 'r')
|
40
|
+
end
|
41
|
+
|
42
|
+
# execute code on each block of the log file. A block is defined as starting
|
43
|
+
# with Processing and ending with a blank line
|
44
|
+
#
|
45
|
+
# ==== Examples
|
46
|
+
# log.each_block{ |b| puts b if b =~ ApplicationController }
|
47
|
+
def each_block
|
48
|
+
while !@fh.eof?
|
49
|
+
yield next_block
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Read the next block from the log file
|
54
|
+
#
|
55
|
+
# TODO: please refactor me
|
56
|
+
def next_block
|
57
|
+
lines = []
|
58
|
+
|
59
|
+
line = @fh.gets
|
60
|
+
while !start_of_block?(line)
|
61
|
+
line = @fh.gets
|
62
|
+
return "" if @fh.eof?
|
63
|
+
end
|
64
|
+
|
65
|
+
while(!@fh.eof? && !end_of_block?(line))
|
66
|
+
lines << line.chomp
|
67
|
+
line = @fh.gets
|
68
|
+
end
|
69
|
+
|
70
|
+
lines.join("\n")
|
71
|
+
end
|
72
|
+
|
73
|
+
# Close the log file
|
74
|
+
def close
|
75
|
+
@fh.close
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def start_of_block?(line)
|
81
|
+
line && line =~ /^Processing/
|
82
|
+
end
|
83
|
+
|
84
|
+
def end_of_block?(line)
|
85
|
+
line.nil? || line =~ /^$/ ||
|
86
|
+
line =~ /^Starting the New Relic/ ||
|
87
|
+
line =~ /^\*\* \[Hoptoad\]/
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# A Gzipped version of the log file
|
92
|
+
# All behavior is the same as a standard log file except it assumes the
|
93
|
+
# content has been gzipped
|
94
|
+
class GzipLogFile < LogFile
|
95
|
+
|
96
|
+
# Create a new GzipLogFile. The file name passed must be gzipped
|
97
|
+
#
|
98
|
+
# ==== Parameters
|
99
|
+
#
|
100
|
+
# * +file_name+ - The name of the gzipped log file to open
|
101
|
+
def initialize(file_name)
|
102
|
+
@fh = Zlib::GzipReader.new(File.open(file_name, 'r'))
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
data/lib/ccdk/tdump.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Ccdk #:nodoc:
|
4
|
+
|
5
|
+
# Create a templated representation of the object. This module can be useful
|
6
|
+
# For debugging objects by dumping them out to a template of your choosing.
|
7
|
+
# Similar in use to <tt>pp</tt>, but more flexible so that you can display
|
8
|
+
# only the information that you care about.
|
9
|
+
module TDump
|
10
|
+
|
11
|
+
# Convert the object to a <tt>String</tt> based on the provided template.
|
12
|
+
# Templates can be specified in several ways
|
13
|
+
#
|
14
|
+
# * A <tt>String</tt> representation of the template.
|
15
|
+
# * A <tt>String</tt> path to a template file
|
16
|
+
# * An <tt>Erb</tt> object of the template
|
17
|
+
# * An <tt>IO<tt> object that can be read to retrieve a template
|
18
|
+
# (Really anything that responds to <tt>#read</tt>)
|
19
|
+
#
|
20
|
+
# ==== Parameters
|
21
|
+
#
|
22
|
+
# * +template+ - The template to use for formatting the object
|
23
|
+
#
|
24
|
+
# ==== Examples
|
25
|
+
# # Output information to a templating string
|
26
|
+
# car.tdump('I have a <%= year %> <%= make %> <%= model %>')
|
27
|
+
#
|
28
|
+
# # Use the path to an erb template file
|
29
|
+
# product.tdump('path/to/product_template.erb')
|
30
|
+
def tdump(template)
|
31
|
+
if template.is_a? ERB
|
32
|
+
tdump_erb(template)
|
33
|
+
elsif template.respond_to?(:read)
|
34
|
+
tdump_io(template)
|
35
|
+
elsif template.is_a? String
|
36
|
+
tdump_string(template)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def tdump_erb(erb)
|
43
|
+
erb.result(binding)
|
44
|
+
end
|
45
|
+
|
46
|
+
def tdump_string(string)
|
47
|
+
if File.exist?(string)
|
48
|
+
tdump_erb(ERB.new(File.read(string)))
|
49
|
+
else
|
50
|
+
tdump_erb(ERB.new(string))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def tdump_io(io)
|
55
|
+
tdump_erb(ERB.new(io.read))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Object
|
61
|
+
include Ccdk::TDump
|
62
|
+
end
|
data/lib/ccdk/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ccdk
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Ryan Burrows
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-01-19 00:00:00 -08:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
description: " Ccdk is a set of extensions and scripts helpful to debugging and development\n"
|
36
|
+
email: rhburrows@crystalcommerce.com
|
37
|
+
executables:
|
38
|
+
- cc_track_user
|
39
|
+
- cc_count_actions
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files: []
|
43
|
+
|
44
|
+
files:
|
45
|
+
- README.markdown
|
46
|
+
- Manifest.txt
|
47
|
+
- bin/cc_track_user
|
48
|
+
- lib/ccdk.rb
|
49
|
+
- lib/ccdk/log_file.rb
|
50
|
+
- lib/ccdk/tdump.rb
|
51
|
+
- lib/ccdk/version.rb
|
52
|
+
- bin/cc_count_actions
|
53
|
+
has_rdoc: true
|
54
|
+
homepage: http://www.crystalcommerce.com
|
55
|
+
licenses: []
|
56
|
+
|
57
|
+
post_install_message:
|
58
|
+
rdoc_options: []
|
59
|
+
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
hash: 3
|
68
|
+
segments:
|
69
|
+
- 0
|
70
|
+
version: "0"
|
71
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: 23
|
77
|
+
segments:
|
78
|
+
- 1
|
79
|
+
- 3
|
80
|
+
- 6
|
81
|
+
version: 1.3.6
|
82
|
+
requirements: []
|
83
|
+
|
84
|
+
rubyforge_project:
|
85
|
+
rubygems_version: 1.3.7
|
86
|
+
signing_key:
|
87
|
+
specification_version: 3
|
88
|
+
summary: Crystal Commerce's Debugging Kit
|
89
|
+
test_files: []
|
90
|
+
|