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
@@ -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