marty 1.0.23 → 1.0.24

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d0f81a144144beb77d9fafc82b8d638be6fbb501
4
- data.tar.gz: 49a509cc8458a156deaf186c4401acbc10411436
3
+ metadata.gz: 86b79d602a84a7fea3fbca2b3704a8f9ccf6b00b
4
+ data.tar.gz: 0720c7fe854939427a378e0cca9cfc8ebf96302f
5
5
  SHA512:
6
- metadata.gz: eb8843be6891a672779cca79c29ca08b8879d58ef1f7c99a6353ecbd1cc5c28ced1f42913e891f7cb4311a0c5f59d50d906899488ea0978c87a2a75c7543a602
7
- data.tar.gz: fd0588d9011f7734e478eca8a51e7f3092bd2fbb4a4734f399c2ee91dacad499b27d6ed70d062c877356c90d9be7235b191151618f7f0e8514b724a963bc3989
6
+ metadata.gz: b4d7f133d884839c72815a068934c54b26f7c366850fda2c313cd600a277eb93d2f438d5ad932fa5b66a9f0b9b25d7177465e209bdd1e8127d0309e4bc37fed7
7
+ data.tar.gz: 429013d616affaa52db3d1eaffe52bc6402fd5e70245fb61f7de855debe7cbc68cd6214cb3e2c29dee96e535f80cc0da5e996c7a8ecc37c37df90e4dda5a873d
data/.gitignore CHANGED
@@ -3,6 +3,7 @@ log/*.log
3
3
  pkg/
4
4
  spec/dummy/db/*.sqlite3
5
5
  spec/dummy/log/*.log
6
+ spec/dummy/log/*.sql
6
7
  spec/dummy/tmp/
7
8
  spec/dummy/.sass-cache
8
9
 
data/Gemfile CHANGED
@@ -10,6 +10,7 @@ gem 'daemons', '~> 1.1.9'
10
10
  gem 'mime-types', '< 3.0', platforms: :ruby_19
11
11
  gem 'rails', '~> 4.2.1'
12
12
  gem 'pg', '~> 0.18.4'
13
+ gem 'sqlite3'
13
14
 
14
15
  group :development, :test do
15
16
  gem 'pry-rails'
@@ -32,13 +32,14 @@ class Marty::Grid < ::Netzke::Grid::Base
32
32
  super
33
33
 
34
34
  c.permissions = {
35
- create: class_can?(:create),
36
- read: class_can?(:read),
37
- update: class_can?(:update),
38
- delete: class_can?(:delete)
35
+ create: class_can?(:create),
36
+ read: class_can?(:read),
37
+ update: class_can?(:update),
38
+ delete: class_can?(:delete)
39
39
  }
40
- c.editing = :both
41
- c.store_config = {page_size: 30}
40
+
41
+ c.editing = :both
42
+ c.store_config = {page_size: 30}
42
43
  end
43
44
 
44
45
  def has_search_action?
@@ -0,0 +1,76 @@
1
+ class Marty::LogView < Marty::Grid
2
+ include Marty::Extras::Layout
3
+ has_marty_permissions read: [:admin],
4
+ update: [:admin]
5
+
6
+ def configure(c)
7
+ super
8
+
9
+ c.title ||= I18n.t('log_viewer', default: "Log Viewer")
10
+ c.model = "Marty::Log"
11
+ c.paging = :buffered
12
+ c.editing = :in_form
13
+ c.attributes = [
14
+ :timestamp,
15
+ :message_type,
16
+ :message,
17
+ :details
18
+ ]
19
+
20
+ c.store_config.merge!(sorters: [{property: :timestamp, direction: 'DESC'}])
21
+ end
22
+
23
+ def default_context_menu
24
+ []
25
+ end
26
+
27
+ def default_form_items
28
+ [
29
+ :timestamp,
30
+ :message_type,
31
+ :message,
32
+ textarea_field(:details).merge!({height: 400})
33
+ ]
34
+ end
35
+
36
+ component :edit_window do |c|
37
+ super(c)
38
+ c.width = 1200
39
+ end
40
+
41
+ attribute :message_type do |c|
42
+ c.text = I18n.t("log_grid.message_type")
43
+ c.width = 100
44
+ c.read_only = true
45
+ end
46
+
47
+ attribute :message do |c|
48
+ c.text = I18n.t("log_grid.message")
49
+ c.width = 400
50
+ c.read_only = true
51
+ end
52
+
53
+ attribute :timestamp do |c|
54
+ c.text = I18n.t("log_grid.timestamp")
55
+ c.width = 200
56
+ c.read_only = true
57
+ c.xtype = :datecolumn
58
+ c.format = 'Y-m-d h:i:s.u'
59
+ c.field_config = {
60
+ xtype: :displayfield,
61
+ }
62
+ c.getter = lambda { |r| Time.at(r.timestamp) }
63
+ end
64
+
65
+ column :details do |c|
66
+ c.getter = lambda { |r| CGI.escapeHTML(r.details) }
67
+ end
68
+
69
+ attribute :details do |c|
70
+ c.text = I18n.t("log_grid.details")
71
+ c.width = 900
72
+ c.read_only = true
73
+ end
74
+ end
75
+
76
+ LogView = Marty::LogView
@@ -47,6 +47,20 @@ class Marty::MainAuthApp < Marty::AuthApp
47
47
  }
48
48
  end
49
49
 
50
+ def log_menu
51
+ [
52
+ {
53
+ text: 'Log Maintenance',
54
+ icon: icon_hack(:wrench),
55
+ disabled: !self.class.has_admin_perm?,
56
+ menu: [
57
+ :log_view,
58
+ :log_cleanup,
59
+ ]
60
+ }
61
+ ]
62
+ end
63
+
50
64
  def system_menu
51
65
  {
52
66
  text: I18n.t("system"),
@@ -60,7 +74,7 @@ class Marty::MainAuthApp < Marty::AuthApp
60
74
  :event_view,
61
75
  :reload_scripts,
62
76
  :load_seed,
63
- ] + background_jobs_menu
77
+ ] + background_jobs_menu + log_menu
64
78
  }
65
79
  end
66
80
 
@@ -235,6 +249,21 @@ class Marty::MainAuthApp < Marty::AuthApp
235
249
  a.disabled = !self.class.has_admin_perm?
236
250
  end
237
251
 
252
+ action :log_view do |a|
253
+ a.text = 'View Log'
254
+ a.tooltip = 'View Log'
255
+ a.handler = :netzke_load_component_by_action
256
+ a.icon = :cog
257
+ a.disabled = !self.class.has_admin_perm?
258
+ end
259
+
260
+ action :log_cleanup do |a|
261
+ a.text = 'Cleanup Log Table'
262
+ a.tooltip = 'Delete old log records'
263
+ a.icon = :cog
264
+ a.disabled = !self.class.has_admin_perm?
265
+ end
266
+
238
267
  ######################################################################
239
268
 
240
269
  def bg_command(param)
@@ -268,6 +297,15 @@ class Marty::MainAuthApp < Marty::AuthApp
268
297
  client.show_detail res.html_safe.gsub("\n","<br/>"), 'Delayed Job Restart'
269
298
  end
270
299
 
300
+ endpoint :log_cleanup do |params|
301
+ begin
302
+ Marty::Log.cleanup(params)
303
+ rescue => e
304
+ res = e.message
305
+ client.show_detail res.html_safe.gsub("\n","<br/>"), 'Log Cleanup'
306
+ end
307
+ end
308
+
271
309
  ######################################################################
272
310
  # Postings
273
311
 
@@ -401,6 +439,23 @@ class Marty::MainAuthApp < Marty::AuthApp
401
439
  this.server.bgStatus({});
402
440
  }
403
441
  JS
442
+
443
+ c.netzke_on_log_cleanup = l(<<-JS)
444
+ function(params) {
445
+ var me = this;
446
+ Ext.Msg.show({
447
+ title: 'Log Cleanup',
448
+ msg: 'Enter number of days to keep',
449
+ width: 375,
450
+ buttons: Ext.Msg.OKCANCEL,
451
+ prompt: true,
452
+ fn: function (btn, value) {
453
+ btn == "ok" && me.server.logCleanup(value);
454
+ }
455
+ });
456
+ }
457
+ JS
458
+
404
459
  end
405
460
 
406
461
  action :select_posting do |a|
@@ -444,6 +499,10 @@ class Marty::MainAuthApp < Marty::AuthApp
444
499
  c.disabled = Marty::Util.warped?
445
500
  end
446
501
 
502
+ component :log_view do |c|
503
+ c.klass = Marty::LogView
504
+ end
505
+
447
506
  endpoint :reload_scripts do |params|
448
507
  Marty::Script.load_scripts
449
508
  client.netzke_notify 'Scripts have been reloaded'
@@ -0,0 +1,56 @@
1
+ class Marty::Log < Marty::Base
2
+
3
+ def self.logfile
4
+ @logfile ||= Rails.root.join('log', Rails.env + '.sql').to_s
5
+ end
6
+
7
+ establish_connection({
8
+ adapter: "sqlite3",
9
+ database: logfile
10
+ })
11
+ self.table_name = "log"
12
+ self.primary_key = "id"
13
+
14
+ def self.db_init
15
+ db = SQLite3::Database.new(Marty::Log.logfile)
16
+ db.execute <<-SQL
17
+ CREATE TABLE IF NOT EXISTS log (
18
+ id INTEGER PRIMARY KEY,
19
+ message_type TEXT,
20
+ message TEXT,
21
+ timestamp REAL,
22
+ details BLOB )
23
+ SQL
24
+ db
25
+ end
26
+
27
+ def self.write_log(type, message, details)
28
+ begin
29
+ @db ||= db_init
30
+ stmt = @db.prepare <<-SQL
31
+ INSERT INTO log (message_type, message, timestamp, details)
32
+ values (?, ?, ?, ?)
33
+ SQL
34
+ stmt.bind_param(1, type.to_s)
35
+ stmt.bind_param(2, message)
36
+ stmt.bind_param(3, Time.zone.now.to_f)
37
+ stmt.bind_param(4, details.pretty_inspect)
38
+
39
+ stmt.execute
40
+ rescue => e
41
+ Marty::Util.logger.error("Marty::Logger failure: #{e.message}")
42
+ ensure
43
+ stmt.close if stmt rescue nil
44
+ end
45
+ end
46
+
47
+ def self.cleanup(days_to_keep)
48
+ raise "Must give numeric value. (Got '#{days_to_keep}')" unless
49
+ (Float(days_to_keep) rescue false)
50
+ @db ||= db_init
51
+ cutoff = Time.zone.now.to_i - days_to_keep.to_i*60*60*24
52
+ @db.execute <<-SQL
53
+ delete from log where timestamp <= #{cutoff}
54
+ SQL
55
+ end
56
+ end
@@ -48,6 +48,12 @@ en:
48
48
  created_dt: Create Date/Time
49
49
  delete: Delete Script
50
50
 
51
+ log_grid:
52
+ message_type: Type
53
+ message: Message
54
+ timestamp: Timestamp
55
+ details: Details
56
+
51
57
  user_grid:
52
58
  title: Users
53
59
  new: New User
@@ -0,0 +1,26 @@
1
+ require 'sqlite3'
2
+
3
+ class Marty::Logger
4
+
5
+ def self.method_missing(m, *args, &block)
6
+ return super unless
7
+ [:debug, :info, :warn, :error, :fatal, :unknown].include?(m)
8
+ Marty::Util.logger.send(m, args[0]) if Marty::Util.logger.respond_to?(m)
9
+ log(m, *args)
10
+ end
11
+
12
+ def self.log(type, message, details=nil)
13
+ Marty::Log.write_log(type, message, details)
14
+ end
15
+
16
+ def self.with_logging(error_message, error_data)
17
+ begin
18
+ yield
19
+ rescue => e
20
+ error(error_message, { message: e.message,
21
+ data: error_data })
22
+ raise "#{error_message}: #{e.message}"
23
+ end
24
+ end
25
+
26
+ end
@@ -1,3 +1,3 @@
1
1
  module Marty
2
- VERSION = "1.0.23"
2
+ VERSION = "1.0.24"
3
3
  end
@@ -40,4 +40,5 @@ Gem::Specification.new do |s|
40
40
  s.add_dependency 'coderay'
41
41
  s.add_dependency 'net-ldap', '~> 0.12.0'
42
42
  s.add_dependency 'rubyzip'
43
+ s.add_dependency 'sqlite3'
43
44
  end
@@ -32,6 +32,8 @@ module Dummy
32
32
  # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
33
33
  # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
34
34
  # config.time_zone = 'Central Time (US & Canada)'
35
+ config.active_record.default_timezone = :local
36
+ config.time_zone = 'Pacific Time (US & Canada)'
35
37
 
36
38
  # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
37
39
  # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
@@ -0,0 +1,90 @@
1
+ require 'spec_helper'
2
+
3
+ feature 'logger view', js: true, capybara: true do
4
+
5
+ def manual_insert(type, message, ts, detail)
6
+ stmt = @db.prepare <<-SQL
7
+ INSERT INTO log (message_type, message, timestamp, details)
8
+ VALUES (?, ?, ?, ?)
9
+ SQL
10
+ stmt.bind_param(1, type)
11
+ stmt.bind_param(2, message)
12
+ stmt.bind_param(3, ts)
13
+ stmt.bind_param(4, detail)
14
+ stmt.execute
15
+ stmt.close
16
+ end
17
+
18
+ before(:all) do
19
+ self.use_transactional_fixtures = false
20
+ @db = SQLite3::Database.new(Marty::Log.logfile)
21
+
22
+ info_s = { info: 'message' }
23
+ error_s = [1, 2, 3, { error: 'message' }]
24
+ fatal_s = ["string", 123, { fatal: "message", another_key: 'value' }]
25
+ Marty::Logger.info('info message', nil)
26
+ Marty::Logger.error('error message', error_s)
27
+ Marty::Logger.fatal('fatal message', fatal_s)
28
+ manual_insert("debug", "hi mom", (Time.zone.now - 5.days).to_i,
29
+ ["one", "two", 3, 4.0].pretty_inspect)
30
+ manual_insert("warn", "all your base", (Time.zone.now - 10.days).to_i,
31
+ [5].pretty_inspect)
32
+ @ts = (@db.execute "select timestamp from log order by timestamp desc").map do
33
+ |(ts)|
34
+ Time.zone.at(ts).strftime('%Y-%m-%dT%H:%M:%S.%L%:z')
35
+ end
36
+
37
+ @clean_file = "/tmp/clean_#{Process.pid}.psql"
38
+ save_clean_db(@clean_file)
39
+ populate_test_users
40
+ end
41
+
42
+ after(:all) do
43
+ restore_clean_db(@clean_file)
44
+ @db.execute "delete from log"
45
+ @db.close
46
+ self.use_transactional_fixtures = true
47
+ end
48
+
49
+ let(:logview) { netzke_find('log_view') }
50
+ it "updates views correctly" do
51
+ log_in_as('marty')
52
+ press('System')
53
+ show_submenu('Log Maintenance')
54
+ press('View Log')
55
+ wait_for_ready
56
+ exp_types = ["fatal", "error", "info", "debug", "warn"]
57
+ exp_messages = ["fatal message", "error message",
58
+ "info message", "hi mom", "all your base"]
59
+ exp_details = [ "[\"string\", 123, {:fatal=>\"message\", "\
60
+ ":another_key=>\"value\"}]\n",
61
+ "[1, 2, 3, {:error=>\"message\"}]\n",
62
+ "nil\n",
63
+ "[\"one\", \"two\", 3, 4.0]\n",
64
+ "[5]\n"]
65
+ [[nil, 5], [7, 4], [3, 3], [0, 0]].each do |days, exp_count|
66
+ if days
67
+ press('System')
68
+ show_submenu('Log Maintenance')
69
+ press('Cleanup Log Table')
70
+ wait_for_ajax
71
+ find(:xpath, "//input[contains(@id, 'textfield')]", wait: 5).set(days)
72
+ press('OK')
73
+ wait_for_ready
74
+ find(:refresh).click
75
+ wait_for_ready
76
+ end
77
+ cnt = logview.row_count()
78
+ expect(cnt).to eq(exp_count)
79
+ types = logview.col_values('message_type', cnt, 0)
80
+ messages = logview.col_values('message', cnt, 0)
81
+ details = logview.col_values('details', cnt, 0).
82
+ map { |d| CGI.unescapeHTML(d) }
83
+ ts = logview.col_values('timestamp', cnt, 0)
84
+ expect(ts).to eq(@ts.slice(0,exp_count))
85
+ expect(types).to eq(exp_types.slice(0,exp_count))
86
+ expect(messages).to eq(exp_messages.slice(0,exp_count))
87
+ expect(details).to eq(exp_details.slice(0,exp_count))
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,100 @@
1
+ require 'spec_helper'
2
+
3
+ module Marty
4
+ describe Logger do
5
+ before(:all) do
6
+ self.use_transactional_fixtures = false
7
+ end
8
+ before(:each) do
9
+ @db = SQLite3::Database.new(Marty::Log.logfile)
10
+ end
11
+ after(:each) do
12
+ @db.execute "delete from log"
13
+ @db.close
14
+ end
15
+ after(:all) do
16
+ self.use_transactional_fixtures = true
17
+ end
18
+
19
+ it "logs" do
20
+ File.open(Rails.root.join("log/test.log")) do |f|
21
+ f.seek(0, IO::SEEK_END)
22
+ info_s = { info: 'message' }
23
+ error_s = [1, 2, 3, { error: 'message' }]
24
+ fatal_s = ["string", 123, { fatal: "message", another_key: 'value' }]
25
+ Marty::Logger.info('info message', info_s)
26
+ Marty::Logger.error('error message', error_s)
27
+ Marty::Logger.fatal('fatal message', fatal_s)
28
+ rails_log = f.readlines
29
+ log = @db.execute "select * from log"
30
+ log_detail = []
31
+ log_ts = []
32
+ log.each do |l|
33
+ id, type, msg, ts, detail_str = l
34
+ log_detail[id] = [type, msg, detail_str]
35
+ log_ts[id] = ts
36
+ end
37
+ expect(rails_log).to eq(["info message\n",
38
+ "error message\n",
39
+ "fatal message\n"])
40
+ expect(log_detail[1]).to eq(["info", "info message",
41
+ info_s.pretty_inspect])
42
+ expect(log_detail[2]).to eq(["error", "error message",
43
+ error_s.pretty_inspect])
44
+ expect(log_detail[3]).to eq(["fatal", "fatal message",
45
+ fatal_s.pretty_inspect])
46
+ (1..3).each do |idx|
47
+ expect(log_ts[idx]).to be_within(5).of(Time.zone.now.to_i)
48
+ end
49
+ end
50
+ end
51
+ it "with_logging" do
52
+ bd = 'block description'
53
+ the_error = 'error during my block'
54
+ data = [1, 2, 3, Marty::User.first]
55
+ begin
56
+ Marty::Logger.with_logging(bd, data) do
57
+ raise the_error
58
+ end
59
+ rescue => e
60
+ raised = e.message
61
+ end
62
+ expect(raised).to eq("#{bd}: #{the_error}")
63
+ log = Marty::Log.first
64
+ expect(log.message_type).to eq('error')
65
+ expect(log.message).to eq(bd)
66
+ expect(log.details).to eq({ message: the_error,
67
+ data: data }.pretty_inspect)
68
+ end
69
+ end
70
+ describe "Logger errors" do
71
+ it "fails gracefully" do
72
+ allow(Marty::Log).to receive(:db_init).
73
+ and_raise("Error initializing DB")
74
+ Marty::Log.instance_variable_set(:@db, nil)
75
+ File.open(Rails.root.join("log/test.log")) do |f|
76
+ f.seek(0, IO::SEEK_END)
77
+ expect{Marty::Logger.info('info message', [1,2,3])}.not_to raise_error
78
+ rails_log = f.readlines
79
+ expect(rails_log).to eq(["info message\n",
80
+ "Marty::Logger failure: Error initializing DB\n"])
81
+ end
82
+ end
83
+ it "fails gracefully in ensure" do
84
+ Marty::Logger.info('init db', [])
85
+ close_err = 'Error closing statement'
86
+ allow_any_instance_of(SQLite3::Statement).to receive(:close).
87
+ and_raise(close_err)
88
+ File.open(Rails.root.join("log/test.log")) do |f|
89
+ f.seek(0, IO::SEEK_END)
90
+ expect{Marty::Logger.info('ensure message', [1,2,3])}.not_to raise_error
91
+ rails_log = f.readlines
92
+ expect(rails_log).to eq(["ensure message\n"])
93
+ allow_any_instance_of(SQLite3::Statement).to receive(:close).
94
+ and_call_original
95
+ sleep 1
96
+ Marty::Log.cleanup(0)
97
+ end
98
+ end
99
+ end
100
+ end
@@ -11,9 +11,12 @@ ActiveRecord::Migrator.migrate File.expand_path("../dummy/db/migrate/", __FILE__
11
11
 
12
12
  Dir[Rails.root.join("../support/**/*.rb")].each { |f| require f }
13
13
 
14
+ CLASSES_TO_EXCLUDE_FROM_SHARED = ["Marty::Log"]
14
15
  class ActiveRecord::Base
15
16
  mattr_accessor :shared_connection
16
-
17
+ class << self
18
+ alias_method :orig_connection, :connection
19
+ end
17
20
  def self.clear_connection
18
21
  @@shared_connection = nil
19
22
  end
@@ -21,7 +24,8 @@ class ActiveRecord::Base
21
24
  clear_connection
22
25
 
23
26
  def self.connection
24
- @@shared_connection || retrieve_connection
27
+ CLASSES_TO_EXCLUDE_FROM_SHARED.include?(model_name) ? orig_connection :
28
+ @@shared_connection || retrieve_connection
25
29
  end
26
30
 
27
31
  def self.reset_shared_connection
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marty
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.23
4
+ version: 1.0.24
5
5
  platform: ruby
6
6
  authors:
7
7
  - Arman Bostani
@@ -14,7 +14,7 @@ authors:
14
14
  autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
- date: 2017-03-01 00:00:00.000000000 Z
17
+ date: 2017-03-21 00:00:00.000000000 Z
18
18
  dependencies:
19
19
  - !ruby/object:Gem::Dependency
20
20
  name: pg
@@ -156,6 +156,20 @@ dependencies:
156
156
  - - ">="
157
157
  - !ruby/object:Gem::Version
158
158
  version: '0'
159
+ - !ruby/object:Gem::Dependency
160
+ name: sqlite3
161
+ requirement: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ type: :runtime
167
+ prerelease: false
168
+ version_requirements: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - ">="
171
+ - !ruby/object:Gem::Version
172
+ version: '0'
159
173
  description: Marty is a framework for viewing and reporting on versioned data.
160
174
  email:
161
175
  - arman.bostani@pnmac.com
@@ -187,6 +201,7 @@ files:
187
201
  - app/components/marty/grid_append_only.rb
188
202
  - app/components/marty/import_type_view.rb
189
203
  - app/components/marty/live_search_grid_panel.rb
204
+ - app/components/marty/log_view.rb
190
205
  - app/components/marty/main_auth_app.rb
191
206
  - app/components/marty/mcfly_grid_panel.rb
192
207
  - app/components/marty/new_posting_form.rb
@@ -392,6 +407,7 @@ files:
392
407
  - app/models/marty/grid_index_numrange.rb
393
408
  - app/models/marty/grid_index_string.rb
394
409
  - app/models/marty/import_type.rb
410
+ - app/models/marty/log.rb
395
411
  - app/models/marty/name_validator.rb
396
412
  - app/models/marty/pg_enum.rb
397
413
  - app/models/marty/posting.rb
@@ -450,6 +466,7 @@ files:
450
466
  - lib/marty/data_importer.rb
451
467
  - lib/marty/engine.rb
452
468
  - lib/marty/lazy_column_loader.rb
469
+ - lib/marty/logger.rb
453
470
  - lib/marty/mcfly_query.rb
454
471
  - lib/marty/migrations.rb
455
472
  - lib/marty/monkey.rb
@@ -1556,6 +1573,7 @@ files:
1556
1573
  - spec/features/javascripts/job_dashboard_live_search.js.coffee
1557
1574
  - spec/features/javascripts/login.js.coffee
1558
1575
  - spec/features/jobs_dashboard_spec.rb
1576
+ - spec/features/log_view_spec.rb
1559
1577
  - spec/features/reporting_spec.rb
1560
1578
  - spec/features/scripting_spec.rb
1561
1579
  - spec/features/scripting_test_spec.rb
@@ -1565,6 +1583,7 @@ files:
1565
1583
  - spec/job_helper.rb
1566
1584
  - spec/lib/data_exporter_spec.rb
1567
1585
  - spec/lib/data_importer_spec.rb
1586
+ - spec/lib/logger_spec.rb
1568
1587
  - spec/lib/migrations/vw_marty_postings.sql.expected
1569
1588
  - spec/lib/migrations_spec.rb
1570
1589
  - spec/lib/xl_spec.rb