appygram-rails 0.9.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/appygram.gemspec +16 -0
 - data/init.rb +39 -0
 - data/lib/appygram/alert_data.rb +15 -0
 - data/lib/appygram/application_environment.rb +55 -0
 - data/lib/appygram/catcher.rb +34 -0
 - data/lib/appygram/config.rb +63 -0
 - data/lib/appygram/controller_exception_data.rb +75 -0
 - data/lib/appygram/exception_data.rb +108 -0
 - data/lib/appygram/integration/alerter.rb +11 -0
 - data/lib/appygram/integration/debug_exceptions.rb +16 -0
 - data/lib/appygram/integration/rack.rb +28 -0
 - data/lib/appygram/integration/rack_rails.rb +26 -0
 - data/lib/appygram/log_factory.rb +39 -0
 - data/lib/appygram/monkeypatches.rb +10 -0
 - data/lib/appygram/rack_exception_data.rb +29 -0
 - data/lib/appygram/railtie.rb +20 -0
 - data/lib/appygram/remote.rb +48 -0
 - data/lib/appygram/startup.rb +14 -0
 - data/lib/appygram/version.rb +3 -0
 - data/lib/appygram.rb +71 -0
 - data/rails/init.rb +1 -0
 - metadata +80 -0
 
    
        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-rails}
         
     | 
| 
      
 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,55 @@ 
     | 
|
| 
      
 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 
     | 
    
         
            +
                      'host' => get_hostname,
         
     | 
| 
      
 14 
     | 
    
         
            +
                      'run_as_user' => get_username,
         
     | 
| 
      
 15 
     | 
    
         
            +
                      'application_root_directory' => (application_root.to_s.respond_to?(:force_encoding) ? application_root.to_s.force_encoding("UTF-8") : application_root),
         
     | 
| 
      
 16 
     | 
    
         
            +
                      'language' => 'ruby',
         
     | 
| 
      
 17 
     | 
    
         
            +
                      'language_version' => language_version_string,
         
     | 
| 
      
 18 
     | 
    
         
            +
                      'framework' => framework,
         
     | 
| 
      
 19 
     | 
    
         
            +
                      'libraries_loaded' => libraries_loaded
         
     | 
| 
      
 20 
     | 
    
         
            +
                    }
         
     | 
| 
      
 21 
     | 
    
         
            +
                  }
         
     | 
| 
      
 22 
     | 
    
         
            +
                end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def self.environment
         
     | 
| 
      
 25 
     | 
    
         
            +
                  Config.application_environment
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def self.application_root
         
     | 
| 
      
 29 
     | 
    
         
            +
                  Config.application_root
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                def self.get_hostname
         
     | 
| 
      
 33 
     | 
    
         
            +
                  require 'socket' unless defined?(Socket)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  Socket.gethostname
         
     | 
| 
      
 35 
     | 
    
         
            +
                rescue
         
     | 
| 
      
 36 
     | 
    
         
            +
                  'UNKNOWN'
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def self.language_version_string
         
     | 
| 
      
 40 
     | 
    
         
            +
                  "#{RUBY_VERSION rescue '?.?.?'} p#{RUBY_PATCHLEVEL rescue '???'} #{RUBY_RELEASE_DATE rescue '????-??-??'} #{RUBY_PLATFORM rescue '????'}"
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                def self.get_username
         
     | 
| 
      
 44 
     | 
    
         
            +
                  ENV['LOGNAME'] || ENV['USER'] || ENV['USERNAME'] || ENV['APACHE_RUN_USER'] || 'UNKNOWN'
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
             
     | 
| 
      
 47 
     | 
    
         
            +
                def self.libraries_loaded
         
     | 
| 
      
 48 
     | 
    
         
            +
                  begin
         
     | 
| 
      
 49 
     | 
    
         
            +
                    return Hash[*Gem.loaded_specs.map{|name, gem_specification| [name, gem_specification.version.to_s]}.flatten]
         
     | 
| 
      
 50 
     | 
    
         
            +
                  rescue
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
                  {}
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
              end
         
     | 
| 
      
 55 
     | 
    
         
            +
            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,63 @@ 
     | 
|
| 
      
 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 api_key
         
     | 
| 
      
 21 
     | 
    
         
            +
                    return @api_key unless @api_key.nil?
         
     | 
| 
      
 22 
     | 
    
         
            +
                    @api_key ||= ENV['EXCEPTIONAL_API_KEY'] unless ENV['EXCEPTIONAL_API_KEY'].nil?
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                  def application_environment
         
     | 
| 
      
 26 
     | 
    
         
            +
                    ENV['RACK_ENV'] || ENV['RAILS_ENV']|| 'development'
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                  def should_send_to_api?
         
     | 
| 
      
 30 
     | 
    
         
            +
                    return @enabled unless @enabled.nil?
         
     | 
| 
      
 31 
     | 
    
         
            +
                    @enabled = !(DEFAULTS[:disabled_by_default].include?(application_environment))
         
     | 
| 
      
 32 
     | 
    
         
            +
                  end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                  def application_root
         
     | 
| 
      
 35 
     | 
    
         
            +
                    (defined?(Rails) && Rails.respond_to?(:root)) ? Rails.root : Dir.pwd
         
     | 
| 
      
 36 
     | 
    
         
            +
                  end
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                  def ssl?
         
     | 
| 
      
 39 
     | 
    
         
            +
                    @ssl ||= DEFAULTS[:ssl]
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
                  def remote_host
         
     | 
| 
      
 43 
     | 
    
         
            +
                    @remote_host ||= DEFAULTS[:remote_host_http]
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                  def remote_port
         
     | 
| 
      
 47 
     | 
    
         
            +
                    @remote_port ||= ssl? ? 443 : 80
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
                  def reset
         
     | 
| 
      
 51 
     | 
    
         
            +
                    @enabled = @ssl = @remote_host = @remote_port = @api_key = nil
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
             
     | 
| 
      
 54 
     | 
    
         
            +
                  def http_open_timeout
         
     | 
| 
      
 55 
     | 
    
         
            +
                    @http_open_timeout ||= DEFAULTS[:http_open_timeout]
         
     | 
| 
      
 56 
     | 
    
         
            +
                  end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                  def http_read_timeout
         
     | 
| 
      
 59 
     | 
    
         
            +
                    @http_read_timeout ||= DEFAULTS[:http_read_timeout]
         
     | 
| 
      
 60 
     | 
    
         
            +
                  end
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
              end
         
     | 
| 
      
 63 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,75 @@ 
     | 
|
| 
      
 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 
     | 
    
         
            +
                  e = {
         
     | 
| 
      
 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 
     | 
    
         
            +
                  if @controller.respond_to?(:current_user)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    cu = @controller.current_user
         
     | 
| 
      
 31 
     | 
    
         
            +
                    if cu and cu.email
         
     | 
| 
      
 32 
     | 
    
         
            +
                      e['request']['current_user'] = {
         
     | 
| 
      
 33 
     | 
    
         
            +
                        'id' => cu.id,
         
     | 
| 
      
 34 
     | 
    
         
            +
                        'email'=> cu.email
         
     | 
| 
      
 35 
     | 
    
         
            +
                      }
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
                  return e
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def filter_hash(keys_to_filter, hash)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  keys_to_filter.map! {|x| x.to_s}
         
     | 
| 
      
 43 
     | 
    
         
            +
                  if keys_to_filter.is_a?(Array) && !keys_to_filter.empty?
         
     | 
| 
      
 44 
     | 
    
         
            +
                    hash.each do |key, value|
         
     | 
| 
      
 45 
     | 
    
         
            +
                      if key_match?(key, keys_to_filter)
         
     | 
| 
      
 46 
     | 
    
         
            +
                        hash[key] = "[FILTERED]"
         
     | 
| 
      
 47 
     | 
    
         
            +
                      elsif value.respond_to?(:to_hash)
         
     | 
| 
      
 48 
     | 
    
         
            +
                        filter_hash(keys_to_filter, hash[key])
         
     | 
| 
      
 49 
     | 
    
         
            +
                      end
         
     | 
| 
      
 50 
     | 
    
         
            +
                    end
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
                  hash
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                # Closer alignment to latest filtered_params:
         
     | 
| 
      
 56 
     | 
    
         
            +
                # https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/http/parameter_filter.rb
         
     | 
| 
      
 57 
     | 
    
         
            +
                # https://github.com/exceptional/exceptional/issues/20
         
     | 
| 
      
 58 
     | 
    
         
            +
                def key_match?(key, keys_to_filter)
         
     | 
| 
      
 59 
     | 
    
         
            +
                  keys_to_filter.any? { |k|
         
     | 
| 
      
 60 
     | 
    
         
            +
                    regexp = k.is_a?(Regexp)? k : Regexp.new(k, true)
         
     | 
| 
      
 61 
     | 
    
         
            +
                    key =~ regexp
         
     | 
| 
      
 62 
     | 
    
         
            +
                  }
         
     | 
| 
      
 63 
     | 
    
         
            +
                end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                def filter_parameters(hash)
         
     | 
| 
      
 66 
     | 
    
         
            +
                  if @request.respond_to?(:env) && @request.env["action_dispatch.parameter_filter"]
         
     | 
| 
      
 67 
     | 
    
         
            +
                    filter_hash(@request.env["action_dispatch.parameter_filter"], hash)
         
     | 
| 
      
 68 
     | 
    
         
            +
                  elsif @controller.respond_to?(:filter_parameters)
         
     | 
| 
      
 69 
     | 
    
         
            +
                    @controller.send(:filter_parameters, hash)
         
     | 
| 
      
 70 
     | 
    
         
            +
                  else
         
     | 
| 
      
 71 
     | 
    
         
            +
                    hash
         
     | 
| 
      
 72 
     | 
    
         
            +
                  end
         
     | 
| 
      
 73 
     | 
    
         
            +
                end
         
     | 
| 
      
 74 
     | 
    
         
            +
              end
         
     | 
| 
      
 75 
     | 
    
         
            +
            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,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,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,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,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,20 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'appygram'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'rails'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Appygram
         
     | 
| 
      
 5 
     | 
    
         
            +
              class Railtie < Rails::Railtie
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                initializer "appygram.middleware" do |app|
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                  if Appygram::Config.should_send_to_api?
         
     | 
| 
      
 10 
     | 
    
         
            +
                    Appygram.logger.info("Loading Appygram #{Appygram::VERSION} for #{Rails::VERSION::STRING}")
         
     | 
| 
      
 11 
     | 
    
         
            +
                    if defined?(ActionDispatch::DebugExceptions)
         
     | 
| 
      
 12 
     | 
    
         
            +
                      ActionDispatch::DebugExceptions.send(:include,Appygram::DebugExceptions)
         
     | 
| 
      
 13 
     | 
    
         
            +
                    else
         
     | 
| 
      
 14 
     | 
    
         
            +
                      app.config.middleware.use "Rack::RailsAppygram"
         
     | 
| 
      
 15 
     | 
    
         
            +
                    end
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,48 @@ 
     | 
|
| 
      
 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 
     | 
    
         
            +
                    #FIXME this is a noop based on an Exceptional behavior Appygram doesn't have
         
     | 
| 
      
 12 
     | 
    
         
            +
                    Appygram.logger.info 'Appygram remote exception forwarding started'
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  def error(exception_data)
         
     | 
| 
      
 16 
     | 
    
         
            +
                    url = "/api/exceptions?api_key=#{::Appygram::Config.api_key}"
         
     | 
| 
      
 17 
     | 
    
         
            +
                    call_remote(url, exception_data.to_json)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  end
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  def call_remote(url, data)
         
     | 
| 
      
 21 
     | 
    
         
            +
                    config = Appygram::Config
         
     | 
| 
      
 22 
     | 
    
         
            +
                    optional_proxy = Net::HTTP::Proxy(config.http_proxy_host,
         
     | 
| 
      
 23 
     | 
    
         
            +
                                                      config.http_proxy_port,
         
     | 
| 
      
 24 
     | 
    
         
            +
                                                      config.http_proxy_username,
         
     | 
| 
      
 25 
     | 
    
         
            +
                                                      config.http_proxy_password)
         
     | 
| 
      
 26 
     | 
    
         
            +
                    client = optional_proxy.new(config.remote_host, config.remote_port)
         
     | 
| 
      
 27 
     | 
    
         
            +
                    client.open_timeout = config.http_open_timeout
         
     | 
| 
      
 28 
     | 
    
         
            +
                    client.read_timeout = config.http_read_timeout
         
     | 
| 
      
 29 
     | 
    
         
            +
                    client.use_ssl = config.ssl?
         
     | 
| 
      
 30 
     | 
    
         
            +
                    client.verify_mode = OpenSSL::SSL::VERIFY_NONE if config.ssl?
         
     | 
| 
      
 31 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 32 
     | 
    
         
            +
                      response = client.post(url, data)
         
     | 
| 
      
 33 
     | 
    
         
            +
                      case response
         
     | 
| 
      
 34 
     | 
    
         
            +
                        when Net::HTTPSuccess
         
     | 
| 
      
 35 
     | 
    
         
            +
                          Appygram.logger.info( "#{url} - #{response.message}")
         
     | 
| 
      
 36 
     | 
    
         
            +
                          return true
         
     | 
| 
      
 37 
     | 
    
         
            +
                        else
         
     | 
| 
      
 38 
     | 
    
         
            +
                          Appygram.logger.error("#{url} - #{response.code} - #{response.message}")
         
     | 
| 
      
 39 
     | 
    
         
            +
                      end
         
     | 
| 
      
 40 
     | 
    
         
            +
                    rescue Exception => e
         
     | 
| 
      
 41 
     | 
    
         
            +
                      Appygram.logger.error('Problem notifying Appygram about the error')
         
     | 
| 
      
 42 
     | 
    
         
            +
                      Appygram.logger.error(e)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    end
         
     | 
| 
      
 44 
     | 
    
         
            +
                    nil
         
     | 
| 
      
 45 
     | 
    
         
            +
                  end
         
     | 
| 
      
 46 
     | 
    
         
            +
                end
         
     | 
| 
      
 47 
     | 
    
         
            +
              end
         
     | 
| 
      
 48 
     | 
    
         
            +
            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'
         
     | 
| 
      
 9 
     | 
    
         
            +
                    end
         
     | 
| 
      
 10 
     | 
    
         
            +
                    Remote.startup_announce(::Appygram::ApplicationEnvironment.to_hash('rails'))
         
     | 
| 
      
 11 
     | 
    
         
            +
                  end
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
            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 = 'appygram-rails'
         
     | 
| 
      
 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,80 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: appygram-rails
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 0.9.1
         
     | 
| 
      
 5 
     | 
    
         
            +
              prerelease: 
         
     | 
| 
      
 6 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 7 
     | 
    
         
            +
            authors:
         
     | 
| 
      
 8 
     | 
    
         
            +
            - rfc2616
         
     | 
| 
      
 9 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 10 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 11 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2012-07-13 00:00:00.000000000 Z
         
     | 
| 
      
 13 
     | 
    
         
            +
            dependencies:
         
     | 
| 
      
 14 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 15 
     | 
    
         
            +
              name: rack
         
     | 
| 
      
 16 
     | 
    
         
            +
              requirement: &70216562368600 !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: *70216562368600
         
     | 
| 
      
 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/rack.rb
         
     | 
| 
      
 42 
     | 
    
         
            +
            - lib/appygram/integration/rack_rails.rb
         
     | 
| 
      
 43 
     | 
    
         
            +
            - lib/appygram/log_factory.rb
         
     | 
| 
      
 44 
     | 
    
         
            +
            - lib/appygram/monkeypatches.rb
         
     | 
| 
      
 45 
     | 
    
         
            +
            - lib/appygram/rack_exception_data.rb
         
     | 
| 
      
 46 
     | 
    
         
            +
            - lib/appygram/railtie.rb
         
     | 
| 
      
 47 
     | 
    
         
            +
            - lib/appygram/remote.rb
         
     | 
| 
      
 48 
     | 
    
         
            +
            - lib/appygram/startup.rb
         
     | 
| 
      
 49 
     | 
    
         
            +
            - lib/appygram/version.rb
         
     | 
| 
      
 50 
     | 
    
         
            +
            - lib/appygram.rb
         
     | 
| 
      
 51 
     | 
    
         
            +
            - rails/init.rb
         
     | 
| 
      
 52 
     | 
    
         
            +
            - init.rb
         
     | 
| 
      
 53 
     | 
    
         
            +
            - appygram.gemspec
         
     | 
| 
      
 54 
     | 
    
         
            +
            homepage: http://appygram.com/
         
     | 
| 
      
 55 
     | 
    
         
            +
            licenses: []
         
     | 
| 
      
 56 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 57 
     | 
    
         
            +
            rdoc_options: []
         
     | 
| 
      
 58 
     | 
    
         
            +
            require_paths:
         
     | 
| 
      
 59 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 60 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 61 
     | 
    
         
            +
              none: false
         
     | 
| 
      
 62 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 63 
     | 
    
         
            +
              - - ! '>='
         
     | 
| 
      
 64 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 65 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 66 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         
     | 
| 
      
 67 
     | 
    
         
            +
              none: false
         
     | 
| 
      
 68 
     | 
    
         
            +
              requirements:
         
     | 
| 
      
 69 
     | 
    
         
            +
              - - ! '>='
         
     | 
| 
      
 70 
     | 
    
         
            +
                - !ruby/object:Gem::Version
         
     | 
| 
      
 71 
     | 
    
         
            +
                  version: '0'
         
     | 
| 
      
 72 
     | 
    
         
            +
            requirements:
         
     | 
| 
      
 73 
     | 
    
         
            +
            - json_pure, json-jruby or json gem required
         
     | 
| 
      
 74 
     | 
    
         
            +
            rubyforge_project: 
         
     | 
| 
      
 75 
     | 
    
         
            +
            rubygems_version: 1.8.17
         
     | 
| 
      
 76 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 77 
     | 
    
         
            +
            specification_version: 3
         
     | 
| 
      
 78 
     | 
    
         
            +
            summary: appygram is a hosted service for sending messages from mobile/web apps
         
     | 
| 
      
 79 
     | 
    
         
            +
            test_files: []
         
     | 
| 
      
 80 
     | 
    
         
            +
            has_rdoc: 
         
     |