needle 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/doc/faq/faq.yml +396 -14
  2. data/doc/manual-html/chapter-1.html +33 -4
  3. data/doc/manual-html/chapter-2.html +44 -6
  4. data/doc/manual-html/chapter-3.html +34 -5
  5. data/doc/manual-html/chapter-4.html +33 -4
  6. data/doc/manual-html/chapter-5.html +208 -4
  7. data/doc/manual-html/chapter-6.html +226 -4
  8. data/doc/manual-html/chapter-7.html +165 -4
  9. data/doc/manual-html/chapter-8.html +138 -6
  10. data/doc/manual-html/index.html +36 -9
  11. data/doc/manual/manual.rb +1 -1
  12. data/doc/manual/manual.yml +17 -4
  13. data/doc/manual/page.erb +2 -1
  14. data/doc/manual/parts/02_namespaces.txt +11 -2
  15. data/doc/manual/parts/03_overview.txt +1 -1
  16. data/doc/manual/parts/interceptors_architecture.txt +5 -0
  17. data/doc/manual/parts/interceptors_attaching.txt +64 -0
  18. data/doc/manual/parts/interceptors_custom.txt +25 -0
  19. data/doc/manual/parts/interceptors_ordering.txt +13 -0
  20. data/doc/manual/parts/interceptors_overview.txt +5 -0
  21. data/doc/manual/parts/libraries_creating.txt +30 -0
  22. data/doc/manual/parts/libraries_overview.txt +3 -0
  23. data/doc/manual/parts/libraries_using.txt +31 -0
  24. data/doc/manual/parts/logging_configuration.txt +30 -0
  25. data/doc/manual/parts/logging_logfactory.txt +31 -0
  26. data/doc/manual/parts/logging_overview.txt +5 -0
  27. data/doc/manual/parts/models_models.txt +35 -0
  28. data/doc/manual/parts/models_overview.txt +3 -0
  29. data/doc/manual/parts/models_pipelines.txt +63 -0
  30. data/lib/needle/container.rb +52 -7
  31. data/lib/needle/lifecycle/initialize.rb +1 -1
  32. data/lib/needle/log-factory.rb +46 -8
  33. data/lib/needle/thread.rb +6 -0
  34. data/lib/needle/version.rb +2 -2
  35. data/test/pipeline/tc_collection.rb +1 -1
  36. data/test/pipeline/tc_element.rb +2 -2
  37. data/test/services.rb +21 -0
  38. data/test/tc_container.rb +18 -0
  39. data/test/tc_logger.rb +35 -1
  40. 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>
@@ -68,16 +68,25 @@ module Needle
68
68
  @container.namespace( *parms, &block )
69
69
  end
70
70
 
71
- # Delegate to Container#namespace!.
72
- def namespace!( *parms, &block )
73
- @container.namespace!( *parms, &block )
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, :calc ) }
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, :calc ) }
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, :calc ) }
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.define_namespace( :operations ) do |b|
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
@@ -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 "level" => nil, "date-format" => nil,
94
- "message-format" => nil
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 = { "level" => value.to_s }
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[ "level" ] || @default_level
168
- date_format = definition[ "date-format" ] || @default_date_format
169
- message_format = definition[ "message-format" ] ||
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