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
@@ -0,0 +1,37 @@
1
+ require "timber/cli/api"
2
+ require "timber/cli/installers/root"
3
+ require "timber/cli/io/messages"
4
+ require "timber/cli/os_helper"
5
+
6
+ module Timber
7
+ class CLI
8
+ module Installers
9
+ def self.run(api_key, io)
10
+ io.puts IO::Messages.header, :green
11
+ io.puts IO::Messages.separator, :green
12
+ io.puts IO::Messages.contact, :green
13
+ io.puts IO::Messages.separator, :green
14
+ io.puts ""
15
+
16
+ if !api_key
17
+ io.puts IO::Messages.no_api_key_provided
18
+
19
+ case io.ask_yes_no("Open the Timber app in your brower now?")
20
+ when :yes
21
+ OSHelper.open(IO::Messages::APP_URL)
22
+ end
23
+
24
+ else
25
+ api = API.new(api_key)
26
+ api.event(:started)
27
+
28
+ io.api = api
29
+
30
+ app = api.application!
31
+
32
+ Root.new(io, api).run(app)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,47 @@
1
+ require "timber/cli/installer"
2
+ require "timber/cli/io/messages"
3
+
4
+ module Timber
5
+ class CLI
6
+ module Installers
7
+ class Other < Installer
8
+ def run(app)
9
+ puts ""
10
+ puts IO::Messages.separator
11
+ puts ""
12
+
13
+ if app.heroku?
14
+ install_stdout
15
+ else
16
+ api_key_storage_preference = get_api_key_storage_preference
17
+ api_key_code = get_api_key_code(api_key_storage_type)
18
+
19
+ install_http(api_key_code)
20
+ end
21
+ end
22
+
23
+ private
24
+ def install_stdout
25
+ puts ""
26
+ puts IO::Messages.separator
27
+ puts ""
28
+ puts "To integrate Timber, we need to instantiate the logger. Your global"
29
+ puts "logger should be set to something like this:"
30
+ puts ""
31
+ puts colorize(" LOGGER = Timber::Logger.new(STDOUT)", :blue)
32
+ end
33
+
34
+ def install_http(api_key_code)
35
+ puts ""
36
+ puts IO::Messages.separator
37
+ puts ""
38
+ puts "To integrate Timber, we need to instantiate the logger. Your global"
39
+ puts "logger should be set to something like this:"
40
+ puts ""
41
+ puts colorize(" log_device = Timber::LogDevices::HTTP.new(#{api_key_code})", :blue)
42
+ puts colorize(" LOGGER = Timber::Logger.new(log_device)", :blue)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,255 @@
1
+ begin
2
+ require "lograge"
3
+ rescue Exception
4
+ end
5
+
6
+ require "timber/cli/config_file"
7
+ require "timber/cli/file_helper"
8
+ require "timber/cli/installer"
9
+ require "timber/cli/io/messages"
10
+
11
+ module Timber
12
+ class CLI
13
+ module Installers
14
+ class Rails < Installer
15
+ # Runs the installer.
16
+ def run(app)
17
+ # Ask all of the questions up front. This allows us to to apply the
18
+ # changes as a neat task list when done.
19
+ development_preference = get_development_preference
20
+ api_key_storage_preference = get_api_key_storage_preference
21
+ should_logrageify = logrageify?
22
+
23
+ io.puts ""
24
+ io.puts IO::Messages.separator
25
+ io.puts ""
26
+
27
+ # Create the initializer
28
+ initializer
29
+
30
+ if should_logrageify
31
+ logrageify!
32
+ end
33
+
34
+ environment_file_paths.each do |environment_file_path|
35
+ environment = File.split(environment_file_path).last.gsub(/\.rb$/, "").to_sym
36
+
37
+ case environment
38
+ when :development
39
+ setup_development_environment(environment_file_path, development_preference)
40
+ when :test
41
+ setup_test_environment(environment_file_path)
42
+ else
43
+ setup_other_environment(app, environment_file_path, api_key_storage_preference)
44
+ end
45
+ end
46
+
47
+ true
48
+ end
49
+
50
+ private
51
+ def logrageify?
52
+ if defined?(::Lograge)
53
+ io.puts ""
54
+ io.puts IO::Messages.separator
55
+ io.puts ""
56
+ io.puts "We noticed you have lograge installed. Would you like to configure "
57
+ io.puts "Timber to function similarly?"
58
+ io.puts "(This silences template renders, sql queries, and controller calls."
59
+ io.puts "You can always do this later in config/initialzers/timber.rb)"
60
+ io.puts ""
61
+ io.puts "y) Yes, configure Timber like lograge", :blue
62
+ io.puts "n) No, use the Rails logging defaults", :blue
63
+ io.puts ""
64
+
65
+ case io.ask_yes_no("Enter your choice:", event_prompt: "Logrageify?")
66
+ when :yes
67
+ true
68
+ when :no
69
+ false
70
+ end
71
+ else
72
+ false
73
+ end
74
+ end
75
+
76
+ def logrageify!
77
+ task_message = "Logrageifying in #{initializer.path}"
78
+ io.task(task_message) do
79
+ initializer.logrageify!
80
+ end
81
+ true
82
+ end
83
+
84
+ # Determines the development preference
85
+ def get_development_preference
86
+ io.puts ""
87
+ io.puts IO::Messages.separator
88
+ io.puts ""
89
+ io.puts "Would you like to temporarily send development logs to Timber?"
90
+ io.puts "(Logs will still go to STDOUT, but this provides an easy way to kick the "
91
+ io.puts "tires. Once you're done testing, you can disable this in "
92
+ io.puts "#{IO::ANSI.colorize("config/environments/development.rb", :yellow)})"
93
+ io.puts ""
94
+ io.puts "y) Yes, send development logs to Timber", :blue
95
+ io.puts "n) No, just print development logs to STDOUT", :blue
96
+ io.puts ""
97
+
98
+ case io.ask_yes_no("Enter your choice:", event_prompt: "Send dev logs to Timber?")
99
+ when :yes
100
+ :send
101
+ when :no
102
+ :dont_send
103
+ end
104
+ end
105
+
106
+ def initializer
107
+ @initializer ||= begin
108
+ initializer_path = File.join("config", "initializers", "timber.rb")
109
+ task_message = "Creating #{initializer_path}"
110
+ io.task(task_message) { ConfigFile.new(initializer_path) }
111
+ end
112
+ end
113
+
114
+ # Wraps the logger in TaggedLogging if it is available. Older versions of Rails
115
+ # do not include this constant.
116
+ def config_set_logger_code
117
+ @config_set_logger_code ||= defined?(::ActiveSupport::TaggedLogging) ?
118
+ "ActiveSupport::TaggedLogging.new(logger)" : "logger"
119
+ end
120
+
121
+ # Traverses the config/environments directory and returns an array of
122
+ # symbols representing the various environments.
123
+ def environment_file_paths
124
+ path = File.join("config", "environments", "*.rb")
125
+ Dir[path]
126
+ end
127
+
128
+ def setup_development_environment(environment_file_path, development_preference)
129
+ if already_configured?(environment_file_path)
130
+ message = "Installing the Timber::Logger in #{environment_file_path}"
131
+ io.puts IO::Messages.task_complete(message), :green
132
+ return true
133
+ end
134
+
135
+ case development_preference
136
+ when :send
137
+ extra_comment = <<-NOTE
138
+ # Note: When you are done testing, simply instantiate the logger like this:
139
+ #
140
+ # logger = Timber::Logger.new(STDOUT)
141
+ #
142
+ # Be sure to remove the "log_device =" and "logger =" lines below.
143
+ NOTE
144
+ extra_comment = extra_comment.rstrip
145
+ install_http(environment_file_path, :inline, extra_comment: extra_comment)
146
+ when :dont_send
147
+ install_stdout(environment_file_path)
148
+ end
149
+ end
150
+
151
+ def setup_test_environment(environment_file_path)
152
+ if already_configured?(environment_file_path)
153
+ message = "Installing the Timber::Logger in #{environment_file_path}"
154
+ io.puts IO::Messages.task_complete(message), :green
155
+ return true
156
+ end
157
+
158
+ # Tests should not be logged by default.
159
+ install_nil(environment_file_path)
160
+ end
161
+
162
+ def setup_other_environment(app, environment_file_path, api_key_storage_preference)
163
+ if already_configured?(environment_file_path)
164
+ message = "Installing the Timber::Logger in #{environment_file_path}"
165
+ io.puts IO::Messages.task_complete(message), :green
166
+ return true
167
+ end
168
+
169
+ if app.heroku?
170
+ install_stdout(environment_file_path)
171
+ else
172
+ install_http(environment_file_path, api_key_storage_preference)
173
+ end
174
+ end
175
+
176
+ def install_nil(environment_file_path)
177
+ logger_code = <<-CODE
178
+ # Install the Timber.io logger but silence all logs (log to nil). We install the
179
+ # logger to ensure the Rails.logger object exposes the proper API.
180
+ logger = Timber::Logger.new(nil)
181
+ logger.level = config.log_level
182
+ config.logger = #{config_set_logger_code}
183
+ CODE
184
+
185
+ install_logger(environment_file_path, logger_code)
186
+ end
187
+
188
+ # Installs the Timber logger using the HTTP transport strategy in the
189
+ # specified environment file.
190
+ def install_http(environment_file_path, api_key_storage_type, options = {})
191
+ api_key_code = get_api_key_code(api_key_storage_type)
192
+ extra_comment = options[:extra_comment] ? "\n #{options[:extra_comment]}" : nil
193
+
194
+ logger_code = <<-CODE
195
+ # Install the Timber.io logger, send logs over HTTP.#{extra_comment}
196
+ log_device = Timber::LogDevices::HTTP.new(#{api_key_code})
197
+ logger = Timber::Logger.new(log_device)
198
+ logger.level = config.log_level
199
+ config.logger = #{config_set_logger_code}
200
+ CODE
201
+
202
+ install_logger(environment_file_path, logger_code)
203
+ end
204
+
205
+ # Installs the Timber logger using the STDOUT transport method in the specified
206
+ # environment file.
207
+ def install_stdout(environment_file_path)
208
+ logger_code = <<-CODE
209
+ # Install the Timber.io logger, send logs over STDOUT. Actual log delivery
210
+ # to the Timber service is handled external of this application.
211
+ logger = Timber::Logger.new(STDOUT)
212
+ logger.level = config.log_level
213
+ config.logger = #{config_set_logger_code}
214
+ CODE
215
+
216
+ install_logger(environment_file_path, logger_code)
217
+ end
218
+
219
+ # Determines if the environment is already configured.
220
+ def already_configured?(environment_file_path)
221
+ environment_file_contents = get_environment_file_contents(environment_file_path)
222
+ logger_installed?(environment_file_contents)
223
+ end
224
+
225
+ # Convenience method for getting the current environment file contents.
226
+ def get_environment_file_contents(environment_file_path)
227
+ FileHelper.read(environment_file_path)
228
+ end
229
+
230
+ # Determines if the Timber logger is already installed in the environment
231
+ # file contents.
232
+ def logger_installed?(environment_file_contents)
233
+ environment_file_contents.include?("Timber::Logger.new")
234
+ end
235
+
236
+ # Installs the Timber logger in the specified environment file with the
237
+ # provided logger code.
238
+ def install_logger(environment_file_path, logger_code)
239
+ current_contents = get_environment_file_contents(environment_file_path)
240
+
241
+ task_message = "Installing the Timber::Logger in #{environment_file_path}"
242
+ io.task(task_message) do
243
+ if !logger_installed?(current_contents)
244
+ new_contents = current_contents.sub(/\nend/, "\n\n#{logger_code}\nend")
245
+ FileHelper.write(environment_file_path, new_contents)
246
+ api.event(:file_written, path: environment_file_path)
247
+ end
248
+ end
249
+
250
+ true
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,189 @@
1
+ # encoding: utf-8
2
+
3
+ # Attempt to load rails so that we can determine the proper sub-installer to use.
4
+ begin
5
+ require "rails"
6
+ rescue LoadError
7
+ end
8
+
9
+ require "timber/cli/api"
10
+ require "timber/cli/installer"
11
+ require "timber/cli/installers/other"
12
+ require "timber/cli/installers/rails"
13
+ require "timber/cli/io/messages"
14
+ require "timber/cli/os_helper"
15
+ require "timber/log_devices/http"
16
+ require "timber/logger"
17
+
18
+ module Timber
19
+ class CLI
20
+ module Installers
21
+ # The root installer is the primary installer that is instantiated and
22
+ # run when the installer starts. It is responsible for instantiating
23
+ # the proper sub installers that install Timber in specific frameworks
24
+ # and environments.
25
+ class Root < Installer
26
+ def run(app)
27
+ io.puts IO::Messages.application_details(app)
28
+ io.puts ""
29
+
30
+ case io.ask_yes_no("Are the above details correct?", event_prompt: "App details correct?")
31
+ when :yes
32
+ install_platform(app)
33
+ run_sub_installer(app)
34
+ send_test_messages
35
+ confirm_log_delivery
36
+
37
+ assist_with_git
38
+
39
+ api.event(:success)
40
+
41
+ collect_feedback
42
+
43
+ io.puts ""
44
+ io.puts IO::Messages.separator
45
+ io.puts ""
46
+ io.puts IO::Messages.free_data
47
+ io.puts ""
48
+
49
+ true
50
+
51
+ when :no
52
+ io.puts ""
53
+ io.puts "Bummer. Head to this URL to update the details:"
54
+ io.puts ""
55
+ io.puts " #{IO::Messages.edit_app_url(app)}", :blue
56
+ io.puts ""
57
+ io.puts "exiting..."
58
+
59
+ false
60
+ end
61
+ end
62
+
63
+ private
64
+ def install_platform(app)
65
+ if app.heroku?
66
+ io.puts ""
67
+ io.puts IO::Messages.separator
68
+ io.puts ""
69
+ io.puts IO::Messages.heroku_install(app)
70
+ io.puts ""
71
+ io.ask_to_proceed
72
+ end
73
+
74
+ true
75
+ end
76
+
77
+ def run_sub_installer(app)
78
+ sub_installer = get_sub_installer
79
+ sub_installer.run(app)
80
+ end
81
+
82
+ def get_sub_installer
83
+ if defined?(::Rails)
84
+ Rails.new(io, api)
85
+ else
86
+ Other.new(io, api)
87
+ end
88
+ end
89
+
90
+ def send_test_messages
91
+ task_message = "Sending test logs"
92
+ io.task(task_message) do
93
+ http_device = LogDevices::HTTP.new(api.api_key)
94
+ logger = Logger.new(http_device)
95
+ logger.info("Welcome to Timber!")
96
+ logger.info("This is a test log to ensure the pipes are working")
97
+ logger.info("Be sure to commit and deploy your app to start seeing real logs")
98
+ # Close flushes and waits
99
+ http_device.close
100
+ end
101
+ end
102
+
103
+ def confirm_log_delivery
104
+ task_message = "Confirming log delivery"
105
+
106
+ io.task(task_message) do
107
+ api.wait_for_logs do |iteration|
108
+ io.write IO::Messages.task_start(task_message), :blue
109
+ io.write IO::Messages.spinner(iteration), :blue
110
+ end
111
+ end
112
+ end
113
+
114
+ def assist_with_git
115
+ io.puts ""
116
+ io.puts IO::Messages.separator
117
+ io.puts ""
118
+ io.puts "Last step! We need to commit these changes via git:"
119
+ io.puts ""
120
+ io.puts IO::Messages.git_commands
121
+ io.puts ""
122
+
123
+ if OSHelper.has_git?
124
+ case io.ask_yes_no("We can run these commands for you. Shall we?", event_prompt: "Run git commands?")
125
+ when :yes
126
+ io.puts ""
127
+
128
+ task_message = "Committing changes via git"
129
+ io.write IO::Messages.task_start(task_message)
130
+
131
+ committed = OSHelper.git_commit_changes
132
+
133
+ if committed
134
+ io.puts IO::Messages.task_complete(task_message), :green
135
+ else
136
+ io.puts IO::Messages.task_failed(task_message), :red
137
+
138
+ io.puts ""
139
+ io.puts "Bummer, it looks like we couldn't access the git command.", :yellow
140
+ io.puts "No problem though, just copy and paste the above commands to", :yellow
141
+ io.puts "run them manually.", :yellow
142
+ end
143
+ when :no
144
+ io.puts ""
145
+ io.puts "No problem. Just copy and paste the above commands to run them manually."
146
+ end
147
+ else
148
+ io.puts ""
149
+ io.puts "Finally, commit your changes:"
150
+ io.puts ""
151
+ io.puts IO::Messages.git_commands
152
+ end
153
+
154
+ io.puts ""
155
+ io.puts "=> Reminder: git push and deploy 🚀 to see logs in staging/production", :yellow
156
+ end
157
+
158
+ def collect_feedback
159
+ io.puts ""
160
+ io.puts IO::Messages.separator
161
+ io.puts ""
162
+
163
+ rating = io.ask("How would rate this install experience? 1 (bad) - 5 (perfect)", ["1", "2", "3", "4", "5"])
164
+
165
+ case rating
166
+ when "4", "5"
167
+ api.event(:feedback, rating: rating.to_i)
168
+ io.puts ""
169
+ io.puts IO::Messages.we_love_you_too
170
+
171
+ when "1", "2", "3"
172
+ io.puts ""
173
+ io.puts IO::Messages.bad_experience_message
174
+ io.puts ""
175
+ io.puts "Type your comments below (enter sends)"
176
+ io.puts ""
177
+
178
+ comments = io.gets
179
+
180
+ api.event(:feedback, rating: rating.to_i, comments: comments)
181
+
182
+ io.puts ""
183
+ io.puts "Thank you! We take feedback seriously and will work to improve this."
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+ end