logster 0.0.10 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: eadf4ac198a2256f98ce3bfcb70be74dbe2056eb
4
- data.tar.gz: c85f2dac78f07d7cc0b77eedcafd1572942e9ebe
3
+ metadata.gz: ffac6b3153b63a23916214aa819293b81483ce41
4
+ data.tar.gz: 3750f61bc3cddfd46bf8069d6a35ac27349cd59c
5
5
  SHA512:
6
- metadata.gz: caee98bce33d83120e194452898da0533a1ffcd180c05f8c893563b0039d453b3cd6620cf9a5ffa333722ee1cabc17eefb1c2c61acddfb31afb739f61642b2c9
7
- data.tar.gz: 7b35e552e33df43b0d3e513f68cc380b21064b48df88b82f5aaa333345844b457f7f677a0ef79a1492e4049cf9bb5d2d91b1333bb42fdc6abaf73a2c60723c85
6
+ metadata.gz: a5099353cb310d52c6095703370c29538c20fae71855b9d4c840cfb0f445c97a7d7805a730eb185b88f92b1374cb48995b05233cb291ba2b8800d7bda5c28d53
7
+ data.tar.gz: 4b0f69413fa4fd45f1d328298aac52232d0578204332263c5b279d64c017c2d09bc9a1d6169095f1e69a5e6c311a0c41742a109e96766d9d44a4df05ee3c3a1e
data/README.md CHANGED
@@ -17,7 +17,15 @@ And then execute:
17
17
 
18
18
  $ bundle
19
19
 
20
- Logster will wire up `/logs` path in your Rails app in **development** mode only. To wire up in production you will need to set
20
+ Make Logster available in your development environment by adding the following to your routes.rb:
21
+
22
+ ```
23
+ if Rails.env == "development"
24
+ mount Logster::Web => "/logs"
25
+ end
26
+ ```
27
+
28
+ Logster will be available in your Rails app in **development** mode only. To wire up in production you will need to set
21
29
 
22
30
  ```
23
31
  Logster.config.authorize_callback = lambda{|env| your_own_can_see_logs? }
@@ -27,17 +27,44 @@ App.ajax = function(url, settings) {
27
27
  return $.ajax(Logger.rootPath + url, settings);
28
28
  };
29
29
 
30
+ App.preloadOrAjax = function(url, settings) {
31
+ var preloaded = Logger.preload[url];
32
+ if (preloaded) {
33
+ // return a pseudo-XHR
34
+ return {
35
+ success: function(callback) {
36
+ setTimeout(function() {
37
+ callback(preloaded);
38
+ }, 0);
39
+ return this;
40
+ },
41
+ error: function() { return this; }
42
+ };
43
+ } else {
44
+ return App.ajax(url, settings);
45
+ }
46
+ };
30
47
 
31
48
  App.Router.map(function(){
32
49
  this.route("index", { path: "/" });
50
+ this.route("show", { path: "/show/:id" });
33
51
  });
34
52
 
35
53
  App.Message = Ember.Object.extend({
36
54
 
37
55
  MAX_LEN: 200,
38
56
 
39
- expand: function(){
40
- this.set("expanded",true);
57
+ expand: function() {
58
+ this.set("expanded", true);
59
+ },
60
+
61
+ protect: function() {
62
+ this.set('saved', true);
63
+ return App.ajax("/protect/" + this.get('key'), { type: "PUT" });
64
+ },
65
+ unprotect: function() {
66
+ this.set('saved', false);
67
+ return App.ajax("/protect/" + this.get('key'), { type: "DELETE" });
41
68
  },
42
69
 
43
70
  hasMore: function(){
@@ -47,6 +74,10 @@ App.Message = Ember.Object.extend({
47
74
  return !expanded && message.length > this.MAX_LEN;
48
75
  }.property("message", "expanded"),
49
76
 
77
+ shareUrl: function() {
78
+ return Logger.rootPath + "/show/" + this.get('key');
79
+ }.property("key"),
80
+
50
81
  displayMessage: function(){
51
82
  var message = this.get("message");
52
83
  var expanded = this.get("expanded");
@@ -143,17 +174,17 @@ App.MessageCollection = Em.Object.extend({
143
174
 
144
175
  App.ajax("/messages.json", {
145
176
  data: data
146
- }).success(function(data){
147
- if(data.messages.length > 0) {
177
+ }).success(function(data) {
178
+ if (data.messages.length > 0) {
148
179
  var newRows = self.toMessages(data.messages);
149
180
  var messages = self.get("messages");
150
- if(opts.before) {
181
+ if (opts.before) {
151
182
  messages.unshiftObjects(newRows);
152
183
  } else {
153
184
  messages.addObjects(newRows);
154
185
  }
155
186
  }
156
- self.set("total",data.total);
187
+ self.set("total", data.total);
157
188
  });
158
189
  },
159
190
 
@@ -221,10 +252,11 @@ App.MessageCollection = Em.Object.extend({
221
252
 
222
253
  App.IndexRoute = Em.Route.extend({
223
254
  model: function(){
255
+ // TODO from preload json?
224
256
  return App.MessageCollection.create();
225
257
  },
226
258
 
227
- setupController: function(controller, model){
259
+ setupController: function(controller, model) {
228
260
  this._super(controller, model);
229
261
  controller.setProperties({
230
262
  "showDebug": true,
@@ -239,6 +271,27 @@ App.IndexRoute = Em.Route.extend({
239
271
  }
240
272
  });
241
273
 
274
+ App.ShowRoute = Em.Route.extend({
275
+ model: function(params) {
276
+ var self = this;
277
+ return new Promise(function(resolve, reject) {
278
+ App.preloadOrAjax("/show/" + params.id + ".json").success(function(json) {
279
+ resolve(App.Message.create(json));
280
+ }).error(reject);
281
+ });
282
+ },
283
+
284
+ actions: {
285
+ protect: function(message) {
286
+ this.get('model').protect();
287
+ },
288
+
289
+ unprotect: function(message) {
290
+ this.get('model').unprotect();
291
+ }
292
+ }
293
+ });
294
+
242
295
  App.IndexController = Em.Controller.extend({
243
296
  actions: {
244
297
  expandMessage: function(message){
@@ -251,6 +304,16 @@ App.IndexController = Em.Controller.extend({
251
304
 
252
305
  loadMore: function(){
253
306
  return this.get('model').loadMore();
307
+ },
308
+
309
+ protect: function(message) {
310
+ this.get('currentMessage').protect().success(function() {
311
+ self.transitionToRoute("show", {id: self.get('key')});
312
+ });
313
+ },
314
+
315
+ unprotect: function(message) {
316
+ this.get('currentMessage').unprotect();
254
317
  }
255
318
  },
256
319
 
@@ -417,10 +480,11 @@ App.ApplicationView = Em.View.extend({
417
480
  $('.auto-update-time').each(function(){
418
481
  var newTime = moment(
419
482
  parseInt(this.getAttribute('data-timestamp'),10)
420
- ).fromNow();
483
+ ).fromNow(),
484
+ elem = this;
421
485
 
422
- if(newTime != this.innerText) {
423
- this.innerText = newTime;
486
+ if(newTime != elem.innerText) {
487
+ elem.innerText = newTime;
424
488
  }
425
489
 
426
490
  });
@@ -439,26 +503,29 @@ Handlebars.registerHelper('timeAgo', function(prop, options){
439
503
  return new Handlebars.SafeString(formatted);
440
504
  });
441
505
 
442
-
443
506
  App.TabbedSectionComponent = Ember.Component.extend({
444
507
  tabs: Em.A(),
445
- selectTab: function(view){
508
+ selectTab: function(view) {
509
+ if (view.get('isLink')) {
510
+ this.triggerAction(view.get('action'));
511
+ return;
512
+ }
513
+
446
514
  var selected = this.get("selected");
447
- if(selected){
448
- selected.set("active",false);
515
+ if (selected) {
516
+ selected.set("active", false);
449
517
  }
450
518
  this.set("selected", view);
451
519
  view.set("active", true);
452
520
  },
453
- addTab: function(tab){
521
+ addTab: function(tab) {
454
522
  this.get("tabs").addObject(tab);
455
- if(!this.get("selected")){
523
+ if (!this.get("selected") && !tab.get('isLink')) {
456
524
  this.selectTab(tab);
457
525
  }
458
526
  },
459
- removeTab: function(tab){
460
-
461
- if(this.get("selected") === tab){
527
+ removeTab: function(tab) {
528
+ if (this.get("selected") === tab) {
462
529
  this.set("selected", null);
463
530
  }
464
531
  this.get("tabs").removeObject(tab);
@@ -467,21 +534,26 @@ App.TabbedSectionComponent = Ember.Component.extend({
467
534
 
468
535
  App.TabContentsComponent = Ember.Component.extend({
469
536
  classNameBindings: ["active", ":content"],
537
+ isLink: false,
470
538
 
471
- invokeParent: function(name){
539
+ invokeParent: function(name) {
472
540
  var current = this.get("parentView");
473
- while(current && !current[name]) {
541
+ while (current && !current[name]) {
474
542
  current = current.get("parentView");
475
543
  }
476
- if(current){
544
+ if (current) {
477
545
  current[name](this);
478
546
  }
479
547
  },
480
548
 
481
- didInsertElement: function(){
549
+ didInsertElement: function() {
482
550
  this.invokeParent("addTab");
483
551
  },
484
- willDestroyElement: function(){
552
+ willDestroyElement: function() {
485
553
  this.invokeParent("removeTab");
486
554
  }
487
555
  });
556
+
557
+ App.TabLinkComponent = App.TabContentsComponent.extend({
558
+ isLink: true
559
+ });
@@ -0,0 +1,19 @@
1
+ <div class="message-info">
2
+ {{#tabbed-section}}
3
+ {{#tab-contents name="info" hint="show info"}}
4
+ <pre>{{currentMessage.message}}</pre>
5
+ {{#if currentMessage}}
6
+ <a {{bind-attr href=currentMessage.shareUrl}} class="share">Share</a>
7
+ {{#if currentMessage.saved}}
8
+ <a {{action unprotect currentMessage}} href class="save">Unprotect</a>
9
+ {{else}}
10
+ <a {{action protect currentMessage}} href class="save">Protect</a>
11
+ {{/if}}
12
+ {{/if}}
13
+ {{/tab-contents}}
14
+ {{#tab-contents name="backtrace" hint="show backtrace"}}<pre>{{currentMessage.backtrace}}</pre>{{/tab-contents}}
15
+ {{#if currentMessage.env}}
16
+ {{#tab-contents name="env" hint="show environment"}}<pre>{{currentMessage.envDebug}}</pre>{{/tab-contents}}
17
+ {{/if}}
18
+ {{/tabbed-section}}
19
+ </div>
@@ -0,0 +1 @@
1
+ {{yield}}
@@ -1,6 +1,6 @@
1
1
  {{yield}}
2
2
  <ul class="tabs">
3
3
  {{#each tab in tabs}}
4
- <li><a {{bind-attr title="tab.hint"}} href="#" {{bind-attr class="tab.active"}} {{action selectTab tab}}>{{tab.name}}</a></li>
4
+ <li><a title="{{unbound tab.hint}}" href="#" {{bind-attr class="tab.active"}} {{action selectTab tab}}>{{unbound tab.name}}</a></li>
5
5
  {{/each}}
6
6
  </ul>
@@ -19,15 +19,7 @@
19
19
  </div>
20
20
  <div id="divider"></div>
21
21
  <div id="bottom-panel">
22
- <div class="message-info">
23
- {{#tabbed-section}}
24
- {{#tab-contents name="info" hint="show info"}}<pre>{{currentMessage.message}}</pre>{{/tab-contents}}
25
- {{#tab-contents name="backtrace" hint="show backtrace"}}<pre>{{currentMessage.backtrace}}</pre>{{/tab-contents}}
26
- {{#if currentMessage.env}}
27
- {{#tab-contents name="env" hint="show environment"}}<pre>{{currentMessage.envDebug}}</pre>{{/tab-contents}}
28
- {{/if}}
29
- {{/tabbed-section}}
30
- </div>
22
+ {{message-info currentMessage=currentMessage}}
31
23
 
32
24
  <div class="action-panel">
33
25
  <label class="debug">
@@ -54,4 +46,4 @@
54
46
  {{input type="textfield" placeholder="Search" value=search}}
55
47
  </label>
56
48
  </div>
57
- </div>
49
+ </div>
@@ -0,0 +1,5 @@
1
+ <div id='top-panel'></div>
2
+ <div id="divider"></div>
3
+ <div id="bottom-panel">
4
+ {{message-info currentMessage=model}}
5
+ </div>
@@ -100,6 +100,17 @@ tr.show-more {
100
100
  font-size: 12px;
101
101
  }
102
102
 
103
+ .share {
104
+ position: fixed;
105
+ bottom: 70px;
106
+ right: 10px;
107
+ }
108
+ .save {
109
+ position: fixed;
110
+ bottom: 70px;
111
+ right: 50px;
112
+ }
113
+
103
114
  #divider {
104
115
  position: fixed;
105
116
  bottom: 310px;
@@ -12,7 +12,7 @@ module Logster
12
12
  HTTP_X_REAL_IP
13
13
  }
14
14
 
15
- attr_accessor :timestamp, :severity, :progname, :message, :key, :backtrace, :env
15
+ attr_accessor :timestamp, :severity, :progname, :message, :key, :backtrace, :count, :env
16
16
 
17
17
  def initialize(severity, progname, message, timestamp = nil, key = nil)
18
18
  @timestamp = timestamp || get_timestamp
@@ -21,6 +21,7 @@ module Logster
21
21
  @message = message
22
22
  @key = key || SecureRandom.hex
23
23
  @backtrace = nil
24
+ @count = 1
24
25
  end
25
26
 
26
27
  def to_h
@@ -31,12 +32,13 @@ module Logster
31
32
  timestamp: @timestamp,
32
33
  key: @key,
33
34
  backtrace: @backtrace,
35
+ count: @count,
34
36
  env: @env
35
37
  }
36
38
  end
37
39
 
38
- def to_json(opts=nil)
39
- JSON.fast_generate(to_h,opts)
40
+ def to_json(opts = nil)
41
+ JSON.fast_generate(to_h, opts)
40
42
  end
41
43
 
42
44
  def self.from_json(json)
@@ -48,6 +50,7 @@ module Logster
48
50
  parsed["key"] )
49
51
  msg.backtrace = parsed["backtrace"]
50
52
  msg.env = parsed["env"]
53
+ msg.count = parsed["count"]
51
54
  msg
52
55
  end
53
56
 
@@ -6,6 +6,7 @@ module Logster
6
6
 
7
7
  PATH_INFO = "PATH_INFO".freeze
8
8
  SCRIPT_NAME = "SCRIPT_NAME".freeze
9
+ REQUEST_METHOD = "REQUEST_METHOD".freeze
9
10
 
10
11
  def initialize(app)
11
12
  @app = app
@@ -28,13 +29,42 @@ module Logster
28
29
 
29
30
  if resource = resolve_path(path)
30
31
 
31
- return @app.call(env) if !Logster.config.authorize_callback.call(env)
32
+ # easier to debug, call per line
33
+ unless Logster.config.authorize_callback.call(env)
34
+ return @app.call(env)
35
+ end
32
36
 
33
37
  if resource =~ /\.js$|\.handlebars$|\.css$/
34
38
  env[PATH_INFO] = resource
35
39
  @fileserver.call(env)
36
40
  elsif resource.start_with?("/messages.json")
37
41
  serve_messages(Rack::Request.new(env))
42
+ elsif resource =~ /\/protect\/([0-9a-f]+)$/
43
+ key = $1
44
+ if env[REQUEST_METHOD] == "PUT"
45
+ Logster.store.protect(key)
46
+ return [200, {"Content-Type" => "text/plain; charset=utf-8"}, ["OK"]]
47
+ elsif env[REQUEST_METHOD] == "DELETE"
48
+ Logster.store.unprotect(key)
49
+ return [200, {"Content-Type" => "text/plain; charset=utf-8"}, ["OK"]]
50
+ else
51
+ return [405, {}, ["Only PUT and DELETE are supported for this URL"]]
52
+ end
53
+ elsif resource =~ /\/show\/([0-9a-f]+)(\.json)?$/
54
+ key = $1
55
+ json = $2 == ".json"
56
+
57
+ message = Logster.store.get(key)
58
+ unless message
59
+ return [404, {}, ["Message not found"]]
60
+ end
61
+
62
+ if json
63
+ [200, {"Content-Type" => "application/json; charset=utf-8"}, [message.to_json]]
64
+ else
65
+ preload = preload_json({"/show/#{key}.json" => message})
66
+ [200, {"Content-Type" => "text/html; charset=utf-8"}, [body(preload)]]
67
+ end
38
68
  elsif resource == "/"
39
69
  [200, {"Content-Type" => "text/html; charset=utf-8"}, [body(preload_json)]]
40
70
  else
@@ -86,7 +116,9 @@ module Logster
86
116
  end
87
117
  end
88
118
 
89
- def preload_json
119
+ def preload_json(extra={})
120
+ values = {}
121
+ values.merge!(extra)
90
122
  end
91
123
 
92
124
  def css(name, attrs={})
@@ -132,16 +164,26 @@ JS
132
164
  #{handlebars("application")}
133
165
  #{handlebars("index")}
134
166
  #{handlebars("message")}
167
+ #{handlebars("show")}
168
+ #{component("message-info")}
135
169
  #{component("tabbed-section")}
136
170
  #{component("tab-contents")}
171
+ #{component("tab-link")}
137
172
  <script>
138
173
  window.Logger = {
139
- rootPath: "#{@logs_path}"
174
+ rootPath: "#{@logs_path}",
175
+ preload: #{JSON.fast_generate(preload)}
140
176
  };
141
177
  </script>
142
178
  </head>
143
179
  <body>
144
180
  #{script("app.js")}
181
+ <script>
182
+ App.Router.reopen({
183
+ rootURL: Logger.rootPath,
184
+ location: 'history'
185
+ });
186
+ </script>
145
187
  </body>
146
188
  </html>
147
189
  HTML
@@ -21,7 +21,7 @@ module Logster
21
21
  return if level && severity < level
22
22
  return if @ignore && @ignore.any?{|pattern| message =~ pattern}
23
23
 
24
- message = Message.new(severity, progname, message)
24
+ message = Logster::Message.new(severity, progname, message)
25
25
 
26
26
  if opts && backtrace = opts[:backtrace]
27
27
  message.backtrace = backtrace
@@ -33,14 +33,21 @@ module Logster
33
33
  message.populate_from_env(env)
34
34
  end
35
35
 
36
- @redis.rpush(list_key, message.to_json)
36
+ # multi for integrity
37
+ @redis.multi do
38
+ @redis.hset(hash_key, message.key, message.to_json)
39
+ @redis.rpush(list_key, message.key)
40
+ end
37
41
 
38
42
  # TODO make it atomic
39
43
  if @redis.llen(list_key) > @max_backlog
40
- @redis.lpop(list_key)
44
+ removed_key = @redis.lpop(list_key)
45
+ if removed_key && !@redis.sismember(protected_key, removed_key)
46
+ @redis.hdel(hash_key, removed_key)
47
+ end
41
48
  end
42
49
 
43
- nil
50
+ message
44
51
  end
45
52
 
46
53
  def count
@@ -63,7 +70,9 @@ module Logster
63
70
  direction = after ? 1 : -1
64
71
 
65
72
  begin
66
- rows = @redis.lrange(list_key, start, finish) || []
73
+ keys = @redis.lrange(list_key, start, finish) || []
74
+ break unless keys and keys.count > 0
75
+ rows = @redis.hmget(hash_key, keys)
67
76
 
68
77
  temp = []
69
78
 
@@ -91,19 +100,85 @@ module Logster
91
100
  results
92
101
  end
93
102
 
94
- def clear(severities=nil)
103
+ def clear
95
104
  @redis.del(list_key)
105
+ keys = @redis.smembers(protected_key) || []
106
+ if keys.empty?
107
+ @redis.del(hash_key)
108
+ else
109
+ protected = @redis.mapped_hmget(hash_key, *keys)
110
+ @redis.del(hash_key)
111
+ @redis.mapped_hmset(hash_key, protected)
112
+ end
113
+ end
114
+
115
+ def clear_all
116
+ @redis.del(list_key)
117
+ @redis.del(protected_key)
118
+ @redis.del(hash_key)
119
+ end
120
+
121
+ def get(message_key)
122
+ json = @redis.hget(hash_key, message_key)
123
+ return nil unless json
124
+
125
+ Message.from_json(json)
126
+ end
127
+
128
+ def protect(message_key)
129
+ index = find_message(list_key, message_key)
130
+ # can't save something we already lost
131
+ return false unless index
132
+
133
+ @redis.sadd(protected_key, message_key)
134
+ true
135
+ end
136
+
137
+ def unprotect(message_key)
138
+ value = @redis.hget(hash_key, message_key)
139
+ # this is a failure of retention
140
+ raise "Message already deleted?" unless value
141
+
142
+ @redis.srem(protected_key, message_key)
143
+
144
+ index = find_message(list_key, message_key)
145
+ if index == nil
146
+ # Message fell off list - delete
147
+ @redis.hdel(hash_key, message_key)
148
+ end
149
+
150
+ true
96
151
  end
97
152
 
98
153
  protected
99
154
 
155
+ def find_message(list, message_key)
156
+ limit = 50
157
+ start = 0
158
+ finish = limit - 1
159
+
160
+ found = nil
161
+ while found == nil
162
+ items = @redis.lrange(list, start, finish)
163
+
164
+ break unless items && items.length > 0
165
+
166
+ found = items.index(message_key)
167
+ break if found
168
+
169
+ start += limit
170
+ finish += limit
171
+ end
172
+
173
+ found
174
+ end
175
+
100
176
  def find_location(before, after, limit)
101
177
  start = -limit
102
178
  finish = -1
103
179
 
104
180
  return [start,finish] unless before || after
105
181
 
106
- # inefficient may change to sorted list, also timing issues
107
182
  found = nil
108
183
  find = before || after
109
184
 
@@ -112,9 +187,7 @@ module Logster
112
187
 
113
188
  break unless items && items.length > 0
114
189
 
115
- found = items.index do |i|
116
- Message.from_json(i).key == find
117
- end
190
+ found = items.index(find)
118
191
 
119
192
  if items.length < limit
120
193
  found += limit - items.length if found
@@ -155,8 +228,15 @@ module Logster
155
228
 
156
229
 
157
230
  def list_key
158
- @list_key ||= "__LOGSTER__LOG"
231
+ @list_key ||= "__LOGSTER__LATEST"
232
+ end
233
+
234
+ def hash_key
235
+ @hash_key ||= "__LOGSTER__MAP"
159
236
  end
160
237
 
238
+ def protected_key
239
+ @saved_key ||= "__LOGSTER__SAVED"
240
+ end
161
241
  end
162
242
  end
@@ -1,3 +1,3 @@
1
1
  module Logster
2
- VERSION = "0.0.10"
2
+ VERSION = "0.0.11"
3
3
  end
@@ -51,6 +51,10 @@ class TestViewer < Minitest::Test
51
51
  end
52
52
 
53
53
  def test_assets
54
+ Logster.config.authorize_callback = lambda{ |env|
55
+ true
56
+ }
57
+
54
58
  env = {}
55
59
  env["PATH_INFO"] = "/logsie/javascript/external/jquery.min.js"
56
60
  env["REQUEST_METHOD"] = "GET"
@@ -69,9 +69,20 @@ class TestRedisStore < Minitest::Test
69
69
 
70
70
  end
71
71
 
72
+ def test_get
73
+ a_message = @store.report(Logger::WARN, "test", "A")
74
+ b_message = @store.report(Logger::WARN, "test", "B")
75
+ @store.report(Logger::WARN, "test", "C")
76
+
77
+ assert_equal("A", @store.get(a_message.key).message)
78
+ assert_equal("B", @store.get(b_message.key).message)
79
+ end
80
+
72
81
  def test_backlog
73
82
  @store.max_backlog = 1
74
83
  @store.report(Logger::WARN, "test", "A")
84
+ @store.report(Logger::WARN, "test", "A")
85
+ @store.report(Logger::WARN, "test", "A")
75
86
  @store.report(Logger::WARN, "test", "B")
76
87
 
77
88
  latest = @store.latest
@@ -80,6 +91,65 @@ class TestRedisStore < Minitest::Test
80
91
  assert_equal("B", latest[0].message)
81
92
  end
82
93
 
94
+ def test_save_unsave
95
+ @store.max_backlog = 2
96
+ @store.report(Logger::WARN, "test", "A")
97
+ b_message = @store.report(Logger::WARN, "test", "B")
98
+ @store.protect b_message.key
99
+ c_message = @store.report(Logger::WARN, "test", "C")
100
+ @store.protect c_message.key
101
+ @store.report(Logger::WARN, "test", "D")
102
+
103
+ latest = @store.latest
104
+
105
+ assert_equal(2, latest.length)
106
+ assert_equal("C", latest[0].message)
107
+ assert_equal("D", latest[1].message)
108
+
109
+ # Saved messages still accessible by key
110
+ assert_equal("B", @store.get(b_message.key).message)
111
+
112
+ # Unsave does not delete message if still recent
113
+ @store.unprotect c_message.key
114
+ assert_equal("C", @store.get(c_message.key).message)
115
+
116
+ # Unsave *does* delete message if not recent
117
+ @store.unprotect b_message.key
118
+ assert_nil(@store.get(b_message.key))
119
+ end
120
+
121
+ def test_clear
122
+ 10.times do
123
+ @store.report(Logger::WARN, "test", "A")
124
+ end
125
+ # Protected messages are not deleted
126
+ b_message = @store.report(Logger::WARN, "test", "B")
127
+ @store.protect b_message.key
128
+ c_message = @store.report(Logger::WARN, "test", "C")
129
+ 10.times do
130
+ @store.report(Logger::WARN, "test", "D")
131
+ end
132
+
133
+ latest = @store.latest
134
+ assert_equal(22, latest.length)
135
+
136
+ @store.clear
137
+
138
+ latest = @store.latest
139
+ assert_equal(0, latest.length)
140
+ assert_equal("B", @store.get(b_message.key).message)
141
+ assert_nil(@store.get(c_message.key))
142
+ end
143
+
144
+ def test_hash_cleanup
145
+ @store.max_backlog = 2
146
+ a_message = @store.report(Logger::WARN, "test", "A")
147
+ @store.report(Logger::WARN, "test", "B")
148
+ @store.report(Logger::WARN, "test", "C")
149
+
150
+ assert_nil(@store.get(a_message.key))
151
+ end
152
+
83
153
  def test_filter_latest
84
154
  @store.report(Logger::INFO, "test", "A")
85
155
  @store.report(Logger::WARN, "test", "B")
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logster
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.10
4
+ version: 0.0.11
5
5
  platform: ruby
6
6
  authors:
7
7
  - UI for viewing logs in Rack
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-24 00:00:00.000000000 Z
11
+ date: 2014-07-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -108,7 +108,9 @@ files:
108
108
  - README.md
109
109
  - Rakefile
110
110
  - assets/javascript/app.js
111
+ - assets/javascript/components/message-info.handlebars
111
112
  - assets/javascript/components/tab-contents.handlebars
113
+ - assets/javascript/components/tab-link.handlebars
112
114
  - assets/javascript/components/tabbed-section.handlebars
113
115
  - assets/javascript/external/ember.js
114
116
  - assets/javascript/external/ember.min.js
@@ -119,6 +121,7 @@ files:
119
121
  - assets/javascript/templates/application.handlebars
120
122
  - assets/javascript/templates/index.handlebars
121
123
  - assets/javascript/templates/message.handlebars
124
+ - assets/javascript/templates/show.handlebars
122
125
  - assets/stylesheets/app.css
123
126
  - bower.json
124
127
  - lib/logster.rb