timber 2.0.24 → 2.1.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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