logjam 1.0.0 → 1.2.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e63f9ce2ae2e48b95364c75fe42b5ed323757de4ff4057863836ddb6c7f4868f
4
+ data.tar.gz: 79b935773b2cd4b733c2faea6f9c3394525edaa5b3072360f3540d9a68e9e9c4
5
+ SHA512:
6
+ metadata.gz: 57048b4c679e0cd033d65506d723fecc639f070cd24f585af57f25b999fe53277a8e104269461ef9237571326d23748eb5928b1265b0666963dd104d1b06e612
7
+ data.tar.gz: f04e403bed681e726200cc2800d2f258e1377c064fbb7d991231e108615dd2b4f1c413c2924b5d8c16aa68c94ed7d75519b6e08ae50f8acfdeef1a4ceda8c41e
@@ -0,0 +1,338 @@
1
+ # LogJam
2
+
3
+ LogJam is a library that attempts to allow for the aggregation and the
4
+ distribution of logging facilities across a range of classes. Goals in
5
+ creating this library were...
6
+
7
+ * Easy of use. Fall back on defaults as much as possible and allow the
8
+ functionality to be integrated and used with the least amount of work.
9
+
10
+ * Flexibility. After easy of use is taken into consideration it should be
11
+ possible to use the library in a more advanced fashion if that is called
12
+ for.
13
+
14
+ * Minimize the code to use it. It shouldn't require a great deal of code to
15
+ deploy or use the facilities and there should be no code required to pass
16
+ entities such as loggers around.
17
+
18
+ * Usable in libraries. I found myself writing a lot of common logging code
19
+ when writing libraries and application and wanted to abstract that out. I
20
+ wanted to minimize the burden this placed on library users at the same
21
+ time.
22
+
23
+ ## Release Log
24
+
25
+ * v1.2.0: This version sees a major rewrite of the internals of the library
26
+ while attempting to retain backward compatibility. Library configuration
27
+ has been changed to get greater flexibility and to allow for the logging
28
+ configuration to be folded into a larger configuration file. The tests were
29
+ all changed to rspec and more extensive tests written.
30
+
31
+ ## Configuration & Setup
32
+
33
+ The simplest setup to use with this library is to create a YAML file in the
34
+ called ```logging.yml```, either in the current working directory or in a
35
+ subdirectory of the working directory called ```config```. Place the following
36
+ contents into this file...
37
+
38
+ development:
39
+ loggers:
40
+ - default: true
41
+ file: STDOUT
42
+ name: devlog
43
+ production:
44
+ loggers:
45
+ - default: true
46
+ file: ./logs/production.log
47
+ name: prodlog
48
+ test:
49
+ loggers:
50
+ - default: true
51
+ file: STDOUT
52
+ name: testlog
53
+
54
+ By doing this you've now created a configuration that is environment dependent
55
+ and that the LogJam library will automatically pick up. When run in the
56
+ development (the default environment if no other is specified) or test
57
+ environments your application will now log to the standard output stream. For
58
+ the production environment the logging output will be written to a file called
59
+ ```production.log``` which will be in the ```logs``` subdirectory.
60
+
61
+ The settings covered in the example configuration above are just some of the
62
+ parameters recognised for the definition of a logger. Here is a more complete
63
+ list of parameters that are used when creating loggers...
64
+
65
+ * default: A boolean indicating whether this logger is the default (i.e. the
66
+ one to be used when no other explicitly fits the bill). Only one logger
67
+ should be declared as a default.
68
+
69
+ * datetime_format: The date/time format to be used by the logger. See the
70
+ documentation for the standard Ruby Logger class for more details.
71
+
72
+ * file: The path and name of the file that logging details will be written to.
73
+ Two special values are recognised in this value. STDOUT and STDERR are
74
+ translated to mean the standard output or error streams respectively.
75
+
76
+ * level: The logging level to set on the logger. Must be one of DEBUG, INFO,
77
+ WARN, ERROR, FATAL or UNKNOWN. If not explicitly specified this defaults
78
+ to DEBUG.
79
+
80
+ * max_size: When rotation is set to an integer value this value can be set to
81
+ indicate the maximum permitted file size for a log file in bytes.
82
+
83
+ * name: The name to associate with the logger. This allows loggers to be tied
84
+ to classes or for the creation of aliases that tie multiple names to a single
85
+ logger. Note that you should always use Strings (and not Symbols) when
86
+ specifying aliases.
87
+
88
+ * rotation: The frequency with which the log file is rotated. This may be an
89
+ integer to indicate how many old log files are retained or may be a String
90
+ such as "daily", "weekly" or "monthly".
91
+
92
+ A note on logger names. Logger names (including alias names) aren't hierarchical
93
+ and should be unique. Note that you may specify multiple logger definitions if
94
+ you wish, which would look like this...
95
+
96
+ development:
97
+ loggers:
98
+ - default: true
99
+ file: STDOUT
100
+ name: devlog
101
+ - file: ./logs/development.log
102
+ name: filelog
103
+
104
+ In addition to specifying logger definitions you can also specify logger
105
+ aliases. This is essentially a mechanism to allow a single logger to be
106
+ available under multiple names and a configuration including an alias definition
107
+ might look as follows...
108
+
109
+ development:
110
+ loggers:
111
+ - default: true
112
+ file: STDOUT
113
+ name: devlog
114
+ aliases:
115
+ database: devlog
116
+
117
+ If you don't provide a logging configuration then the LogJam library will fall
118
+ back on creating a single default logger that writes everything to the standard
119
+ output stream.
120
+
121
+ ## Logging With The Library
122
+
123
+ The stated goals of the LogJam library are to avoid the need to pass Logger
124
+ instances around while still allowing potentially complex configuration with a
125
+ minimum of code. The first step in this process has been covered in the
126
+ Configuration & Setup section in which it's explained how to configure logging
127
+ from a single Hash or file. This section will provide details on how to deploy
128
+ loggers to various classes.
129
+
130
+ The LogJam library extends the object class to make access to a logger available
131
+ at both the class and the instance level. The obtain a logger object you can
132
+ make a call to the ```#log()``` method. If you haven't explicitly configured a
133
+ logger for a class this will return an instance of the default logger. A version
134
+ of this method is also available at the instance level.
135
+
136
+ If you want to get more advanced and configure a particular logger for a
137
+ specific class or group of classes then you have to explicitly set the logger
138
+ on those classes. To do that you define multiple loggers in your configuration
139
+ and then make a call to the ```#set_logger_name()``` method for the affected
140
+ class. For example, if you defined a logger called string_logger that you wanted
141
+ to use just for String objects you could do that like so...
142
+
143
+ String.set_logger_name("string_logger")
144
+
145
+ With your code you can obtain a logger instance and then use the method common
146
+ to Ruby's Logger class on the object returned. So, to log a statement at the
147
+ info level in a piece of code you would do something like this...
148
+
149
+ log.info("This is a statement that I am logging.")
150
+
151
+ Consult the documentation of the Ruby Logger class for more information on the
152
+ methods and logging levels available.
153
+
154
+ ## Advanced Usage
155
+
156
+ The hope would be that this library can be used in the creation of other
157
+ libraries and allow for control of the logging generated by those libraries
158
+ without having to dig into the workings of the library or to pass around Logger
159
+ instances as constructor parameters or static data. In this case I recommend
160
+ explicitly declaring logger names for your library classes and making the name
161
+ that the library uses available with the library documentation so that the
162
+ libraries logging can be switched off or on as needed.
163
+
164
+ It's intended that, in general, the configure() method on the LogJam module
165
+ should only be called once. Calling it a second time will clear all existing
166
+ logging configuration and set up. This may or may not be an issue depending on
167
+ whether you decide to cache logger inside class instances instead of always
168
+ accessing them through the class level accessor.
169
+
170
+ The Logger instance returned from a LogJam are intended to be fully compatible
171
+ with the class defined within the standard Ruby Logger library. If you need to
172
+ change elements, such as the formatter, you should just do so on the logger in
173
+ the normal fashion. If you define multiple Logger instances then you will have
174
+ to change each individually.
175
+
176
+ Using the log=() method that is added to each class by the LogJam facilities it
177
+ is possible to change the Logger being used. If you want to use this method
178
+ please note that changing a Logger that is created via an alias will change the
179
+ original Logger and thereby affect all classes that make use of that Logger (and
180
+ not necessarily just the one making the change). If you want to do this give the
181
+ class it's own logger instance.
182
+
183
+ Finally, any logger can be fetched from the library using it's name and making
184
+ a call to the LogJam.get_logger() method. Note if you omit the name or pass in
185
+ nil you will retrieve the libraries default logger.
186
+
187
+ ## Example Configurations
188
+
189
+ This section contains some example configurations. A short explanation is given
190
+ for each configuration and then the configuration itself in Hash, YAML and JSON
191
+ formats is provided.
192
+
193
+ This represents the most basic configuration possible. In passing an empty Hash
194
+ to the configure method the system creates a single, default logger that writes
195
+ everything on the standard output stream...
196
+
197
+ Hash
198
+ ```
199
+ {}
200
+ ```
201
+
202
+ YAML
203
+ ```
204
+ {}
205
+ ```
206
+
207
+ JSON
208
+ ```
209
+ {}
210
+ ```
211
+
212
+ The following simple configuration writes all logging output to a file called
213
+ application.log in the current working directory. If a logging level is not
214
+ explicitly specified then DEBUG is the default...
215
+
216
+ Hash
217
+ ```
218
+ {:loggers => [{:default => true, :file => "application.log"}]}
219
+ ```
220
+
221
+ YAML
222
+ ```
223
+ :loggers:
224
+ - :default: true
225
+ :file: application.log
226
+ ```
227
+
228
+ JSON
229
+ ```
230
+ {"loggers": {"default": true, "file": "application.log"}}
231
+ ```
232
+
233
+ This configuration declares two loggers. The first is called 'silent' and will
234
+ log nothing. The silent logger is the default and so will be used for any class
235
+ that doesn't have an explicitly named logger. The second is called 'verbose' and
236
+ logs everything from the debug level up on the standard output stream. The
237
+ configuration also declares an alias pointing the name 'database' to refer to
238
+ the verbose logger. An class that declares it uses the 'database' logger will
239
+ generate output while all others will be silenced.
240
+
241
+ Hash
242
+ ```
243
+ {:loggers => [{:default => true,
244
+ :file => "STDOUT",
245
+ :level => "UNKNOWN",
246
+ :name => "silent"},
247
+ {:file => "STDOUT",
248
+ :name => "verbose"}],
249
+ :aliases => {"database" => "verbose"}}
250
+ ```
251
+
252
+ YAML
253
+ ```
254
+ :loggers:
255
+ - :default: true
256
+ :file: STDOUT
257
+ :level: UNKNOWN
258
+ :name: silent
259
+ - :file: STDOUT
260
+ :name: verbose
261
+ :aliases:
262
+ database: verbose
263
+ ```
264
+
265
+ JSON
266
+ ```
267
+ {"loggers": [{"default":true,
268
+ "file": "STDOUT",
269
+ "level": "UNKNOWN",
270
+ "name": "silent"},
271
+ {"file": "STDOUT",
272
+ "name": "verbose"}],
273
+ "aliases": {"database":"verbose"}}
274
+ ```
275
+
276
+ The following configuration can be used as an example of how to drive logging
277
+ from different parts of the code to different destinations. The configuration
278
+ declares two loggers which deliver their output to two different log files and
279
+ then declares aliases for those loggers that can be used to divide up the
280
+ logging coming from different areas of the code.
281
+
282
+ Hash
283
+ ```
284
+ {:loggers => [{:default => true,
285
+ :file => "./log/main.log",
286
+ :name => "main"},
287
+ {:file => "./log/secondary.log",
288
+ :name => "secondary"}],
289
+ :aliases => {"database" => "secondary",
290
+ "model" => "secondary",
291
+ "controller" => "main"}}
292
+ ```
293
+
294
+ YAML
295
+ ```
296
+ :loggers:
297
+ - :default: true
298
+ :file: ./log/main.log
299
+ :name: main
300
+ - :file: ./log/secondary.log
301
+ :name: secondary
302
+ :aliases:
303
+ database: secondary
304
+ model: secondary
305
+ controller: main
306
+ ```
307
+
308
+ JSON
309
+ ```
310
+ {"loggers": [{"default":true,
311
+ "file": "./log/main.log",
312
+ "name": "main"},
313
+ {"file": "./log/secondary.log",
314
+ "name": "secondary"}],
315
+ "aliases": {"database":"secondary",
316
+ "model": "secondary",
317
+ "controller": "main"}}
318
+ ```
319
+
320
+ ## Testing
321
+
322
+ LogJam uses the RSpec Ruby library for testing. The best approach to running
323
+ the tests are to create a new gemset (assuming you're using RVM), do a bundle
324
+ install on this gemset from within the LogJam root directory and then use a
325
+ command such as the following to run the tests...
326
+
327
+ ```
328
+ $> rspec
329
+ ```
330
+
331
+ Individual tests can be run by appending the path to the file that you want to
332
+ execute after the ```rspec``` command. For example...
333
+
334
+ ```
335
+ $> rake spec/logjam_spec.rb
336
+ ```
337
+
338
+ ...would run only the the tests in the logjam_spec.rb test file.
@@ -3,9 +3,187 @@
3
3
  # Copyright (c), 2012 Peter Wood
4
4
  # See the license.txt for details of the licensing of the code in this file.
5
5
 
6
+ require 'forwardable'
6
7
  require 'logger'
8
+ require 'configurative'
7
9
  require 'logjam/version'
8
10
  require 'logjam/exceptions'
9
- require 'logjam/logjam_logger'
10
- require 'logjam/logjam'
11
+ require 'logjam/configuration'
12
+ require 'logjam/logger'
13
+ require 'logjam/object'
11
14
 
15
+ module LogJam
16
+ # Module constants.
17
+ LEVEL_MAP = {"debug" => Logger::DEBUG,
18
+ "info" => Logger::INFO,
19
+ "warn" => Logger::WARN,
20
+ "error" => Logger::ERROR,
21
+ "fatal" => Logger::FATAL,
22
+ "unknown" => Logger::UNKNOWN}
23
+ STREAM_MAP = {"stdout" => STDOUT,
24
+ "stderr" => STDERR}
25
+
26
+ # Module static properties.
27
+ @@logjam_modules = {}
28
+ @@logjam_loggers = {}
29
+ @@logjam_contexts = {}
30
+
31
+ # This method is used to configure the LogJam module with the various loggers
32
+ # it will use.
33
+ #
34
+ # ==== Parameters
35
+ # source:: Either a Hash containing the configuration to be used or nil to
36
+ # indicate the use of default configuration settings.
37
+ def self.configure(source=nil)
38
+ @@logjam_modules = {}
39
+ @@logjam_loggers = {}
40
+ LogJam.process_configuration(source ? source : Configuration.instance)
41
+ end
42
+
43
+ # This method is used to install logging facilities at the class level for a
44
+ # given class. Once 'logified' a class will possess two new methods. The
45
+ # first, #log(), retrieves the logger associated with the class. The second,
46
+ # #log=(), allows the assignment of the logger associated with the class.
47
+ # Note that changing the logger associated with a class will impact all other
48
+ # classes that use the same logger.
49
+ #
50
+ # ==== Parameters
51
+ # target:: The target class that is to be extended.
52
+ # name:: The name of the logger to be used by the class. Defaults to nil
53
+ # to indicate use of the default logger.
54
+ # context:: A Hash of additional parameters that are specific to the class
55
+ # to which LogJam is being applied.
56
+ def self.apply(target, name=nil, context={})
57
+ @@logjam_contexts[target] = {}.merge(context)
58
+ target.extend(LogJam.get_module(name, @@logjam_contexts[target]))
59
+ target.send(:define_method, :log) {LogJam.get_logger(name)} if !target.method_defined?(:log)
60
+ end
61
+
62
+ # This method attempts to fetch the logger for a specified name. If this
63
+ # logger does not exist then a default logger will be returned instead.
64
+ #
65
+ # ==== Parameters
66
+ # name:: The name of the logger to retrieve.
67
+ def self.get_logger(name=nil)
68
+ LogJam.process_configuration(Configuration.instance) if @@logjam_loggers.empty?
69
+ @@logjam_loggers.fetch(name, @@logjam_loggers[nil])
70
+ end
71
+
72
+ # This method fetches a list of the names currently defined within the LogJam
73
+ # internal settings.
74
+ def self.names
75
+ @@logjam_loggers.keys.compact
76
+ end
77
+
78
+ # A convenience mechanism that provides an instance level access to the
79
+ # class level logger.
80
+ def log
81
+ self.class.log
82
+ end
83
+
84
+ private
85
+
86
+ # This method fetches the module associated with a name. If the module does
87
+ # not exist the default module is returned instead.
88
+ #
89
+ # ==== Parameters
90
+ # name:: The name associated with the module to return.
91
+ # context:: The context that applies to the module to be retrieved.
92
+ def self.get_module(name, context={})
93
+ LogJam.create_module(name)
94
+ end
95
+
96
+ # This method processes a logger configuration and generates the appropriate
97
+ # set of loggers and internal objects from it.
98
+ #
99
+ # ==== Parameters
100
+ # settings:: A collection of the settings to be processed.
101
+ def self.process_configuration(settings)
102
+ settings = Configurative::SettingsParser.new.parse(settings) if settings.kind_of?(Hash)
103
+ if settings && !settings.empty?
104
+ loggers = settings.loggers
105
+ if loggers
106
+ if loggers.kind_of?(Array)
107
+ loggers.each {|definition| LogJam.create_logger(definition)}
108
+ elsif loggers.kind_of?(Hash)
109
+ LogJam.create_logger(loggers)
110
+ else
111
+ raise Error, "The loggers configuration entry is in an "\
112
+ "unrecognised format. Must be either a Hash or an "\
113
+ "Array."
114
+ end
115
+ end
116
+
117
+ aliases = settings.aliases
118
+ if aliases
119
+ aliases.each do |name, equivalent|
120
+ @@logjam_loggers[name] = @@logjam_loggers[equivalent]
121
+ @@logjam_modules[name] = LogJam.get_module(equivalent)
122
+ end
123
+ end
124
+ end
125
+
126
+ # Create a default logger if one hasn't been specified.
127
+ LogJam.create_logger({default: true, file: "STDOUT"}) if @@logjam_loggers[nil].nil?
128
+ end
129
+
130
+ # This method is used to create an anonymous module under a given name (if it
131
+ # doesn't already exist) and return it to the caller.
132
+ #
133
+ # ==== Parameters
134
+ # name:: The name to create the module under.
135
+ def self.create_module(name)
136
+ if !@@logjam_modules.include?(name)
137
+ # Create the anonymous module and add methods to it.
138
+ @@logjam_modules[name] = Module.new
139
+ @@logjam_modules[name].send(:define_method, :log) do
140
+ LogJam.get_logger(name)
141
+ end
142
+ @@logjam_modules[name].send(:define_method, :log=) do |logger|
143
+ LogJam.get_logger(name).logger = logger
144
+ end
145
+ end
146
+ @@logjam_modules[name]
147
+ end
148
+
149
+ # This method creates a logger from a given definition. A definition should
150
+ # be a Hash containing the values that are used to configure the Logger with.
151
+ #
152
+ # ==== Parameters
153
+ # definition:: A Hash containing the configuration details for the logger.
154
+ def self.create_logger(definition)
155
+ # Fetch the configuration values.
156
+ definition = to_definition(definition)
157
+ rotation = definition.rotation
158
+ max_size = definition.max_size
159
+ device = STREAM_MAP.fetch(definition.file.downcase.strip, definition.file)
160
+
161
+ if rotation.kind_of?(String) && /^\s*\d+\s*$/ =~ rotation
162
+ rotation = rotation.to_i
163
+ rotation = 0 if rotation < 0
164
+ end
165
+
166
+ max_size = max_size.to_i if !max_size.nil? && max_size.kind_of?(String)
167
+ max_size = 1048576 if !max_size.nil? && max_size < 1024
168
+
169
+ # Create the actual logger and associated module.
170
+ logger = LogJam::Logger.new(device, rotation, max_size)
171
+ logger.level = LEVEL_MAP.fetch(definition.level.downcase.strip, Logger::DEBUG)
172
+ logger.name = definition.name
173
+ logger.progname = definition.name
174
+ @@logjam_loggers[definition.name] = logger
175
+ logger_module = LogJam.create_module(name)
176
+ if definition.default
177
+ @@logjam_loggers[nil] = logger
178
+ @@logjam_modules[nil] = logger_module
179
+ end
180
+ logger
181
+ end
182
+
183
+ def self.to_definition(settings)
184
+ settings = Configurative::SettingsParser.new.parse(settings) if settings.kind_of?(Hash)
185
+ settings.file = "stdout" if !settings.include?(:file) || settings.file == ""
186
+ settings.level = "debug" if !settings.include?(:level) || settings.level == ""
187
+ settings
188
+ end
189
+ end