kcaco 0.0.1

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/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in kcaco.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,28 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ kcaco (0.0.1)
5
+ guid
6
+
7
+ GEM
8
+ remote: http://rubygems.org/
9
+ specs:
10
+ diff-lcs (1.1.3)
11
+ guid (0.1.1)
12
+ rake (0.9.2.2)
13
+ rspec (2.8.0)
14
+ rspec-core (~> 2.8.0)
15
+ rspec-expectations (~> 2.8.0)
16
+ rspec-mocks (~> 2.8.0)
17
+ rspec-core (2.8.0)
18
+ rspec-expectations (2.8.0)
19
+ diff-lcs (~> 1.1.2)
20
+ rspec-mocks (2.8.0)
21
+
22
+ PLATFORMS
23
+ ruby
24
+
25
+ DEPENDENCIES
26
+ kcaco!
27
+ rake
28
+ rspec
data/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # Keep Calm and Carry On
2
+
3
+ ## Synopsis
4
+
5
+ Ever written a long running process before? Ever had it raise
6
+ and exception and die? Ever wrapped your main loop in a
7
+ `begin..rescue` so you can just Keep Calm and Carry On? Ever found it
8
+ really annoying that logging the damn thing is so difficult?
9
+
10
+ Kcaco helps by providing some helper functions around dealing with
11
+ exceptions. Specifically with regards to logging the bastard so you
12
+ can deal with it. Example:
13
+
14
+ ``` ruby
15
+ require "kcaco"
16
+ begin
17
+ raise RuntimeError.new("Goodbye, cruel world")
18
+ rescue => e
19
+ puts Kcaco.pretty(e)
20
+ end
21
+ ```
22
+
23
+ This will print out a line like so:
24
+ `a84aedf9-8cda-13dd-123f-c8572078ea90 RuntimeError: Goodbye, cruel
25
+ world [(irb) L3]`. In case it's not obvious, the output contains a
26
+ GUID, the class of the error and the filename and linenumber it
27
+ occured on. In a single line ideal for logging. Additionally, we
28
+ serialize the exception to disk (hence the GUID - the filename we save
29
+ it too) so you can look at it later.
30
+
31
+ ## Usage
32
+
33
+ ``` ruby
34
+ require "kcaco"
35
+ # in a rescue block, instead of the manual work:
36
+ logger.error Kcaco.pretty(exception)
37
+ ```
38
+
39
+ This will print out a message to your logs in the format: `GUID
40
+ ExceptionClass: the message [filename.rb L123]`. Additionally, it will
41
+ write out the exception object to `Kcaco.save_path` in a file named
42
+ the same as that GUID. You can disable the save behavior with
43
+ `Kcaco.auto_save = false`.
44
+
45
+ ## Example
46
+
47
+ $ bundle exec examples/simple.rb
48
+ 900308c3-16ac-2803-ac97-db64464baae8 MichaelBay::Boom: ba da bloom [simple.rb L10]
49
+
50
+ cat /tmp/kcaco/exceptions/900308c3-16ac-2803-ac97-db64464baae8
51
+ time: 2012-01-06T16:01:53+02:00
52
+ type: MichaelBay::Boom
53
+ message: ba da bloom
54
+ backtrace:
55
+ examples/simple.rb:10:in `explosions'
56
+ examples/simple.rb:16
57
+
58
+ --- !ruby/exception:MichaelBay::Boom
59
+ message: ba da bloom
60
+
61
+ ## Why?
62
+
63
+ Long running processes (daemons) tend to err on the side of logging
64
+ exceptions and carrying on. This is peachy, but there are some things
65
+ you definitely want to do. Without a catch-all `begin..rescue`, your
66
+ application might do this:
67
+
68
+ irb> raise RuntimeError.new("death")
69
+ RuntimeError: death
70
+ from (irb):1
71
+
72
+ That's pretty useful. Right?
73
+
74
+ In a daemon, its natural to want just as much information. Except you
75
+ will want to use a logger - especially at the appropriate level. You
76
+ may write something like this to get similar output in your logs:
77
+
78
+ ``` ruby
79
+ begin
80
+ # ...
81
+ rescue => e
82
+ logger.error "#{e.class.name}: #{e.message}"
83
+ end
84
+ ```
85
+
86
+ But now you don't know where the error came from (backtrace line #1)
87
+ or actually anything else from the backtrace. You can extend the code
88
+ with something like:
89
+
90
+ ``` ruby
91
+ e.backtrace.each do |line| logger.error(line); end
92
+ ```
93
+
94
+ But now you've done something horrible: if you `grep ERROR` in your
95
+ log file, the number of lines returned is no longer the number of
96
+ errors in your log. There are a bunch of ways around this:
97
+
98
+ Log the entire backtrace on 1 line, This gives you ugly logs
99
+ especially with long backtraces. For example:
100
+
101
+ $ bundle exec examples/no_kcaco_one_line.rb
102
+ E, [2012-01-06T16:26:27.705513 #647] ERROR -- : MichaelBay::Boom: ba da bloom ["examples/no_kcaco_one_line.rb:10:in `explosions'", "examples/no_kcaco_one_line.rb:17"]
103
+
104
+ Log the backtrace at a different level, e.g. `DEBUG`. This means you
105
+ have to run with verbose logging:
106
+
107
+ $ bundle exec examples/no_kcaco_different_levels.rb
108
+ E, [2012-01-06T16:28:33.903684 #736] ERROR -- : MichaelBay::Boom: ba da bloom
109
+ D, [2012-01-06T16:28:33.903784 #736] DEBUG -- : examples/no_kcaco_different_levels.rb:10:in `explosions'
110
+ D, [2012-01-06T16:28:33.903814 #736] DEBUG -- : examples/no_kcaco_different_levels.rb:17
111
+
112
+ Or you can roll your own smarts. Repeatedly. Or just use some like Kcaco. Quite simply what it does is:
113
+
114
+ * Save you having to format the log message by doing it for you.
115
+ * Write more detail to a separate file where you can inspect it later.
116
+
117
+ .. and you get
118
+
119
+ bundle exec examples/simple.rb
120
+ E, [2012-01-06T16:29:50.730922 #793] ERROR -- : ae58d80f-4bc3-899a-fd0b-2c831833510c MichaelBay::Boom: ba da bloom [simple.rb L11]
121
+
122
+ In this example, you could see the saved exception
123
+ `ae58d80f-4bc3-899a-fd0b-2c831833510c` on disk to get the full backtrace.
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require "rspec/core/rake_task"
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "logger"
4
+
5
+ class MichaelBay
6
+
7
+ class Boom < RuntimeError; end
8
+
9
+ def explosions
10
+ raise Boom.new("ba da bloom")
11
+ end
12
+ end
13
+
14
+ if __FILE__ == $0
15
+ logger = Logger.new(STDOUT)
16
+ begin
17
+ MichaelBay.new.explosions
18
+ rescue => e
19
+ logger.error "#{e.class.name}: #{e.message}"
20
+ e.backtrace.each do |line|
21
+ logger.debug line
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "logger"
4
+
5
+ class MichaelBay
6
+
7
+ class Boom < RuntimeError; end
8
+
9
+ def explosions
10
+ raise Boom.new("ba da bloom")
11
+ end
12
+ end
13
+
14
+ if __FILE__ == $0
15
+ logger = Logger.new(STDOUT)
16
+ begin
17
+ MichaelBay.new.explosions
18
+ rescue => e
19
+ logger.error "#{e.class.name}: #{e.message} #{e.backtrace.inspect}"
20
+ end
21
+ end
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "logger"
4
+ require "kcaco"
5
+
6
+ class MichaelBay
7
+
8
+ class Boom < RuntimeError; end
9
+
10
+ def explosions
11
+ raise Boom.new("ba da bloom")
12
+ end
13
+ end
14
+
15
+ if __FILE__ == $0
16
+ logger = Logger.new(STDOUT)
17
+ begin
18
+ MichaelBay.new.explosions
19
+ rescue => e
20
+ logger.error Kcaco.pretty(e)
21
+ end
22
+ end
data/kcaco.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/kcaco/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Marc Bowes"]
6
+ gem.email = ["marcbowes@gmail.com"]
7
+ gem.description = %q{Helpers for dealing with exceptions}
8
+ gem.summary = %q{Bored of writing out exceptions to logs?}
9
+ gem.homepage = ""
10
+
11
+ gem.files = Dir["**/*"]
12
+ gem.test_files = Dir["spec/**/*"]
13
+ gem.name = "kcaco"
14
+ gem.require_paths = ["lib"]
15
+ gem.version = Kcaco.version
16
+
17
+ gem.add_runtime_dependency("guid")
18
+ gem.add_development_dependency("rake")
19
+ gem.add_development_dependency("rspec")
20
+ end
data/lib/kcaco.rb ADDED
@@ -0,0 +1,41 @@
1
+ require "kcaco/version"
2
+ require "kcaco/wrapped_exception"
3
+ require "kcaco/file_writer"
4
+
5
+ module Kcaco
6
+
7
+ class << self
8
+
9
+ def auto_save=(bool)
10
+ @auto_save = bool
11
+ end
12
+
13
+ def auto_save?
14
+ @auto_save != false
15
+ end
16
+
17
+ def save_path=(path)
18
+ @save_path = path
19
+ end
20
+
21
+ def save_path(wrapped_exception = nil)
22
+ @save_path ||= "/tmp/kcaco/exceptions"
23
+ if wrapped_exception
24
+ File.join(@save_path, wrapped_exception.uuid)
25
+ else
26
+ @save_path
27
+ end
28
+ end
29
+
30
+ def save(wrapped_exception)
31
+ Kcaco::FileWriter.new(wrapped_exception).
32
+ save(save_path(wrapped_exception))
33
+ end
34
+
35
+ def pretty(exception)
36
+ wrapped_exception = Kcaco::WrappedException.new(exception)
37
+ save(wrapped_exception) if auto_save?
38
+ wrapped_exception.pretty
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,37 @@
1
+ module Kcaco
2
+ class FileWriter
3
+
4
+ require "time"
5
+ require "fileutils"
6
+ require "yaml"
7
+
8
+
9
+ attr_accessor :exception
10
+
11
+ def initialize(exception)
12
+ self.exception = exception
13
+ end
14
+
15
+
16
+ def save(path)
17
+ FileUtils.mkdir_p(File.dirname(path))
18
+ File.open(path, "w") do |f|
19
+ [
20
+ ["time", Time.now.iso8601],
21
+ ["type", exception.type],
22
+ ["message", exception.message],
23
+ ].each do |label, text|
24
+ f.puts([label, text].join(": "))
25
+ end
26
+
27
+ f.puts("backtrace:")
28
+ exception.backtrace.each do |line|
29
+ f.puts(line)
30
+ end
31
+
32
+ f.puts
33
+ f.puts(exception.to_yaml)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,7 @@
1
+ module Kcaco
2
+
3
+ def version
4
+ "0.0.1"
5
+ end
6
+ module_function :version
7
+ end
@@ -0,0 +1,65 @@
1
+ module Kcaco
2
+ class WrappedException
3
+
4
+ require "guid"
5
+
6
+
7
+ attr_accessor :exception
8
+
9
+ def initialize(exception)
10
+ self.exception = exception
11
+ end
12
+
13
+
14
+ def type
15
+ exception.class.name
16
+ end
17
+
18
+ def message
19
+ exception.message
20
+ end
21
+
22
+ def backtrace
23
+ exception.backtrace
24
+ end
25
+
26
+ def to_yaml
27
+ exception.to_yaml
28
+ end
29
+
30
+ def extract_filename_and_line_no(line)
31
+ if (m = line.match(/(.+):(\d+):/))
32
+ [File.basename(m[1]), m[2].to_i]
33
+ end
34
+ end
35
+
36
+ def filename_and_line_no
37
+ @filename_and_line_no ||=
38
+ extract_filename_and_line_no(backtrace.first)
39
+ end
40
+
41
+ def filename
42
+ filename_and_line_no.first
43
+ end
44
+
45
+ def line_no
46
+ filename_and_line_no.last
47
+ end
48
+
49
+ def title
50
+ [type, message].join(": ")
51
+ end
52
+
53
+ def pretty
54
+ [
55
+ uuid,
56
+ title,
57
+ "[%s L%i]" % [filename, line_no],
58
+ ].join(" ")
59
+ end
60
+
61
+ def uuid
62
+ @uuid ||= Guid.new.to_s
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,27 @@
1
+ require "spec_helper"
2
+
3
+ require "fileutils"
4
+ require "kcaco/wrapped_exception"
5
+ require "kcaco/file_writer"
6
+
7
+ module Kcaco
8
+ describe Exception do
9
+
10
+ let(:boom) { Boom.new }
11
+ let(:wrapped_exception) { WrappedException.new(boom.rescued) }
12
+ let(:fw) { FileWriter.new(wrapped_exception) }
13
+
14
+
15
+ it "should save the exception" do
16
+ path = File.join("/tmp", [File.basename(__FILE__), Process.pid.to_s].join("-"))
17
+ begin
18
+ fw.save(path)
19
+ ensure
20
+ FileUtils.rm(path)
21
+ end
22
+
23
+ # Kind of difficult to test the content of this file because of
24
+ # times and paths.
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,18 @@
1
+ require "spec_helper"
2
+
3
+ require "kcaco"
4
+
5
+ describe Kcaco do
6
+
7
+ let(:boom) { Boom.new }
8
+
9
+ before do
10
+ Kcaco.auto_save = false
11
+ end
12
+
13
+ it "should create a pretty log entry" do
14
+ exception = boom.rescued
15
+ rest = Regexp.escape("RuntimeError: exception [boom.rb L10]")
16
+ Kcaco.pretty(exception).should =~ /[a-z0-9\-]+ #{rest}/
17
+ end
18
+ end
@@ -0,0 +1 @@
1
+ require "support/boom"
@@ -0,0 +1,36 @@
1
+ class Boom
2
+
3
+ DEFAULT_MESSAGE = "exception"
4
+
5
+ def create(message = DEFAULT_MESSAGE)
6
+ RuntimeError.new(message)
7
+ end
8
+
9
+ def raise(message = DEFAULT_MESSAGE)
10
+ Kernel.raise(create(message))
11
+ end
12
+
13
+ def rescued(message = DEFAULT_MESSAGE)
14
+ begin
15
+ self.raise
16
+ rescue RuntimeError => e
17
+ e
18
+ end
19
+ end
20
+
21
+ class << self
22
+
23
+ def raise_line_no
24
+ @raise_line_no ||= get_raise_line_no
25
+ end
26
+
27
+ def get_raise_line_no
28
+ File.open(__FILE__, "r") do |f|
29
+ f.each do |line|
30
+ return f.lineno if line.include?("Kernel.raise")
31
+ end
32
+ end
33
+ nil
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,35 @@
1
+ require "spec_helper"
2
+
3
+ require "kcaco/wrapped_exception"
4
+
5
+ module Kcaco
6
+ describe WrappedException do
7
+
8
+ let(:boom) { Boom.new }
9
+ let(:we) { WrappedException.new(boom.rescued) }
10
+
11
+
12
+ it "should generate an we title" do
13
+ we.title.should == "RuntimeError: exception"
14
+ end
15
+
16
+ it "should figure out the file name that caused the we" do
17
+ we.filename.should == "boom.rb"
18
+ end
19
+
20
+ it "should figure out the line number that caused the we" do
21
+ we.line_no.should == Boom.raise_line_no
22
+ end
23
+
24
+ it "should create a pretty version" do
25
+ we.should_receive(:uuid).and_return("uuid")
26
+ we.pretty.should == "uuid RuntimeError: exception [boom.rb L10]"
27
+ end
28
+
29
+ it "should make a uuid" do
30
+ uuid = we.uuid
31
+ we.uuid.should =~ /[a-z0-9\-]+/
32
+ we.uuid.should == uuid
33
+ end
34
+ end
35
+ end
metadata ADDED
@@ -0,0 +1,129 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kcaco
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Marc Bowes
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-01-06 00:00:00 +02:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ type: :runtime
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ name: guid
33
+ version_requirements: *id001
34
+ prerelease: false
35
+ - !ruby/object:Gem::Dependency
36
+ type: :development
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ none: false
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ hash: 3
43
+ segments:
44
+ - 0
45
+ version: "0"
46
+ name: rake
47
+ version_requirements: *id002
48
+ prerelease: false
49
+ - !ruby/object:Gem::Dependency
50
+ type: :development
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ name: rspec
61
+ version_requirements: *id003
62
+ prerelease: false
63
+ description: Helpers for dealing with exceptions
64
+ email:
65
+ - marcbowes@gmail.com
66
+ executables: []
67
+
68
+ extensions: []
69
+
70
+ extra_rdoc_files: []
71
+
72
+ files:
73
+ - examples/no_kcaco_different_levels.rb
74
+ - examples/simple.rb
75
+ - examples/no_kcaco_one_line.rb
76
+ - Rakefile
77
+ - README.md
78
+ - Gemfile
79
+ - kcaco.gemspec
80
+ - spec/support/boom.rb
81
+ - spec/wrapped_exception_spec.rb
82
+ - spec/spec_helper.rb
83
+ - spec/kcaco_spec.rb
84
+ - spec/file_writer_spec.rb
85
+ - Gemfile.lock
86
+ - lib/kcaco.rb
87
+ - lib/kcaco/file_writer.rb
88
+ - lib/kcaco/wrapped_exception.rb
89
+ - lib/kcaco/version.rb
90
+ has_rdoc: true
91
+ homepage: ""
92
+ licenses: []
93
+
94
+ post_install_message:
95
+ rdoc_options: []
96
+
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ hash: 3
105
+ segments:
106
+ - 0
107
+ version: "0"
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ hash: 3
114
+ segments:
115
+ - 0
116
+ version: "0"
117
+ requirements: []
118
+
119
+ rubyforge_project:
120
+ rubygems_version: 1.6.2
121
+ signing_key:
122
+ specification_version: 3
123
+ summary: Bored of writing out exceptions to logs?
124
+ test_files:
125
+ - spec/support/boom.rb
126
+ - spec/wrapped_exception_spec.rb
127
+ - spec/spec_helper.rb
128
+ - spec/kcaco_spec.rb
129
+ - spec/file_writer_spec.rb