timber 2.0.24 → 2.1.0.rc1

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.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +2 -2
  3. data/CHANGELOG +3 -0
  4. data/README.md +314 -59
  5. data/bin/timber +11 -2
  6. data/lib/timber.rb +2 -7
  7. data/lib/timber/cli.rb +16 -28
  8. data/lib/timber/cli/api.rb +80 -14
  9. data/lib/timber/cli/api/application.rb +30 -0
  10. data/lib/timber/cli/config_file.rb +66 -0
  11. data/lib/timber/cli/file_helper.rb +43 -0
  12. data/lib/timber/cli/installer.rb +58 -0
  13. data/lib/timber/cli/installers.rb +37 -0
  14. data/lib/timber/cli/installers/other.rb +47 -0
  15. data/lib/timber/cli/installers/rails.rb +255 -0
  16. data/lib/timber/cli/installers/root.rb +189 -0
  17. data/lib/timber/cli/io.rb +97 -0
  18. data/lib/timber/cli/io/ansi.rb +22 -0
  19. data/lib/timber/cli/io/messages.rb +213 -0
  20. data/lib/timber/cli/os_helper.rb +53 -0
  21. data/lib/timber/config.rb +97 -43
  22. data/lib/timber/config/integrations.rb +63 -0
  23. data/lib/timber/config/integrations/rack.rb +74 -0
  24. data/lib/timber/context.rb +13 -10
  25. data/lib/timber/contexts.rb +1 -0
  26. data/lib/timber/contexts/custom.rb +16 -3
  27. data/lib/timber/contexts/http.rb +10 -3
  28. data/lib/timber/contexts/organization.rb +4 -0
  29. data/lib/timber/contexts/release.rb +46 -0
  30. data/lib/timber/contexts/runtime.rb +7 -1
  31. data/lib/timber/contexts/session.rb +8 -1
  32. data/lib/timber/contexts/system.rb +5 -1
  33. data/lib/timber/contexts/user.rb +9 -2
  34. data/lib/timber/current_context.rb +43 -11
  35. data/lib/timber/events/controller_call.rb +4 -0
  36. data/lib/timber/events/custom.rb +13 -5
  37. data/lib/timber/events/exception.rb +4 -0
  38. data/lib/timber/events/http_client_request.rb +4 -0
  39. data/lib/timber/events/http_client_response.rb +4 -0
  40. data/lib/timber/events/http_server_request.rb +5 -0
  41. data/lib/timber/events/http_server_response.rb +15 -3
  42. data/lib/timber/events/sql_query.rb +3 -0
  43. data/lib/timber/events/template_render.rb +3 -0
  44. data/lib/timber/integration.rb +40 -0
  45. data/lib/timber/integrations.rb +21 -14
  46. data/lib/timber/integrations/action_controller.rb +18 -0
  47. data/lib/timber/integrations/action_controller/log_subscriber.rb +2 -0
  48. data/lib/timber/integrations/action_controller/log_subscriber/timber_log_subscriber.rb +6 -0
  49. data/lib/timber/integrations/action_dispatch.rb +23 -0
  50. data/lib/timber/integrations/action_dispatch/debug_exceptions.rb +2 -0
  51. data/lib/timber/integrations/action_view.rb +18 -0
  52. data/lib/timber/integrations/action_view/log_subscriber.rb +2 -0
  53. data/lib/timber/integrations/action_view/log_subscriber/timber_log_subscriber.rb +10 -0
  54. data/lib/timber/integrations/active_record.rb +18 -0
  55. data/lib/timber/integrations/active_record/log_subscriber.rb +2 -0
  56. data/lib/timber/integrations/active_record/log_subscriber/timber_log_subscriber.rb +8 -0
  57. data/lib/timber/integrations/rack.rb +12 -2
  58. data/lib/timber/integrations/rack/exception_event.rb +38 -5
  59. data/lib/timber/integrations/rack/http_context.rb +4 -6
  60. data/lib/timber/integrations/rack/http_events.rb +177 -27
  61. data/lib/timber/integrations/rack/middleware.rb +28 -0
  62. data/lib/timber/integrations/rack/session_context.rb +5 -6
  63. data/lib/timber/integrations/rack/user_context.rb +90 -43
  64. data/lib/timber/integrations/rails.rb +22 -0
  65. data/lib/timber/integrations/rails/rack_logger.rb +2 -0
  66. data/lib/timber/integrator.rb +18 -3
  67. data/lib/timber/log_devices/http.rb +107 -99
  68. data/lib/timber/log_devices/http/dropping_sized_queue.rb +26 -0
  69. data/lib/timber/log_devices/http/flushable_sized_queue.rb +42 -0
  70. data/lib/timber/log_entry.rb +14 -2
  71. data/lib/timber/logger.rb +51 -36
  72. data/lib/timber/overrides.rb +2 -0
  73. data/lib/timber/overrides/active_support_3_tagged_logging.rb +103 -0
  74. data/lib/timber/overrides/active_support_tagged_logging.rb +53 -90
  75. data/lib/timber/timer.rb +21 -0
  76. data/lib/timber/util/hash.rb +1 -1
  77. data/lib/timber/util/http_event.rb +16 -3
  78. data/lib/timber/version.rb +1 -1
  79. data/spec/support/timber.rb +2 -3
  80. data/spec/timber/cli/installers/rails_spec.rb +160 -0
  81. data/spec/timber/cli/installers/root_spec.rb +100 -0
  82. data/spec/timber/config_spec.rb +28 -0
  83. data/spec/timber/current_context_spec.rb +61 -12
  84. data/spec/timber/events/custom_spec.rb +13 -2
  85. data/spec/timber/events/exception_spec.rb +15 -0
  86. data/spec/timber/events/http_server_request_spec.rb +3 -3
  87. data/spec/timber/integrations/rack/http_events_spec.rb +101 -0
  88. data/spec/timber/log_devices/http_spec.rb +20 -4
  89. data/spec/timber/log_entry_spec.rb +2 -1
  90. data/spec/timber/logger_spec.rb +8 -8
  91. metadata +40 -9
  92. data/benchmarks/rails.rb +0 -122
  93. data/lib/timber/cli/application.rb +0 -28
  94. data/lib/timber/cli/install.rb +0 -196
  95. data/lib/timber/cli/io_helper.rb +0 -65
  96. data/lib/timber/cli/messages.rb +0 -180
  97. data/lib/timber/integrations/active_support/tagged_logging.rb +0 -71
data/bin/timber CHANGED
@@ -2,12 +2,21 @@
2
2
 
3
3
  $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))
4
4
  require "timber/cli"
5
+ require "timber/cli/io"
6
+ require "timber/cli/io/messages"
5
7
 
6
8
  begin
7
9
  Timber::CLI.run
8
10
  rescue => e
9
11
  raise e if $DEBUG
10
- STDERR.puts e.message
11
- STDERR.puts e.backtrace.join("\n")
12
+
13
+ io = Timber::CLI::IO.new(io_out: STDERR)
14
+ io.puts ""
15
+ io.puts e.message, :red
16
+ io.puts ""
17
+ io.puts Timber::CLI::IO::Messages.separator
18
+ io.puts "Backtrace:"
19
+ io.puts ""
20
+ io.puts e.backtrace[0..25].join("\n")
12
21
  exit 1
13
22
  end
data/lib/timber.rb CHANGED
@@ -1,14 +1,8 @@
1
- # core classes
2
- require "json" # brings to_json to the core classes
3
-
4
1
  # Base (must come first, order matters)
2
+ require "timber/version"
5
3
  require "timber/overrides"
6
4
  require "timber/config"
7
- require "timber/context"
8
- require "timber/event"
9
- require "timber/integrator"
10
5
  require "timber/util"
11
- require "timber/version"
12
6
 
13
7
  # Other (sorted alphabetically)
14
8
  require "timber/contexts"
@@ -18,6 +12,7 @@ require "timber/log_devices"
18
12
  require "timber/log_entry"
19
13
  require "timber/logger"
20
14
  require "timber/integrations"
15
+ require "timber/timer"
21
16
 
22
17
  # Load frameworks
23
18
  require "timber/frameworks"
data/lib/timber/cli.rb CHANGED
@@ -1,14 +1,10 @@
1
1
  require "optparse"
2
2
  require "yaml"
3
- require "timber"
4
-
5
3
 
6
4
  require "timber/cli/api"
7
- require "timber/cli/application"
8
- require "timber/cli/io_helper"
9
- require "timber/cli/messages"
10
-
11
- require "timber/cli/install"
5
+ require "timber/cli/installers"
6
+ require "timber/cli/io"
7
+ require "timber/version"
12
8
 
13
9
  module Timber
14
10
  # @private
@@ -19,27 +15,25 @@ module Timber
19
15
  attr_accessor :options
20
16
 
21
17
  def run(argv = ARGV)
22
- @options = {}
23
18
  global = global_option_parser
24
- commands = command_option_parser
25
19
  global.order!(argv)
26
20
  command = argv.shift
27
- if command
28
- if AVAILABLE_COMMANDS.include?(command)
29
- commands[command].parse!(argv)
30
- case command.to_sym
31
- when :install
32
- Timber::CLI::Install.run(argv.shift)
33
- end
34
- else
35
- puts "Command '#{command}' does not exist, run timber -h to "\
36
- "see the help"
37
- exit(1)
38
- end
39
- else
21
+
22
+ case command
23
+ when nil
40
24
  # Print help
41
25
  puts global
42
26
  exit(0)
27
+
28
+ when "install"
29
+ api_key = argv.shift
30
+ io = IO.new
31
+ Installers.run(api_key, io)
32
+
33
+ else
34
+ puts "Command '#{command}' does not exist, run timber -h to "\
35
+ "see the help"
36
+ exit(1)
43
37
  end
44
38
  end
45
39
 
@@ -61,12 +55,6 @@ module Timber
61
55
  o.separator "Available commands: #{AVAILABLE_COMMANDS.join(", ")}"
62
56
  end
63
57
  end
64
-
65
- def command_option_parser
66
- {
67
- "install" => OptionParser.new
68
- }
69
- end
70
58
  end
71
59
  end
72
60
  end
@@ -4,41 +4,96 @@ require "net/https"
4
4
  require "securerandom"
5
5
  require "uri"
6
6
 
7
+ require "timber/cli/api/application"
8
+ require "timber/cli/io/messages"
9
+ require "timber/version"
10
+
7
11
  module Timber
8
12
  class CLI
13
+ # The API class provides an interface for all Timber API requests, parsing response
14
+ # and returning the appropriate objects.
9
15
  class API
16
+
17
+ # Raise when the API key provided is invalid.
10
18
  class APIKeyInvalidError < StandardError
11
19
  def message
12
- "Uh oh! The API key supplied is invalid. Please ensure that you copied the" \
13
- " key properly.\n\n#{Messages.obtain_key_instructions}"
20
+ "Uh oh! The API key supplied is invalid. Please ensure that you copied the \n" \
21
+ "key properly.\n\n#{IO::Messages.obtain_key_instructions}"
22
+ end
23
+ end
24
+
25
+ class LogsNotReceivedError< StandardError
26
+ def message
27
+ "Bummer, we couldn't confirm log delivery with the Timber API, something is off. " \
28
+ "If you email support@timber.io, we'll work with you to figure out what's going on. " \
29
+ "And as a thank you sticking with us, we'll set you up with a 25% indefinite discount."
14
30
  end
15
31
  end
16
32
 
17
- class NoAPIKeyError < StandardError
33
+ # Raised when Timber is returning 500s
34
+ class ServerError < StandardError
18
35
  def message
19
- "Uh oh! You didn't supply an API key.\n\n#{Messages.obtain_key_instructions}"
36
+ "Crap, it looks like the Timber API is returning 500s :/. In order to properly " \
37
+ "install Timber and test integration we need the Timber API to work correctly. " \
38
+ "Chances are we're aware of the issue and if you try again later the API should " \
39
+ "be working. \n\n" \
40
+ "Status updates: http://status.timber.io \n" \
41
+ "Yell at us via email: support@timber.io \n"
20
42
  end
21
43
  end
22
44
 
23
- TIMBER_API_URI = URI.parse('https://api.timber.io')
45
+ # Raised when the API returns a response that a particular method is not expecting.
46
+ class UnrecognizedAPIResponse < StandardError
47
+ def initialize(res)
48
+ @res = res
49
+ end
50
+
51
+ def message
52
+ "Uh oh, we received a response from the Timber API that was not recognized " \
53
+ "(#{res.code}). We've been notified of the issue, but please feel free to " \
54
+ "yell at us via email to make sure we're aware: support@timber.io"
55
+ end
56
+ end
57
+
58
+ TIMBER_PRODUCTION_API_URL = "https://api.timber.io".freeze
59
+ TIMBER_STAGING_API_URL = "https://api.timber-staging.io".freeze
60
+ TIMBER_API_URL = ENV['TIMBER_STAGING'] ? TIMBER_STAGING_API_URL : TIMBER_PRODUCTION_API_URL
61
+ TIMBER_API_URI = URI.parse(TIMBER_API_URL)
24
62
  APPLICATION_PATH = "/installer/application".freeze
25
63
  EVENT_PATH = "/installer/events".freeze
26
64
  HAS_LOGS_PATH = "/installer/has_logs".freeze
27
65
  USER_AGENT = "Timber Ruby/#{Timber::VERSION} (HTTP)".freeze
28
66
 
67
+ attr_reader :api_key
68
+
29
69
  def initialize(api_key)
30
70
  @api_key = api_key
31
71
  @session_id = SecureRandom.uuid
32
72
  end
33
73
 
74
+ # Returns the application for the given API key.
34
75
  def application!
35
- get!(APPLICATION_PATH)
76
+ res = get!(APPLICATION_PATH)
77
+ build_application(res)
78
+ end
79
+
80
+ # Hits the API to clone the app for the provided API key to the specified environment.
81
+ def clone_application!(environment)
82
+ return nil
36
83
  end
37
84
 
38
- def event!(name, data = {})
85
+ # Sends an event to Timber so that we can understand how the installer is performing
86
+ # an ensure a top notch user experience. We do not raise here because it is not
87
+ # critical for the install process.
88
+ def event(name, data = {})
39
89
  post!(EVENT_PATH, event: {name: name, data: data})
90
+ true
91
+ rescue Exception
92
+ false
40
93
  end
41
94
 
95
+ # After test logs are sent to the Timber API this method waits for them to be
96
+ # received. This is how we test integration.
42
97
  def wait_for_logs(iteration = 0, &block)
43
98
  if block_given?
44
99
  yield iteration
@@ -46,9 +101,11 @@ module Timber
46
101
 
47
102
  case iteration
48
103
  when 0
49
- event!(:waiting_for_logs)
50
- when 30
51
- event!(:excessively_waiting_for_logs)
104
+ event(:waiting_for_logs)
105
+ when 20
106
+ event(:excessively_waiting_for_logs)
107
+ when 60
108
+ raise LogsNotReceivedError.new
52
109
  end
53
110
 
54
111
  sleep 0.5
@@ -58,13 +115,20 @@ module Timber
58
115
  case res.code
59
116
  when "202"
60
117
  wait_for_logs(iteration + 1, &block)
61
-
62
118
  when "204"
63
119
  true
120
+ else
121
+ raise UnrecognizedAPIResponse.new(res)
64
122
  end
65
123
  end
66
124
 
67
125
  private
126
+ def build_application(res)
127
+ parsed_body = JSON.parse(res.body)
128
+ attributes = parsed_body.fetch("data")
129
+ Application.new(attributes)
130
+ end
131
+
68
132
  def get!(path)
69
133
  req = Net::HTTP::Get.new(path)
70
134
  issue!(req)
@@ -85,10 +149,12 @@ module Timber
85
149
  http.request(req)
86
150
  end
87
151
 
88
- if res.code == "401"
89
- raise NoAPIKeyError.new
90
- elsif res.code == "403"
152
+ code = Integer(res.code)
153
+
154
+ if [401, 403].include?(code)
91
155
  raise APIKeyInvalidError.new
156
+ elsif code >= 500
157
+ raise ServerError.new
92
158
  else
93
159
  res
94
160
  end
@@ -0,0 +1,30 @@
1
+ module Timber
2
+ class CLI
3
+ class API
4
+ class Application
5
+ DEVELOPMENT = "development".freeze
6
+ HEROKU = "heroku".freeze
7
+
8
+ attr_accessor :api_key, :environment, :framework_type, :heroku_drain_url,
9
+ :name, :platform_type
10
+
11
+ def initialize(attributes)
12
+ @api_key = attributes.fetch("api_key")
13
+ @environment = attributes.fetch("environment")
14
+ @framework_type = attributes.fetch("framework_type")
15
+ @heroku_drain_url = attributes.fetch("heroku_drain_url")
16
+ @name = attributes.fetch("name")
17
+ @platform_type = attributes.fetch("platform_type")
18
+ end
19
+
20
+ def development?
21
+ environment == DEVELOPMENT
22
+ end
23
+
24
+ def heroku?
25
+ platform_type == HEROKU
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,66 @@
1
+ require "timber/cli/file_helper"
2
+
3
+ module Timber
4
+ class CLI
5
+ class ConfigFile
6
+ attr_reader :path
7
+
8
+ def initialize(path)
9
+ @path = path
10
+ FileHelper.read_or_create(path, initial_content)
11
+ end
12
+
13
+ def logrageify!
14
+ append("config.logrageify!")
15
+ end
16
+
17
+ private
18
+ def get_content
19
+ FileHelper.read(path)
20
+ end
21
+
22
+ def append(code)
23
+ current_content = get_content
24
+ if !current_content.include?(code)
25
+ if current_content.include?(insert_hook)
26
+ new_content = current_content.gsub(insert_hook, "#{code}\n\n#{insert_hook}")
27
+ FileHelper.write(path, new_content)
28
+ else
29
+ FileHelper.append(path, new_content)
30
+ end
31
+ end
32
+
33
+ true
34
+ end
35
+
36
+ def insert_hook
37
+ @insert_hook ||= "# Add additional configuration here."
38
+ end
39
+
40
+ # We provide this as an instance method so that the string is only defined when needed.
41
+ # This avoids allocating this string during normal app runtime.
42
+ def initial_content
43
+ <<-CONTENT
44
+ # Timber.io Ruby Configuration - Simple Structured Logging
45
+ #
46
+ # ^ ^ ^ ^ ___I_ ^ ^ ^ ^ ^ ^ ^
47
+ # /|\\/|\\/|\\ /|\\ /\\-_--\\ /|\\/|\\ /|\\/|\\/|\\ /|\\/|\\
48
+ # /|\\/|\\/|\\ /|\\ / \\_-__\\ /|\\/|\\ /|\\/|\\/|\\ /|\\/|\\
49
+ # /|\\/|\\/|\\ /|\\ |[]| [] | /|\\/|\\ /|\\/|\\/|\\ /|\\/|\\
50
+ # -------------------------------------------------------------------
51
+ # Website: https://timber.io
52
+ # Documentation: https://timber.io/docs
53
+ # Support: support@timber.io
54
+ # -------------------------------------------------------------------
55
+
56
+ config = Timber::Config.instance
57
+
58
+ #{insert_hook}
59
+ # For a full list of configuration options and their explanations see:
60
+ # http://www.rubydoc.info/github/timberio/timber-ruby/Timber/Config
61
+
62
+ CONTENT
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,43 @@
1
+ module Timber
2
+ class CLI
3
+ module FileHelper
4
+ def self.append(path, contents)
5
+ File.open(path, "a") do |f|
6
+ f.write(contents)
7
+ end
8
+ end
9
+
10
+ def self.read_or_create(path, contents)
11
+ if !File.exists?(path)
12
+ write(path, contents)
13
+ end
14
+
15
+ File.read(path)
16
+ end
17
+
18
+ def self.read(path)
19
+ File.read(path)
20
+ end
21
+
22
+ def self.write(path, contents)
23
+ File.open(path, "w") do |f|
24
+ f.write(contents)
25
+ end
26
+ end
27
+
28
+ def self.verify(path, io)
29
+ if !File.exists?(path)
30
+ io.puts ""
31
+ io.puts "Uh oh! It looks like we couldn't locate the #{path} file. "
32
+ io.puts "Please enter the correct path:"
33
+ io.puts
34
+
35
+ new_path = io.gets
36
+ verify(new_path, io)
37
+ else
38
+ path
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,58 @@
1
+ require "timber/cli/io/messages"
2
+
3
+ module Timber
4
+ class CLI
5
+ class Installer
6
+ attr_reader :io, :api
7
+
8
+ def initialize(io, api)
9
+ @io = io
10
+ @api = api
11
+ end
12
+
13
+ def run(app)
14
+ raise NotImplementedError.new
15
+ end
16
+
17
+ private
18
+ # Determines the API key storage prference (environment variable or inline)
19
+ def get_api_key_storage_preference
20
+ io.puts ""
21
+ io.puts IO::Messages.separator
22
+ io.puts ""
23
+ io.puts "For production/staging would you like to store your Timber API key"
24
+ io.puts "in an environment variable? (TIMBER_API_KEY)"
25
+ io.puts ""
26
+ io.puts "y) Yes, store in TIMBER_API_KEY", :blue
27
+ io.puts "n) No, just paste the API key inline", :blue
28
+ io.puts ""
29
+
30
+ case io.ask_yes_no("Enter your choice:", event_prompt: "Store API key in env?")
31
+ when :yes
32
+ io.puts ""
33
+ io.puts IO::Messages.http_environment_variables(api.api_key)
34
+ io.puts ""
35
+
36
+ io.ask_to_proceed
37
+
38
+ :environment
39
+ when :no
40
+ :inline
41
+ end
42
+ end
43
+
44
+ # Based on the API key storage preference, we generate the proper code.
45
+ def get_api_key_code(storage_type)
46
+ case storage_type
47
+ when :environment
48
+ "ENV['TIMBER_API_KEY']"
49
+ when :inline
50
+ "'#{api.api_key}'"
51
+ else
52
+ raise ArgumentError.new("API key storage type not recognized! " \
53
+ "#{storage_type.inspect}")
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end