needle 0.9.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/doc/faq/faq.yml +396 -14
- data/doc/manual-html/chapter-1.html +33 -4
- data/doc/manual-html/chapter-2.html +44 -6
- data/doc/manual-html/chapter-3.html +34 -5
- data/doc/manual-html/chapter-4.html +33 -4
- data/doc/manual-html/chapter-5.html +208 -4
- data/doc/manual-html/chapter-6.html +226 -4
- data/doc/manual-html/chapter-7.html +165 -4
- data/doc/manual-html/chapter-8.html +138 -6
- data/doc/manual-html/index.html +36 -9
- data/doc/manual/manual.rb +1 -1
- data/doc/manual/manual.yml +17 -4
- data/doc/manual/page.erb +2 -1
- data/doc/manual/parts/02_namespaces.txt +11 -2
- data/doc/manual/parts/03_overview.txt +1 -1
- data/doc/manual/parts/interceptors_architecture.txt +5 -0
- data/doc/manual/parts/interceptors_attaching.txt +64 -0
- data/doc/manual/parts/interceptors_custom.txt +25 -0
- data/doc/manual/parts/interceptors_ordering.txt +13 -0
- data/doc/manual/parts/interceptors_overview.txt +5 -0
- data/doc/manual/parts/libraries_creating.txt +30 -0
- data/doc/manual/parts/libraries_overview.txt +3 -0
- data/doc/manual/parts/libraries_using.txt +31 -0
- data/doc/manual/parts/logging_configuration.txt +30 -0
- data/doc/manual/parts/logging_logfactory.txt +31 -0
- data/doc/manual/parts/logging_overview.txt +5 -0
- data/doc/manual/parts/models_models.txt +35 -0
- data/doc/manual/parts/models_overview.txt +3 -0
- data/doc/manual/parts/models_pipelines.txt +63 -0
- data/lib/needle/container.rb +52 -7
- data/lib/needle/lifecycle/initialize.rb +1 -1
- data/lib/needle/log-factory.rb +46 -8
- data/lib/needle/thread.rb +6 -0
- data/lib/needle/version.rb +2 -2
- data/test/pipeline/tc_collection.rb +1 -1
- data/test/pipeline/tc_element.rb +2 -2
- data/test/services.rb +21 -0
- data/test/tc_container.rb +18 -0
- data/test/tc_logger.rb +35 -1
- metadata +17 -2
@@ -0,0 +1,25 @@
|
|
1
|
+
Creating your own interceptors is very easy. As was demonstrated earlier, you can always use blocks to implement an interceptor. However, for more complex interceptors, or for interceptors that you want to reuse across multiple services, you can also implement your own interceptor _factories_.
|
2
|
+
|
3
|
+
An interceptor factory can be any object, as long as it implements the method @#new@ with two parameters, the _service point_ (service "definition") of the service that the interceptor will be bound to, and a hash of the options that were passed to the interceptor when it was attached to the service. This method should then return a new interceptor instance, which must implement the @#process@ method. The @#process@ method should accept two parameters: an object representing the _chain_ of interceptors, and the invocation _context_.
|
4
|
+
|
5
|
+
<pre>
|
6
|
+
class MyInterceptorFactory
|
7
|
+
def initialize( point, options )
|
8
|
+
...
|
9
|
+
end
|
10
|
+
|
11
|
+
def process( chain, context )
|
12
|
+
# context.sym : the name of the method that was invoked
|
13
|
+
# context.args : the array of arguments passed to the method
|
14
|
+
# context.block : the block passed to the method, if any
|
15
|
+
# context.data : a hash that may be used to share data between interceptors
|
16
|
+
return context.process_next( context )
|
17
|
+
end
|
18
|
+
end
|
19
|
+
</pre>
|
20
|
+
|
21
|
+
Once you've created your factory, you can attach it to a service:
|
22
|
+
|
23
|
+
<pre>
|
24
|
+
reg.intercept( :foo ).with { MyInterceptorFactory }
|
25
|
+
</pre>
|
@@ -0,0 +1,13 @@
|
|
1
|
+
As was mentioned, a service may have multiple interceptors attached to it. By default, a method will be filtered by the interceptors in the same order that they were attached, with the first interceptor that was attached being the first one to intercept every method call.
|
2
|
+
|
3
|
+
You can specify a different ordering of the interceptors by giving each one a _priority_. The priority is a number, where interceptors with a higher priority sort _closer_ to the service, and those with lower priorities sort _further_ from the service.
|
4
|
+
|
5
|
+
You can specify the priority as an option when attaching an interceptor:
|
6
|
+
|
7
|
+
<pre>
|
8
|
+
reg.register( :foo ) { ... }
|
9
|
+
reg.intercept( :foo ).with { Something }.with_options( :priority => 100 )
|
10
|
+
reg.intercept( :foo ).with { SomethingElse }.with_options( :priority => 50 )
|
11
|
+
</pre>
|
12
|
+
|
13
|
+
Without the priorities, when a method of @:foo@ was invoked, Something would be called first, and then SomethingElse. _With_ the priorities (as specified), SomethingElse would be called before Something (since SomethingElse has a lower priority).
|
@@ -0,0 +1,5 @@
|
|
1
|
+
Interceptors are objects (or blocks) that sit between a client and a service and _intercept_ messages (methods) sent to the service. Each service may have many such interceptors. When control is passed to an interceptor, it may then do something before and after passing control to the next interceptor, possibly even returning instead of passing control. This allows for some simple AOP(Aspect-Oriented Programming)-like hooks to be placed on your services.
|
2
|
+
|
3
|
+
Needle comes with one interceptor, the LoggingInterceptor. This allows you to easily trace the execution of your services by logging method entry and exit, as well as any exceptions that are raised.
|
4
|
+
|
5
|
+
You can, of course, implement your own interceptors as well.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
This centralization is implemented by creating a module for each library you want to have services for. That module should then define a module function called (by default) @register_services@. This function should accept a single parameter--the Needle container that the services will be added to.
|
2
|
+
|
3
|
+
For example, if I had a library of cryptographic routines and I wanted to make them accessible as Needle services, I would create a module to contain the service definitions. Typically, this module will be defined in a file called "services.rb", although you can certainly name it whatever you like.
|
4
|
+
|
5
|
+
<pre>
|
6
|
+
module Crypto
|
7
|
+
|
8
|
+
def register_services( container )
|
9
|
+
container.namespace_define( :crypto ) do |b|
|
10
|
+
b.prng do
|
11
|
+
require 'crypto/prng'
|
12
|
+
PRNG.new
|
13
|
+
end
|
14
|
+
|
15
|
+
b.des do
|
16
|
+
require 'crypto/des'
|
17
|
+
DES.new
|
18
|
+
end
|
19
|
+
|
20
|
+
...
|
21
|
+
end
|
22
|
+
end
|
23
|
+
module_function :register_services
|
24
|
+
|
25
|
+
end
|
26
|
+
</pre>
|
27
|
+
|
28
|
+
Notice that there are no explicit dependencies on Needle, only on the interfaces Needle publishes. Thus, third-parties can add service configuration modules to their libraries without introducing dependencies on Needle.
|
29
|
+
|
30
|
+
Once a service library has been created, it can then be accessed from another library or application that wishes to import those services.
|
@@ -0,0 +1,3 @@
|
|
1
|
+
Using Needle as it has been presented so far works fine when you are dealing with a single application, all self-encapsulated. When you start dealing with combining multiple libraries, each potentially written by a different author, into a single registry, things get a little more complicated.
|
2
|
+
|
3
|
+
Needle provides a way for service authors to share their services. All it requires is that authors centralize their service configuration.
|
@@ -0,0 +1,31 @@
|
|
1
|
+
Using the libraries is as simple as requiring the file that has the service definitions, and then invoking the @#register_services@ module function:
|
2
|
+
|
3
|
+
<pre>
|
4
|
+
require 'needle'
|
5
|
+
|
6
|
+
reg = Needle::Registry.new
|
7
|
+
reg.define do |b|
|
8
|
+
b.foo { Foo.new }
|
9
|
+
|
10
|
+
require 'crypto/services'
|
11
|
+
Crypto.register_services( reg )
|
12
|
+
end
|
13
|
+
|
14
|
+
prng = reg.crypto.prng
|
15
|
+
</pre>
|
16
|
+
|
17
|
+
To make this easier, the Container class has a convenience method named @#require@:
|
18
|
+
|
19
|
+
<pre>
|
20
|
+
require 'needle'
|
21
|
+
|
22
|
+
reg = Needle::Registry.new
|
23
|
+
reg.define do |b|
|
24
|
+
b.foo { Foo.new }
|
25
|
+
b.require 'crypto/services', "Crypto"
|
26
|
+
end
|
27
|
+
|
28
|
+
prng = reg.crypto.prng
|
29
|
+
</pre>
|
30
|
+
|
31
|
+
The @Container#require@ method will require the file, and then look for a @#register_services@ method of the named module. It will execute that method, passing the container as an argument.
|
@@ -0,0 +1,30 @@
|
|
1
|
+
You can configure the LogFactory when you create the registry by passing a hash as the @:logs@ option to @Registry.new@. The hash may have the following properties:
|
2
|
+
|
3
|
+
|^. @:device@|Should refer to an IO or pseudo-IO object that will receive @#write@ and @#close@ messages.|
|
4
|
+
|^. @:filename@|The name of the file to write log messages to.|
|
5
|
+
|^. @:roll_age@|The number of days before the log should be rolled.|
|
6
|
+
|^. @:roll_frequency@|Either 'daily', 'weekly', or 'monthly', indicating how often the log should be rolled.|
|
7
|
+
|^. @:roll_size@|The maximum size that a log file may grow to before it is rolled.|
|
8
|
+
|^. @:default_date_format@|A @strftime@-formatted string to use for formatting message dates and times.|
|
9
|
+
|^. @:default_message_format@|A printf-styled format string to use when formatting messages. It's format is reminiscent of Log4r, but differs in some options. See the API documentation for Needle::Logger for details.|
|
10
|
+
|^. @:default_level@|One of @:debug@, @:info@, @:warn@, @:error@, or @:fatal@, indicating the default level of logging detail to use.|
|
11
|
+
|^. @:levels@|Another hash of pattern/level or pattern/hash pairs, where the pattern is a string describing the logger name (or names) that it matches. If the corresponding value of a key is a symbol or a string, then it describes the logging level to use for loggers that match it. If the value is a hash, then it may contain @:level@, @:date_format@, or @:message_format@ keys that apply to matching logs.|
|
12
|
+
|
13
|
+
By default, the filename is "./needle.log", and the roll_size is one megabyte. The default level is @:debug@. If you wanted to specify a different filename and default level of @:warn@, you could do:
|
14
|
+
|
15
|
+
<pre>
|
16
|
+
reg = Needle::Registry.new(
|
17
|
+
:logs => {
|
18
|
+
:filename => "./my-app.log",
|
19
|
+
:default_level => :warn }
|
20
|
+
)
|
21
|
+
</pre>
|
22
|
+
|
23
|
+
Alternatively, you can easily put the logging configuration in a YAML file and read it in, like so:
|
24
|
+
|
25
|
+
<pre>
|
26
|
+
require 'yaml'
|
27
|
+
reg = Needle::Registry.new(
|
28
|
+
:logs => YAML.load( File.read( "log-conf.yml" ) )
|
29
|
+
)
|
30
|
+
</pre>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
The LogFactory is available as a service, so that any component that needs a logger instance can gain easy access to one. The factory's service name is @:logs@.
|
2
|
+
|
3
|
+
<pre>
|
4
|
+
factory = reg.logs
|
5
|
+
</pre>
|
6
|
+
|
7
|
+
You obtain logger instances from the factory by passing a logger name to @#get@:
|
8
|
+
|
9
|
+
<pre>
|
10
|
+
logger = reg.logs.get "a logger name"
|
11
|
+
</pre>
|
12
|
+
|
13
|
+
Subsequent calls to @#get@ will return the same logger instance for the given logger name.
|
14
|
+
|
15
|
+
h3. Loggers for Services
|
16
|
+
|
17
|
+
Typically, you'll use this to assign a logger instance to a service when it is constructed. In that case, the name of the logger is the fully-qualified name of the service:
|
18
|
+
|
19
|
+
<pre>
|
20
|
+
reg.register( :foo ) do |c,p|
|
21
|
+
Foo.new( c.logs.get( p.fullname ) )
|
22
|
+
end
|
23
|
+
</pre>
|
24
|
+
|
25
|
+
As a convenience, if the value passed to @#get@ responds to either @fullname@ or @name@, the return value of that message will be used as the name. Thus, you can do the following:
|
26
|
+
|
27
|
+
<pre>
|
28
|
+
reg.register( :foo ) do |c,p|
|
29
|
+
Foo.new( c.logs.get( p ) )
|
30
|
+
end
|
31
|
+
</pre>
|
@@ -0,0 +1,5 @@
|
|
1
|
+
Logging is a pretty common activity in many applications. Because it is so common, Needle provides a powerful framework for logging messages in a consistent manner.
|
2
|
+
|
3
|
+
Needle's logging framework is built on top of the standard Ruby @Logger@ library. However, it extends that library to add additional functionality, including customizable message formats and a centralized location for obtaining logger instances.
|
4
|
+
|
5
|
+
This centralized location is the LogFactory.
|
@@ -0,0 +1,35 @@
|
|
1
|
+
Specifying an entire pipeline for every service point can be tedious. For that reason, there are _service models_. A service model is a kind of template that names a pre-configured sequence of pipeline elements. Needle comes preconfigured with many service models.
|
2
|
+
|
3
|
+
h3. Standard Service Models:
|
4
|
+
|
5
|
+
table{border: 1px solid black}.
|
6
|
+
|_<. Name|_<. Pipeline|_<. Effect|
|
7
|
+
|^. @:prototype@|^. (empty)|Immediately instantiate the service, at every request.|
|
8
|
+
|^. @:prorotype_deferred@|^. @:deferred@|Return a proxy, that will instantiate the service the first time a method is invoked on the proxy. A new proxy instance will be returned for each request.|
|
9
|
+
|^. @:prototype_initialize@|^. @:initialize@|Immediately instantiate the service, and invoke an initialization method, at every request.|
|
10
|
+
|^. @:prototype_deferred_initialize@|^. @:deferred@, @:initialize@|Return a proxy, that will instantiate the service and invoke an initialization method the first time a method is invoked on the proxy. A new proxy instance will be returned for each request.|
|
11
|
+
|^. @:singleton@|^. @:singleton@|Immediately instantiate the service the first time it is requested, returning a cached instance subsequently.|
|
12
|
+
|^. @:singleton_deferred@|^. @:singleton@, @:deferred@|Return a proxy that will instantiate the service the first time a method is requested on the proxy. Subsequent requests for this service will return the same proxy instance.|
|
13
|
+
|^. @:singleton_initialize@|^. @:singleton@, @:initialize@|Immediately instantiate a service and invoke an initialization method on it. Subsequent requests for this service will return a cached instance.|
|
14
|
+
|^. @:singleton_deferred_initialize@|^. @:singleton@, @:deferred@, @:initialize@|Return a proxy that will instantiate the service and invoke an initialization method on it the first time a method is requested on the proxy. Subsequent requests for this service will return the same proxy instance.|
|
15
|
+
|^. @:threaded@|^. @:threaded@|Immediately instantiate the service the first time it is requested from the current thread, returning a cached instance for every subsequent request from the same thread.|
|
16
|
+
|^. @:threaded_deferred@|^. @:threaded@, @:deferred@|Return a proxy object that will instantiate the service the first time a method is invoked on the proxy. Subsequent requests for this service from a given thread will return the thread's cached instance.|
|
17
|
+
|^. @:threaded_initialize@|^. @:threaded@, @:initialize@|Immediately instantiate the service the first time it is requested from the current thread and invoke an initialization method on it. Subsequent requests for this service from the same thread will return the cached instance.|
|
18
|
+
|^. @:threaded_deferred_initialize@|^. @:threaded@, @:deferred@, @:initialize@|Return a proxy object that will instantiate the service and invoke an initialization method on it the first time a method is invoked on the proxy. Subsequent requests for this service from a given thread will return the thread's cached instance.|
|
19
|
+
|
20
|
+
h3. Specifying a Service Model
|
21
|
+
|
22
|
+
You specify the service model by passing the @:model@ option when you register a service. (You must only specify _either_ the model, _or_ the pipeline, but not both.)
|
23
|
+
|
24
|
+
<pre>
|
25
|
+
reg.register( :foo, :model => :singleton_deferred ) {...}
|
26
|
+
</pre>
|
27
|
+
|
28
|
+
h3. Defining New Models
|
29
|
+
|
30
|
+
You can create your own service models by adding the corresponding pipelines to the @:service_models@ service:
|
31
|
+
|
32
|
+
<pre>
|
33
|
+
reg.service_models[ :my_service_model ] = [ :singleton, :my_pipeline_element ]
|
34
|
+
reg.register( :foo, :model => :my_service_model ) {...}
|
35
|
+
</pre>
|
@@ -0,0 +1,3 @@
|
|
1
|
+
Service models are the mechanism by which a client can specify how the lifecycle of a particular service should be managed. By default, all services are managed as _singletons_, but it is a simple matter to choose a different behavior when a service is registered.
|
2
|
+
|
3
|
+
Underneath, service models are implemented using an _instantiation pipeline_.
|
@@ -0,0 +1,63 @@
|
|
1
|
+
An _instantiation pipeline_ is a sequence of elements, each of which knows how to perform some aspect of the instantiation of a service.
|
2
|
+
|
3
|
+
Every service consists of at least one instantiation element--the block that was given when the service was registered. Other elements may be combined with this block to enforce various aspects of lifecycle management, such as _multiplicity_ (singleton vs. prototype) and _laziness_ (deferred vs. immediate instantiation).
|
4
|
+
|
5
|
+
h3. Standard Pipeline Elements
|
6
|
+
|
7
|
+
There are five standard pipeline elements available in Needle (although you may certainly create your own):
|
8
|
+
|
9
|
+
* @deferred@: this will always return a proxy that wraps subsequent pipeline elements, causing the subsequent elements to be executed only when a method is invoked on the proxy (at which point the method is then delegated to the resulting service).
|
10
|
+
* @initialize@: this will invoke a method on the resulting service (defaults to @initialize_service@, though it can be changed). It is used for doing final initialization of services (for services that need it).
|
11
|
+
* @interceptor@: this element is used to implement the proxy that wraps the interceptors around the service. It is only attached to the pipeline when an interceptor is attached to a service.
|
12
|
+
* @singleton@: this is a multiplicity guard that ensures a service is instantiated only once per process.
|
13
|
+
* @threaded@: this is like the @singleton@ element, but it ensures that a service is instantiated no more than once _per thread_.
|
14
|
+
|
15
|
+
h3. Priorities
|
16
|
+
|
17
|
+
Just like interceptors, pipeline elements have priorities as well. These priorities determine the order in which the elements are executed in the pipeline.
|
18
|
+
|
19
|
+
Each element type has a default priority, although that priority can be overridden when the element is added to the pipeline.
|
20
|
+
|
21
|
+
h3. Custom Pipeline Elements
|
22
|
+
|
23
|
+
Creating new pipeline elements simple. Just create a new class that extends @Needle::Pipeline::Element@. Set the default pipeline priority (using the @#set_default_priority@ class method), and then implement the @#call@ method (accepting at least two parameters: the container and the service point).
|
24
|
+
|
25
|
+
<pre>
|
26
|
+
require 'needle/pipeline/element'
|
27
|
+
|
28
|
+
class MyPipelineElement < Needle::Pipeline::Element
|
29
|
+
set_default_priority 50
|
30
|
+
|
31
|
+
def call( container, point, *args )
|
32
|
+
...
|
33
|
+
result = succ.call( container, point, *args )
|
34
|
+
...
|
35
|
+
return result
|
36
|
+
end
|
37
|
+
end
|
38
|
+
</pre>
|
39
|
+
|
40
|
+
To invoke the next element of the pipeline, just invoke @#succ.call(...)@.
|
41
|
+
|
42
|
+
If needed, you can also implement @#initialize_element@ (with no arguments), which you may invoke to perform initialization of the element. From there, you can access the options that were given to the element via the @#options@ accessor.
|
43
|
+
|
44
|
+
See the implementations of the existing elements in @needle/lifecycle@ for examples.
|
45
|
+
|
46
|
+
h3. Added Pipelines Elements to Services
|
47
|
+
|
48
|
+
You can specify the pipeline elements to use for a service via the @:pipeline@ option. This must refer to an array, each element of which must be either a symbol (in which case it references an element of the @:pipeline_elements@ service), or a class (in which case it must implement the interface required by @Needle::Pipeline::Element).
|
49
|
+
|
50
|
+
<pre>
|
51
|
+
reg.register( :foo, :pipeline => [ :singleton, MyPipelineElement ] ) { ... }
|
52
|
+
</pre>
|
53
|
+
|
54
|
+
The elements will be sorted based on their priorities, with lower priorities sorting closer to the instantiation block, and higher priorities sorting closer to the client.
|
55
|
+
|
56
|
+
h3. Making Custom Pipeline Elements Available
|
57
|
+
|
58
|
+
You can make your custom pipeline elements available (so they can be referenced by symbol, instead of class name) by adding them to the @:pipeline_elements@ service:
|
59
|
+
|
60
|
+
<pre>
|
61
|
+
reg.pipeline_elements[ :my_pipeline_element ] = MyPipelineElement
|
62
|
+
reg.register( :foo, :pipeline => [ :singleton, :my_pipeline_element ] ) { ... }
|
63
|
+
</pre>
|
data/lib/needle/container.rb
CHANGED
@@ -68,16 +68,25 @@ module Needle
|
|
68
68
|
@container.namespace( *parms, &block )
|
69
69
|
end
|
70
70
|
|
71
|
-
# Delegate to Container#
|
72
|
-
def
|
73
|
-
@container.
|
71
|
+
# Delegate to Container#namespace_define!.
|
72
|
+
def namespace_define!( *parms, &block )
|
73
|
+
@container.namespace_define!( *parms, &block )
|
74
74
|
end
|
75
75
|
|
76
|
+
alias :namespace! :namespace_define!
|
77
|
+
|
76
78
|
# Delegate to Container#define on the new namespace.
|
77
79
|
def namespace_define( *parms, &block )
|
78
80
|
@container.namespace( *parms ) { |ns| ns.define( &block ) }
|
79
81
|
end
|
80
82
|
|
83
|
+
# Delegate to Container#require on the current container.
|
84
|
+
def require( *parms )
|
85
|
+
# this is necessary to work around an rdoc bug...rdoc doesn't like
|
86
|
+
# calling require with a variable number of arguments.
|
87
|
+
@container.__send__( :require, *parms )
|
88
|
+
end
|
89
|
+
|
81
90
|
# Any method invocation with no block and no parameters is interpreted to
|
82
91
|
# be a service reference on the wrapped container, and delegates to
|
83
92
|
# Container#[]. If the block is not given but the args are not empty, a
|
@@ -202,7 +211,7 @@ module Needle
|
|
202
211
|
# by Container. The two statements are really identical:
|
203
212
|
#
|
204
213
|
# container.namespace( :calc )
|
205
|
-
# container.register( :calc ) { |c| Needle::Container.new( c,
|
214
|
+
# container.register( :calc ) { |c,p| Needle::Container.new( c, p.name ) }
|
206
215
|
#
|
207
216
|
# Note that this means that namespaces may be singletons or prototypes, or
|
208
217
|
# have immediate or deferred instantiation, and so forth. (The default of
|
@@ -238,7 +247,7 @@ module Needle
|
|
238
247
|
# by Container. The two statements are really identical:
|
239
248
|
#
|
240
249
|
# container.namespace( :calc )
|
241
|
-
# container.register( :calc ) { |c| Needle::Container.new( c,
|
250
|
+
# container.register( :calc ) { |c,p| Needle::Container.new( c, p.name ) }
|
242
251
|
#
|
243
252
|
# Note that this means that namespaces may be singletons or prototypes, or
|
244
253
|
# have immediate or deferred instantiation, and so forth. (The default of
|
@@ -275,7 +284,7 @@ module Needle
|
|
275
284
|
# by Container. The two statements are really identical:
|
276
285
|
#
|
277
286
|
# container.namespace( :calc )
|
278
|
-
# container.register( :calc ) { |c| Needle::Container.new( c,
|
287
|
+
# container.register( :calc ) { |c,p| Needle::Container.new( c, p.name ) }
|
279
288
|
#
|
280
289
|
# Note that this means that namespaces may be singletons or prototypes, or
|
281
290
|
# have immediate or deferred instantiation, and so forth. (The default of
|
@@ -284,7 +293,7 @@ module Needle
|
|
284
293
|
#
|
285
294
|
# Usage:
|
286
295
|
#
|
287
|
-
# container.
|
296
|
+
# container.namespace_define( :operations ) do |b|
|
288
297
|
# b.add { Adder.new }
|
289
298
|
# ...
|
290
299
|
# end
|
@@ -374,6 +383,42 @@ module Needle
|
|
374
383
|
@service_points.keys
|
375
384
|
end
|
376
385
|
|
386
|
+
# Require the given file, and then invoke the given registration method on
|
387
|
+
# the target module. The container will be passed as the sole parameter to
|
388
|
+
# the registration method. This allows you to easily decentralize the
|
389
|
+
# definition of services.
|
390
|
+
#
|
391
|
+
# Usage:
|
392
|
+
#
|
393
|
+
# container.require( "app/services", "App::Services" )
|
394
|
+
#
|
395
|
+
# # in app/services.rb:
|
396
|
+
#
|
397
|
+
# module App
|
398
|
+
# module Services
|
399
|
+
#
|
400
|
+
# def register_services( container )
|
401
|
+
# ...
|
402
|
+
# end
|
403
|
+
# module_function :register_services
|
404
|
+
#
|
405
|
+
# end
|
406
|
+
# end
|
407
|
+
def require( file, target_name, registration_method=:register_services )
|
408
|
+
Kernel.require file
|
409
|
+
|
410
|
+
if target_name.is_a?( Module )
|
411
|
+
target = target_name
|
412
|
+
else
|
413
|
+
target = Object
|
414
|
+
target_name.to_s.split( /::/ ).each do |element|
|
415
|
+
target = target.const_get( element )
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
target.__send__( registration_method, self )
|
420
|
+
end
|
421
|
+
|
377
422
|
# As a convenience for accessing services, this delegates any message
|
378
423
|
# sent to the container (which has no parameters and no block) to
|
379
424
|
# Container#[]. Note that this incurs slightly more overhead than simply
|
@@ -25,7 +25,7 @@ module Needle
|
|
25
25
|
# (hence, the low default priority).
|
26
26
|
class Initialize < Needle::Pipeline::Element
|
27
27
|
|
28
|
-
set_default_priority -100
|
28
|
+
set_default_priority( -100 )
|
29
29
|
|
30
30
|
# Initialize the element. Looks at the options hash to determine the
|
31
31
|
# name of the initialization method to call, defaulting to
|
data/lib/needle/log-factory.rb
CHANGED
@@ -14,6 +14,7 @@
|
|
14
14
|
# =============================================================================
|
15
15
|
#++
|
16
16
|
|
17
|
+
require 'needle/errors'
|
17
18
|
require 'needle/logger'
|
18
19
|
require 'thread'
|
19
20
|
|
@@ -52,6 +53,12 @@ module Needle
|
|
52
53
|
# The device that logs will write to.
|
53
54
|
attr_reader :device
|
54
55
|
|
56
|
+
VALID_OPTIONS = [ :device, :filename, :roll_age, :roll_frequency,
|
57
|
+
:roll_size, :default_date_format,
|
58
|
+
:default_message_format, :default_level, :levels ]
|
59
|
+
|
60
|
+
VALID_LEVEL_OPTIONS = [ :level, :date_format, :message_format ]
|
61
|
+
|
55
62
|
# Create a new LogFactory using the given initialization parameters.
|
56
63
|
# The valid options are:
|
57
64
|
#
|
@@ -71,6 +78,11 @@ module Needle
|
|
71
78
|
# 'date_format', and 'message_format' keys, specifying the log level,
|
72
79
|
# date format, and message format for any log whose name matches the key.
|
73
80
|
def initialize( opts={} )
|
81
|
+
opts = convert_keys_to_symbols( opts )
|
82
|
+
bad = opts.keys - VALID_OPTIONS
|
83
|
+
raise ArgumentError,
|
84
|
+
"invalid option(s) to LogFactory (#{bad.inspect})" unless bad.empty?
|
85
|
+
|
74
86
|
if opts[:device]
|
75
87
|
@device = opts[:device]
|
76
88
|
else
|
@@ -87,17 +99,33 @@ module Needle
|
|
87
99
|
@default_level = opts[:default_level]
|
88
100
|
|
89
101
|
if @default_level.is_a?( String ) || @default_level.is_a?( Symbol )
|
90
|
-
@default_level = LEVEL_TRANSLATOR[@default_level.to_s]
|
102
|
+
@default_level = LEVEL_TRANSLATOR[@default_level.to_s.upcase]
|
103
|
+
if @default_level.nil?
|
104
|
+
raise ArgumentError,
|
105
|
+
"invalid logging level (#{@default_level.inspect})"
|
106
|
+
end
|
91
107
|
end
|
92
108
|
|
93
|
-
@levels = Hash.new
|
94
|
-
|
109
|
+
@levels = Hash.new :level => nil, :date_format => nil,
|
110
|
+
:message_format => nil
|
95
111
|
|
96
112
|
( opts[:levels] || {} ).each_pair do |key, value|
|
97
113
|
key = Regexp.new( "^" + key.gsub( /\./, "\\." ).gsub( /\*/, ".*" ) )
|
98
114
|
|
99
115
|
if value.is_a?( String ) || value.is_a?( Symbol )
|
100
|
-
value = {
|
116
|
+
value = { :level => value.to_s }
|
117
|
+
else
|
118
|
+
value = convert_keys_to_symbols( value )
|
119
|
+
end
|
120
|
+
|
121
|
+
bad = value.keys - VALID_LEVEL_OPTIONS
|
122
|
+
raise ArgumentError,
|
123
|
+
"invalid log level option(s) #{bad.inspect}" unless bad.empty?
|
124
|
+
|
125
|
+
value[ :level ] = LEVEL_TRANSLATOR[ value[:level].to_s.upcase ]
|
126
|
+
if value[:level].nil?
|
127
|
+
raise ArgumentError,
|
128
|
+
"invalid logging level (#{value[:level].inspect})"
|
101
129
|
end
|
102
130
|
|
103
131
|
@levels[ key ] = value
|
@@ -150,6 +178,9 @@ module Needle
|
|
150
178
|
# Retrieves the logger with the given name. If no such log has been
|
151
179
|
# created, the log will be created and initialized. Otherwise, the
|
152
180
|
# log with the given name is returned.
|
181
|
+
#
|
182
|
+
# If +name+ responds to either +fullname+ or +name+, then the result
|
183
|
+
# of invoking that message on +name+ will be used as the name.
|
153
184
|
def get( name )
|
154
185
|
name = name.fullname if name.respond_to?( :fullname )
|
155
186
|
name = name.name if name.respond_to?( :name )
|
@@ -164,12 +195,12 @@ module Needle
|
|
164
195
|
|
165
196
|
definition = find_definition( name )
|
166
197
|
|
167
|
-
level = definition[
|
168
|
-
date_format = definition[
|
169
|
-
message_format = definition[
|
198
|
+
level = definition[ :level ] || @default_level
|
199
|
+
date_format = definition[ :date_format ] || @default_date_format
|
200
|
+
message_format = definition[ :message_format ] ||
|
170
201
|
@default_message_format
|
171
202
|
|
172
|
-
level = LEVEL_TRANSLATOR[ level ] || level
|
203
|
+
level = LEVEL_TRANSLATOR[ level.to_s.upcase ] || level
|
173
204
|
|
174
205
|
logger = Needle::Logger.new( @device )
|
175
206
|
logger.level = level if level
|
@@ -202,6 +233,13 @@ module Needle
|
|
202
233
|
@closed
|
203
234
|
end
|
204
235
|
|
236
|
+
# Converts the keys of the given hash to symbols, and returns a new
|
237
|
+
# hash.
|
238
|
+
def convert_keys_to_symbols( hash )
|
239
|
+
Hash[ *hash.collect { |k,v| [ k.to_s.tr('-','_').to_sym, v ] }.flatten ]
|
240
|
+
end
|
241
|
+
private :convert_keys_to_symbols
|
242
|
+
|
205
243
|
end
|
206
244
|
|
207
245
|
end
|