logtail 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.
- checksums.yaml +7 -0
- data/.github/workflows/main.yml +33 -0
- data/.gitignore +24 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +12 -0
- data/Gemfile +10 -0
- data/LICENSE.md +15 -0
- data/README.md +4 -0
- data/Rakefile +72 -0
- data/lib/logtail.rb +36 -0
- data/lib/logtail/config.rb +154 -0
- data/lib/logtail/config/integrations.rb +17 -0
- data/lib/logtail/context.rb +9 -0
- data/lib/logtail/contexts.rb +12 -0
- data/lib/logtail/contexts/http.rb +31 -0
- data/lib/logtail/contexts/release.rb +52 -0
- data/lib/logtail/contexts/runtime.rb +23 -0
- data/lib/logtail/contexts/session.rb +24 -0
- data/lib/logtail/contexts/system.rb +29 -0
- data/lib/logtail/contexts/user.rb +28 -0
- data/lib/logtail/current_context.rb +168 -0
- data/lib/logtail/event.rb +36 -0
- data/lib/logtail/events.rb +10 -0
- data/lib/logtail/events/controller_call.rb +44 -0
- data/lib/logtail/events/error.rb +40 -0
- data/lib/logtail/events/exception.rb +10 -0
- data/lib/logtail/events/sql_query.rb +26 -0
- data/lib/logtail/events/template_render.rb +25 -0
- data/lib/logtail/integration.rb +40 -0
- data/lib/logtail/integrator.rb +50 -0
- data/lib/logtail/log_devices.rb +8 -0
- data/lib/logtail/log_devices/http.rb +368 -0
- data/lib/logtail/log_devices/http/flushable_dropping_sized_queue.rb +52 -0
- data/lib/logtail/log_devices/http/request_attempt.rb +20 -0
- data/lib/logtail/log_entry.rb +110 -0
- data/lib/logtail/logger.rb +270 -0
- data/lib/logtail/logtail.rb +36 -0
- data/lib/logtail/timer.rb +21 -0
- data/lib/logtail/util.rb +7 -0
- data/lib/logtail/util/non_nil_hash_builder.rb +40 -0
- data/lib/logtail/version.rb +3 -0
- data/logtail.gemspec +43 -0
- data/spec/README.md +13 -0
- data/spec/logtail/current_context_spec.rb +113 -0
- data/spec/logtail/events/controller_call_spec.rb +12 -0
- data/spec/logtail/events/error_spec.rb +15 -0
- data/spec/logtail/log_devices/http_spec.rb +185 -0
- data/spec/logtail/log_entry_spec.rb +22 -0
- data/spec/logtail/logger_spec.rb +227 -0
- data/spec/spec_helper.rb +22 -0
- data/spec/support/logtail.rb +5 -0
- data/spec/support/socket_hostname.rb +12 -0
- data/spec/support/timecop.rb +3 -0
- data/spec/support/webmock.rb +3 -0
- metadata +238 -0
| @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            # Base (must come first, order matters)
         | 
| 2 | 
            +
            require "logtail/version"
         | 
| 3 | 
            +
            require "logtail/config"
         | 
| 4 | 
            +
            require "logtail/util"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            # Load frameworks
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            # Other (sorted alphabetically)
         | 
| 9 | 
            +
            require "logtail/contexts"
         | 
| 10 | 
            +
            require "logtail/current_context"
         | 
| 11 | 
            +
            require "logtail/events"
         | 
| 12 | 
            +
            require "logtail/integration"
         | 
| 13 | 
            +
            require "logtail/log_devices"
         | 
| 14 | 
            +
            require "logtail/log_entry"
         | 
| 15 | 
            +
            require "logtail/logger"
         | 
| 16 | 
            +
            require "logtail/timer"
         | 
| 17 | 
            +
            require "logtail/integrator"
         | 
| 18 | 
            +
            require "logtail/integration"
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            module Logtail
         | 
| 21 | 
            +
              # Access the main configuration object. Please see {{Logtail::Config}} for more details.
         | 
| 22 | 
            +
              def self.config
         | 
| 23 | 
            +
                Config.instance
         | 
| 24 | 
            +
              end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              # Starts a timer for timing events. Please see {{Logtail::Logtail.start}} for more details.
         | 
| 27 | 
            +
              def self.start_timer
         | 
| 28 | 
            +
                Timer.start
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
              # Adds context to all logs written within the passed block. Please see
         | 
| 32 | 
            +
              # {{Logtail::CurrentContext.with}} for a more detailed description with examples.
         | 
| 33 | 
            +
              def self.with_context(context, &block)
         | 
| 34 | 
            +
                CurrentContext.with(context, &block)
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| @@ -0,0 +1,21 @@ | |
| 1 | 
            +
            module Logtail
         | 
| 2 | 
            +
              # This is an ultra-simple abstraction for timing code. This provides a little
         | 
| 3 | 
            +
              # more control around how Logtail automatically processes "timers".
         | 
| 4 | 
            +
              #
         | 
| 5 | 
            +
              # @example
         | 
| 6 | 
            +
              #   timer = Logtail::Timer.start
         | 
| 7 | 
            +
              #   # ... code to time
         | 
| 8 | 
            +
              #   logger.info("My log message", my_event: {time_ms: timer})
         | 
| 9 | 
            +
              module Timer
         | 
| 10 | 
            +
                # Abstract for starting a logtail. Currently this is simply calling `Time.now`.
         | 
| 11 | 
            +
                def self.start
         | 
| 12 | 
            +
                  Time.now
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                # Get the duration in milliseconds from the object returned in {#start}
         | 
| 16 | 
            +
                def self.duration_ms(timer)
         | 
| 17 | 
            +
                  now = Time.now
         | 
| 18 | 
            +
                  (now - timer) * 1000.0
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
            end
         | 
    
        data/lib/logtail/util.rb
    ADDED
    
    
| @@ -0,0 +1,40 @@ | |
| 1 | 
            +
            require 'json'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Logtail
         | 
| 4 | 
            +
              module Util
         | 
| 5 | 
            +
                # @private
         | 
| 6 | 
            +
                #
         | 
| 7 | 
            +
                # The purpose of this class is to efficiently build a hash that does not
         | 
| 8 | 
            +
                # include nil values. It's proactive instead of reactive, avoiding the
         | 
| 9 | 
            +
                # need to traverse and reduce a new hash dropping blanks.
         | 
| 10 | 
            +
                class NonNilHashBuilder
         | 
| 11 | 
            +
                  class << self
         | 
| 12 | 
            +
                    def build(&block)
         | 
| 13 | 
            +
                      builder = new
         | 
| 14 | 
            +
                      yield builder
         | 
| 15 | 
            +
                      builder.target
         | 
| 16 | 
            +
                    end
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  attr_reader :target
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def initialize
         | 
| 22 | 
            +
                    @target = {}
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                  def add(k, v, options = {})
         | 
| 26 | 
            +
                    if !v.nil?
         | 
| 27 | 
            +
                      if options[:json_encode]
         | 
| 28 | 
            +
                        v = v.to_json
         | 
| 29 | 
            +
                      end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                      if options[:limit]
         | 
| 32 | 
            +
                        v = v.byteslice(0, options[:limit])
         | 
| 33 | 
            +
                      end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                      @target[k] = v
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
              end
         | 
| 40 | 
            +
            end
         | 
    
        data/logtail.gemspec
    ADDED
    
    | @@ -0,0 +1,43 @@ | |
| 1 | 
            +
            # -*- encoding: utf-8 -*-
         | 
| 2 | 
            +
            $LOAD_PATH.push File.expand_path("../lib", __FILE__)
         | 
| 3 | 
            +
            require "logtail/version"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Gem::Specification.new do |spec|
         | 
| 6 | 
            +
              spec.name          = "logtail"
         | 
| 7 | 
            +
              spec.version       = Logtail::VERSION
         | 
| 8 | 
            +
              spec.platform      = Gem::Platform::RUBY
         | 
| 9 | 
            +
              spec.authors       = ["Logtail"]
         | 
| 10 | 
            +
              spec.email         = ["hi@logtail.com"]
         | 
| 11 | 
            +
              spec.homepage      = "https://github.com/logtail/logtail-ruby"
         | 
| 12 | 
            +
              spec.license       = "ISC"
         | 
| 13 | 
            +
             | 
| 14 | 
            +
              spec.summary       = "Query logs like you query your database. https://logtail.com"
         | 
| 15 | 
            +
             | 
| 16 | 
            +
              spec.metadata["bug_tracker_uri"] = "#{spec.homepage}/issues"
         | 
| 17 | 
            +
              spec.metadata["changelog_uri"] = "#{spec.homepage}/tree/master/CHANGELOG.md"
         | 
| 18 | 
            +
              spec.metadata["homepage_uri"] = spec.homepage
         | 
| 19 | 
            +
              spec.metadata["source_code_uri"] = spec.homepage
         | 
| 20 | 
            +
             | 
| 21 | 
            +
              spec.required_ruby_version = Gem::Requirement.new(">= 2.2.0")
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              spec.files         = `git ls-files`.split("\n")
         | 
| 24 | 
            +
              spec.test_files    = `git ls-files -- {test,spec,features}/*`.split("\n")
         | 
| 25 | 
            +
              spec.executables   = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
         | 
| 26 | 
            +
              spec.require_paths = ["lib"]
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              spec.add_dependency('msgpack', '~> 1.0')
         | 
| 29 | 
            +
             | 
| 30 | 
            +
              spec.add_development_dependency('bundler-audit', '>= 0')
         | 
| 31 | 
            +
              spec.add_development_dependency('rails_stdout_logging', '>= 0')
         | 
| 32 | 
            +
              spec.add_development_dependency('rake', '>= 0')
         | 
| 33 | 
            +
              spec.add_development_dependency('rspec', '~> 3.4')
         | 
| 34 | 
            +
              spec.add_development_dependency('rspec-its', '>= 0')
         | 
| 35 | 
            +
              spec.add_development_dependency('timecop', '>= 0')
         | 
| 36 | 
            +
              spec.add_development_dependency('webmock', '~> 2.3')
         | 
| 37 | 
            +
             | 
| 38 | 
            +
              if RUBY_PLATFORM == "java"
         | 
| 39 | 
            +
                spec.add_development_dependency('activerecord-jdbcsqlite3-adapter', '>= 0')
         | 
| 40 | 
            +
              else
         | 
| 41 | 
            +
                spec.add_development_dependency('sqlite3', '>= 0')
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
            end
         | 
    
        data/spec/README.md
    ADDED
    
    
| @@ -0,0 +1,113 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Logtail::CurrentContext do
         | 
| 4 | 
            +
              describe ".initialize" do
         | 
| 5 | 
            +
                it "should not set the release context" do
         | 
| 6 | 
            +
                  context = described_class.send(:new)
         | 
| 7 | 
            +
                  expect(context.send(:hash)[:release]).to be_nil
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                it "should set the system context" do
         | 
| 11 | 
            +
                  context = described_class.send(:new)
         | 
| 12 | 
            +
                  system_content = context.fetch(:system)
         | 
| 13 | 
            +
                  expect(system_content[:hostname]).to_not be_nil
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                it "should set the runtime context" do
         | 
| 17 | 
            +
                  context = described_class.send(:new)
         | 
| 18 | 
            +
                  runtime_context = context.fetch(:runtime)
         | 
| 19 | 
            +
                  expect(runtime_context[:thread_id]).to_not be_nil
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                context "with Heroku dyno metadata" do
         | 
| 23 | 
            +
                  around(:each) do |example|
         | 
| 24 | 
            +
                    ENV['HEROKU_SLUG_COMMIT'] = "2c3a0b24069af49b3de35b8e8c26765c1dba9ff0"
         | 
| 25 | 
            +
                    ENV['HEROKU_RELEASE_CREATED_AT'] = "2015-04-02T18:00:42Z"
         | 
| 26 | 
            +
                    ENV['HEROKU_RELEASE_VERSION'] = "v2.3.1"
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    example.run
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    ENV.delete('HEROKU_SLUG_COMMIT')
         | 
| 31 | 
            +
                    ENV.delete('HEROKU_RELEASE_CREATED_AT')
         | 
| 32 | 
            +
                    ENV.delete('HEROKU_RELEASE_VERSION')
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    described_class.reset
         | 
| 35 | 
            +
                  end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                  it "should automatically set the release context" do
         | 
| 38 | 
            +
                    context = described_class.send(:new)
         | 
| 39 | 
            +
                    expect(context.send(:hash)[:release]).to eq({:commit_hash=>"2c3a0b24069af49b3de35b8e8c26765c1dba9ff0", :created_at=>"2015-04-02T18:00:42Z", :version=>"v2.3.1"})
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                context "with generic env vars" do
         | 
| 44 | 
            +
                  around(:each) do |example|
         | 
| 45 | 
            +
                    ENV['RELEASE_COMMIT'] = "2c3a0b24069af49b3de35b8e8c26765c1dba9ff0"
         | 
| 46 | 
            +
                    ENV['RELEASE_CREATED_AT'] = "2015-04-02T18:00:42Z"
         | 
| 47 | 
            +
                    ENV['RELEASE_VERSION'] = "v2.3.1"
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    example.run
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    ENV.delete('RELEASE_COMMIT')
         | 
| 52 | 
            +
                    ENV.delete('RELEASE_CREATED_AT')
         | 
| 53 | 
            +
                    ENV.delete('RELEASE_VERSION')
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    described_class.reset
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  it "should automatically set the release context" do
         | 
| 59 | 
            +
                    context = described_class.send(:new)
         | 
| 60 | 
            +
                    expect(context.send(:hash)[:release]).to eq({:commit_hash=>"2c3a0b24069af49b3de35b8e8c26765c1dba9ff0", :created_at=>"2015-04-02T18:00:42Z", :version=>"v2.3.1"})
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
             | 
| 65 | 
            +
              describe ".add" do
         | 
| 66 | 
            +
                after(:each) do
         | 
| 67 | 
            +
                  described_class.reset
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                it "should add the context" do
         | 
| 71 | 
            +
                  expect(described_class.instance.send(:hash)[:build]).to be_nil
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                  described_class.add({build: {version: "1.0.0"}})
         | 
| 74 | 
            +
                  expect(described_class.instance.send(:hash)[:build]).to eq({:version=>"1.0.0"})
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                  described_class.add({testing: {key: "value"}})
         | 
| 77 | 
            +
                  expect(described_class.instance.send(:hash)[:build]).to eq({:version=>"1.0.0"})
         | 
| 78 | 
            +
                  expect(described_class.instance.send(:hash)[:testing]).to eq({:key=>"value"})
         | 
| 79 | 
            +
                end
         | 
| 80 | 
            +
              end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
              describe ".remove" do
         | 
| 83 | 
            +
                it "should remove context by key" do
         | 
| 84 | 
            +
                  context = {:build=>{:version=>"1.0.0"}}
         | 
| 85 | 
            +
                  described_class.add(context)
         | 
| 86 | 
            +
                  expect(described_class.instance.send(:hash)[:build]).to eq({:version=>"1.0.0"})
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  described_class.remove(:build)
         | 
| 89 | 
            +
                  expect(described_class.instance.send(:hash)[:build]).to be_nil
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
              end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
              describe ".with" do
         | 
| 94 | 
            +
                it "should merge the context and cleanup on block exit" do
         | 
| 95 | 
            +
                  expect(described_class.instance.send(:hash)[:build]).to be_nil
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  described_class.with({build: {version: "1.0.0"}}) do
         | 
| 98 | 
            +
                    expect(described_class.instance.send(:hash)[:build]).to eq({:version=>"1.0.0"})
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                    described_class.with({testing: {key: "value"}}) do
         | 
| 101 | 
            +
                      expect(described_class.instance.send(:hash)[:build]).to eq({:version=>"1.0.0"})
         | 
| 102 | 
            +
                      expect(described_class.instance.send(:hash)[:testing]).to eq({:key=>"value"})
         | 
| 103 | 
            +
                    end
         | 
| 104 | 
            +
             | 
| 105 | 
            +
                    expect(described_class.instance.send(:hash)[:build]).to eq({:version=>"1.0.0"})
         | 
| 106 | 
            +
                    expect(described_class.instance.send(:hash)[:testing]).to be_nil
         | 
| 107 | 
            +
                  end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                  expect(described_class.instance.send(:hash)[:build]).to be_nil
         | 
| 110 | 
            +
                  expect(described_class.instance.send(:hash)[:testing]).to be_nil
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
              end
         | 
| 113 | 
            +
            end
         | 
| @@ -0,0 +1,12 @@ | |
| 1 | 
            +
            # encoding: UTF-8
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "spec_helper"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            describe Logtail::Events::ControllerCall do
         | 
| 6 | 
            +
              describe ".initialize" do
         | 
| 7 | 
            +
                it "sanitizes the password param" do
         | 
| 8 | 
            +
                  # event = described_class.new(controller: 'controller', action: 'action', params: {password: 'password'})
         | 
| 9 | 
            +
                  # expect(event.params).to eq({'password' => '[sanitized]'})
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
              end
         | 
| 12 | 
            +
            end
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Logtail::Events::Error do
         | 
| 4 | 
            +
              describe "#to_hash" do
         | 
| 5 | 
            +
                it "should jsonify the stacktrace" do
         | 
| 6 | 
            +
                  backtrace = [
         | 
| 7 | 
            +
                    "/path/to/file1.rb:26:in `function1'",
         | 
| 8 | 
            +
                    "path/to/file2.rb:86:in `function2'"
         | 
| 9 | 
            +
                  ]
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  exception_event = described_class.new(name: "RuntimeError", error_message: "Boom", backtrace: backtrace)
         | 
| 12 | 
            +
                  expect(exception_event.backtrace_json).to eq("[\"/path/to/file1.rb:26:in `function1'\",\"path/to/file2.rb:86:in `function2'\"]")
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -0,0 +1,185 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # Note: these tests access instance variables and private methods as a means of
         | 
| 4 | 
            +
            # not muddying the public API. This object should expose a simple buffer like
         | 
| 5 | 
            +
            # API, tests should not alter that.
         | 
| 6 | 
            +
            describe Logtail::LogDevices::HTTP do
         | 
| 7 | 
            +
              describe "#initialize" do
         | 
| 8 | 
            +
                it "should initialize properly" do
         | 
| 9 | 
            +
                  http = described_class.new("MYKEY", flush_interval: 0.1)
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  # Ensure that threads have not started
         | 
| 12 | 
            +
                  thread = http.instance_variable_get(:@flush_thread)
         | 
| 13 | 
            +
                  expect(thread).to be_nil
         | 
| 14 | 
            +
                  thread = http.instance_variable_get(:@request_outlet_thread)
         | 
| 15 | 
            +
                  expect(thread).to be_nil
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
              describe "#write" do
         | 
| 20 | 
            +
                let(:http) { described_class.new("MYKEY") }
         | 
| 21 | 
            +
                let(:msg_queue) { http.instance_variable_get(:@msg_queue) }
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                it "should buffer the messages" do
         | 
| 24 | 
            +
                  http.write("test log message")
         | 
| 25 | 
            +
                  expect(msg_queue.flush).to eq(["test log message"])
         | 
| 26 | 
            +
                  http.close
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                it "should start the flush threads" do
         | 
| 30 | 
            +
                  http.write("test log message")
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                  thread = http.instance_variable_get(:@flush_thread)
         | 
| 33 | 
            +
                  expect(thread).to be_alive
         | 
| 34 | 
            +
                  thread = http.instance_variable_get(:@request_outlet_thread)
         | 
| 35 | 
            +
                  expect(thread).to be_alive
         | 
| 36 | 
            +
                  expect(http).to receive(:flush).exactly(1).times
         | 
| 37 | 
            +
                  http.close
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                context "with a low batch size" do
         | 
| 41 | 
            +
                  let(:http) { described_class.new("MYKEY", :batch_size => 2) }
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  it "should attempt a delivery when the limit is exceeded" do
         | 
| 44 | 
            +
                    http.write("test")
         | 
| 45 | 
            +
                    expect(http).to receive(:flush_async).exactly(1).times
         | 
| 46 | 
            +
                    http.write("my log message")
         | 
| 47 | 
            +
                    expect(http).to receive(:flush).exactly(1).times
         | 
| 48 | 
            +
                    http.close
         | 
| 49 | 
            +
                  end
         | 
| 50 | 
            +
                end
         | 
| 51 | 
            +
              end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
              describe "#close" do
         | 
| 54 | 
            +
                let(:http) { described_class.new("MYKEY") }
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                it "should kill the threads" do
         | 
| 57 | 
            +
                  http.send(:ensure_flush_threads_are_started)
         | 
| 58 | 
            +
                  http.close
         | 
| 59 | 
            +
                  thread = http.instance_variable_get(:@flush_thread)
         | 
| 60 | 
            +
                  sleep 0.1 # too fast!
         | 
| 61 | 
            +
                  expect(thread).to_not be_alive
         | 
| 62 | 
            +
                  thread = http.instance_variable_get(:@request_outlet_thread)
         | 
| 63 | 
            +
                  sleep 0.1 # too fast!
         | 
| 64 | 
            +
                  expect(thread).to_not be_alive
         | 
| 65 | 
            +
                end
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                it "should attempt a delivery" do
         | 
| 68 | 
            +
                  message = "a" * 19
         | 
| 69 | 
            +
                  http.write(message)
         | 
| 70 | 
            +
                  expect(http).to receive(:flush).exactly(1).times
         | 
| 71 | 
            +
                  http.close
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
              end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
              # Testing a private method because it helps break down our tests
         | 
| 76 | 
            +
              describe "#flush" do
         | 
| 77 | 
            +
                let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                it "should deliver the request" do
         | 
| 80 | 
            +
                  http = described_class.new("MYKEY", flush_continuously: false)
         | 
| 81 | 
            +
                  log_entry = Logtail::LogEntry.new("INFO", time, nil, "test log message 1", nil, nil)
         | 
| 82 | 
            +
                  http.write(log_entry)
         | 
| 83 | 
            +
                  log_entry = Logtail::LogEntry.new("INFO", time, nil, "test log message 2", nil, nil)
         | 
| 84 | 
            +
                  http.write(log_entry)
         | 
| 85 | 
            +
                  expect(http).to receive(:flush_async).exactly(2).times
         | 
| 86 | 
            +
                  http.send(:flush)
         | 
| 87 | 
            +
                  http.close
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
              end
         | 
| 90 | 
            +
             | 
| 91 | 
            +
              # Testing a private method because it helps break down our tests
         | 
| 92 | 
            +
              describe "#flush_async" do
         | 
| 93 | 
            +
                let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
         | 
| 94 | 
            +
             | 
| 95 | 
            +
                it "should add a request to the queue" do
         | 
| 96 | 
            +
                  http = described_class.new("MYKEY", flush_continuously: false)
         | 
| 97 | 
            +
                  log_entry = Logtail::LogEntry.new("INFO", time, nil, "test log message 1", nil, nil)
         | 
| 98 | 
            +
                  http.write(log_entry)
         | 
| 99 | 
            +
                  log_entry = Logtail::LogEntry.new("INFO", time, nil, "test log message 2", nil, nil)
         | 
| 100 | 
            +
                  http.write(log_entry)
         | 
| 101 | 
            +
                  http.send(:flush_async)
         | 
| 102 | 
            +
                  request_queue = http.instance_variable_get(:@request_queue)
         | 
| 103 | 
            +
                  request_attempt = request_queue.deq
         | 
| 104 | 
            +
                  expect(request_attempt.request).to be_kind_of(Net::HTTP::Post)
         | 
| 105 | 
            +
                  expect(request_attempt.request.body).to start_with("\x92\x83\xA5level\xA4INFO\xA2dt\xBB2016-09-01T12:00:00.000000Z\xA7message\xB2test log message 1".force_encoding("ASCII-8BIT"))
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                  message_queue = http.instance_variable_get(:@msg_queue)
         | 
| 108 | 
            +
                  expect(message_queue.size).to eq(0)
         | 
| 109 | 
            +
                end
         | 
| 110 | 
            +
              end
         | 
| 111 | 
            +
             | 
| 112 | 
            +
              # Testing a private method because it helps break down our tests
         | 
| 113 | 
            +
              describe "#intervaled_flush" do
         | 
| 114 | 
            +
                it "should start a intervaled flush thread and flush on an interval" do
         | 
| 115 | 
            +
                  http = described_class.new("MYKEY", flush_interval: 0.1)
         | 
| 116 | 
            +
                  http.send(:ensure_flush_threads_are_started)
         | 
| 117 | 
            +
                  expect(http).to receive(:flush_async).at_least(3).times
         | 
| 118 | 
            +
                  sleep 1.1 # iterations check every 0.5 seconds
         | 
| 119 | 
            +
                  http.close
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
              end
         | 
| 122 | 
            +
             | 
| 123 | 
            +
              # Outlet
         | 
| 124 | 
            +
              describe "#request_outlet" do
         | 
| 125 | 
            +
                let(:time) { Time.utc(2016, 9, 1, 12, 0, 0) }
         | 
| 126 | 
            +
             | 
| 127 | 
            +
                it "should deliver requests on an interval" do
         | 
| 128 | 
            +
                  stub = stub_request(:post, "https://in.logtail.com/").
         | 
| 129 | 
            +
                    with(
         | 
| 130 | 
            +
                      :body => start_with("\x92\x83\xA5level\xA4INFO\xA2dt\xBB2016-09-01T12:00:00.000000Z\xA7message\xB2test log message 1".force_encoding("ASCII-8BIT")),
         | 
| 131 | 
            +
                      :headers => {
         | 
| 132 | 
            +
                        'Authorization' => 'Bearer MYKEY',
         | 
| 133 | 
            +
                        'Content-Type' => 'application/msgpack',
         | 
| 134 | 
            +
                        'User-Agent' => "Logtail Ruby/#{Logtail::VERSION} (HTTP)"
         | 
| 135 | 
            +
                      }
         | 
| 136 | 
            +
                    ).
         | 
| 137 | 
            +
                    to_return(:status => 200, :body => "", :headers => {})
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                  http = described_class.new("MYKEY", flush_interval: 0.1)
         | 
| 140 | 
            +
                  log_entry1 = Logtail::LogEntry.new("INFO", time, nil, "test log message 1", nil, nil)
         | 
| 141 | 
            +
                  http.write(log_entry1)
         | 
| 142 | 
            +
                  log_entry2 = Logtail::LogEntry.new("INFO", time, nil, "test log message 2", nil, nil)
         | 
| 143 | 
            +
                  http.write(log_entry2)
         | 
| 144 | 
            +
                  sleep 2
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  expect(stub).to have_been_requested.times(1)
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                  http.close
         | 
| 149 | 
            +
                end
         | 
| 150 | 
            +
              end
         | 
| 151 | 
            +
             | 
| 152 | 
            +
              describe "#deliver_requests" do
         | 
| 153 | 
            +
                it "should handle exceptions properly and return" do
         | 
| 154 | 
            +
                  allow_any_instance_of(Net::HTTP).to receive(:request).and_raise("boom")
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                  http_device = described_class.new("MYKEY", flush_continuously: false)
         | 
| 157 | 
            +
                  req_queue = http_device.instance_variable_get(:@request_queue)
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                  # Place a request on the queue
         | 
| 160 | 
            +
                  request = Net::HTTP::Post.new("/")
         | 
| 161 | 
            +
                  request_attempt = Logtail::LogDevices::HTTP::RequestAttempt.new(request)
         | 
| 162 | 
            +
                  request_attempt.attempted!
         | 
| 163 | 
            +
                  req_queue.enq(request_attempt)
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                  # Start a HTTP connection to test the method directly
         | 
| 166 | 
            +
                  http = http_device.send(:build_http)
         | 
| 167 | 
            +
                  http.start do |conn|
         | 
| 168 | 
            +
                    result = http_device.send(:deliver_requests, conn)
         | 
| 169 | 
            +
                    expect(result).to eq(false)
         | 
| 170 | 
            +
                  end
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                  expect(req_queue.size).to eq(1)
         | 
| 173 | 
            +
             | 
| 174 | 
            +
                  # Start a HTTP connection to test the method directly
         | 
| 175 | 
            +
                  http = http_device.send(:build_http)
         | 
| 176 | 
            +
                  http.start do |conn|
         | 
| 177 | 
            +
                    result = http_device.send(:deliver_requests, conn)
         | 
| 178 | 
            +
                    expect(result).to eq(false)
         | 
| 179 | 
            +
                  end
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                  # Ensure the request gets discards after 3 attempts
         | 
| 182 | 
            +
                  expect(req_queue.size).to eq(0)
         | 
| 183 | 
            +
                end
         | 
| 184 | 
            +
              end
         | 
| 185 | 
            +
            end
         |