synapse_redis_logger 0.1.0

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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,22 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+ .idea
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Mason Jones
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,76 @@
1
+ = redis_logger
2
+
3
+ Application logging to redis.
4
+
5
+ == Version
6
+
7
+ Version 0.1.0
8
+
9
+ == Installation
10
+
11
+ sudo gem install redis_logger
12
+
13
+ You will also want to install the web interface for viewing the log entries:
14
+
15
+ http://github.com/masonoise/redis_logger-web
16
+
17
+
18
+ == Using RedisLogger
19
+
20
+ 1. Install redis (http://code.google.com/p/redis/) and redis_logger
21
+
22
+ 2. In your application code, place a call to RedisLogger, using one of: debug, warn, error. Each call is expected to
23
+ pass a Hash of data, and an optional list of groups to which you want the log entry to belong. For example, you
24
+ might want to log all of your requests to a group called "requests", so you might put this call into your
25
+ application_controller.rb file in a before_filter:
26
+
27
+ RedisLogger.debug({ "request" => request.url,
28
+ "remote_ip" => request.remote_ip,
29
+ "user_id" => current_user.id,
30
+ "username" => current_user.email
31
+ }, "requests")
32
+
33
+ That would store the Hash containing four key/value pairs into the log entry, place it automatically in the
34
+ "debug" group of entries, and also in the "requests" group of entries. Elsewhere in your code, you might log
35
+ all user signup activity into a "signup" group, for example. Using the web interface, you can view the entries
36
+ in a group or all the entries in an intersection of multiple groups -- for example, all errors in the signup
37
+ process.
38
+
39
+ If you want to use redis-cli to look things over instead of the web interface, here are the basics:
40
+
41
+ ./redis-cli
42
+ sort logger:set:debug limit 0 10 desc
43
+ ==> returns most recent 10 keys from the debug group, each of which maps to a Hash of log fields
44
+ hgetall log:1271274209
45
+ ==> returns the keys and values for that log entry
46
+ smembers logger:sets
47
+ ==> returns list of the groups that have been logged into
48
+
49
+ == Known Issues
50
+
51
+ 1. Currently the log keys are simply the longint timestamps of each entry. If multiple threads are going
52
+ it's very possible that they'll log at the same instant and only one entry will survive. Need to work
53
+ out a way to identify each server with a unique id that could be added to the log keys, such as
54
+ log:1:<timestamp>, log:2:<timestamp>, etc.
55
+ 2. It would be nice to cache the group names so it doesn't have to add the group to logger:sets each time.
56
+ 3. Support for viewing intersection of groups is limited to most recent 100 entries right now. It will
57
+ be nice to keep the stored intersection set around for viewing, pagination, and export, but we need to
58
+ have a way to clean up the stored sets periodically.
59
+
60
+ == Contributing
61
+
62
+ This is very young and simple thus far. If you have some cool ideas, go for it, give it a try, if it seems
63
+ good to you then get in touch and we'll see about merging it in.
64
+
65
+ * Fork the project.
66
+ * Make your feature addition or bug fix.
67
+ * Add tests for it. This is important so I don't break it in a
68
+ future version unintentionally.
69
+ * Commit, do not mess with rakefile, version, or history.
70
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
71
+ * Send me a pull request. Bonus points for topic branches.
72
+
73
+ == Author
74
+
75
+ Mason Jones ~~~ stuff@charnel.com ~~~ @masonoise
76
+
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "synapse_redis_logger"
8
+ gem.summary = %Q{Application logging into Redis}
9
+ gem.description = %Q{Provides support for applicationlogging to Redis, including grouping log entries and timestamping}
10
+ gem.email = "adams@synapse.com"
11
+ gem.homepage = "https://github.com/synapsepd/synapse_redis_logger"
12
+ gem.authors = ["masonoise", "saegey"]
13
+ gem.add_dependency('redis', '>= 1.0.4')
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'rake/testtask'
22
+ Rake::TestTask.new(:test) do |test|
23
+ test.libs << 'lib' << 'test'
24
+ test.pattern = 'test/**/test_*.rb'
25
+ test.verbose = true
26
+ end
27
+
28
+ begin
29
+ require 'rcov/rcovtask'
30
+ Rcov::RcovTask.new do |test|
31
+ test.libs << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+ rescue LoadError
36
+ task :rcov do
37
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
38
+ end
39
+ end
40
+
41
+ task :test => :check_dependencies
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "redis_logger #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,141 @@
1
+ require 'redis'
2
+
3
+ #
4
+ # redis_logger
5
+ # http://github.com/masonoise/redis_logger
6
+ #
7
+ # Enable logging into redis database, with support for grouping log entries into one or more
8
+ # groups.
9
+ #
10
+ # Log entries are stored in redis using keys of the form: log:<timestamp> with timestamp being
11
+ # a longint representation of the time. Log entries are then also added to a set associated
12
+ # with the log level, and then with any other sets specified in a list of "log groups".
13
+ #
14
+ # A set is maintained with the list of all of the groups: "logger:sets". Each group is
15
+ # represented by a set called "logger:set:<name>" where name is the group name.
16
+ #
17
+ class RedisLogger
18
+
19
+ def self.redis=(server)
20
+ host, port, db = server.split(':')
21
+ @redis = Redis.new(:host => host, :port => port, :thread_safe => true, :db => db)
22
+ end
23
+
24
+ def self.redis
25
+ return @redis if @redis
26
+ self.redis = 'localhost:6379'
27
+ self.redis
28
+ end
29
+
30
+ #
31
+ # Provide standard methods for various log levels. Each just calls the private
32
+ # add_entry() method passing in its level name to use as the group name.
33
+ #
34
+ # For each, the log_entry is a Hash of items to include in the log, and sets is
35
+ # either a string or an array of strings, which are the groups into which the
36
+ # entry will be added (in addition to the standard log level group).
37
+ #
38
+
39
+ def self.debug(log_entry, sets = nil)
40
+ add_entry(log_entry, "debug", sets)
41
+ end
42
+
43
+
44
+ def self.warn(log_entry, sets = nil)
45
+ add_entry(log_entry, "warn", sets)
46
+ end
47
+
48
+ # Standard method for error messages. See comment above about debug()
49
+ def self.error(log_entry, sets = nil)
50
+ add_entry(log_entry, "error", sets)
51
+ end
52
+
53
+ #
54
+ # Utility methods, mainly used by the web interface to display the lists of
55
+ # groups and entries.
56
+ #
57
+
58
+ #
59
+ # Get the list of all of the log groups that exist.
60
+ #
61
+ def self.groups
62
+ group_keys = redis.smembers "logger:sets"
63
+ groups = {}
64
+ group_keys.each do |k|
65
+ groups[k] = redis.scard("logger:set:#{k}")
66
+ end
67
+ return groups
68
+ end
69
+
70
+ # How many entries are in the specified log group?
71
+ def self.size(group)
72
+ redis.scard("logger:set:#{group}")
73
+ end
74
+
75
+ #
76
+ # Get the entries from a log group, with optional start index and per_page count,
77
+ # which default to 0/50 if not specified. Entries are returned in reverse order,
78
+ # most recent to oldest.
79
+ #
80
+ def self.entries(group, start=0, per_page=50)
81
+ entry_list = redis.sort("logger:set:#{group}", { :limit => [ start, per_page ], :order => "DESC" })
82
+ fetch_entries(entry_list)
83
+ end
84
+
85
+ #
86
+ # Get the entries for an intersection of groups. Takes an array of group names and
87
+ # returns the top 100 resulting entries. This is done by intersecting into a new set,
88
+ # fetching the first 100 entries, then deleting the set.
89
+ # TODO: Save the intersected set, allow paginating, and use a cron to delete the temp sets
90
+ #
91
+ def self.intersect(groups)
92
+ counter = redis.incrby("logger:index", 1)
93
+ redis.sinterstore("logger:inter:#{counter}", groups.collect {|g| "logger:set:#{g}"})
94
+ entry_list = redis.sort("logger:inter:#{counter}", { :limit => [ 0, 100 ], :order => "DESC" })
95
+ entries = fetch_entries(entry_list)
96
+ redis.del("logger:inter:#{counter}")
97
+ return entries
98
+ end
99
+
100
+ #
101
+ # Utility method to fetch entries given an array returned from a group set.
102
+ #
103
+ def self.fetch_entries(entry_list)
104
+ entries = []
105
+ entry_list.each do |e|
106
+ entries << redis.hgetall("log:#{e}")
107
+ end
108
+ return entries
109
+ end
110
+
111
+
112
+ private
113
+
114
+ #
115
+ # Add the log entry. The level is passed in separately rather than being merged with the
116
+ # other sets just in case we want to treat it differently in the future.
117
+ #
118
+ def self.add_entry(log_entry, level, sets = nil)
119
+ # TODO: Need to add unique id to timestamp to prevent multiple servers from causing collisions
120
+ tstamp = Time.now.to_i
121
+ log_entry["timestamp"] = tstamp
122
+ log_entry.each { |key, value| redis.hset "log:#{tstamp}", key, value }
123
+ # hmset() seems to be broken so skip it for now. Could pipeline the above commands.
124
+ #redis.hmset tstamp, *(log_entry.to_a)
125
+
126
+ # Add entry to the proper log-level set, and desired group sets if any
127
+ case sets.class
128
+ when 'String'
129
+ sets = [sets]
130
+ when 'NilClass'
131
+ sets = []
132
+ end
133
+ # TODO: Shouldn't need to add the level every time; could do it once at startup?
134
+ redis.sadd "logger:sets", level
135
+ redis.sadd "logger:set:#{level}", tstamp
136
+ sets.each do |set|
137
+ redis.sadd "logger:sets", set
138
+ redis.sadd "logger:set:#{set}", tstamp
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,54 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{synapse_redis_logger}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["masonoise", "saegey"]
12
+ s.date = %q{2010-04-16}
13
+ s.description = %q{Provides support for application logging to Redis, including grouping log entries and timestamping}
14
+ s.email = %q{adams@synapse.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ ".gitignore",
22
+ "LICENSE",
23
+ "README.rdoc",
24
+ "Rakefile",
25
+ "VERSION",
26
+ "lib/redis_logger.rb",
27
+ "redis_logger.gemspec",
28
+ "test/helper.rb",
29
+ "test/test_redis_logger.rb"
30
+ ]
31
+ s.homepage = %q{https://github.com/synapsepd/synapse_redis_logger}
32
+ s.rdoc_options = ["--charset=UTF-8"]
33
+ s.require_paths = ["lib"]
34
+ s.rubygems_version = %q{1.3.5}
35
+ s.summary = %q{Application logging into Redis}
36
+ s.test_files = [
37
+ "test/helper.rb",
38
+ "test/test_redis_logger.rb"
39
+ ]
40
+
41
+ if s.respond_to? :specification_version then
42
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
43
+ s.specification_version = 3
44
+
45
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
46
+ s.add_runtime_dependency(%q<redis>, [">= 1.0.4"])
47
+ else
48
+ s.add_dependency(%q<redis>, [">= 1.0.4"])
49
+ end
50
+ else
51
+ s.add_dependency(%q<redis>, [">= 1.0.4"])
52
+ end
53
+ end
54
+
@@ -0,0 +1,9 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+
4
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
5
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
+ require 'redis_logger'
7
+
8
+ class Test::Unit::TestCase
9
+ end
@@ -0,0 +1,7 @@
1
+ require 'helper'
2
+
3
+ class TestRedisLogger < Test::Unit::TestCase
4
+ def test_something_for_real
5
+ flunk "hey buddy, you should probably rename this file and start testing for real"
6
+ end
7
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: synapse_redis_logger
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - masonoise
9
+ - saegey
10
+ autorequire:
11
+ bindir: bin
12
+ cert_chain: []
13
+ date: 2010-04-16 00:00:00.000000000 Z
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: redis
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ! '>='
21
+ - !ruby/object:Gem::Version
22
+ version: 1.0.4
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ! '>='
29
+ - !ruby/object:Gem::Version
30
+ version: 1.0.4
31
+ description: Provides support for application logging to Redis, including grouping
32
+ log entries and timestamping
33
+ email: adams@synapse.com
34
+ executables: []
35
+ extensions: []
36
+ extra_rdoc_files:
37
+ - LICENSE
38
+ - README.rdoc
39
+ files:
40
+ - .document
41
+ - .gitignore
42
+ - LICENSE
43
+ - README.rdoc
44
+ - Rakefile
45
+ - VERSION
46
+ - lib/redis_logger.rb
47
+ - redis_logger.gemspec
48
+ - test/helper.rb
49
+ - test/test_redis_logger.rb
50
+ homepage: https://github.com/synapsepd/synapse_redis_logger
51
+ licenses: []
52
+ post_install_message:
53
+ rdoc_options:
54
+ - --charset=UTF-8
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ required_rubygems_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ requirements: []
70
+ rubyforge_project:
71
+ rubygems_version: 1.8.19
72
+ signing_key:
73
+ specification_version: 3
74
+ summary: Application logging into Redis
75
+ test_files:
76
+ - test/helper.rb
77
+ - test/test_redis_logger.rb
78
+ has_rdoc: