appygram 0.1.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/appygram.gemspec ADDED
@@ -0,0 +1,16 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/appygram/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.name = %q{appygram}
6
+ gem.version = Appygram::VERSION
7
+ gem.authors = ["rfc2616"]
8
+ gem.summary = %q{ appygram is a hosted service for sending messages from mobile/web apps }
9
+ gem.description = %q{Appygram is the Ruby gem for communicating with http://appygram.com (hosted messaging service). It supports an exception reporting mechanism similar to http://exceptional.io, and this Ruby gem, forked from the Exceptional gem, emulates Exceptional functionality.}
10
+ gem.email = %q{heittman.rob@gmail.com}
11
+ gem.files = Dir['lib/**/*'] + Dir['spec/**/*'] + Dir['spec/**/*'] + Dir['rails/**/*'] + Dir['tasks/**/*'] + Dir['*.rb'] + ["appygram.gemspec"]
12
+ gem.homepage = %q{http://appygram.com/}
13
+ gem.require_paths = ["lib"]
14
+ gem.requirements << "json_pure, json-jruby or json gem required"
15
+ gem.add_dependency 'rack'
16
+ end
data/init.rb ADDED
@@ -0,0 +1,39 @@
1
+ require 'appygram'
2
+
3
+ # If old plugin still installed then we don't want to install this one.
4
+ # In production environments we should continue to work as before, but in development/test we should
5
+ # advise how to correct the problem and exit
6
+ if (defined?(Appygram::VERSION::STRING) rescue nil) && %w(development test).include?(RAILS_ENV)
7
+ message = %Q(
8
+ ***********************************************************************
9
+ You seem to still have an old version of the Appygram plugin installed.
10
+ Remove it from /vendor/plugins and try again.
11
+ ***********************************************************************
12
+ )
13
+ puts message
14
+ exit -1
15
+ else
16
+ begin
17
+
18
+ if (Rails::VERSION::MAJOR < 3)
19
+ Appygram::Config.load(File.join(RAILS_ROOT, "/config/Appygram.yml"))
20
+ if Appygram::Config.should_send_to_api?
21
+ Appygram.logger.info("Loading Appygram #{Appygram::VERSION} for #{Rails::VERSION::STRING}")
22
+ require File.join('Appygram', 'integration', 'rails')
23
+ require File.join('Appygram', 'integration', 'dj') if defined?(Delayed::Job)
24
+ end
25
+ else
26
+ Appygram::Config.load(File.join(Rails.root, "/config/Appygram.yml"))
27
+
28
+ if Appygram::Config.should_send_to_api?
29
+ Appygram.logger.info("Loading Appygram #{Appygram::VERSION} for #{Rails::VERSION::STRING}")
30
+ Rails.configuration.middleware.use "Rack::RailsAppygram"
31
+ require File.join('Appygram', 'integration', 'dj') if defined?(Delayed::Job)
32
+ end
33
+ end
34
+ rescue => e
35
+ STDERR.puts "Problem starting Appygram Plugin. Your app will run as normal. #{e.message}"
36
+ Appygram.logger.error(e.message)
37
+ Appygram.logger.error(e.backtrace)
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ module Appygram
2
+ class AlertData < ExceptionData
3
+ # Overwrite backtrace, since it is irrelevant
4
+ def extra_data
5
+ {
6
+ 'exception' => {
7
+ 'exception_class' => @exception.class.to_s,
8
+ 'message' => @exception.message,
9
+ 'backtrace' => "",
10
+ 'occurred_at' => Time.now
11
+ }
12
+ }
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,63 @@
1
+ require 'digest/md5'
2
+
3
+ module Appygram
4
+ class ApplicationEnvironment
5
+ def self.to_hash(framework)
6
+ {
7
+ 'client' => {
8
+ 'name' => Appygram::CLIENT_NAME,
9
+ 'version' => Appygram::VERSION,
10
+ 'protocol_version' => Appygram::PROTOCOL_VERSION
11
+ },
12
+ 'application_environment' => {
13
+ 'environment' => environment,
14
+ 'env' => extract_environment(ENV),
15
+ 'host' => get_hostname,
16
+ 'run_as_user' => get_username,
17
+ 'application_root_directory' => (application_root.to_s.respond_to?(:force_encoding) ? application_root.to_s.force_encoding("UTF-8") : application_root),
18
+ 'language' => 'ruby',
19
+ 'language_version' => language_version_string,
20
+ 'framework' => framework,
21
+ 'libraries_loaded' => libraries_loaded
22
+ }
23
+ }
24
+ end
25
+
26
+ def self.environment
27
+ Config.application_environment
28
+ end
29
+
30
+ def self.application_root
31
+ Config.application_root
32
+ end
33
+
34
+ def self.extract_environment(env)
35
+ env.reject do |k, v|
36
+ (k =~ /^HTTP_/) || Appygram::ENVIRONMENT_FILTER.include?(k)
37
+ end
38
+ end
39
+
40
+ def self.get_hostname
41
+ require 'socket' unless defined?(Socket)
42
+ Socket.gethostname
43
+ rescue
44
+ 'UNKNOWN'
45
+ end
46
+
47
+ def self.language_version_string
48
+ "#{RUBY_VERSION rescue '?.?.?'} p#{RUBY_PATCHLEVEL rescue '???'} #{RUBY_RELEASE_DATE rescue '????-??-??'} #{RUBY_PLATFORM rescue '????'}"
49
+ end
50
+
51
+ def self.get_username
52
+ ENV['LOGNAME'] || ENV['USER'] || ENV['USERNAME'] || ENV['APACHE_RUN_USER'] || 'UNKNOWN'
53
+ end
54
+
55
+ def self.libraries_loaded
56
+ begin
57
+ return Hash[*Gem.loaded_specs.map{|name, gem_specification| [name, gem_specification.version.to_s]}.flatten]
58
+ rescue
59
+ end
60
+ {}
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,34 @@
1
+ module Appygram
2
+ class Catcher
3
+ class << self
4
+ def handle_with_controller(exception, controller=nil, request=nil)
5
+ if Config.should_send_to_api?
6
+ data = ControllerExceptionData.new(exception, controller, request)
7
+ Remote.error(data)
8
+ else
9
+ raise exception
10
+ end
11
+ end
12
+
13
+ # unspeced
14
+ def handle_with_rack(exception, environment, request)
15
+ if Config.should_send_to_api?
16
+ data = RackExceptionData.new(exception, environment, request)
17
+ Remote.error(data)
18
+ else
19
+ raise exception
20
+ end
21
+ end
22
+
23
+ # unspeced
24
+ def handle(exception, name=nil)
25
+ if Config.should_send_to_api?
26
+ data = ExceptionData.new(exception, name)
27
+ Remote.error(data)
28
+ else
29
+ raise exception
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,87 @@
1
+ require 'yaml'
2
+
3
+ module Appygram
4
+ class Config
5
+ class ConfigurationException < StandardError; end
6
+
7
+ class << self
8
+ DEFAULTS = {
9
+ :ssl => false,
10
+ :remote_host_http => 'api.appygram.com',
11
+ :http_open_timeout => 2,
12
+ :http_read_timeout => 4,
13
+ :disabled_by_default => %w(development test)
14
+ }
15
+
16
+ attr_accessor :api_key, :enabled
17
+ attr_accessor :http_proxy_host, :http_proxy_port, :http_proxy_username, :http_proxy_password
18
+ attr_writer :ssl
19
+
20
+ def load(config_file=nil)
21
+ if (config_file && File.file?(config_file))
22
+ begin
23
+ config = YAML::load_file(config_file)
24
+ env_config = config[application_environment] || {}
25
+ @api_key = config['api-key'] || env_config['api-key']
26
+
27
+ @http_proxy_host = config['http-proxy-host']
28
+ @http_proxy_port = config['http-proxy-port']
29
+ @http_proxy_username = config['http-proxy-username']
30
+ @http_proxy_password = config['http-proxy-password']
31
+ @http_open_timeout = config['http-open-timeout']
32
+ @http_read_timeout = config['http-read-timeout']
33
+
34
+ @ssl = config['ssl'] || env_config['ssl']
35
+ @enabled = env_config['enabled']
36
+ @remote_port = config['remote-port'].to_i unless config['remote-port'].nil?
37
+ @remote_host = config['remote-host'] unless config['remote-host'].nil?
38
+ rescue Exception => e
39
+ raise ConfigurationException.new("Unable to load configuration #{config_file} for environment #{application_environment} : #{e.message}")
40
+ end
41
+ end
42
+ end
43
+
44
+ def api_key
45
+ return @api_key unless @api_key.nil?
46
+ @api_key ||= ENV['EXCEPTIONAL_API_KEY'] unless ENV['EXCEPTIONAL_API_KEY'].nil?
47
+ end
48
+
49
+ def application_environment
50
+ ENV['RACK_ENV'] || ENV['RAILS_ENV']|| 'development'
51
+ end
52
+
53
+ def should_send_to_api?
54
+ return @enabled unless @enabled.nil?
55
+ @enabled = !(DEFAULTS[:disabled_by_default].include?(application_environment))
56
+ end
57
+
58
+ def application_root
59
+ (defined?(Rails) && Rails.respond_to?(:root)) ? Rails.root : Dir.pwd
60
+ end
61
+
62
+ def ssl?
63
+ @ssl ||= DEFAULTS[:ssl]
64
+ end
65
+
66
+ def remote_host
67
+ @remote_host ||= DEFAULTS[:remote_host_http]
68
+ end
69
+
70
+ def remote_port
71
+ @remote_port ||= ssl? ? 443 : 80
72
+ end
73
+
74
+ def reset
75
+ @enabled = @ssl = @remote_host = @remote_port = @api_key = nil
76
+ end
77
+
78
+ def http_open_timeout
79
+ @http_open_timeout ||= DEFAULTS[:http_open_timeout]
80
+ end
81
+
82
+ def http_read_timeout
83
+ @http_read_timeout ||= DEFAULTS[:http_read_timeout]
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,65 @@
1
+ require 'digest/md5'
2
+
3
+ module Appygram
4
+ class ControllerExceptionData < ExceptionData
5
+ def initialize(exception, controller=nil, request=nil)
6
+ super(exception)
7
+ @request = request
8
+ @controller = controller
9
+ end
10
+
11
+ def framework
12
+ "rails"
13
+ end
14
+
15
+ def extra_stuff
16
+ return {} if @request.nil?
17
+ {
18
+ 'request' => {
19
+ 'url' => (@request.respond_to?(:url) ? @request.url : "#{@request.protocol}#{@request.host}#{@request.request_uri}"),
20
+ 'controller' => @controller.class.to_s,
21
+ 'action' => (@request.respond_to?(:parameters) ? @request.parameters['action'] : @request.params['action']),
22
+ 'parameters' => filter_parameters(@request.respond_to?(:parameters) ? @request.parameters : @request.params),
23
+ 'request_method' => @request.request_method.to_s,
24
+ 'remote_ip' => (@request.respond_to?(:remote_ip) ? @request.remote_ip : @request.ip),
25
+ 'headers' => extract_http_headers(@request.env),
26
+ 'session' => self.class.sanitize_session(@request)
27
+ }
28
+ }
29
+ end
30
+
31
+ def filter_hash(keys_to_filter, hash)
32
+ keys_to_filter.map! {|x| x.to_s}
33
+ if keys_to_filter.is_a?(Array) && !keys_to_filter.empty?
34
+ hash.each do |key, value|
35
+ if key_match?(key, keys_to_filter)
36
+ hash[key] = "[FILTERED]"
37
+ elsif value.respond_to?(:to_hash)
38
+ filter_hash(keys_to_filter, hash[key])
39
+ end
40
+ end
41
+ end
42
+ hash
43
+ end
44
+
45
+ # Closer alignment to latest filtered_params:
46
+ # https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/parameter_filter.rb
47
+ # https://github.com/exceptional/exceptional/issues/20
48
+ def key_match?(key, keys_to_filter)
49
+ keys_to_filter.any? { |k|
50
+ regexp = k.is_a?(Regexp)? k : Regexp.new(k, true)
51
+ key =~ regexp
52
+ }
53
+ end
54
+
55
+ def filter_parameters(hash)
56
+ if @request.respond_to?(:env) && @request.env["action_dispatch.parameter_filter"]
57
+ filter_hash(@request.env["action_dispatch.parameter_filter"], hash)
58
+ elsif @controller.respond_to?(:filter_parameters)
59
+ @controller.send(:filter_parameters, hash)
60
+ else
61
+ hash
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,108 @@
1
+ require 'digest/md5'
2
+ require 'time'
3
+
4
+ module Appygram
5
+ class ExceptionData
6
+
7
+ def initialize(exception, name=nil)
8
+ @exception = exception
9
+ @name = name
10
+ end
11
+
12
+ def to_hash
13
+ hash = ::Appygram::ApplicationEnvironment.to_hash(framework)
14
+ hash.merge!({
15
+ 'exception' => {
16
+ 'exception_class' => @exception.class.to_s,
17
+ 'message' => @exception.message,
18
+ 'backtrace' => @exception.backtrace,
19
+ 'occurred_at' => Time.now.utc.iso8601
20
+ }
21
+ })
22
+ hash.merge!(extra_stuff)
23
+ hash.merge!(context_stuff)
24
+ self.class.sanitize_hash(hash)
25
+ end
26
+
27
+ def extra_stuff
28
+ { 'rescue_block' => { 'name' => @name} }
29
+ end
30
+
31
+ def context_stuff
32
+ context = Thread.current[:appygram_context]
33
+ (context.nil? || context.empty?) ? {} : {'context' => context}
34
+ end
35
+
36
+ def to_json
37
+ begin
38
+ to_hash.to_json
39
+ rescue NoMethodError
40
+ begin
41
+ require 'json'
42
+ return to_hash.to_json
43
+ rescue StandardError => e
44
+ Appygram.logger.error(e.message)
45
+ Appygram.logger.error(e.backtrace)
46
+ raise StandardError.new("You need a json gem/library installed to send errors to Appygram (Object.to_json not defined). \nInstall json_pure, yajl-ruby, json-jruby, or the c-based json gem")
47
+ end
48
+ end
49
+ end
50
+
51
+ def framework
52
+ nil
53
+ end
54
+
55
+ def uniqueness_hash
56
+ return nil if (@exception.backtrace.nil? || @exception.backtrace.empty?)
57
+ Digest::MD5.hexdigest(@exception.backtrace.join)
58
+ end
59
+
60
+ def self.sanitize_hash(hash)
61
+
62
+ case hash
63
+ when Hash
64
+ hash.inject({}) do |result, (key, value)|
65
+ result.update(key => sanitize_hash(value))
66
+ end
67
+ when Array
68
+ hash.collect{|value| sanitize_hash(value)}
69
+ when Fixnum, String, Bignum
70
+ hash
71
+ else
72
+ hash.to_s
73
+ end
74
+ rescue Exception => e
75
+ Appygram.logger.error(hash)
76
+ Appygram.logger.error(e.message)
77
+ Appygram.logger.error(e.backtrace)
78
+ {}
79
+ end
80
+
81
+ def extract_http_headers(env)
82
+ headers = {}
83
+ env.select{|k, v| k =~ /^HTTP_/}.each do |name, value|
84
+ proper_name = name.sub(/^HTTP_/, '').split('_').map{|upper_case| upper_case.capitalize}.join('-')
85
+ headers[proper_name] = value
86
+ end
87
+ unless headers['Cookie'].nil?
88
+ headers['Cookie'] = headers['Cookie'].sub(/_session=\S+/, '_session=[FILTERED]')
89
+ end
90
+ headers
91
+ end
92
+
93
+ def self.sanitize_session(request)
94
+ session_hash = {'session_id' => "", 'data' => {}}
95
+
96
+ if request.respond_to?(:session)
97
+ session = request.session
98
+ session_hash['session_id'] = request.session_options ? request.session_options[:id] : nil
99
+ session_hash['session_id'] ||= session.respond_to?(:session_id) ? session.session_id : session.instance_variable_get("@session_id")
100
+ session_hash['data'] = session.respond_to?(:to_hash) ? session.to_hash : session.instance_variable_get("@data") || {}
101
+ session_hash['session_id'] ||= session_hash['data'][:session_id]
102
+ session_hash['data'].delete(:session_id)
103
+ end
104
+
105
+ self.sanitize_hash(session_hash)
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,11 @@
1
+ module Appygram
2
+ class Alert <StandardError;
3
+ end
4
+
5
+ module Integration
6
+ def self.alert(msg, env={})
7
+ return Appygram::Remote.error(Appygram::AlertData.new(Alert.new(msg), "Alert"))
8
+ end
9
+ end
10
+ end
11
+
@@ -0,0 +1,16 @@
1
+ module Appygram
2
+ module DebugExceptions
3
+
4
+ def self.included(base)
5
+ base.send(:alias_method_chain,:render_exception,:appygram)
6
+ end
7
+
8
+ def render_exception_with_appygram(env,exception)
9
+ ::Appygram::Catcher.handle_with_controller(exception,
10
+ env['action_controller.instance'],
11
+ Rack::Request.new(env))
12
+ render_exception_without_appygram(env,exception)
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,15 @@
1
+ if Delayed::Worker.method_defined? :handle_failed_job
2
+ class Delayed::Worker
3
+ def handle_failed_job_with_appygram(job, e)
4
+ Appygram.handle(e, "Delayed::Job #{job.name}")
5
+ handle_failed_job_without_appygram(job, e)
6
+ Appygram.context.clear!
7
+ end
8
+ alias_method_chain :handle_failed_job, :appygram
9
+ Appygram.logger.info "DJ integration enabled"
10
+ end
11
+ else
12
+ message = "\n\n\nThe Appygram gem does not support Delayed Job 1.8.4 or earlier.\n\n\n"
13
+ STDERR.puts(message)
14
+ Appygram.logger.error(message)
15
+ end
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'rack'
3
+
4
+ module Rack
5
+ class Appygram
6
+
7
+ def initialize(app, api_key = nil)
8
+ @app = app
9
+ if api_key.nil?
10
+ appygram_config = "config/appygram.yml"
11
+ ::Appygram::Config.load(appygram_config)
12
+ else
13
+ ::Appygram.configure(api_key)
14
+ ::Appygram::Config.enabled = true
15
+ ::Appygram.logger.info "Enabling Appygram for Rack"
16
+ end
17
+ end
18
+
19
+ def call(env)
20
+ begin
21
+ status, headers, body = @app.call(env)
22
+ rescue Exception => e
23
+ ::Appygram::Catcher.handle_with_rack(e,env, Rack::Request.new(env))
24
+ raise(e)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ require 'rack'
3
+
4
+ module Rack
5
+ class RailsAppygram
6
+
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(env)
12
+ begin
13
+ body = @app.call(env)
14
+ rescue Exception => e
15
+ ::Appygram::Catcher.handle_with_controller(e,env['action_controller.instance'], Rack::Request.new(env))
16
+ raise
17
+ end
18
+
19
+ if env['rack.exception']
20
+ ::Appygram::Catcher.handle_with_controller(env['rack.exception'],env['action_controller.instance'], Rack::Request.new(env))
21
+ end
22
+
23
+ body
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ # force Rails < 2.0 to use quote keys as per the JSON standard...
2
+ if defined?(ActiveSupport) && defined?(ActiveSupport::JSON) && ActiveSupport::JSON.respond_to?(:unquote_hash_key_identifiers)
3
+ ActiveSupport::JSON.unquote_hash_key_identifiers = false
4
+ end
5
+
6
+ if defined? ActionController
7
+ module ActionController
8
+ class Base
9
+ def rescue_action_with_appygram(exception)
10
+ unless exception_handled_by_rescue_from?(exception)
11
+ Appygram::Catcher.handle_with_controller(exception, self, request)
12
+ Appygram.context.clear!
13
+ end
14
+ rescue_action_without_appygram exception
15
+ end
16
+
17
+ alias_method :rescue_action_without_appygram, :rescue_action
18
+ alias_method :rescue_action, :rescue_action_with_appygram
19
+ protected :rescue_action
20
+
21
+ private
22
+ def exception_handled_by_rescue_from?(exception)
23
+ respond_to?(:handler_for_rescue) && handler_for_rescue(exception)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,6 @@
1
+ if defined? Sinatra::Request
2
+ error do
3
+ Appygram::Catcher.handle_with_rack(request.env['sinatra.error'], request.env, request)
4
+ raise request.env['sinatra.error']
5
+ end
6
+ end
@@ -0,0 +1,39 @@
1
+ require 'logger'
2
+
3
+ module Appygram
4
+ class LogFactory
5
+ def self.logger
6
+ @logger ||= create_logger_with_fallback
7
+ end
8
+
9
+ private
10
+ def self.create_logger_with_fallback
11
+ begin
12
+ log_dir = File.join(Config.application_root, 'log')
13
+ Dir.mkdir(log_dir) unless File.directory?(log_dir)
14
+ log_path = File.join(log_dir, "/appygram.log")
15
+ log = Logger.new(log_path)
16
+ log.level = Logger::INFO
17
+ def log.format_message(severity, timestamp, progname, msg)
18
+ "[#{severity.upcase}] (#{[Kernel.caller[2].split('/').last]}) #{timestamp.utc.to_s} - #{msg2str(msg).gsub(/\n/, '').lstrip}\n"
19
+ end
20
+ def log.msg2str(msg)
21
+ case msg
22
+ when ::String
23
+ msg
24
+ when ::Exception
25
+ "#{ msg.message } (#{ msg.class }): " <<
26
+ (msg.backtrace || []).join(" | ")
27
+ else
28
+ msg.inspect
29
+ end
30
+ end
31
+ log
32
+ rescue
33
+ return Rails.logger if defined?(Rails) && defined?(Rails.logger)
34
+ return RAILS_DEFAULT_LOGGER if defined?(RAILS_DEFAULT_LOGGER)
35
+ return Logger.new(STDERR)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,10 @@
1
+ class Regexp
2
+ def to_json(options = {})
3
+ "\"#{self.to_s}\""
4
+ end
5
+ end
6
+ class Fixnum
7
+ def to_json(options = {})
8
+ to_s
9
+ end
10
+ end
@@ -0,0 +1,29 @@
1
+ require 'digest/md5'
2
+
3
+ module Appygram
4
+ class RackExceptionData < ExceptionData
5
+ def initialize(exception, environment, request)
6
+ super(exception)
7
+ @environment = environment
8
+ @request = request
9
+ end
10
+
11
+ def framework
12
+ "rack"
13
+ end
14
+
15
+ def extra_stuff
16
+ return {} if @request.nil?
17
+ {
18
+ 'request' => {
19
+ 'url' => "#{@request.url}",
20
+ 'parameters' => @request.params,
21
+ 'request_method' => @request.request_method.to_s,
22
+ 'remote_ip' => @request.ip,
23
+ 'headers' => extract_http_headers(@environment),
24
+ 'session' => self.class.sanitize_session(@request)
25
+ }
26
+ }
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,23 @@
1
+ require 'appygram'
2
+ require 'rails'
3
+
4
+ module Appygram
5
+ class Railtie < Rails::Railtie
6
+
7
+ initializer "appygram.middleware" do |app|
8
+
9
+ config_file = File.join(Rails.root, "/config/appygram.yml")
10
+ Appygram::Config.load config_file if File.exist?(config_file)
11
+ # On Heroku config is loaded via the ENV so no need to load it from the file
12
+
13
+ if Appygram::Config.should_send_to_api?
14
+ Appygram.logger.info("Loading Appygram #{Appygram::VERSION} for #{Rails::VERSION::STRING}")
15
+ if defined?(ActionDispatch::DebugExceptions)
16
+ ActionDispatch::DebugExceptions.send(:include,Appygram::DebugExceptions)
17
+ else
18
+ app.config.middleware.use "Rack::RailsAppygram"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,52 @@
1
+ require 'zlib'
2
+ require 'cgi'
3
+ require 'net/http'
4
+ require 'net/https'
5
+ require 'digest/md5'
6
+
7
+ module Appygram
8
+ class Remote
9
+ class << self
10
+ def startup_announce(startup_data)
11
+ url = "/api/announcements?api_key=#{::Appygram::Config.api_key}&protocol_version=#{::Appygram::PROTOCOL_VERSION}"
12
+ compressed = Zlib::Deflate.deflate(startup_data.to_json, Zlib::BEST_SPEED)
13
+ call_remote(url, compressed)
14
+ end
15
+
16
+ def error(exception_data)
17
+ uniqueness_hash = exception_data.uniqueness_hash
18
+ hash_param = uniqueness_hash.nil? ? nil: "&hash=#{uniqueness_hash}"
19
+ url = "/api/errors?api_key=#{::Appygram::Config.api_key}&protocol_version=#{::Appygram::PROTOCOL_VERSION}#{hash_param}"
20
+ compressed = Zlib::Deflate.deflate(exception_data.to_json, Zlib::BEST_SPEED)
21
+ call_remote(url, compressed)
22
+ end
23
+
24
+ def call_remote(url, data)
25
+ config = Appygram::Config
26
+ optional_proxy = Net::HTTP::Proxy(config.http_proxy_host,
27
+ config.http_proxy_port,
28
+ config.http_proxy_username,
29
+ config.http_proxy_password)
30
+ client = optional_proxy.new(config.remote_host, config.remote_port)
31
+ client.open_timeout = config.http_open_timeout
32
+ client.read_timeout = config.http_read_timeout
33
+ client.use_ssl = config.ssl?
34
+ client.verify_mode = OpenSSL::SSL::VERIFY_NONE if config.ssl?
35
+ begin
36
+ response = client.post(url, data)
37
+ case response
38
+ when Net::HTTPSuccess
39
+ Appygram.logger.info( "#{url} - #{response.message}")
40
+ return true
41
+ else
42
+ Appygram.logger.error("#{url} - #{response.code} - #{response.message}")
43
+ end
44
+ rescue Exception => e
45
+ Appygram.logger.error('Problem notifying Appygram about the error')
46
+ Appygram.logger.error(e)
47
+ end
48
+ nil
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,14 @@
1
+ module Appygram
2
+ class StartupException < StandardError;
3
+ end
4
+ class Startup
5
+ class << self
6
+ def announce
7
+ if Config.api_key.blank?
8
+ raise StartupException, 'API Key must be configured (/config/appygram.yml)'
9
+ end
10
+ Remote.startup_announce(::Appygram::ApplicationEnvironment.to_hash('rails'))
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Appygram
2
+ VERSION = '0.1.0'
3
+ end
data/lib/appygram.rb ADDED
@@ -0,0 +1,71 @@
1
+ $:.unshift File.dirname(__FILE__)
2
+
3
+ require 'appygram/monkeypatches'
4
+ require 'appygram/catcher'
5
+ require 'appygram/startup'
6
+ require 'appygram/log_factory'
7
+ require 'appygram/config'
8
+ require 'appygram/application_environment'
9
+ require 'appygram/exception_data'
10
+ require 'appygram/controller_exception_data'
11
+ require 'appygram/rack_exception_data'
12
+ require 'appygram/alert_data'
13
+ require 'appygram/remote'
14
+ require 'appygram/integration/rack'
15
+ require 'appygram/integration/rack_rails'
16
+ require 'appygram/integration/alerter'
17
+ require 'appygram/version'
18
+ require 'appygram/integration/debug_exceptions'
19
+
20
+ require 'appygram/railtie' if defined?(Rails::Railtie)
21
+
22
+ module Appygram
23
+ PROTOCOL_VERSION = 5
24
+ CLIENT_NAME = 'getappygram-gem'
25
+ ENVIRONMENT_FILTER = []
26
+
27
+ def self.logger
28
+ ::Appygram::LogFactory.logger
29
+ end
30
+
31
+ def self.configure(api_key)
32
+ Appygram::Config.api_key = api_key
33
+ end
34
+
35
+ def self.handle(exception, name=nil)
36
+ Appygram::Catcher.handle(exception, name)
37
+ end
38
+
39
+ def self.rescue(name=nil, context=nil, &block)
40
+ begin
41
+ self.context(context) unless context.nil?
42
+ block.call
43
+ rescue Exception => e
44
+ Appygram::Catcher.handle(e,name)
45
+ ensure
46
+ self.clear!
47
+ end
48
+ end
49
+
50
+ def self.rescue_and_reraise(name=nil, context=nil, &block)
51
+ begin
52
+ self.context(context) unless context.nil?
53
+ block.call
54
+ rescue Exception => e
55
+ Appygram::Catcher.handle(e,name)
56
+ raise(e)
57
+ ensure
58
+ self.clear!
59
+ end
60
+ end
61
+
62
+ def self.clear!
63
+ Thread.current[:appygram_context] = nil
64
+ end
65
+
66
+ def self.context(hash = {})
67
+ Thread.current[:appygram_context] ||= {}
68
+ Thread.current[:appygram_context].merge!(hash)
69
+ self
70
+ end
71
+ end
data/rails/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__) , '../init.rb')
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: appygram
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - rfc2616
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: &70157985912000 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70157985912000
25
+ description: Appygram is the Ruby gem for communicating with http://appygram.com (hosted
26
+ messaging service). It supports an exception reporting mechanism similar to http://exceptional.io,
27
+ and this Ruby gem, forked from the Exceptional gem, emulates Exceptional functionality.
28
+ email: heittman.rob@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - lib/appygram/alert_data.rb
34
+ - lib/appygram/application_environment.rb
35
+ - lib/appygram/catcher.rb
36
+ - lib/appygram/config.rb
37
+ - lib/appygram/controller_exception_data.rb
38
+ - lib/appygram/exception_data.rb
39
+ - lib/appygram/integration/alerter.rb
40
+ - lib/appygram/integration/debug_exceptions.rb
41
+ - lib/appygram/integration/dj.rb
42
+ - lib/appygram/integration/rack.rb
43
+ - lib/appygram/integration/rack_rails.rb
44
+ - lib/appygram/integration/rails.rb
45
+ - lib/appygram/integration/sinatra.rb
46
+ - lib/appygram/log_factory.rb
47
+ - lib/appygram/monkeypatches.rb
48
+ - lib/appygram/rack_exception_data.rb
49
+ - lib/appygram/railtie.rb
50
+ - lib/appygram/remote.rb
51
+ - lib/appygram/startup.rb
52
+ - lib/appygram/version.rb
53
+ - lib/appygram.rb
54
+ - rails/init.rb
55
+ - init.rb
56
+ - appygram.gemspec
57
+ homepage: http://appygram.com/
58
+ licenses: []
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements:
76
+ - json_pure, json-jruby or json gem required
77
+ rubyforge_project:
78
+ rubygems_version: 1.8.17
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: appygram is a hosted service for sending messages from mobile/web apps
82
+ test_files: []