urbivore 0.0.1
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/.gitignore +22 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +2 -0
- data/lib/urbivore.rb +11 -0
- data/lib/urbivore/exceptions.rb +12 -0
- data/lib/urbivore/interface.rb +100 -0
- data/lib/urbivore/logger.rb +121 -0
- data/lib/urbivore/message.rb +37 -0
- data/lib/urbivore/message_packet.rb +64 -0
- data/lib/urbivore/processor.rb +13 -0
- data/lib/urbivore/version.rb +3 -0
- data/urbivore.gemspec +23 -0
- metadata +92 -0
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 EC
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# Urbivore
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'urbivore'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install urbivore
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it ( https://github.com/[my-github-username]/urbivore/fork )
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/lib/urbivore.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require "urbivore/version"
|
2
|
+
require "urbivore/interface"
|
3
|
+
require "urbivore/exceptions"
|
4
|
+
require "urbivore/message"
|
5
|
+
require "urbivore/message_packet"
|
6
|
+
require "urbivore/logger"
|
7
|
+
require "urbivore/processor"
|
8
|
+
|
9
|
+
module Urbivore
|
10
|
+
# Your code goes here...
|
11
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Urbivore
|
2
|
+
module Interface
|
3
|
+
# we expect logger to provide:
|
4
|
+
# #configured_level (i.e., the default)
|
5
|
+
# #configured_levels (i.e., the scale)
|
6
|
+
# ...if these are nil, we send system defaults from here
|
7
|
+
STANDARD_LEVELS = [:debug, :info, :user1, :warn, :error, :user2, :fatal, :unknown]
|
8
|
+
|
9
|
+
# allow config here...
|
10
|
+
def default_logger
|
11
|
+
Urbivore::Logger
|
12
|
+
end
|
13
|
+
|
14
|
+
def default_level
|
15
|
+
logger.configured_level || :info
|
16
|
+
end
|
17
|
+
|
18
|
+
def default_levels
|
19
|
+
levels_by_minimum(default_level)
|
20
|
+
end
|
21
|
+
|
22
|
+
def available_levels
|
23
|
+
logger.configured_levels || STANDARD_LEVELS
|
24
|
+
end
|
25
|
+
|
26
|
+
def levels_by_minimum(level)
|
27
|
+
levels = available_levels
|
28
|
+
# memoize this locally in these methods,
|
29
|
+
# not in the getter above---we should
|
30
|
+
# anticipate runtime changes
|
31
|
+
levels.include?(level) ? levels.reject { |l| levels.index(l) < levels.index(level) } : nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def levels_by_subset(levels)
|
35
|
+
available_levels & levels
|
36
|
+
end
|
37
|
+
|
38
|
+
def current_levels(requested = nil)
|
39
|
+
if requested
|
40
|
+
requested.is_a?(Array) ? levels_by_subset(requested) : levels_by_minimum(requested)
|
41
|
+
else
|
42
|
+
default_levels
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# helpers for standard levels--could make these configurable
|
47
|
+
STANDARD_LEVELS.each do |level|
|
48
|
+
define_method(level) do |*args, &block|
|
49
|
+
add(level, *args, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
define_method("#{level}?") do |level|
|
53
|
+
level_active?(level)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def add(level, message_arg = nil, options = {}, &message_block)
|
58
|
+
level = level.is_a?(Numeric) ? available_levels.at(level) : level.to_sym
|
59
|
+
unless current_levels.include?(level)
|
60
|
+
return false
|
61
|
+
end
|
62
|
+
|
63
|
+
# support stdlib-style prognames thus:
|
64
|
+
if options.is_a?(String)
|
65
|
+
progname = options
|
66
|
+
options = { progname: options }
|
67
|
+
end
|
68
|
+
|
69
|
+
unless options.is_a?(Hash)
|
70
|
+
raise Urbivore::Exceptions::TypeError.new("options must be a String or a Hash")
|
71
|
+
end
|
72
|
+
|
73
|
+
message = block_given? ? yield : message_arg
|
74
|
+
options.merge!(level: level)
|
75
|
+
append_message(generate_message_object(message, options))
|
76
|
+
end
|
77
|
+
|
78
|
+
def append_message(message_object)
|
79
|
+
# allow this object to queue the message for
|
80
|
+
# later processing by defininng #message_collector
|
81
|
+
if respond_to?(:message_collector)
|
82
|
+
# check for optional live stream here
|
83
|
+
message_collector(message_object)
|
84
|
+
else
|
85
|
+
logger.submit(message_object)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# this is the interface to use for building novel stuff with messages
|
90
|
+
# don't instantiate Message directly
|
91
|
+
def generate_message_object(message, options = {})
|
92
|
+
# ability to specify time useful for testing?
|
93
|
+
timestamp = options[:timestamp] || Time.now
|
94
|
+
level = options[:level] || default_level
|
95
|
+
progname = options[:progname]
|
96
|
+
data = options[:data]
|
97
|
+
Urbivore::Message.new(timestamp, message, level, progname, data)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
module Urbivore
|
2
|
+
class Logger
|
3
|
+
class << self
|
4
|
+
include Urbivore::Interface
|
5
|
+
|
6
|
+
attr_reader :configured_levels, :configured_level
|
7
|
+
|
8
|
+
# worker constants
|
9
|
+
# default to 250ms sleep (more? less?)
|
10
|
+
DEFAULT_SLEEP_TIME = (1.0/4.0)
|
11
|
+
DEFAULT_TOTAL_WORKERS = 1
|
12
|
+
DEFAULT_BATCH_SIZE = 5
|
13
|
+
|
14
|
+
# use this to enqueue entries
|
15
|
+
def submit(entry)
|
16
|
+
# any prep work on the object goes here...
|
17
|
+
queue.push(entry)
|
18
|
+
end
|
19
|
+
|
20
|
+
def process(entry)
|
21
|
+
processor.process(entry)
|
22
|
+
end
|
23
|
+
|
24
|
+
def processor(processor = nil)
|
25
|
+
if processor
|
26
|
+
unless processor.respond_to?(:process)
|
27
|
+
raise Urbivore::Exceptions::ConfigurationError.new("Processor must respond_to?(:process)")
|
28
|
+
end
|
29
|
+
@processor = processor
|
30
|
+
else
|
31
|
+
@processor ||= Urbs::BasicProcessor
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def logger
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
def workers
|
40
|
+
@workers ||= []
|
41
|
+
end
|
42
|
+
|
43
|
+
def queue
|
44
|
+
@queue ||= Queue.new
|
45
|
+
end
|
46
|
+
|
47
|
+
def sleep_time
|
48
|
+
@sleep_time || DEFAULT_SLEEP_TIME
|
49
|
+
end
|
50
|
+
|
51
|
+
def total_workers
|
52
|
+
@total_workers || DEFAULT_TOTAL_WORKERS
|
53
|
+
end
|
54
|
+
|
55
|
+
def batch_size
|
56
|
+
@batch_size || DEFAULT_BATCH_SIZE
|
57
|
+
end
|
58
|
+
|
59
|
+
def sleep_time=(sleep_time)
|
60
|
+
unless sleep_time.is_a?(Numeric) || !sleep_time
|
61
|
+
raise Urbivore::Exceptions::ConfigurationError.new("Log.sleep_time must be a number")
|
62
|
+
end
|
63
|
+
|
64
|
+
# we should figure out whether access to this instance var
|
65
|
+
# should be locked, in this method and the getter...
|
66
|
+
@sleep_time = sleep_time
|
67
|
+
end
|
68
|
+
|
69
|
+
def total_workers=(total_workers)
|
70
|
+
unless total_workers.is_a?(Fixnum) || !total_workers
|
71
|
+
raise Urbivore::Exceptions::ConfigurationError.new("Log.total_workers must be a number")
|
72
|
+
end
|
73
|
+
@total_workers = total_workers
|
74
|
+
end
|
75
|
+
|
76
|
+
def batch_size=(batch_size)
|
77
|
+
unless batch_size.is_a?(Fixnum) || !batch_size
|
78
|
+
raise Urbivore::Exceptions::ConfigurationError.new("Log.batch_size must be a number")
|
79
|
+
end
|
80
|
+
@batch_size = batch_size
|
81
|
+
end
|
82
|
+
|
83
|
+
# THREAD SAFETY...
|
84
|
+
def restart_logging(halt = false)
|
85
|
+
Thread.exclusive do
|
86
|
+
reset_state
|
87
|
+
if halt
|
88
|
+
true
|
89
|
+
else
|
90
|
+
@keep_going = true
|
91
|
+
total_workers.times do
|
92
|
+
workers << Thread.new do
|
93
|
+
while @keep_going
|
94
|
+
sleep sleep_time
|
95
|
+
entries = []
|
96
|
+
batch_size.times do
|
97
|
+
entries << queue.pop
|
98
|
+
end
|
99
|
+
entries.each { |entry| process(entry) }
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def reset_state
|
110
|
+
@keep_going = false
|
111
|
+
workers.each { |thr| thr.join }
|
112
|
+
@workers = nil
|
113
|
+
@logging = false
|
114
|
+
# close file handles, network connections and the like
|
115
|
+
# note we leave queue alone, so it can keep getting entries
|
116
|
+
# while we're reloading
|
117
|
+
# processor.reset_state
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Urbivore
|
2
|
+
class Message
|
3
|
+
attr_reader :timestamp, :message, :level, :progname, :data
|
4
|
+
|
5
|
+
# generally speaking we don't want users initializing
|
6
|
+
# these directly---either Logger or MessagePacket should
|
7
|
+
# be doing that in most cases
|
8
|
+
def initialize(timestamp, message, level, progname = nil, data = nil)
|
9
|
+
# that said, let's raise an error if at least they're
|
10
|
+
# calling this wrong...
|
11
|
+
unless timestamp.is_a?(Time)
|
12
|
+
raise Urbivore::Exceptions::TypeError.new("Message expected timestamp to be a time")
|
13
|
+
end
|
14
|
+
|
15
|
+
@timestamp = timestamp
|
16
|
+
@message = message
|
17
|
+
@level = level.to_sym
|
18
|
+
@progname = progname
|
19
|
+
@data = data
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
str = "#{timestamp} [#{level}] #{message}"
|
24
|
+
progname ? str += " (#{progname})" : str
|
25
|
+
end
|
26
|
+
|
27
|
+
def <=>(comp)
|
28
|
+
if self.timestamp < comp.timestamp
|
29
|
+
-1
|
30
|
+
elsif self.timestamp > comp.timestamp
|
31
|
+
1
|
32
|
+
else
|
33
|
+
0
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Urbivore
|
2
|
+
class MessagePacket
|
3
|
+
include Urbivore::Interface
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def logger(logger = nil)
|
7
|
+
if logger
|
8
|
+
@logger = logger
|
9
|
+
else
|
10
|
+
@logger
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
## instance methods
|
16
|
+
|
17
|
+
attr_reader :logger
|
18
|
+
|
19
|
+
def initialize(options = {})
|
20
|
+
@logger = options[:default_level] || self.class.logger || default_logger
|
21
|
+
@default_level = options[:default_level]
|
22
|
+
@message_store = {}
|
23
|
+
unless @logger.ancestors.include?(Urbivore::Logger)
|
24
|
+
raise Urbivore::Exceptions::TypeError.new("MessagePacket expected a Logger")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def message_collector(message_object)
|
29
|
+
# it may not matter... but keying these in a hash by level
|
30
|
+
# seems like better design than putting them in a big pile
|
31
|
+
# and using #select---if you generate a massive amount of debugging
|
32
|
+
# info you don't want iterate over all of it just to generate a production
|
33
|
+
# log message, right?
|
34
|
+
@message_store[level] ||= []
|
35
|
+
# shit like this needs to be made threadsafe
|
36
|
+
@message_store[level] << message_object
|
37
|
+
end
|
38
|
+
|
39
|
+
def messages(requested = nil)
|
40
|
+
# pull all desired levels and collate
|
41
|
+
current_levels(requested).map { |level| @levels[level] }.flatten.sort
|
42
|
+
end
|
43
|
+
|
44
|
+
# send self to the logging machine
|
45
|
+
def submit
|
46
|
+
if @submitted
|
47
|
+
false
|
48
|
+
else
|
49
|
+
logger.submit(self)
|
50
|
+
@submitted = true
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# as above, but raise exception when trying to submit
|
55
|
+
# an already submitted log
|
56
|
+
def submit!
|
57
|
+
if @submitted
|
58
|
+
raise Urbivore::Exceptions::RuntimeError.new("MessagePacket already submitted")
|
59
|
+
else
|
60
|
+
submit
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/urbivore.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'urbivore/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "urbivore"
|
8
|
+
spec.version = Urbivore::VERSION
|
9
|
+
spec.authors = ["EC"]
|
10
|
+
spec.email = ["erik.cameron@gmail.com"]
|
11
|
+
spec.summary = "The Urbs structured logger"
|
12
|
+
spec.description = ""
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: urbivore
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- EC
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-12-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '1.6'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.6'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
description: ''
|
47
|
+
email:
|
48
|
+
- erik.cameron@gmail.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- Gemfile
|
55
|
+
- LICENSE.txt
|
56
|
+
- README.md
|
57
|
+
- Rakefile
|
58
|
+
- lib/urbivore.rb
|
59
|
+
- lib/urbivore/exceptions.rb
|
60
|
+
- lib/urbivore/interface.rb
|
61
|
+
- lib/urbivore/logger.rb
|
62
|
+
- lib/urbivore/message.rb
|
63
|
+
- lib/urbivore/message_packet.rb
|
64
|
+
- lib/urbivore/processor.rb
|
65
|
+
- lib/urbivore/version.rb
|
66
|
+
- urbivore.gemspec
|
67
|
+
homepage: ''
|
68
|
+
licenses:
|
69
|
+
- MIT
|
70
|
+
post_install_message:
|
71
|
+
rdoc_options: []
|
72
|
+
require_paths:
|
73
|
+
- lib
|
74
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ! '>='
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
requirements: []
|
87
|
+
rubyforge_project:
|
88
|
+
rubygems_version: 1.8.23
|
89
|
+
signing_key:
|
90
|
+
specification_version: 3
|
91
|
+
summary: The Urbs structured logger
|
92
|
+
test_files: []
|