workety 2.0.3

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/lib/workety.rb ADDED
@@ -0,0 +1,53 @@
1
+
2
+ # Copyright 2006-2011 Stanislav Senotrusov <stan@senotrusov.com>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+
17
+ require 'pp'
18
+
19
+
20
+ gems = lambda do |name|
21
+ if Gem::Specification.respond_to?(:find_all_by_name)
22
+ Gem::Specification.find_all_by_name(name).any?
23
+ elsif Gem.respond_to?(:available?)
24
+ Gem.available?(name)
25
+ else
26
+ raise "Don't know how to check gem availability"
27
+ end
28
+ end
29
+
30
+
31
+ if gems['exceptional']
32
+ require 'exceptional'
33
+ require 'workety/extensions/exceptional.rb'
34
+ end
35
+
36
+ if gems['toadhopper']
37
+ require 'toadhopper'
38
+ end
39
+
40
+
41
+ require 'workety/extensions/exception.rb'
42
+ require 'workety/extensions/kernel.rb'
43
+ require 'workety/extensions/process.rb'
44
+ require 'workety/extensions/signal.rb'
45
+ require 'workety/extensions/socket.rb'
46
+ require 'workety/extensions/thread.rb'
47
+
48
+
49
+ require 'workety/timed_exit.rb'
50
+
51
+
52
+ require 'workety/workety.rb'
53
+
@@ -0,0 +1,140 @@
1
+
2
+ # Copyright 2006-2011 Stanislav Senotrusov <stan@senotrusov.com>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+
17
+ class Exception
18
+
19
+ def details
20
+ @details ||= {}
21
+ end
22
+
23
+ # begin
24
+ # raise StandardError.details("ALARM!", :a=>1)
25
+ # rescue => ex
26
+ # puts ex.view
27
+ # end
28
+ #
29
+ def self.details message = nil, details = {}
30
+ if message.kind_of? Hash
31
+ message, details = nil, message
32
+ end
33
+
34
+ ex = new message
35
+ ex.details.merge! details
36
+ ex
37
+ end
38
+
39
+ def view!
40
+ STDERR.write "#{view}\n"
41
+ end
42
+
43
+ def view
44
+ [summary_view, details_view, backtrace_view].compact.join("\n")
45
+ rescue ScriptError, StandardError
46
+ "ERROR CREATING EXCEPTION VIEW"
47
+ end
48
+
49
+ def summary_view
50
+ "#{self.class.name}: #{message}"
51
+ end
52
+
53
+ def details_view
54
+ "Details: #{details.inspect}" if details.any?
55
+ rescue ScriptError, StandardError => ex
56
+ "Details: ERROR CREATING DETAILS VIEW:\n" \
57
+ "#{ex.summary_view}\n" \
58
+ "#{ex.backtrace_view}"
59
+ end
60
+
61
+ def backtrace_view
62
+ ((bt = backtrace) && bt.collect{|line|"\t#{line}\n"}.join("") || "\tBacktrace undefined")
63
+ rescue ScriptError, StandardError => ex
64
+ "\tERROR CREATING BACKTRACE VIEW: #{ex.summary_view}"
65
+ end
66
+
67
+
68
+ # The following methods should not be called before Rails initialization.
69
+
70
+ def logger
71
+ Rails.logger
72
+ end
73
+
74
+ def report!
75
+ log!
76
+ report_to_trackers! if Rails.env == "production"
77
+ end
78
+
79
+ def report_to_trackers!
80
+ report_to_exceptional! if defined?(Exceptional)
81
+ report_to_airbrake! if defined?(Toadhopper)
82
+ end
83
+
84
+ def log!
85
+ logger.error view
86
+ logger.flush if logger.respond_to? :flush
87
+ rescue ScriptError, StandardError => ex
88
+ STDERR.write "ERROR: LOGGING ANOTHER EXCEPTION THE FOLLOWING EXCEPTION OCCURED:\n" \
89
+ "#{ex.view}\n" \
90
+ "THE FOLLOWING EXCEPTION WAS NOT STORED IN LOG:\n" \
91
+ "#{view}\n"
92
+ end
93
+
94
+
95
+ def report_to_exceptional!
96
+ Exceptional::Remote.error Exceptional::DetailsExceptionData.new(self)
97
+ Exceptional.context.clear!
98
+ rescue ScriptError, StandardError => ex
99
+ ex.log!
100
+ end
101
+
102
+
103
+ # Airbrake API requires the following elements to be present:
104
+ # /notice/error/class
105
+ # /notice/error/backtrace/line
106
+ # /notice/server-environment/environment-name
107
+ #
108
+ def report_to_airbrake!
109
+ File.readable?(file = Rails.root + 'config' + 'airbrake.yml') &&
110
+ (yaml = YAML.load_file file).kind_of?(Hash) &&
111
+ (api_key = yaml["api-key"]).kind_of?(String) ||
112
+ raise("Unable to read Airbrake api-key from #{file}")
113
+
114
+ options = if details[:request].kind_of? Hash
115
+ params = details.dup
116
+ request = params.delete :request
117
+
118
+ { url: request[:url],
119
+ component: request[:controller],
120
+ action: request[:action],
121
+ params: (request[:params] || {}).merge(params),
122
+ session: request[:session],
123
+ framework_env: Rails.env }
124
+
125
+ else
126
+ { params: details,
127
+ framework_env: Rails.env }
128
+ end
129
+
130
+ response = Toadhopper(api_key).post!(self, options)
131
+
132
+ if response.status != 200
133
+ raise StandardError.details("Tracker responded with status #{response.status}", body: response.body)
134
+ end
135
+
136
+ rescue ScriptError, StandardError => ex
137
+ ex.log!
138
+ end
139
+ end
140
+
@@ -0,0 +1,52 @@
1
+
2
+ # Copyright 2006-2011 Stanislav Senotrusov <stan@senotrusov.com>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+
17
+ class Exceptional::DetailsExceptionData < Exceptional::ExceptionData
18
+ def initialize exception
19
+ @exception = exception
20
+ @details = @exception.details.dup
21
+ @name = @details.delete(:name)
22
+ @request = @details[:request].kind_of?(Hash) && @details.delete(:request)
23
+ end
24
+
25
+ alias_method :context_stuff_orig, :context_stuff
26
+
27
+ def context_stuff
28
+ data = context_stuff_orig
29
+
30
+ if @details.any?
31
+ if data['context']
32
+ data['context'].merge! @details
33
+ else
34
+ data['context'] = @details
35
+ end
36
+ end
37
+
38
+ if @request
39
+ data['request'] = {
40
+ 'url' => @request[:url],
41
+ 'controller' => @request[:controller],
42
+ 'action' => @request[:action],
43
+ 'parameters' => @request[:params],
44
+ 'headers' => @request[:headers],
45
+ 'session' => @request[:session]
46
+ }
47
+ end
48
+
49
+ data
50
+ end
51
+ end
52
+
@@ -0,0 +1,28 @@
1
+
2
+ # Copyright 2006-2011 Stanislav Senotrusov <stan@senotrusov.com>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+
17
+ module Kernel
18
+ def rescue_exit
19
+ yield
20
+ rescue ScriptError, StandardError => exception
21
+ begin
22
+ exception.report!
23
+ ensure
24
+ Process.exit(false)
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,38 @@
1
+
2
+ # Copyright 2006-2011 Stanislav Senotrusov <stan@senotrusov.com>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+
17
+ require 'etc'
18
+
19
+ module Process
20
+
21
+ # Drop privilegies and chown logfile
22
+ #
23
+ # http://timetobleed.com/5-things-you-dont-know-about-user-ids-that-will-destroy-you/
24
+ # http://www.ruby-forum.com/topic/110492
25
+ #
26
+ def self.change_privilegies user, group
27
+ user = user ? Etc.getpwnam(user) : Etc.getpwuid(Process.euid)
28
+ group = group ? Etc.getgrnam(group) : Etc.getgrgid(user.gid)
29
+
30
+ Rails.logger.chown_logfile(user.uid, group.gid) if Rails.logger.respond_to?(:chown_logfile)
31
+
32
+ Process.initgroups(user.name, group.gid)
33
+
34
+ Process::GID.change_privilege(group.gid)
35
+ Process::UID.change_privilege(user.uid)
36
+ end
37
+ end
38
+
@@ -0,0 +1,31 @@
1
+
2
+ # Copyright 2006-2011 Stanislav Senotrusov <stan@senotrusov.com>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+
17
+ module Signal
18
+ def self.threaded_trap(signal)
19
+ trap(signal) do
20
+ rescue_exit do # This rescue_exit is the case for ThreadError: can't create Thread (see example below)
21
+ Thread.rescue_exit { yield }
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+
28
+ # 1000000.times { |i| begin; Thread.new {sleep 100}; rescue => e; puts i; raise e; end }
29
+ # 2848
30
+ # ThreadError: can't create Thread (1)
31
+
@@ -0,0 +1,51 @@
1
+
2
+ # Copyright 2006-2011 Stanislav Senotrusov <stan@senotrusov.com>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+
17
+
18
+ require 'socket'
19
+
20
+ # rescue *(Socket::NETWORK_EXEPTIONS) => exception
21
+
22
+ class Socket
23
+ NETWORK_EXEPTIONS = [
24
+ IOError,
25
+ EOFError,
26
+ Errno::EBADF,
27
+ Errno::ECONNRESET,
28
+ Errno::ECONNREFUSED,
29
+ Errno::EPIPE,
30
+ Errno::ETIMEDOUT,
31
+ Errno::EHOSTUNREACH,
32
+ Errno::ESHUTDOWN,
33
+ Errno::ENETDOWN,
34
+ Errno::ENETUNREACH,
35
+ Errno::ENETRESET,
36
+ Errno::EIO,
37
+ Errno::EHOSTDOWN,
38
+ Errno::ECONNABORTED,
39
+ Errno::ENOTCONN, # In attempt to shutdown() socket with remote side disconnected (send responds with Errno::EPIPE: Broken pipe)
40
+ ]
41
+
42
+ # At least Windows XP does not have it
43
+ NETWORK_EXEPTIONS.push(Errno::EPROTO) if defined?(Errno::EPROTO)
44
+ NETWORK_EXEPTIONS.push(Errno::ECOMM) if defined?(Errno::ECOMM)
45
+
46
+ def self.may_fail
47
+ yield
48
+ rescue *(Socket::NETWORK_EXEPTIONS)
49
+ end
50
+ end
51
+
@@ -0,0 +1,109 @@
1
+
2
+ # Copyright 2006-2011 Stanislav Senotrusov <stan@senotrusov.com>
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+
16
+
17
+ require 'thread'
18
+
19
+ class Thread
20
+
21
+ def self.workety(*args)
22
+ new do
23
+ begin
24
+ Workety.rescue_abort { yield(*args) }
25
+
26
+ ensure
27
+ # That block is executed on Thread#kill as well
28
+ #
29
+ # I think an error in clear_active_connections! is not the case
30
+ # for panic and instant process shutdown but for normal shutdown procedure.
31
+ #
32
+ Workety.rescue_abort { ActiveRecord::Base.clear_active_connections! }
33
+
34
+ end
35
+ end
36
+ end
37
+
38
+
39
+ def self.networkety(*args)
40
+ workety do
41
+ begin
42
+ yield(*args)
43
+ rescue *(Socket::NETWORK_EXEPTIONS) => exception
44
+ Rails.logger.warn "Thread stopped due a network error listed in Socket::NETWORK_EXEPTIONS"
45
+ Rails.logger.warn exception.view
46
+ Rails.logger.flush if Rails.logger.respond_to?(:flush)
47
+
48
+ # If thread is blocked by Socket#read and then are forced to unblock by using Socket#shutdown and then Socket#close methods,
49
+ # that will raise an exception. That exception has no value to set Workety.aborted? flag.
50
+ # If Workety.must_stop? is set we suggest that such technique was used.
51
+ Workety.abort unless Workety.must_stop?
52
+ end
53
+ end
54
+ end
55
+
56
+
57
+ def self.rescue_exit
58
+ new do
59
+ Kernel.rescue_exit { yield }
60
+ end
61
+ end
62
+
63
+
64
+ def view
65
+ "#{summary_view}\n#{thread_local_vars_view}#{backtrace_view}"
66
+ end
67
+
68
+ def summary_view
69
+ "#{self.class.name} 0x#{object_id.to_s(16)}: #{status_view}"
70
+ end
71
+
72
+ def status_view
73
+ st = status; (st == false) ? "terminated normally" : (st || "terminated with an exception")
74
+ end
75
+
76
+ def thread_local_vars_view
77
+ if (ks = keys).any?
78
+ vars = {}; ks.each {|k| vars[k] = self[k]}
79
+ "Thread-local variables: #{vars.inspect}\n"
80
+ end
81
+ end
82
+
83
+ def backtrace_view
84
+ ((bt = backtrace) && bt.collect{|line| "\t#{line}\n"}.join("") || "\tBacktrace undefined")
85
+ end
86
+
87
+
88
+ def self.log message = nil
89
+ threads = self.list
90
+ Rails.logger.warn(message) if message
91
+ Rails.logger.warn "Thread list: #{threads.length} threads total at #{Time.now}"
92
+
93
+ threads.each_with_index do |item, index|
94
+ Rails.logger.warn msg = "Thread #{index + 1} of #{threads.length}"
95
+ Rails.logger.warn "-" * msg.length
96
+ Rails.logger.warn item.view
97
+ end
98
+
99
+ Rails.logger.flush if Rails.logger.respond_to?(:flush)
100
+ end
101
+
102
+
103
+ def log_join(message, limit = nil)
104
+ join limit
105
+ Rails.logger.info message
106
+ end
107
+
108
+ end
109
+