ccdk 0.2.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.
- 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
|
+
|