polylog 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/History.txt +4 -0
- data/LICENSE.md +22 -0
- data/README.md +200 -0
- data/Rakefile +26 -0
- data/lib/polylog.rb +162 -0
- data/lib/polylog/errors.rb +15 -0
- data/lib/polylog/multi_provider.rb +80 -0
- data/lib/polylog/null_logger.rb +28 -0
- data/lib/polylog/solo_provider.rb +41 -0
- data/script/bootstrap +9 -0
- data/spec/polylog/multi_provider_spec.rb +41 -0
- data/spec/polylog/null_logger_spec.rb +82 -0
- data/spec/polylog/solo_provider_spec.rb +14 -0
- data/spec/polylog_spec.rb +124 -0
- data/spec/spec_helper.rb +31 -0
- data/version.txt +1 -0
- metadata +118 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 30bf99c4368b2308581792e2f887e75e2533b1ef
|
4
|
+
data.tar.gz: 59f9b5c104970d4d73244db7381c9a197358271c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 29508ecd47dd8b423e799f8a7c71b8dc7bb8a8ed8de2120b15563f06e44bd126421ef342cedf615fc8f02967a839d9a13321d4641311f59823ca874d8bfda15d
|
7
|
+
data.tar.gz: d00c98550192a44ce783f61bd61c5c087a9c669e643921dee8efbe4a39c1bb6ac24d446049d1c63b3f1ffcd1337565f59133614b6c75d10dc698751efd3a1bae
|
data/.gitignore
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# The list of files that should be ignored by Mr Bones.
|
2
|
+
# Lines that start with '#' are comments.
|
3
|
+
#
|
4
|
+
# A .gitignore file can be used instead by setting it as the ignore
|
5
|
+
# file in your Rakefile:
|
6
|
+
#
|
7
|
+
# Bones {
|
8
|
+
# ignore_file '.gitignore'
|
9
|
+
# }
|
10
|
+
#
|
11
|
+
# For a project with a C extension, the following would be a good set of
|
12
|
+
# exclude patterns (uncomment them if you want to use them):
|
13
|
+
# *.[oa]
|
14
|
+
# *~
|
15
|
+
announcement.txt
|
16
|
+
coverage
|
17
|
+
doc
|
18
|
+
pkg
|
19
|
+
vendor
|
data/History.txt
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2012-2013 by Tim Pease
|
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 NONINFRINGEMENT.
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,200 @@
|
|
1
|
+
## polylog
|
2
|
+
|
3
|
+
Decoupling the logging layer since 2012
|
4
|
+
|
5
|
+
### Description
|
6
|
+
|
7
|
+
Logging provides a simple mechanism for observing the state of a running
|
8
|
+
application, and most ruby gems and applications use some sort of logging.
|
9
|
+
This is great! Setting up a logging infrastructure across various gems and
|
10
|
+
applications is the not so great part.
|
11
|
+
|
12
|
+
In a Rails application it is assumed that all gems will use the `Rails.logger`
|
13
|
+
and that's the end of it. So as a gem developer you can just grab the Rails
|
14
|
+
logger and be done. The downside of that choice is your gem is now locked to
|
15
|
+
Rails and cannot be used elsewhere. Another option is to provide a
|
16
|
+
`setup( options = {} )` method (or something similar) and the application can
|
17
|
+
provide you with a logger. The downside of that choice is that applications
|
18
|
+
need to configure your gem and every other gem that adopts the same
|
19
|
+
convention.
|
20
|
+
|
21
|
+
A third option is to be clever and see if the Rails module exists when your
|
22
|
+
gem is loaded and then just grab the Rails logger. This makes testing
|
23
|
+
difficult and can lead to unintended side effects. Eschew clever code.
|
24
|
+
|
25
|
+
Polylog is an agreement between the gem developer and the application
|
26
|
+
developer on how logging is configured. For the gem developer, instead of
|
27
|
+
assuming the Rails environment or providing a `setup` method you ask Polylog
|
28
|
+
for a logger instance. For the application developer, instead of configuring
|
29
|
+
logging for multiple gems you configure Polylog with the logging
|
30
|
+
infrastructure you plan on using.
|
31
|
+
|
32
|
+
The gem developer no longer needs to worry what the application is going to
|
33
|
+
do. The logging for the gem does not need to be configured by the application.
|
34
|
+
Conversely, the application does not have to configure logging for every gem.
|
35
|
+
The application provides the logging infrastructure once to Polylog.
|
36
|
+
|
37
|
+
Let's see what this looks like in practice.
|
38
|
+
|
39
|
+
### Usage
|
40
|
+
|
41
|
+
The two primary use cases for Polylog are:
|
42
|
+
|
43
|
+
* obtaining a logger
|
44
|
+
* configuring the logging provider
|
45
|
+
|
46
|
+
Obtaining a logger is the domain of the gem developer (or supporting
|
47
|
+
application code). Configuring the logging provider is the domain of the
|
48
|
+
application.
|
49
|
+
|
50
|
+
#### Obtaining a Logger
|
51
|
+
|
52
|
+
Obtaining a logger is simple:
|
53
|
+
|
54
|
+
```ruby
|
55
|
+
Polylog.logger self
|
56
|
+
```
|
57
|
+
|
58
|
+
Passing `self` is optional but recommended. It provides context to Polylog and
|
59
|
+
allows object specific loggers to be returned (actually, they are class
|
60
|
+
specific). The application can configure different logger instances to be
|
61
|
+
returned depending upon the requesting class. This is useful if you want to
|
62
|
+
enable debugging for just one portion of the application.
|
63
|
+
|
64
|
+
In practice, you can create a module and include it where you want a `logger`
|
65
|
+
method to be available:
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
module MyGem::Logger
|
69
|
+
def logger
|
70
|
+
Polylog.logger self
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class MyGem::SomeClass
|
75
|
+
include MyGem::Logger
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
In this example each class in `MyGem` can obtain its own unique logger if
|
80
|
+
Polylog is configured to do so. But you can also develop your gem to use a
|
81
|
+
single logger instance, too.
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
module MyGem::Logger
|
85
|
+
def logger
|
86
|
+
Polylog.logger 'MyGem'
|
87
|
+
end
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
Instead of passing `self` the top level namespace of the gem is used. Anywhere
|
92
|
+
this logger module is included the returned logger will be the same.
|
93
|
+
|
94
|
+
#### Configuring the Logging Provider
|
95
|
+
|
96
|
+
Without any configuration Polylog with provide a null logger for all requests.
|
97
|
+
This logger implements all the methods of the Ruby `Logger` class, but all the
|
98
|
+
methods do nothing. Not very useful except for preventing exceptions.
|
99
|
+
|
100
|
+
So lets log everything to standard out (a useful start):
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
provider = Polylog::SoloProvider.new(Logger.new(STDOUT))
|
104
|
+
Polylog.register_provider 'stdout', provider
|
105
|
+
|
106
|
+
Polylog.use_provider 'stdout'
|
107
|
+
```
|
108
|
+
|
109
|
+
Yikes! That looks pretty complicated. The three lines of code are the three
|
110
|
+
steps to configuring a logging provider.
|
111
|
+
|
112
|
+
The first line creates the provider we are going to use. The `SoloProvider`
|
113
|
+
provides exactly one logger. In the example it is providing a standard Ruby
|
114
|
+
`Logger` configured to log everything to `STDOUT`.
|
115
|
+
|
116
|
+
The second line registers the provider we created with Polylog. Multiple
|
117
|
+
providers can be registered but only one will be used. Each provider is
|
118
|
+
registered with a unique name. Logging frameworks like
|
119
|
+
[Log4r](https://github.com/colbygk/log4r), [Logging](https://github.com/twp/logging),
|
120
|
+
and [Scrolls](https://github.com/asenchi/scrolls) can register themselves with
|
121
|
+
Polylog. In this case, applications can skip the provider creation and just use
|
122
|
+
one that is already registered.
|
123
|
+
|
124
|
+
The third line tells Polylog to use the 'stdout' provider. All calls to
|
125
|
+
`Polylog.logger()` will lookup this provider and then call the `logger()`
|
126
|
+
method implemented by the provider. In this case, it is the solo-provider
|
127
|
+
which returns a STDOUT logger instance for all requests.
|
128
|
+
|
129
|
+
Lets register a few more providers:
|
130
|
+
|
131
|
+
```ruby
|
132
|
+
Polylog.register_provider 'stdout', Polylog::SoloProvider.new(Logger.new(STDOUT))
|
133
|
+
Polylog.register_provider 'stderr', Polylog::SoloProvider.new(Logger.new(STDERR))
|
134
|
+
Polylog.register_provider 'file', Polylog::SoloProvider.new(Logger.new('app.log'))
|
135
|
+
|
136
|
+
Polylog.use_provider 'file'
|
137
|
+
```
|
138
|
+
|
139
|
+
Even though we have providers for logging to `STDOUT` and `STDERR`, we have
|
140
|
+
chosen to send all our logs to a file instead. It is easy enough to swap
|
141
|
+
providers now. Because we are using the solo-provider all log messages will
|
142
|
+
end up going to the same destination.
|
143
|
+
|
144
|
+
We can send log messages to multiple destinations by using a multi-provider.
|
145
|
+
|
146
|
+
```ruby
|
147
|
+
provider = Polylog::MultiProvider.new(Logger.new(STDOUT))
|
148
|
+
provider['MyClass'] = Logger.new('my_class.log')
|
149
|
+
|
150
|
+
Polylog.register_provider 'my_class', provider
|
151
|
+
Polylog.use_provider 'my_class'
|
152
|
+
|
153
|
+
Polylog.logger # STDOUT logger
|
154
|
+
Polylog.logger(MyClass) # 'my_class.log' logger
|
155
|
+
```
|
156
|
+
|
157
|
+
The multi-provider can return more than one logger instance. It is configured
|
158
|
+
to return a default logger for all requests. However, a different logger can
|
159
|
+
be returned based on the Class of the object passed to the `Polylog.logger()`
|
160
|
+
method. In the example we are passing in `MyClass` to get the specific logger
|
161
|
+
for that class; instances of `MyClass` would also return the same logger.
|
162
|
+
|
163
|
+
If you want to get more creative with your logging configuration, I highly
|
164
|
+
recommend checking out the [Logging](https://github.com/twp/logging)
|
165
|
+
framework. It is designed to provide unique loggers and logging destinations.
|
166
|
+
|
167
|
+
#### Integrating With Rails
|
168
|
+
|
169
|
+
It would be wonderful if Polylog were integrated directly with Rails. This
|
170
|
+
would require Rails to adopt the `Polylog.logger()` style of acquiring a
|
171
|
+
logger instance. However, until that time happens, Polylog needs to be
|
172
|
+
configured with a provider that returns the `Rails.logger` instance.
|
173
|
+
|
174
|
+
```ruby
|
175
|
+
Polylog.register_provider 'rails', Polylog::SoloProvider.new(Rails.logger)
|
176
|
+
Polylog.use_provider 'rails'
|
177
|
+
```
|
178
|
+
|
179
|
+
And that's pretty much it.
|
180
|
+
|
181
|
+
### Developing
|
182
|
+
|
183
|
+
Polylog makes use of Mr Bones to handle general project tasks such as running
|
184
|
+
tests, generating a gem file, interacting with GitHub, and publishing
|
185
|
+
to rubygems.org. A `script/bootstrap` script is provided to setup the
|
186
|
+
development environment:
|
187
|
+
|
188
|
+
```shell
|
189
|
+
script/bootstrap
|
190
|
+
```
|
191
|
+
|
192
|
+
After this is run, you can use `rake` normally to run tests. You can view all
|
193
|
+
the available rake tasks via `rake -T`.
|
194
|
+
|
195
|
+
```shell
|
196
|
+
rake -T
|
197
|
+
rake spec
|
198
|
+
```
|
199
|
+
|
200
|
+
Thank you for reading! Any contributions are greatly appreciated.
|
data/Rakefile
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
|
2
|
+
begin
|
3
|
+
require 'bones'
|
4
|
+
rescue LoadError
|
5
|
+
abort '### Please install the "bones" gem ###'
|
6
|
+
end
|
7
|
+
|
8
|
+
task :default => 'spec:run'
|
9
|
+
task 'gem:release' => 'spec:run'
|
10
|
+
|
11
|
+
|
12
|
+
Bones {
|
13
|
+
name 'polylog'
|
14
|
+
authors 'Tim Pease'
|
15
|
+
email 'tim.pease@gmail.com'
|
16
|
+
url 'http://rubygems.org/gems/polylog'
|
17
|
+
|
18
|
+
spec.opts << '--color' << '--format documentation'
|
19
|
+
|
20
|
+
use_gmail
|
21
|
+
|
22
|
+
depend_on 'bones-rspec', :development => true
|
23
|
+
depend_on 'bones-git', :development => true
|
24
|
+
depend_on 'rdoc', :development => true
|
25
|
+
}
|
26
|
+
|
data/lib/polylog.rb
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
|
2
|
+
module Polylog
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# Request a logger instance for the given object from the configured
|
6
|
+
# provider.
|
7
|
+
#
|
8
|
+
# object - Any ruby object
|
9
|
+
#
|
10
|
+
# Returns a logger instance from the provider.
|
11
|
+
def logger( object = nil )
|
12
|
+
name = logger_name object
|
13
|
+
provider.logger name
|
14
|
+
end
|
15
|
+
|
16
|
+
# Return the currently selected provider. If a provider has not yet been
|
17
|
+
# specified, then the "null" provider will be used.
|
18
|
+
#
|
19
|
+
# Returns a logger provider.
|
20
|
+
def provider
|
21
|
+
use_provider('null') unless defined? @provider
|
22
|
+
@provider
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the list of available providers as an Array of Strings. These
|
26
|
+
# names are used to select which provider to use.
|
27
|
+
def providers
|
28
|
+
@providers.keys
|
29
|
+
end
|
30
|
+
|
31
|
+
# Configure the logger provider that will be used by Polylog. The provider
|
32
|
+
# must already be registered using the same `name`.
|
33
|
+
#
|
34
|
+
# name - The provider name as a String.
|
35
|
+
#
|
36
|
+
# Returns the logger provider.
|
37
|
+
# Raises a Polylog::UnknownProvider exception.
|
38
|
+
def use_provider( name )
|
39
|
+
name = name.to_s
|
40
|
+
raise Polylog::UnknownProvider, "unknown provider: #{name.inspect}" unless @providers.key? name
|
41
|
+
|
42
|
+
@provider = @providers[name]
|
43
|
+
end
|
44
|
+
|
45
|
+
# Register a logger provider under the given name. The provider can be any
|
46
|
+
# Ruby object that responds to the `logger` method and returns valid logger
|
47
|
+
# instances. An exception is raised if the provider does not respond to the
|
48
|
+
# `logger` method or the `logger` method does not have an arity of 1.
|
49
|
+
#
|
50
|
+
# name - The provider name as a String.
|
51
|
+
# provider - The provider object that responds to the `logger` method.
|
52
|
+
#
|
53
|
+
# Returns the logger provider.
|
54
|
+
# Raises a Polylog::InvalidProvider exception.
|
55
|
+
def register_provider( name, provider )
|
56
|
+
@providers ||= Hash.new
|
57
|
+
name = name.to_s
|
58
|
+
|
59
|
+
unless provider.respond_to?(:logger)
|
60
|
+
raise Polylog::InvalidProvider, "`logger` method not found for provider #{name.inspect}"
|
61
|
+
end
|
62
|
+
|
63
|
+
arity = provider.method(:logger).arity
|
64
|
+
unless 1 == arity
|
65
|
+
raise Polylog::InvalidProvider, "`logger` method arity must be 1; arity is #{arity} for provider #{name.inspect}"
|
66
|
+
end
|
67
|
+
|
68
|
+
@providers[name] = provider
|
69
|
+
end
|
70
|
+
|
71
|
+
# Internal: Given any ruby object, try to construct a sensible logger name
|
72
|
+
# to use for looking up specific logger instances from a provider. For
|
73
|
+
# nearly every object passed in this will be the class name of the instance.
|
74
|
+
#
|
75
|
+
# If you pass in `nil` or a String then the original object is return.
|
76
|
+
# Symbols will be converted to Strings.
|
77
|
+
#
|
78
|
+
# The only objects we cannot generate a logger name for are anonymous
|
79
|
+
# modules. For these we just return `nil`.
|
80
|
+
#
|
81
|
+
# object - Any ruby object
|
82
|
+
#
|
83
|
+
# Returns the logger name String or nil.
|
84
|
+
def logger_name( object )
|
85
|
+
case object
|
86
|
+
when nil, String; object
|
87
|
+
when Symbol; object.to_s
|
88
|
+
when Module; logger_name_for_module(object)
|
89
|
+
when Object; logger_name_for_module(object.class)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Internal: Generate logger names for classes, modules, singleton classes,
|
94
|
+
# and anonymous modules. For all of these we use the class name or the
|
95
|
+
# module name. For anonymous modules we return `nil`.
|
96
|
+
#
|
97
|
+
# mod - A Module or Class
|
98
|
+
#
|
99
|
+
# Returns the logger name String or nil.
|
100
|
+
def logger_name_for_module( mod )
|
101
|
+
return mod.name unless mod.name.nil? or mod.name.empty?
|
102
|
+
|
103
|
+
# check if we have a metaclass (or eigenclass)
|
104
|
+
if mod.ancestors.include? Class
|
105
|
+
mod.inspect =~ %r/#<Class:([^#>]+)>/
|
106
|
+
return $1
|
107
|
+
end
|
108
|
+
|
109
|
+
# see if we have a superclass
|
110
|
+
if mod.respond_to? :superclass
|
111
|
+
return logger_name_for_module(mod.superclass)
|
112
|
+
end
|
113
|
+
|
114
|
+
# we have an anonymous module
|
115
|
+
return nil
|
116
|
+
end
|
117
|
+
|
118
|
+
LIBPATH = ::File.expand_path('../', __FILE__)
|
119
|
+
PATH = ::File.dirname(LIBPATH)
|
120
|
+
|
121
|
+
# Returns the version String for the library.
|
122
|
+
def version
|
123
|
+
@version ||= File.read(path('version.txt')).strip
|
124
|
+
end
|
125
|
+
|
126
|
+
# Internal: Returns the library path for the module. If any arguments are
|
127
|
+
# given, they will be joined to the end of the library path using
|
128
|
+
# `File.join`.
|
129
|
+
def libpath( *args )
|
130
|
+
rv = args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
131
|
+
if block_given?
|
132
|
+
begin
|
133
|
+
$LOAD_PATH.unshift LIBPATH
|
134
|
+
rv = yield
|
135
|
+
ensure
|
136
|
+
$LOAD_PATH.shift
|
137
|
+
end
|
138
|
+
end
|
139
|
+
return rv
|
140
|
+
end
|
141
|
+
|
142
|
+
# Internal: Returns the path for the module. If any arguments are given,
|
143
|
+
# they will be joined to the end of the path using `File.join`.
|
144
|
+
def path( *args )
|
145
|
+
rv = args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
146
|
+
if block_given?
|
147
|
+
begin
|
148
|
+
$LOAD_PATH.unshift PATH
|
149
|
+
rv = yield
|
150
|
+
ensure
|
151
|
+
$LOAD_PATH.shift
|
152
|
+
end
|
153
|
+
end
|
154
|
+
return rv
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
require Polylog.libpath('polylog/errors')
|
160
|
+
require Polylog.libpath('polylog/solo_provider')
|
161
|
+
require Polylog.libpath('polylog/multi_provider')
|
162
|
+
require Polylog.libpath('polylog/null_logger')
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
module Polylog
|
3
|
+
|
4
|
+
# Parent class for all Polylog errors.
|
5
|
+
Error = Class.new StandardError
|
6
|
+
|
7
|
+
# This is error is raised when an unknown provider is requested by the user.
|
8
|
+
UnknownProvider = Class.new Error
|
9
|
+
|
10
|
+
# This is error is raised when a provider is registered and it does not
|
11
|
+
# respond to the `logger` method or the `logger` method has an arity other
|
12
|
+
# than 1.
|
13
|
+
InvalidProvider = Class.new Error
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
module Polylog
|
3
|
+
|
4
|
+
# The purpose of a MultiProvider is to provide a default logger instance
|
5
|
+
# when requested. Other loggers can be provided by registering them with the
|
6
|
+
# provider under the appropriate logger name. So when this name is passed to
|
7
|
+
# the `logger` method the registered logger instance will be returned.
|
8
|
+
#
|
9
|
+
# The example below shows how to use the MultiProvider to send all log
|
10
|
+
# messages from all instances of a single class to a log file instead of
|
11
|
+
# STDOUT. You might want to do this when debugging the class; you can set
|
12
|
+
# the logger level to debug and capture lots of text to a single location.
|
13
|
+
#
|
14
|
+
# Examples
|
15
|
+
#
|
16
|
+
# provider = Polylog::MultiProvider.new(Logger.new(STDOUT))
|
17
|
+
# provider['MyClass'] = Logger.new('my_class.log')
|
18
|
+
#
|
19
|
+
# Polylog.register_provider 'example', provider
|
20
|
+
# Polylog.use_provider 'example'
|
21
|
+
#
|
22
|
+
# logger = Polylog.logger # STDOUT logger
|
23
|
+
# my_class_logger = Polylog.logger(MyClass) # 'my_class.log' logger
|
24
|
+
#
|
25
|
+
class MultiProvider
|
26
|
+
|
27
|
+
# Create a new MultiProvider configured with the given logger as the
|
28
|
+
# default logger. Other loggers can be added to the provider. These will
|
29
|
+
# be returned by the provider (instead of the default logger) when their
|
30
|
+
# name is passed to the `logger` method.
|
31
|
+
#
|
32
|
+
# logger - The default logger to return.
|
33
|
+
def initialize( logger )
|
34
|
+
@hash = Hash.new
|
35
|
+
@default_logger = logger
|
36
|
+
end
|
37
|
+
|
38
|
+
# Accessor for getting and setting the default logger.
|
39
|
+
attr_accessor :default_logger
|
40
|
+
|
41
|
+
# Returns the named logger if it is present in the provider. If the named
|
42
|
+
# logger is not present then the default logger is returned.
|
43
|
+
#
|
44
|
+
# name - The logger name as a String.
|
45
|
+
def logger( name )
|
46
|
+
return default_logger if name.nil?
|
47
|
+
@hash.fetch(name, default_logger)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the logger or `nil` if the logger is not present.
|
51
|
+
#
|
52
|
+
# name - The logger name as a String.
|
53
|
+
def []( name )
|
54
|
+
@hash[name]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Add a logger provider under the given name.
|
58
|
+
#
|
59
|
+
# name - The logger name as a String.
|
60
|
+
# logger - The logger.
|
61
|
+
#
|
62
|
+
# Returns the logger.
|
63
|
+
def []=( name, logger )
|
64
|
+
@hash[name] = logger
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns an Array with the names of the loggers present in the provider.
|
68
|
+
def loggers
|
69
|
+
@hash.keys
|
70
|
+
end
|
71
|
+
|
72
|
+
# Returns `true` if the logger is present in the provider.
|
73
|
+
#
|
74
|
+
# name - The logger name as a String.
|
75
|
+
def logger?( name )
|
76
|
+
@hash.key? name
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
|
2
|
+
module Polylog
|
3
|
+
|
4
|
+
# This is a no-op logger that implements the principal methods from the
|
5
|
+
# standard Ruby logger. All methods do nothing and return either `false`
|
6
|
+
# or `nil`.
|
7
|
+
class NullLogger
|
8
|
+
def <<(msg) nil end
|
9
|
+
def close() nil end
|
10
|
+
|
11
|
+
def debug?() false end
|
12
|
+
alias :info? :debug?
|
13
|
+
alias :warn? :debug?
|
14
|
+
alias :error? :debug?
|
15
|
+
alias :fatal? :debug?
|
16
|
+
|
17
|
+
def add(*args) false end
|
18
|
+
alias :debug :add
|
19
|
+
alias :info :add
|
20
|
+
alias :warn :add
|
21
|
+
alias :error :add
|
22
|
+
alias :fatal :add
|
23
|
+
alias :unknown :add
|
24
|
+
alias :log :add
|
25
|
+
end
|
26
|
+
|
27
|
+
register_provider('null', SoloProvider.new(NullLogger.new))
|
28
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Polylog
|
2
|
+
|
3
|
+
# The purpose of the SoloProvider is to return a single instance for all
|
4
|
+
# logger requests. You would use this provider to supply all parts of the
|
5
|
+
# application with the exact same logger.
|
6
|
+
#
|
7
|
+
# The NullLogger uses the SoloProvider to register itself under the "null"
|
8
|
+
# provider name.
|
9
|
+
#
|
10
|
+
# Examples
|
11
|
+
#
|
12
|
+
# Polylog.register_provider 'stdout', Polylog::SoloProvider.new(Logger.new(STDOUT))
|
13
|
+
# Polylog.register_provider 'stderr', Polylog::SoloProvider.new(Logger.new(STDERR))
|
14
|
+
# Polylog.register_provider 'file', Polylog::SoloProvider.new(Logger.new('app.log'))
|
15
|
+
#
|
16
|
+
# Polylog.use_provider 'stderr'
|
17
|
+
# logger = Polylog.logger # returns a STDERR logger
|
18
|
+
#
|
19
|
+
# Polylog.use_provider 'file'
|
20
|
+
# logger = Polylog.logger # returns an 'app.log' file logger
|
21
|
+
#
|
22
|
+
class SoloProvider
|
23
|
+
|
24
|
+
# Constructs a new SoloProvider that will provide the same instance for
|
25
|
+
# each request for a logger.
|
26
|
+
#
|
27
|
+
# logger - The logger instance that will be provided.
|
28
|
+
def initialize( logger )
|
29
|
+
@logger = logger
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the same logger instance for all requests. The `name` is not
|
33
|
+
# used by this this method.
|
34
|
+
#
|
35
|
+
# name - The logger name as a String
|
36
|
+
def logger( name )
|
37
|
+
@logger
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
data/script/bootstrap
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
3
|
+
|
4
|
+
describe Polylog::SoloProvider do
|
5
|
+
attr_reader :provider
|
6
|
+
|
7
|
+
context 'with only a default logger' do
|
8
|
+
before(:all) { @provider = Polylog::MultiProvider.new 'Multipass' }
|
9
|
+
|
10
|
+
it 'provides the same default logger' do
|
11
|
+
default = provider.default_logger
|
12
|
+
|
13
|
+
expect(provider.logger 'foo').to equal default
|
14
|
+
expect(provider.logger 'bar').to equal default
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'when configured with multiple loggers' do
|
19
|
+
before(:all) do
|
20
|
+
@provider = Polylog::MultiProvider.new 'Multipass'
|
21
|
+
@provider['foo'] = 'FooLogger'
|
22
|
+
@provider['bar'] = 'BarLogger'
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'provides loggers based on name' do
|
26
|
+
expect(provider.logger 'foo').to equal provider['foo']
|
27
|
+
expect(provider.logger 'bar').to equal provider['bar']
|
28
|
+
expect(provider.logger 'baz').to equal provider.default_logger
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'returns the list of available loggers' do
|
32
|
+
expect(provider.loggers).to match_array %w[foo bar]
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'determines if a logger is present' do
|
36
|
+
expect(provider.logger? 'foo').to be_true
|
37
|
+
expect(provider.logger? 'bar').to be_true
|
38
|
+
expect(provider.logger? 'baz').to be_false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
|
2
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
3
|
+
|
4
|
+
describe Polylog::NullLogger do
|
5
|
+
attr_reader :null_logger
|
6
|
+
before(:all) { @null_logger = Polylog::NullLogger.new }
|
7
|
+
|
8
|
+
it 'responds to `<<`' do
|
9
|
+
expect(null_logger).to respond_to :<<
|
10
|
+
expect(null_logger << 'foo').to be_nil
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'responds to `close`' do
|
14
|
+
expect(null_logger).to respond_to :close
|
15
|
+
expect(null_logger.close).to be_nil
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'responds to `debug?`' do
|
19
|
+
expect(null_logger).to respond_to :debug?
|
20
|
+
expect(null_logger.debug?).to be_false
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'responds to `info?`' do
|
24
|
+
expect(null_logger).to respond_to :info?
|
25
|
+
expect(null_logger.info?).to be_false
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'responds to `warn?`' do
|
29
|
+
expect(null_logger).to respond_to :warn?
|
30
|
+
expect(null_logger.warn?).to be_false
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'responds to `error?`' do
|
34
|
+
expect(null_logger).to respond_to :error?
|
35
|
+
expect(null_logger.error?).to be_false
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'responds to `fatal?`' do
|
39
|
+
expect(null_logger).to respond_to :fatal?
|
40
|
+
expect(null_logger.fatal?).to be_false
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'responds to `add`' do
|
44
|
+
expect(null_logger).to respond_to :add
|
45
|
+
expect(null_logger.add 'foo').to be_false
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'responds to `debug`' do
|
49
|
+
expect(null_logger).to respond_to :debug
|
50
|
+
expect(null_logger.debug 'foo').to be_false
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'responds to `info`' do
|
54
|
+
expect(null_logger).to respond_to :info
|
55
|
+
expect(null_logger.info 'foo').to be_false
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'responds to `warn`' do
|
59
|
+
expect(null_logger).to respond_to :warn
|
60
|
+
expect(null_logger.warn 'foo').to be_false
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'responds to `error`' do
|
64
|
+
expect(null_logger).to respond_to :error
|
65
|
+
expect(null_logger.error 'foo').to be_false
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'responds to `fatal`' do
|
69
|
+
expect(null_logger).to respond_to :fatal
|
70
|
+
expect(null_logger.fatal 'foo').to be_false
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'responds to `unknown`' do
|
74
|
+
expect(null_logger).to respond_to :unknown
|
75
|
+
expect(null_logger.unknown 'foo').to be_false
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'responds to `log`' do
|
79
|
+
expect(null_logger).to respond_to :log
|
80
|
+
expect(null_logger.log 'foo').to be_false
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
3
|
+
|
4
|
+
describe Polylog::SoloProvider do
|
5
|
+
attr_reader :provider
|
6
|
+
before(:all) { @provider = Polylog::SoloProvider.new 'Han Solo' }
|
7
|
+
|
8
|
+
it 'always provides the same instance' do
|
9
|
+
str = provider.logger nil
|
10
|
+
|
11
|
+
expect(provider.logger 'foo').to equal str
|
12
|
+
expect(provider.logger 'bar').to equal str
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
|
2
|
+
require File.expand_path('../spec_helper', __FILE__)
|
3
|
+
|
4
|
+
describe Polylog do
|
5
|
+
|
6
|
+
it 'provides a canonical version string' do
|
7
|
+
expect(Polylog.version).to match %r/^\d+\.\d+\.\d+$/
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'without any configuration' do
|
11
|
+
it 'provides a null logger' do
|
12
|
+
expect(Polylog.logger).to be_an_instance_of Polylog::NullLogger
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'provides the smae null logger for all requests' do
|
16
|
+
null_logger = Polylog.logger
|
17
|
+
|
18
|
+
expect(Polylog.logger 'foo').to equal null_logger
|
19
|
+
expect(Polylog.logger 'bar').to equal null_logger
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'only has the null provider registered' do
|
23
|
+
expect(Polylog.providers).to eq %w[null]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'when registering a provider' do
|
28
|
+
it 'raises an error for providers that have no `logger` method' do
|
29
|
+
expect {
|
30
|
+
Polylog.register_provider('nope', Object.new)
|
31
|
+
}.to raise_error(Polylog::InvalidProvider, /method not found for provider/)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'raises an error when the `logger` method has the wrong airty' do
|
35
|
+
provider = Object.new
|
36
|
+
class << provider
|
37
|
+
def logger() nil; end
|
38
|
+
end
|
39
|
+
|
40
|
+
expect {
|
41
|
+
Polylog.register_provider('nope', provider)
|
42
|
+
}.to raise_error(Polylog::InvalidProvider, /method arity must be 1/)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'adds the provider to the registry' do
|
46
|
+
expect(Polylog.providers).not_to include('test1')
|
47
|
+
|
48
|
+
provider = Polylog::SoloProvider.new(:test1)
|
49
|
+
Polylog.register_provider 'test1', provider
|
50
|
+
|
51
|
+
expect(Polylog.providers).to include('test1')
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'overwrites existing providers' do
|
55
|
+
provider = Polylog::SoloProvider.new(:test2)
|
56
|
+
Polylog.register_provider 'test2', provider
|
57
|
+
expect(Polylog.providers).to include('test2')
|
58
|
+
Polylog.use_provider 'test2'
|
59
|
+
|
60
|
+
provider = Polylog::SoloProvider.new(:overwrite)
|
61
|
+
Polylog.register_provider 'test2', provider
|
62
|
+
|
63
|
+
expect(Polylog.logger).to equal :test2
|
64
|
+
Polylog.use_provider 'test2'
|
65
|
+
expect(Polylog.logger).to equal :overwrite
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'when selecting a provider to use' do
|
70
|
+
it 'raises an error for unknown providers' do
|
71
|
+
expect {
|
72
|
+
Polylog.use_provider 'foo'
|
73
|
+
}.to raise_error(Polylog::UnknownProvider, 'unknown provider: "foo"')
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'can use a registered provider' do
|
77
|
+
provider = Polylog::SoloProvider.new(:test3)
|
78
|
+
Polylog.register_provider 'test3', provider
|
79
|
+
|
80
|
+
expect(Polylog.providers).to include('test3')
|
81
|
+
|
82
|
+
Polylog.use_provider 'test3'
|
83
|
+
expect(Polylog.logger).to equal :test3
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'when generating logger names' do
|
88
|
+
it 'uses nil as the default' do
|
89
|
+
expect(Polylog.logger_name nil).to be_nil
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'uses String names as is' do
|
93
|
+
expect(Polylog.logger_name 'foo').to eql 'foo'
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'converts Symbols to Strings' do
|
97
|
+
expect(Polylog.logger_name :foo).to eql 'foo'
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'uses Module names' do
|
101
|
+
expect(Polylog.logger_name Enumerable).to eql 'Enumerable'
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'uses Class names for instances' do
|
105
|
+
expect(Polylog.logger_name Object).to eql 'Object'
|
106
|
+
expect(Polylog.logger_name Object.new).to eql 'Object'
|
107
|
+
expect(Polylog.logger_name []).to eql 'Array'
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'uses the orignal Class name for singletons (eigenclasses)' do
|
111
|
+
ary = []
|
112
|
+
eigen = class << ary; self; end
|
113
|
+
|
114
|
+
expect(Polylog.logger_name eigen).to eql 'Array'
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'gives up when confronted with an anonymous module' do
|
118
|
+
m = Module.new { def foo() nil end }
|
119
|
+
expect(Polylog.logger_name m).to be_nil
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
require File.expand_path('../../lib/polylog', __FILE__)
|
3
|
+
|
4
|
+
RSpec.configure do |config|
|
5
|
+
config.before(:all) { Polylog.reset! }
|
6
|
+
|
7
|
+
# == Mock Framework
|
8
|
+
#
|
9
|
+
# RSpec uses it's own mocking framework by default. If you prefer to
|
10
|
+
# use mocha, flexmock or RR, uncomment the appropriate line:
|
11
|
+
#
|
12
|
+
# config.mock_with :mocha
|
13
|
+
# config.mock_with :flexmock
|
14
|
+
# config.mock_with :rr
|
15
|
+
end
|
16
|
+
|
17
|
+
module Polylog
|
18
|
+
# Only used for testing. This resets the Polylog providers configuration to
|
19
|
+
# its uninitialized state.
|
20
|
+
def reset!
|
21
|
+
remove_instance_variable :@provider if defined? @provider
|
22
|
+
|
23
|
+
@providers.keys.each do |key|
|
24
|
+
next if key == 'null'
|
25
|
+
@providers.delete key
|
26
|
+
end
|
27
|
+
|
28
|
+
self
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
data/version.txt
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: polylog
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Tim Pease
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-10-14 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bones-rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.0.1
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.0.1
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bones-git
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.3.0
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.3.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rdoc
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 3.12.2
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 3.12.2
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bones
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 3.8.1
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 3.8.1
|
69
|
+
description: ''
|
70
|
+
email: tim.pease@gmail.com
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files:
|
74
|
+
- History.txt
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- History.txt
|
78
|
+
- LICENSE.md
|
79
|
+
- README.md
|
80
|
+
- Rakefile
|
81
|
+
- lib/polylog.rb
|
82
|
+
- lib/polylog/errors.rb
|
83
|
+
- lib/polylog/multi_provider.rb
|
84
|
+
- lib/polylog/null_logger.rb
|
85
|
+
- lib/polylog/solo_provider.rb
|
86
|
+
- script/bootstrap
|
87
|
+
- spec/polylog/multi_provider_spec.rb
|
88
|
+
- spec/polylog/null_logger_spec.rb
|
89
|
+
- spec/polylog/solo_provider_spec.rb
|
90
|
+
- spec/polylog_spec.rb
|
91
|
+
- spec/spec_helper.rb
|
92
|
+
- version.txt
|
93
|
+
homepage: http://rubygems.org/gems/polylog
|
94
|
+
licenses: []
|
95
|
+
metadata: {}
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options:
|
98
|
+
- --main
|
99
|
+
- README.md
|
100
|
+
require_paths:
|
101
|
+
- lib
|
102
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
103
|
+
requirements:
|
104
|
+
- - '>='
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '0'
|
107
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
108
|
+
requirements:
|
109
|
+
- - '>='
|
110
|
+
- !ruby/object:Gem::Version
|
111
|
+
version: '0'
|
112
|
+
requirements: []
|
113
|
+
rubyforge_project: polylog
|
114
|
+
rubygems_version: 2.0.3
|
115
|
+
signing_key:
|
116
|
+
specification_version: 4
|
117
|
+
summary: ''
|
118
|
+
test_files: []
|