logster 0.0.2 → 0.0.3

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: ab6df8f8f3acb188c5c06f1a5a191f8f2a737575
4
- data.tar.gz: 9c28f65b32c19ce9b3f4d52667689812c0ea14cf
3
+ metadata.gz: 2dad3528f0f66416dd6be075bc89aa5a143a6cc0
4
+ data.tar.gz: 6d187ba9d934f8fc1ba770cb1edf67c1e2d22fbc
5
5
  SHA512:
6
- metadata.gz: 7bbbb4fccfa6b6512694b3e52ad0ac7d107b555c54af0365676ac1e56e704573780912206fbc5acddd6d413c205c953eaddd110b928550db40d5a2138fc2b1a2
7
- data.tar.gz: 678e0f79da17b9e00c03b4df43aa13df8f6bf8da7e9923ab751cac13bd7f26e563687a4c63f9bb9a21204f38a4fc3b360f8c7dba90af90070817ee9d8fc59f0d
6
+ metadata.gz: 3fc3c01b210b3ca0d0b039971122fdce7654b8c59f91cb6ff01dc2ec255f3af7688faf46db7988c3c95f7b3aa7341274dfbc3689628ce871cfea4a0602870d99
7
+ data.tar.gz: 50271d90874e29781082f723017f121745793ef36fcf7c886b53da7e69549a8bdb53998c3d70088c46e08d917aafdcf67b420e1800e09fee2c212535658a3ccd
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 TODO: Write your name
1
+ Copyright (c) 2014 Sam Saffron
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  A web log viewer and logging framework for Rack applications
4
4
 
5
+ ## [Live Demo](http://logster.info/logs)
6
+
5
7
  ![logster](http://i.imgur.com/cvfcQpv.png)
6
8
 
7
9
  ## Installation
@@ -19,7 +21,7 @@ Logster will wire up `/logs` path in your Rails app in **development** mode only
19
21
 
20
22
  ## Usage
21
23
 
22
- Logster is in current development, at the moment the focus is on a decent tool for dev. Once that is complete production mode will be build.
24
+ Logster is in current development, at the moment the focus is on a decent tool for dev. Once that is complete production mode will be built.
23
25
 
24
26
  The concept is to have an embedded "exception reporting service" admins can view on live sites.
25
27
 
@@ -36,3 +38,7 @@ Logster UI is built using [Ember.js](http://emberjs.com/)
36
38
  3. Commit your changes (`git commit -am 'Add some feature'`)
37
39
  4. Push to the branch (`git push origin my-new-feature`)
38
40
  5. Create a new Pull Request
41
+
42
+ # CHANGELOG
43
+
44
+ - 2014-05-07: Started changelog :)
@@ -101,6 +101,15 @@ App.MessageCollection = Em.Object.extend({
101
101
  filter: this.get("filter").join("_")
102
102
  };
103
103
 
104
+ search = this.get("search");
105
+ if (!_.isEmpty(search)) {
106
+ data.search = search;
107
+ var regexSearch = this.get("regexSearch");
108
+ if(regexSearch) {
109
+ data.regex_search = "true";
110
+ }
111
+ }
112
+
104
113
  if(opts.before){
105
114
  data.before = opts.before;
106
115
  }
@@ -162,6 +171,23 @@ App.MessageCollection = Em.Object.extend({
162
171
  });
163
172
  },
164
173
 
174
+ regexSearch: function() {
175
+ search = this.get("search");
176
+ if( search &&
177
+ search.length > 2 &&
178
+ search[0] === "/"
179
+ ){
180
+ var match = search.match(/\/(.*)\/(.*)/);
181
+ if(match && match.length === 3){
182
+ try {
183
+ return new RegExp(match[1], match[2]);
184
+ } catch(err) {
185
+ // don't care
186
+ }
187
+ }
188
+ }
189
+ }.property("search"),
190
+
165
191
  toMessages: function(messages){
166
192
  return messages.map(function(m){
167
193
  return App.Message.create(m);
@@ -182,7 +208,8 @@ App.IndexRoute = Em.Route.extend({
182
208
  "showInfo": true,
183
209
  "showWarn": true,
184
210
  "showErr": true,
185
- "showFatal": true
211
+ "showFatal": true,
212
+ "search": ''
186
213
  });
187
214
  controller.set("initialized", true);
188
215
  model.reload();
@@ -228,6 +255,19 @@ App.IndexController = Em.Controller.extend({
228
255
  "showFatal"
229
256
  ),
230
257
 
258
+ searchChanged: function(){
259
+ var search = this.get("search");
260
+ var model = this.get("model");
261
+ model.set("search", search);
262
+
263
+ if(this.get("initialized")){
264
+ model.reload();
265
+ }
266
+ }.observes(
267
+ "search"
268
+ ),
269
+
270
+
231
271
  checkIfAtBottom: function(){
232
272
  if (this.checkedBottom) {
233
273
  return;
@@ -44,5 +44,8 @@
44
44
  {{input type="checkbox" checked=showFatal}}
45
45
  Fatal
46
46
  </label>
47
+ <label class="search">
48
+ {{input type="textfield" placeholder="Search" value=search}}
49
+ </label>
47
50
  </div>
48
51
  </div>
@@ -68,7 +68,14 @@ th.time{
68
68
  color: #777;
69
69
  }
70
70
 
71
+ .search input {
72
+ border: 1px solid #DDD;
73
+ padding: 3px;
74
+ }
71
75
 
76
+ .regex input {
77
+ margin-left: 2px !important;
78
+ }
72
79
 
73
80
  tr.show-more {
74
81
  text-align: center;
@@ -155,7 +162,7 @@ tr.show-more {
155
162
  }
156
163
  .action-panel input {
157
164
  position: relative;
158
- top: 2px;
165
+ top: -1px;
159
166
  margin-left: 20px;
160
167
  }
161
168
 
data/lib/logster.rb CHANGED
@@ -1,7 +1,31 @@
1
1
  require 'logster/logger'
2
2
  require 'logster/message'
3
+ require 'logster/configuration'
3
4
 
4
5
  module Logster
6
+ def self.logger=(logger)
7
+ @logger = logger
8
+ end
9
+
10
+ def self.logger
11
+ @logger
12
+ end
13
+
14
+ def self.store=(store)
15
+ @store=store
16
+ end
17
+
18
+ def self.store
19
+ @store
20
+ end
21
+
22
+ def self.config=(config)
23
+ @config = config
24
+ end
25
+
26
+ def self.config
27
+ @config ||= Configuration.new
28
+ end
5
29
  end
6
30
 
7
31
  if defined?(::Rails) && ::Rails::VERSION::MAJOR.to_i >= 3
@@ -0,0 +1,5 @@
1
+ module Logster
2
+ class Configuration
3
+ attr_accessor :authorize_callback, :subdirectory
4
+ end
5
+ end
@@ -9,11 +9,26 @@ module Logster
9
9
  @store = store
10
10
  end
11
11
 
12
+ def chain(logger)
13
+ @chained ||= []
14
+ @chained << logger
15
+ end
16
+
12
17
  def add(severity, message, progname, &block)
13
18
  if severity < @level
14
19
  return true
15
20
  end
16
21
 
22
+ if @chained
23
+ i = 0
24
+ # micro optimise for logging
25
+ while i < @chained.length
26
+ # TODO double yielding blocks
27
+ @chained[i].add(severity, message, progname, &block)
28
+ i += 1
29
+ end
30
+ end
31
+
17
32
  progname ||= @progname
18
33
  if message.nil?
19
34
  if block_given?
@@ -24,7 +39,7 @@ module Logster
24
39
  end
25
40
  end
26
41
 
27
- @store.report(severity, progname, message)
42
+ @store.report(severity, progname, message)
28
43
 
29
44
  end
30
45
  end
@@ -5,23 +5,32 @@ module Logster
5
5
  class Viewer
6
6
 
7
7
  PATH_INFO = "PATH_INFO".freeze
8
+ SCRIPT_NAME = "SCRIPT_NAME".freeze
8
9
 
9
- def initialize(app, config)
10
+ def initialize(app)
10
11
  @app = app
11
- @logs_path = config[:path] || "/logs"
12
- @path_regex = Regexp.new("(#{@logs_path}$)|(#{@logs_path}(/.*))$")
13
12
 
14
- @store = config[:store] or raise ArgumentError.new("store")
13
+ @logs_path = Logster.config.subdirectory || "/logs"
14
+ @path_regex = Regexp.new("^(#{@logs_path}$)|^(#{@logs_path}(/.*))$")
15
+ @store = Logster.store or raise ArgumentError.new("store")
15
16
 
16
17
  @assets_path = File.expand_path("../../../../assets", __FILE__)
17
18
  @fileserver = Rack::File.new(@assets_path)
19
+ @authorize_callback = Logster.config.authorize_callback
18
20
  end
19
21
 
20
22
  def call(env)
21
23
  path = env[PATH_INFO]
24
+ script_name = env[SCRIPT_NAME]
25
+
26
+ if script_name && script_name.length > 0
27
+ path = script_name + path
28
+ end
22
29
 
23
30
  if resource = resolve_path(path)
24
31
 
32
+ return @app.call(env) if @authorize_callback && !@authorize_callback.call(env)
33
+
25
34
  if resource =~ /\.js$|\.handlebars$|\.css$/
26
35
  env[PATH_INFO] = resource
27
36
  @fileserver.call(env)
@@ -50,6 +59,11 @@ module Logster
50
59
  opts[:severity] = filter
51
60
  end
52
61
 
62
+ if search = req["search"]
63
+ search = (parse_regex(search) || search) if req["regex_search"] == "true"
64
+ opts[:search] = search
65
+ end
66
+
53
67
  payload = {
54
68
  messages: @store.latest(opts),
55
69
  total: @store.count
@@ -59,6 +73,14 @@ module Logster
59
73
  [200, {"Content-Type" => "application/json"}, [json]]
60
74
  end
61
75
 
76
+ def parse_regex(string)
77
+ if string =~ /\/(.+)\/(.*)/
78
+ s = $1
79
+ flags = Regexp::IGNORECASE if $2 && $2.include?("i")
80
+ Regexp.new(s, flags) rescue nil
81
+ end
82
+ end
83
+
62
84
  def resolve_path(path)
63
85
  if path =~ @path_regex
64
86
  $3 || "/"
@@ -1,26 +1,34 @@
1
1
  module Logster::Rails
2
2
 
3
3
  def self.set_logger(config)
4
- return unless Rails.env.development?
4
+ return unless Rails.env.development? || Rails.env.production?
5
5
 
6
6
  if defined?(Redis)
7
7
  require 'logster/middleware/viewer'
8
8
  require 'logster/redis_store'
9
9
 
10
- store = Logster::RedisStore.new
10
+ store = Logster.store ||= Logster::RedisStore.new
11
+ store.level = Logger::Severity::WARN if Rails.env.production?
12
+
11
13
  logger = Logster::Logger.new(store)
14
+ logger.chain(::Rails.logger)
15
+ logger.level = ::Rails.logger.level
12
16
 
13
- ::Rails.logger = config.logger = logger
17
+ Logster.logger = ::Rails.logger = config.logger = logger
14
18
  else
15
19
  Rails.logger.warn "Not loading logster, Redis missing"
16
20
  end
17
21
  end
18
22
 
19
23
  def self.initialize!(app)
20
- return unless Rails.env.development?
24
+ return unless Rails.env.development? || Rails.env.production?
21
25
 
22
26
  if Logster::Logger === Rails.logger
23
- app.middleware.use Logster::Middleware::Viewer, store: Rails.logger.store
27
+ if Rails.env.development?
28
+ # in production you must mount in routes.rb
29
+ # or by inserting middleware
30
+ app.middleware.use Logster::Middleware::Viewer
31
+ end
24
32
 
25
33
  app.config.colorize_logging = false
26
34
  end
@@ -3,7 +3,8 @@ require 'json'
3
3
  module Logster
4
4
  class RedisStore
5
5
 
6
- attr_accessor :max_backlog, :dedup, :max_retention, :skip_empty
6
+ attr_accessor :level, :redis, :max_backlog,
7
+ :dedup, :max_retention, :skip_empty
7
8
 
8
9
  def initialize(redis = nil)
9
10
  @redis = redis || Redis.new
@@ -16,6 +17,7 @@ module Logster
16
17
 
17
18
  def report(severity, progname, message)
18
19
  return if (!message || (String === message && message.empty?)) && skip_empty
20
+ return if level && severity < level
19
21
 
20
22
  message = Message.new(severity, progname, message)
21
23
  @redis.rpush(list_key, message.to_json)
@@ -37,46 +39,11 @@ module Logster
37
39
  severity = opts[:severity]
38
40
  before = opts[:before]
39
41
  after = opts[:after]
40
- start = -limit
41
- finish = -1
42
-
43
- if before || after
44
- # inefficient may change to sorted list, also timing issues
45
- found = nil
46
- find = before || after
47
-
48
- while !found
49
- items = @redis.lrange(list_key, start, finish)
50
-
51
- break unless items && items.length > 0
42
+ search = opts[:search]
52
43
 
53
- found = items.index do |i|
54
- Message.from_json(i).key == find
55
- end
44
+ start, finish = find_location(before, after, limit)
56
45
 
57
- if items.length < limit
58
- found += limit - items.length if found
59
- break
60
- end
61
- break if found
62
- start -= limit
63
- finish -= limit
64
- end
65
-
66
- if found
67
- if before
68
- offset = -(limit - found)
69
- else
70
- offset = found + 1
71
- end
72
-
73
- start += offset
74
- finish += offset
75
-
76
- finish = -1 if finish > -1
77
- return [] if start > -1
78
- end
79
- end
46
+ return [] unless start && finish
80
47
 
81
48
  results = []
82
49
 
@@ -91,6 +58,8 @@ module Logster
91
58
  row = Message.from_json(s)
92
59
  break if before && before == row.key
93
60
  row = nil if severity && !severity.include?(row.severity)
61
+
62
+ row = filter_search(row, search)
94
63
  temp << row if row
95
64
  end
96
65
 
@@ -115,6 +84,62 @@ module Logster
115
84
 
116
85
  protected
117
86
 
87
+ def find_location(before, after, limit)
88
+ start = -limit
89
+ finish = -1
90
+
91
+ return [start,finish] unless before || after
92
+
93
+ # inefficient may change to sorted list, also timing issues
94
+ found = nil
95
+ find = before || after
96
+
97
+ while !found
98
+ items = @redis.lrange(list_key, start, finish)
99
+
100
+ break unless items && items.length > 0
101
+
102
+ found = items.index do |i|
103
+ Message.from_json(i).key == find
104
+ end
105
+
106
+ if items.length < limit
107
+ found += limit - items.length if found
108
+ break
109
+ end
110
+ break if found
111
+ start -= limit
112
+ finish -= limit
113
+ end
114
+
115
+ if found
116
+ if before
117
+ offset = -(limit - found)
118
+ else
119
+ offset = found + 1
120
+ end
121
+
122
+ start += offset
123
+ finish += offset
124
+
125
+ finish = -1 if finish > -1
126
+ return nil if start > -1
127
+ end
128
+
129
+ [start, finish]
130
+ end
131
+
132
+ def filter_search(row, search)
133
+ return row unless row && search
134
+
135
+ if Regexp === search
136
+ row if row.message =~ search
137
+ elsif row.message.include?(search)
138
+ row
139
+ end
140
+
141
+ end
142
+
118
143
 
119
144
  def list_key
120
145
  @list_key ||= "__LOGSTER__LOG"
@@ -1,3 +1,3 @@
1
1
  module Logster
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -5,18 +5,46 @@ require 'logster/middleware/viewer'
5
5
 
6
6
  class TestViewer < Minitest::Test
7
7
 
8
+ class BrokenApp
9
+ def call(env)
10
+ [500, {}, ["broken"]]
11
+ end
12
+ end
13
+
14
+ def setup
15
+ Logster.store = Logster::RedisStore.new
16
+ end
17
+
8
18
  def teardown
19
+ Logster.config.subdirectory = nil
20
+ Logster.config.authorize_callback = nil
21
+ Logster.store = nil
9
22
  end
10
23
 
11
24
  def viewer
12
25
  @viewer ||= begin
13
- store = Logster::RedisStore.new
14
- Logster::Middleware::Viewer.new(nil, store: store, path: "/logsie")
26
+ Logster.config.subdirectory = "/logsie"
27
+ Logster::Middleware::Viewer.new(nil)
15
28
  end
16
29
  end
17
30
 
31
+ def test_authorize_callback
32
+ Logster.config.authorize_callback = lambda{ |env|
33
+ env["authorized"]
34
+ }
35
+
36
+ viewer = Logster::Middleware::Viewer.new(BrokenApp.new)
37
+ status, _ = viewer.call({"PATH_INFO" => "/logs"})
38
+ assert_equal(500, status)
39
+
40
+ status, _ = viewer.call({"PATH_INFO" => "/logs", "authorized" => true})
41
+ assert_equal(200, status)
42
+ end
43
+
18
44
  def test_path_resolution
19
45
  assert_nil(viewer.send(:resolve_path, "/logs"))
46
+ assert_nil(viewer.send(:resolve_path, "/admin/logsie"))
47
+ assert_nil(viewer.send(:resolve_path, "/admin/logsie/bla"))
20
48
  assert_equal("/",viewer.send(:resolve_path, "/logsie"))
21
49
  assert_equal("/",viewer.send(:resolve_path, "/logsie/"))
22
50
  assert_equal("/hello/world",viewer.send(:resolve_path, "/logsie/hello/world"))
@@ -31,4 +59,8 @@ class TestViewer < Minitest::Test
31
59
  assert_equal(200, result)
32
60
  end
33
61
 
62
+ def test_regex_parse
63
+ assert_equal(/hello/i, viewer.send(:parse_regex, '/hello/i'))
64
+ end
65
+
34
66
  end
@@ -0,0 +1,29 @@
1
+ require_relative '../test_helper'
2
+ require 'logster/logger'
3
+ require 'logger'
4
+
5
+ class NullStore
6
+ def report(severity,progname,message)
7
+ end
8
+ end
9
+
10
+ class TestLogger < Minitest::Test
11
+
12
+ def setup
13
+ @logger = Logster::Logger.new(NullStore.new)
14
+ end
15
+
16
+ def teardown
17
+ end
18
+
19
+ def test_chain
20
+ io = StringIO.new
21
+ @logger.chain Logger.new(io)
22
+ @logger.warn "boom"
23
+
24
+ assert_match(/W,.*boom/, io.string)
25
+
26
+ end
27
+
28
+ end
29
+
@@ -109,4 +109,28 @@ class TestRedisStore < Minitest::Test
109
109
  assert_equal(1, latest.length);
110
110
  end
111
111
 
112
+ def test_search
113
+ @store.report(Logger::INFO, "test", "A")
114
+ @store.report(Logger::INFO, "test", "B")
115
+
116
+ messages = @store.latest
117
+ assert_equal(2, messages.length)
118
+
119
+ latest = @store.latest(search: "B")
120
+
121
+ assert_equal(1, latest.length)
122
+ end
123
+
124
+ def test_regex_search
125
+ @store.report(Logger::INFO, "test", "pattern_1")
126
+ @store.report(Logger::INFO, "test", "pattern_2")
127
+
128
+ messages = @store.latest
129
+ assert_equal(2, messages.length)
130
+
131
+ latest = @store.latest(search: /^pattern_[1]$/)
132
+
133
+ assert_equal(1, latest.length)
134
+ end
135
+
112
136
  end
data/test/test_helper.rb CHANGED
@@ -1,6 +1,7 @@
1
- require 'logster'
2
- require 'redis'
3
1
  require 'minitest'
4
2
  require 'minitest/unit'
5
3
  require 'minitest/autorun'
6
4
  require 'minitest/pride'
5
+ require 'logster'
6
+ require 'redis'
7
+
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.2
4
+ version: 0.0.3
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-05 00:00:00.000000000 Z
11
+ date: 2014-05-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -119,6 +119,7 @@ files:
119
119
  - assets/javascript/templates/message.handlebars
120
120
  - assets/stylesheets/app.css
121
121
  - lib/logster.rb
122
+ - lib/logster/configuration.rb
122
123
  - lib/logster/logger.rb
123
124
  - lib/logster/message.rb
124
125
  - lib/logster/middleware/reporter.rb
@@ -128,6 +129,7 @@ files:
128
129
  - lib/logster/version.rb
129
130
  - logster.gemspec
130
131
  - test/logster/middleware/test_viewer.rb
132
+ - test/logster/test_logger.rb
131
133
  - test/logster/test_redis_store.rb
132
134
  - test/test_helper.rb
133
135
  homepage: ''
@@ -156,5 +158,6 @@ specification_version: 4
156
158
  summary: UI for viewing logs in Rack
157
159
  test_files:
158
160
  - test/logster/middleware/test_viewer.rb
161
+ - test/logster/test_logger.rb
159
162
  - test/logster/test_redis_store.rb
160
163
  - test/test_helper.rb