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.
- data/.document +5 -0
- data/.gitignore +22 -0
- data/LICENSE +20 -0
- data/README.rdoc +76 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/lib/redis_logger.rb +141 -0
- data/redis_logger.gemspec +54 -0
- data/test/helper.rb +9 -0
- data/test/test_redis_logger.rb +7 -0
- metadata +78 -0
data/.document
ADDED
data/.gitignore
ADDED
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.
|
data/README.rdoc
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
@@ -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
|
data/lib/redis_logger.rb
ADDED
@@ -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
|
+
|
data/test/helper.rb
ADDED
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:
|