logster 0.0.10 → 0.0.11

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