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