logjam 1.0.0 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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