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 +4 -4
- data/README.md +9 -1
- data/assets/javascript/app.js +96 -24
- data/assets/javascript/components/message-info.handlebars +19 -0
- data/assets/javascript/components/tab-link.handlebars +1 -0
- data/assets/javascript/components/tabbed-section.handlebars +1 -1
- data/assets/javascript/templates/index.handlebars +2 -10
- data/assets/javascript/templates/show.handlebars +5 -0
- data/assets/stylesheets/app.css +11 -0
- data/lib/logster/message.rb +6 -3
- data/lib/logster/middleware/viewer.rb +45 -3
- data/lib/logster/redis_store.rb +91 -11
- data/lib/logster/version.rb +1 -1
- data/test/logster/middleware/test_viewer.rb +4 -0
- data/test/logster/test_redis_store.rb +70 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ffac6b3153b63a23916214aa819293b81483ce41
|
4
|
+
data.tar.gz: 3750f61bc3cddfd46bf8069d6a35ac27349cd59c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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? }
|
data/assets/javascript/app.js
CHANGED
@@ -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 !=
|
423
|
-
|
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 {{
|
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
|
-
|
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>
|
data/assets/stylesheets/app.css
CHANGED
@@ -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;
|
data/lib/logster/message.rb
CHANGED
@@ -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
|
-
|
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
|
data/lib/logster/redis_store.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
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 ||= "
|
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
|
data/lib/logster/version.rb
CHANGED
@@ -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.
|
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-
|
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
|