liquid-logging 2.0.0

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
+ SHA1:
3
+ metadata.gz: 00283f12fce4c72f5d0a60aab70c3fa62dfbcd94
4
+ data.tar.gz: ec0ab79a50e4e326d08eac1fcd197183423c7c20
5
+ SHA512:
6
+ metadata.gz: 7290dc769e77b2f5b13586dc6a7777e767864fd46e98d9203b801119a0ba52205096d89bd4930307951e11aa03d7a9fda2c5d5e8eb560d7db963d954c0c8128d
7
+ data.tar.gz: 93ecc1de6af1cece32f77ac352d6f0dae9d7e68f2b521a2d2c91a1c838400669438d362b3de3f7999d746e5791c2826483e5235a28f2268d88cdd2149778a04d
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --require spec_helper
2
+ --color
3
+ --format Fuubar
@@ -0,0 +1,5 @@
1
+ language: ruby
2
+ rvm:
3
+ - jruby
4
+ - 2.0.0
5
+ - 1.9.3
@@ -0,0 +1 @@
1
+ --no-private
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'syslogger', :require => false
6
+ gem 'liquid-ext', path: '../ext'
7
+
8
+ group :development, :test do
9
+ gem 'liquid-development'
10
+ end
data/LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2012 madvertise Mobile Advertising GmbH
2
+ Copyright (c) 2013 LiquidM, Inc.
3
+
4
+ MIT License
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining
7
+ a copy of this software and associated documentation files (the
8
+ "Software"), to deal in the Software without restriction, including
9
+ without limitation the rights to use, copy, modify, merge, publish,
10
+ distribute, sublicense, and/or sell copies of the Software, and to
11
+ permit persons to whom the Software is furnished to do so, subject to
12
+ the following conditions:
13
+
14
+ The above copyright notice and this permission notice shall be
15
+ included in all copies or substantial portions of the Software.
16
+
17
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,35 @@
1
+ # Liquid Logging
2
+
3
+ The liquid-logging gem is a collection of classes for logging related tasks.
4
+ The main class (`ImprovedLogger`) is an improved version of DaemonKits
5
+ `AbstractLogger` class including token support, buffer backend and more.
6
+
7
+ [![Build Status](https://secure.travis-ci.org/liquidm/logging.png)](http://travis-ci.org/liquidm/logging)
8
+ [![Code Climate](https://codeclimate.com/github/liquidm/logging.png)](https://codeclimate.com/github/liquidm/logging)
9
+ [![Dependency Status](https://gemnasium.com/liquidm/logging.png)](https://gemnasium.com/liquidm/logging)
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'liquid-logging'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install liquid-logging
24
+
25
+ ## Usage
26
+
27
+ Please refer to the [API documentation](http://rubydoc.info/gems/liquid-logging/frames).
28
+
29
+ ## Contributing
30
+
31
+ 1. Fork it ( http://github.com/liquidm/logging/fork )
32
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
33
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
34
+ 4. Push to the branch (`git push origin my-new-feature`)
35
+ 5. Create new Pull Request
@@ -0,0 +1,3 @@
1
+ require "bundler/setup"
2
+ require "bundler/gem_tasks"
3
+ require "liquid/tasks"
@@ -0,0 +1 @@
1
+ require 'liquid/logging'
@@ -0,0 +1,5 @@
1
+ require 'liquid/logging/improved_logger'
2
+ require 'liquid/logging/multi_logger'
3
+
4
+ ImprovedLogger = Liquid::Logging::ImprovedLogger
5
+ MultiLogger = Liquid::Logging::MultiLogger
@@ -0,0 +1,21 @@
1
+ require 'airbrake'
2
+
3
+ module Liquid
4
+ module Logging
5
+ class ImprovedLogger
6
+ # Log an exception with airbrake.
7
+ #
8
+ # @param [Exception] exc The exception to log.
9
+ # @param [String] message Additional reason to log.
10
+ def exception_with_airbrake(exc, message = nil, attribs = {})
11
+ Airbrake.notify_or_ignore(exc, {
12
+ :error_message => message,
13
+ :cgi_data => ENV.to_hash,
14
+ }.merge(attribs))
15
+ end
16
+
17
+ alias_method :exception_without_airbrake, :exception
18
+ alias_method :exception, :exception_with_airbrake
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,49 @@
1
+ require 'logger'
2
+
3
+ module Liquid
4
+ module Logging
5
+
6
+ ##
7
+ # DocumentLogger is a Logger compliant class that keeps a structured
8
+ # document per log message in memory.
9
+ #
10
+ class DocumentLogger < ::Logger
11
+
12
+ attr_accessor :attrs
13
+ attr_accessor :messages
14
+
15
+ def initialize
16
+ super(nil)
17
+ @messages = []
18
+ @attrs = {}
19
+ end
20
+
21
+ def add(severity, message = nil, progname = nil, &block)
22
+ severity ||= UNKNOWN
23
+ if severity < @level
24
+ return true
25
+ end
26
+
27
+ progname ||= @progname
28
+
29
+ if message.nil?
30
+ if block_given?
31
+ message = yield
32
+ else
33
+ message = progname
34
+ progname = @progname
35
+ end
36
+ end
37
+
38
+ @messages << @attrs.merge({
39
+ severity: severity,
40
+ time: Time.now.to_f,
41
+ progname: progname,
42
+ message: message,
43
+ })
44
+
45
+ true
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,159 @@
1
+ module Liquid
2
+ module Logging
3
+
4
+ ##
5
+ # ImprovedIO is a subclass of IO with a bunch of methods reimplemented so
6
+ # that subclasses don't have to reimplement every IO method. Unfortunately
7
+ # this is necessary because Ruby does not provide a sane interface to IO
8
+ # like Enumerable for Arrays and Hashes.
9
+ #
10
+ class ImprovedIO < IO
11
+
12
+ def flush
13
+ self
14
+ end
15
+
16
+ def external_encoding
17
+ nil
18
+ end
19
+
20
+ def internal_encoding
21
+ nil
22
+ end
23
+
24
+ def set_encoding
25
+ self
26
+ end
27
+
28
+ def readbyte
29
+ getbyte.tap do |byte|
30
+ raise EOFError unless byte
31
+ end
32
+ end
33
+
34
+ def readchar
35
+ getc.tap do |char|
36
+ raise EOFError unless char
37
+ end
38
+ end
39
+
40
+ def readline
41
+ gets.tap do |string|
42
+ raise EOFError unless string
43
+ end
44
+ end
45
+
46
+ def tty?
47
+ false
48
+ end
49
+
50
+ def printf(format_string, *arguments)
51
+ write(sprintf(format_string, *arguments))
52
+ return nil
53
+ end
54
+
55
+ def print(*arguments)
56
+ args = if arguments.empty?
57
+ [$_]
58
+ else
59
+ arguments
60
+ end
61
+
62
+ write(args.join($,))
63
+ return nil
64
+ end
65
+
66
+ def putc
67
+ end
68
+
69
+ def puts(*arguments)
70
+ return nil if arguments.empty?
71
+
72
+ arguments.each do |arg|
73
+ if arg.is_a?(Array)
74
+ puts(*arg)
75
+ elsif arg.is_a?(String)
76
+ write(arg)
77
+ else
78
+ write(arg.to_s)
79
+ end
80
+ end
81
+
82
+ return nil
83
+ end
84
+
85
+ # provide sane aliases for IO compat
86
+ begin
87
+ alias_method :each_byte, :bytes
88
+ alias_method :each_char, :chars
89
+ alias_method :each_codepoint, :codepoints
90
+ alias_method :each_line, :lines
91
+ alias_method :each, :lines
92
+ alias_method :eof, :eof?
93
+ alias_method :isatty, :tty?
94
+ alias_method :sysread, :read
95
+ alias_method :syswrite, :write
96
+ rescue NameError
97
+ # do nothing, method may not exist in ruby 1.8
98
+ end
99
+
100
+ # skip these IO methods
101
+ [
102
+ :advise,
103
+ :autoclose=,
104
+ :autoclose?,
105
+ :binmode,
106
+ :binmode?,
107
+ :close_on_exec=,
108
+ :close_on_exec?,
109
+ :fcntl,
110
+ :fdatasync,
111
+ :fileno,
112
+ :fsync,
113
+ :ioctl,
114
+ :lineno,
115
+ :lineno=,
116
+ :pid,
117
+ :read_nonblock,
118
+ :stat,
119
+ :sysseek,
120
+ :tell,
121
+ :to_i,
122
+ :to_io,
123
+ :write_nonblock,
124
+ ].each do |meth|
125
+ begin
126
+ undef_method meth
127
+ rescue NameError
128
+ # do nothing, method may not exist in ruby 1.8
129
+ end
130
+ end
131
+
132
+ class << self
133
+ # skip these IO methods
134
+ [
135
+ :binread,
136
+ :binwrite,
137
+ :copy_stream,
138
+ :for_fd,
139
+ :foreach,
140
+ :open,
141
+ :pipe,
142
+ :popen,
143
+ :read,
144
+ :readlines,
145
+ :select,
146
+ :sysopen,
147
+ :try_convert,
148
+ :write,
149
+ ].each do |meth|
150
+ begin
151
+ undef_method meth
152
+ rescue NameError
153
+ # do nothing, method may not exist in ruby 1.8
154
+ end
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,448 @@
1
+ require 'logger'
2
+ require 'stringio'
3
+ require 'benchmark'
4
+
5
+ require 'liquid/logging/improved_io'
6
+ require 'liquid/logging/document_logger'
7
+
8
+ class String
9
+ def clean_quote
10
+ if index(/["\s]/)
11
+ %{"#{tr('"', "'")}"}
12
+ else
13
+ self
14
+ end
15
+ end
16
+ end
17
+
18
+ module Liquid
19
+ module Logging
20
+
21
+ ##
22
+ # ImprovedLogger is an enhanced version of DaemonKits AbstractLogger class
23
+ # with token support, buffer backend and more.
24
+ #
25
+ class ImprovedLogger < ImprovedIO
26
+
27
+ # Program name prefix. Used as ident for syslog backends.
28
+ attr_accessor :progname
29
+
30
+ # Arbitrary token to prefix log messages with.
31
+ attr_accessor :token
32
+
33
+ # Log the file/line where the message came from
34
+ attr_accessor :log_caller
35
+
36
+ # Log filename for file backend.
37
+ attr_reader :logfile
38
+
39
+ @severities = {
40
+ :debug => Logger::DEBUG,
41
+ :info => Logger::INFO,
42
+ :warn => Logger::WARN,
43
+ :error => Logger::ERROR,
44
+ :fatal => Logger::FATAL,
45
+ :unknown => Logger::UNKNOWN
46
+ }
47
+
48
+ @silencer = true
49
+
50
+ class << self
51
+ # Hash of Symbol/Fixnum pairs to map Logger levels.
52
+ attr_reader :severities
53
+
54
+ # Enable/disable the silencer on a global basis. Useful for debugging
55
+ # otherwise silenced code blocks.
56
+ attr_accessor :silencer
57
+ end
58
+
59
+ def initialize(backend = STDERR, progname = nil)
60
+ self.progname = progname || File.basename($0)
61
+ self.logger = backend
62
+ self.log_caller = false
63
+ end
64
+
65
+ # Get the backend logger.
66
+ #
67
+ # @return [Logger] The currently active backend logger object.
68
+ def logger
69
+ @logger ||= create_backend
70
+ end
71
+
72
+ # Set a different backend.
73
+ #
74
+ # @param [Symbol, String, IO, Logger] value The new logger backend. Either a
75
+ # Logger object, an IO object, a String containing the logfile path or a Symbol to
76
+ # create a default backend for :syslog or :buffer
77
+ # @return [Logger] The newly created backend logger object.
78
+ def logger=(value)
79
+ @backend = value
80
+ @logger = create_backend
81
+ define_level_methods
82
+ end
83
+
84
+ # Close any connections/descriptors that may have been opened by the
85
+ # current backend.
86
+ def close
87
+ @logger.close rescue nil
88
+ @logger = nil
89
+ end
90
+
91
+ # Retrieve the current buffer in case this instance is a buffered logger.
92
+ #
93
+ # @return [String] Contents of the buffer.
94
+ def buffer
95
+ @logfile.string if @backend == :buffer
96
+ end
97
+
98
+ # Retrieve collected messages in case this instance is a document logger.
99
+ #
100
+ # @return [Array] An array of logged messages.
101
+ def messages
102
+ logger.messages if @backend == :document
103
+ end
104
+
105
+ # Get the current logging level.
106
+ #
107
+ # @return [Symbol] Current logging level.
108
+ def level
109
+ logger.level
110
+ end
111
+
112
+ # Set the logging level.
113
+ #
114
+ # @param [Symbol, Fixnum] level New level as Symbol or Fixnum from Logger class.
115
+ # @return [Fixnum] New level converted to Fixnum from Logger class.
116
+ def level=(level)
117
+ logger.level = level.is_a?(Symbol) ? self.class.severities[level] : level
118
+ configure_log4j(logger)
119
+ define_level_methods
120
+ end
121
+
122
+ # @private
123
+ def define_level_methods
124
+ # We do this dynamically here, so we can implement a no-op for levels
125
+ # which are disabled.
126
+ self.class.severities.each do |severity, num|
127
+ if num >= level
128
+ instance_eval(<<-EOM, __FILE__, __LINE__)
129
+ def #{severity}(*args, &block)
130
+ if block_given?
131
+ add(:#{severity}, *yield)
132
+ else
133
+ add(:#{severity}, *args)
134
+ end
135
+ end
136
+
137
+ def #{severity}?
138
+ true
139
+ end
140
+ EOM
141
+ else
142
+ instance_eval("def #{severity}(*args); end", __FILE__, __LINE__)
143
+ instance_eval("def #{severity}?; false; end", __FILE__, __LINE__)
144
+ end
145
+ end
146
+ end
147
+
148
+ # Compatibility method
149
+ # @private
150
+ def <<(msg)
151
+ add(:info, msg)
152
+ end
153
+
154
+ alias write <<
155
+
156
+ # Log an exception with fatal level.
157
+ #
158
+ # @param [Exception] exc The exception to log.
159
+ # @param [String] message Additional reason to log.
160
+ def exception(exc, message = nil, attribs = {})
161
+ fatal("exception", {
162
+ class: exc.class,
163
+ reason: exc.message,
164
+ message: message,
165
+ backtrace: clean_trace(exc.backtrace)
166
+ }.merge(attribs).merge(called_from))
167
+ end
168
+
169
+ # Log a realtime benchmark
170
+ #
171
+ # @param [String] msg The log message
172
+ # @param [String,Symbol] key The realtime key
173
+ def realtime(severity, msg, attribs = {}, &block)
174
+ result = nil
175
+ rt = Benchmark.realtime { result = yield }
176
+ add(severity, msg, attribs.merge({rt: rt}))
177
+ return result
178
+ end
179
+
180
+ def add(severity, message, attribs = {})
181
+ severity = severity.is_a?(Symbol) ? severity : self.class.severities.key(severity)
182
+
183
+ attribs.merge!(called_from) if @log_caller
184
+ attribs.merge!(token: @token) if @token
185
+ attribs = attribs.map do |k,v|
186
+ "#{k}=#{v.to_s.clean_quote}"
187
+ end.join(' ')
188
+
189
+ message = "#{message} #{attribs}" if attribs.length > 0
190
+ logger.send(severity) { message }
191
+
192
+ return nil
193
+ end
194
+
195
+ # Save the current token and associate it with obj#object_id.
196
+ def save_token(obj)
197
+ if @token
198
+ @tokens ||= {}
199
+ @tokens[obj.object_id] = @token
200
+ end
201
+ end
202
+
203
+ # Restore the token that has been associated with obj#object_id.
204
+ def restore_token(obj)
205
+ @tokens ||= {}
206
+ @token = @tokens.delete(obj.object_id)
207
+ end
208
+
209
+ # Silence the logger for the duration of the block.
210
+ def silence(temporary_level = :error)
211
+ if self.class.silencer
212
+ begin
213
+ old_level, self.level = self.level, temporary_level
214
+ yield self
215
+ ensure
216
+ self.level = old_level
217
+ end
218
+ else
219
+ yield self
220
+ end
221
+ end
222
+
223
+ # Remove references to the liquid-logging gem from exception
224
+ # backtraces.
225
+ #
226
+ # @private
227
+ def clean_trace(trace)
228
+ return unless trace
229
+ trace.reject do |line|
230
+ line =~ /(gems|vendor)\/liquid-logging/
231
+ end
232
+ end
233
+
234
+ private
235
+
236
+ # Return the first callee outside the liquid-logging gem. Used in add
237
+ # to figure out where in the source code a message has been produced.
238
+ def called_from
239
+ location = caller.detect('unknown:0') do |line|
240
+ line.match(/(improved_logger|multi_logger)\.rb/).nil?
241
+ end
242
+
243
+ file, line, _ = location.split(':')
244
+ { :file => file, :line => line }
245
+ end
246
+
247
+ def create_backend
248
+ self.close
249
+
250
+ case @backend
251
+ when :log4j
252
+ create_log4j_logger
253
+ when :ruby
254
+ create_ruby_logger(STDOUT)
255
+ when :stdout
256
+ create_io_backend(STDOUT)
257
+ when :stderr
258
+ create_io_backend(STDERR)
259
+ when :syslog
260
+ create_syslog_backend
261
+ when :buffer
262
+ create_buffer_backend
263
+ when :document
264
+ create_document_backend
265
+ when String
266
+ create_file_backend
267
+ when IO
268
+ create_io_backend(@backend)
269
+ when Logger
270
+ @backend
271
+ else
272
+ raise "unknown backend: #{@backend.inspect}"
273
+ end
274
+ end
275
+
276
+ def create_syslog_backend
277
+ begin
278
+ require 'syslogger'
279
+ Syslogger.new(progname, Syslog::LOG_PID, Syslog::LOG_LOCAL1)
280
+ rescue LoadError
281
+ self.logger = $stderr
282
+ error("Couldn't load syslogger gem, reverting to STDERR for logging")
283
+ end
284
+ end
285
+
286
+ def create_buffer_backend
287
+ @logfile = StringIO.new
288
+ create_logger
289
+ end
290
+
291
+ def create_document_backend
292
+ DocumentLogger.new.tap do |logger|
293
+ logger.formatter = Formatter.new
294
+ logger.progname = progname
295
+ end
296
+ end
297
+
298
+ def create_io_backend(backend)
299
+ @logfile = backend
300
+ @logfile.sync = true
301
+ create_logger
302
+ end
303
+
304
+ def create_file_backend
305
+ @logfile = @backend
306
+
307
+ begin
308
+ FileUtils.mkdir_p(File.dirname(@logfile))
309
+ rescue
310
+ self.logger = $stderr
311
+ error("#{@logfile} not writable, using STDERR for logging")
312
+ else
313
+ create_logger
314
+ end
315
+ end
316
+
317
+ def create_logger
318
+ create_ruby_logger(@logfile)
319
+ end
320
+
321
+ def create_log4j_logger
322
+ Log4jruby::Logger.get($0).tap do |logger|
323
+ @backend = :log4j
324
+ configure_log4j(logger)
325
+ end
326
+ end
327
+
328
+ def configure_log4j(logger)
329
+ return unless RUBY_PLATFORM == 'java'
330
+
331
+ require 'log4j'
332
+ require 'log4jruby'
333
+
334
+ @console = org.apache.log4j.ConsoleAppender.new
335
+ @console.setLayout(org.apache.log4j.PatternLayout.new(Formatter.log4j_format))
336
+ @console.setThreshold(org.apache.log4j.Level.const_get(self.class.severities.key(logger.level).to_s.upcase.to_sym))
337
+ @console.activateOptions
338
+
339
+ org.apache.log4j.Logger.getRootLogger.tap do |root|
340
+ root.getLoggerRepository.resetConfiguration
341
+ root.addAppender(@console)
342
+ end
343
+ end
344
+
345
+ def create_ruby_logger(io)
346
+ Logger.new(io).tap do |logger|
347
+ logger.formatter = Formatter.new
348
+ logger.progname = progname
349
+ end
350
+ end
351
+
352
+ ##
353
+ # The Formatter class is responsible for formatting log messages. The
354
+ # default format is:
355
+ #
356
+ # YYYY:MM:DD HH:MM:SS.MS daemon_name(pid) level: message
357
+ #
358
+ class Formatter
359
+
360
+ @format = "%{time} %{progname}(%{pid}) [%{severity}] %{msg}\n"
361
+ @log4j_format = "%d %c(%t) [%p] %m%n"
362
+ @time_format = "%Y-%m-%d %H:%M:%S.%N"
363
+
364
+ class << self
365
+ # Format string for log messages.
366
+ attr_accessor :format
367
+ attr_accessor :log4j_format
368
+
369
+ # Format string for timestamps in log messages.
370
+ attr_accessor :time_format
371
+ end
372
+
373
+ RUBY2SYSLOG = {
374
+ :debug => 7,
375
+ :info => 6,
376
+ :warn => 4,
377
+ :error => 3,
378
+ :fatal => 2,
379
+ :unknown => 3,
380
+ }
381
+
382
+ # @private
383
+ def call(severity, time, progname, msg)
384
+ self.class.format % {
385
+ :time => time.strftime(self.class.time_format),
386
+ :progname => progname,
387
+ :pid => $$,
388
+ :severity => severity,
389
+ :syslog_severity => RUBY2SYSLOG[severity.downcase.to_sym],
390
+ :msg => msg.to_s,
391
+ }
392
+ end
393
+ end
394
+
395
+ # @private
396
+ module IOCompat
397
+ def close_read
398
+ nil
399
+ end
400
+
401
+ def close_write
402
+ close
403
+ end
404
+
405
+ def closed?
406
+ raise NotImplementedError
407
+ end
408
+
409
+ def sync
410
+ @backend != :buffer
411
+ end
412
+
413
+ def sync=(value)
414
+ raise NotImplementedError, "#{self} cannot change sync mode"
415
+ end
416
+
417
+ # ImprovedLogger is write-only
418
+ def _raise_write_only
419
+ raise IOError, "#{self} is a buffer-less, write-only, non-seekable stream."
420
+ end
421
+
422
+ [
423
+ :bytes,
424
+ :chars,
425
+ :codepoints,
426
+ :lines,
427
+ :eof?,
428
+ :getbyte,
429
+ :getc,
430
+ :gets,
431
+ :pos,
432
+ :pos=,
433
+ :read,
434
+ :readlines,
435
+ :readpartial,
436
+ :rewind,
437
+ :seek,
438
+ :ungetbyte,
439
+ :ungetc
440
+ ].each do |meth|
441
+ alias_method meth, :_raise_write_only
442
+ end
443
+ end
444
+
445
+ include IOCompat
446
+ end
447
+ end
448
+ end
@@ -0,0 +1,14 @@
1
+ ---
2
+ LongParameterList:
3
+ exclude:
4
+ - Formatter#call
5
+ FeatureEnvy:
6
+ exclude:
7
+ - ImprovedLogger#clean_trace
8
+ - ImprovedLogger#exception
9
+ UtilityFunction:
10
+ exclude:
11
+ - ImprovedLogger#clean_trace
12
+ LargeClass:
13
+ exclude:
14
+ - ImprovedLogger
@@ -0,0 +1,34 @@
1
+ module Liquid
2
+ module Logging
3
+
4
+ ##
5
+ # MultiLogger is a simple class for multiplexing ImprovedLogger objects. It
6
+ # support attach/detach to send messages to any number of loggers.
7
+
8
+ class MultiLogger
9
+ def initialize(*loggers)
10
+ @loggers = loggers
11
+ end
12
+
13
+ # Attach an ImprovedLogger object.
14
+ def attach(logger)
15
+ logger.token = @loggers.first.token rescue nil
16
+ @loggers << logger
17
+ end
18
+
19
+ # Detach an ImprovedLogger object.
20
+ def detach(logger)
21
+ @loggers.delete(logger)
22
+ end
23
+
24
+ # Delegate all method calls to all attached loggers.
25
+ #
26
+ # @private
27
+ def method_missing(name, *args, &block)
28
+ @loggers.map do |logger|
29
+ logger.send(name, *args, &block)
30
+ end.first
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "liquid-logging"
5
+ spec.version = "2.0.0"
6
+ spec.authors = ["LiquidM, Inc."]
7
+ spec.email = ["opensource@liquidm.com"]
8
+ spec.description = %q{Advanced logging classes with buffer backend, transactions, multi logger, etc}
9
+ spec.summary = %q{Advanced logging classes with buffer backend, transactions, multi logger, etc}
10
+ spec.homepage = "https://github.com/liquidm/logging"
11
+
12
+ spec.files = `git ls-files`.split($/)
13
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
14
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
15
+ spec.require_paths = ["lib"]
16
+
17
+ if RUBY_PLATFORM == "java"
18
+ spec.platform = 'java'
19
+ spec.add_dependency "log4jruby", "~> 1.0.0.rc1"
20
+ spec.add_dependency "slyphon-log4j", "~> 1.2.15"
21
+ end
22
+ end
@@ -0,0 +1,322 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ describe ImprovedLogger do
4
+
5
+ let(:logger) { ImprovedLogger.new(:document) }
6
+
7
+ before(:each) { logger.level = :debug }
8
+
9
+ subject { logger.messages }
10
+
11
+ ImprovedLogger.severities.keys.each do |level|
12
+ describe level do
13
+ before { logger.send(level, "testing #{level}") }
14
+ let(:prefix) { level == :unknown ? "ANY" : level.to_s.upcase }
15
+ it "logs #{level} messages" do
16
+ subject.last[:message].should == "testing #{level}"
17
+ end
18
+ end
19
+ end
20
+
21
+ it "logs info level messages with <<" do
22
+ logger << "Info test <<"
23
+ subject.last[:message].should == "Info test <<"
24
+ end
25
+
26
+ it "logs info level messages with write" do
27
+ logger.write("Info test write")
28
+ subject.last[:message].should == "Info test write"
29
+ end
30
+
31
+ it "supports additional attributes" do
32
+ logger.info("foo", key: "value", test: "with space")
33
+ subject.last[:message].should == 'foo key=value test="with space"'
34
+ end
35
+
36
+ it "supports lazy-evaluation via blocks" do
37
+ logger.debug { "debug message" }
38
+ subject.last[:message].should == "debug message"
39
+ end
40
+
41
+ it "supports lazy-evaluation with attributes" do
42
+ logger.debug { ["debug message", {key: "value"}] }
43
+ subject.last[:message].should == "debug message key=value"
44
+ end
45
+
46
+ it "accepts a different backend" do
47
+ l = Logger.new('/dev/null')
48
+ logger.logger = l
49
+ logger.logger.should == l
50
+ end
51
+
52
+ describe :log_caller do
53
+ it "logs the caller file and line number" do
54
+ f = __FILE__
55
+ l = __LINE__ + 3
56
+
57
+ logger.log_caller = true
58
+ logger.info("Caller test")
59
+ subject.last[:message].should == "Caller test file=#{f} line=#{l}"
60
+ end
61
+
62
+ it "does not log the caller file and line number" do
63
+ f = File.basename(__FILE__)
64
+ l = __LINE__ + 3
65
+
66
+ logger.log_caller = false
67
+ logger.info("Caller test")
68
+ subject.last[:message].should_not == "Caller test file=#{f} line=#{l}"
69
+ end
70
+ end
71
+
72
+ let(:fake_trace) do
73
+ [
74
+ "/home/jdoe/app/libexec/app.rb:1:in `foo'",
75
+ "/usr/lib/ruby/gems/1.8/gems/liquid-logging-0.1.0/lib/liquid/logging/improved_logger.rb:42: in `info'"
76
+ ]
77
+ end
78
+
79
+ describe :exceptions do
80
+ let(:exc) do
81
+ RuntimeError.new('Test error').tap do |exc|
82
+ exc.set_backtrace(fake_trace)
83
+ end
84
+ end
85
+
86
+ it "logs an exception object" do
87
+ logger.exception(exc)
88
+ subject.last[:message].should match(%r{exception class=RuntimeError reason=\"Test error\" message= backtrace=\"\['/home/jdoe/app/libexec/app\.rb:1:in `foo''\]\"})
89
+ end
90
+
91
+ it "logs an exception object and prefix" do
92
+ logger.exception(exc, "app failed to foo")
93
+ subject.last[:message].should match(%r{exception class=RuntimeError reason=\"Test error\" message=\"app failed to foo\" backtrace=\"\['/home/jdoe/app/libexec/app\.rb:1:in `foo''\]\"})
94
+ end
95
+ end
96
+
97
+ describe :clean_trace do
98
+ subject { logger.clean_trace(fake_trace) }
99
+ it { should include("/home/jdoe/app/libexec/app.rb:1:in `foo'") }
100
+ it { should_not include("/usr/lib/ruby/gems/1.8/gems/liquid-logging-0.1.0/lib/liquid/logging/improved_logger.rb:42: in `info'") }
101
+ end
102
+
103
+ it "should support silencing" do
104
+ logger.silence do |logger|
105
+ logger.info "This should never be logged"
106
+ end
107
+
108
+ subject.last.should be_nil
109
+ end
110
+
111
+ it "should not discard messages if silencer is disabled globally" do
112
+ ImprovedLogger.silencer = false
113
+
114
+ logger.silence do |logger|
115
+ logger.info "This should actually be logged"
116
+ end
117
+
118
+ subject.last[:message].should == "This should actually be logged"
119
+
120
+ ImprovedLogger.silencer = true
121
+ end
122
+
123
+ it "should support a token" do
124
+ token = "3d5e27f7-b97c-4adc-b1fd-adf1bd4314e0"
125
+
126
+ logger.token = token
127
+ logger.info "This should include a token"
128
+ subject.last[:message].should match(token)
129
+
130
+ logger.token = nil
131
+ logger.info "This should not include a token"
132
+ subject.last[:message].should_not match(token)
133
+ end
134
+
135
+ it "should support save/restore on tokens" do
136
+ token1 = "3d5e27f7-b97c-4adc-b1fd-adf1bd4314e0"
137
+ token2 = "1bdef605-34b9-4ec7-9a1c-cb58efc8a635"
138
+
139
+ obj = Object.new
140
+
141
+ logger.token = token1
142
+ logger.info "This should include token1"
143
+ subject.last[:message].should match(token1)
144
+
145
+ logger.save_token(obj)
146
+ logger.token = token2
147
+ logger.info "This should include token2"
148
+ subject.last[:message].should match(token2)
149
+
150
+ logger.restore_token(obj)
151
+ logger.info "This should include token1"
152
+ subject.last[:message].should match(token1)
153
+
154
+ logger.token = nil
155
+ logger.info "This should not include a token"
156
+ subject.last[:message].should_not match(token1)
157
+ subject.last[:message].should_not match(token2)
158
+ end
159
+
160
+ it "should fall back to stderr if logfile is not writable" do
161
+ $stderr.should_receive(:write).with(/not writable.*STDERR/)
162
+
163
+ @logfile = "/not/writable/spec.log"
164
+ logger = ImprovedLogger.new(@logfile)
165
+ logger.level = :debug
166
+
167
+ $stderr.should_receive(:write).with(/test/)
168
+ logger.info "test"
169
+ end
170
+
171
+ it "should fallback to standard logger if syslogger gem is missing" do
172
+ syslogger_paths = $:.select { |p| p.match(/gems\/.*syslogger-/) }
173
+ $:.replace($: - syslogger_paths)
174
+
175
+ $stderr.should_receive(:write).with(/reverting to STDERR/)
176
+ logger = ImprovedLogger.new(:syslog)
177
+ logger.logger.should be_instance_of(Logger)
178
+
179
+ $:.replace($: + syslogger_paths)
180
+ end
181
+
182
+ context "should behave like write-only IO and" do
183
+ subject { logger }
184
+
185
+ it { should be_a IO }
186
+ its(:logger) { should_not be_nil }
187
+ its(:flush) { should == logger }
188
+ its(:set_encoding) { should == logger }
189
+ its(:sync) { should == true }
190
+ its(:tty?) { should == false }
191
+
192
+ it "should close on close_write" do
193
+ logger.should_receive(:close)
194
+ logger.close_write
195
+ end
196
+
197
+ it "should not implement closed?" do
198
+ expect { logger.closed? }.to raise_error(NotImplementedError)
199
+ end
200
+
201
+ it "should not implement sync=" do
202
+ expect { logger.sync = false }.to raise_error(NotImplementedError)
203
+ end
204
+
205
+ it "should implement readbyte, readchar, readline" do
206
+ {
207
+ :readbyte => :getbyte,
208
+ :readchar => :getc,
209
+ :readline => :gets,
210
+ }.each do |m, should|
211
+ logger.should_receive(should)
212
+ expect { logger.send(m) }.to raise_error(IOError)
213
+ end
214
+ end
215
+
216
+ [
217
+ :bytes,
218
+ :chars,
219
+ :codepoints,
220
+ :lines,
221
+ :eof?,
222
+ :getbyte,
223
+ :getc,
224
+ :gets,
225
+ :pos,
226
+ :pos=,
227
+ :read,
228
+ :readlines,
229
+ :readpartial,
230
+ :rewind,
231
+ :seek,
232
+ :ungetbyte,
233
+ :ungetc
234
+ ].each do |m|
235
+ it "should raise IOError for method #{m}" do
236
+ expect { logger.send(m) }.to raise_error(IOError)
237
+ end
238
+ end
239
+
240
+ context "print functions" do
241
+ subject { logger.messages }
242
+
243
+ it "should support printf" do
244
+ logger.printf("%.2f %s", 1.12345, "foo")
245
+ subject.last[:message].should == "1.12 foo"
246
+ end
247
+
248
+ it "should support print" do
249
+ $,, old = ' ', $,
250
+ logger.print("foo", "bar", 123, ["baz", 345])
251
+ subject.last[:message].should == "foo bar 123 baz 345"
252
+ $, = old
253
+ end
254
+
255
+ it "should support puts" do
256
+ logger.puts("a", "b")
257
+ subject.last[:message].should == "b"
258
+ logger.puts(["c", "d"])
259
+ subject.last[:message].should == "d"
260
+ logger.puts(1, 2, 3)
261
+ subject.last[:message].should == "3"
262
+ end
263
+ end
264
+ end
265
+
266
+ context "buffer backend" do
267
+ let(:logger) { ImprovedLogger.new(:buffer) }
268
+ subject { logger }
269
+
270
+ its(:sync) { should == false }
271
+
272
+ it "should support a buffered logger" do
273
+ logger.info "test"
274
+ logger.buffer.should match(/test/)
275
+ end
276
+ end
277
+
278
+ context "document backend" do
279
+ let(:logger) { ImprovedLogger.new(:document) }
280
+
281
+ before do
282
+ @msg = "test"
283
+
284
+ @now = Time.now
285
+ Time.stub(:now).and_return(@now)
286
+
287
+ @expected = {
288
+ severity: Logger::INFO,
289
+ time: @now.to_f,
290
+ progname: "rspec",
291
+ message: @msg
292
+ }
293
+ end
294
+
295
+ it "should store all messages as documents" do
296
+ logger.info(@msg)
297
+ logger.messages.first.should == @expected
298
+ end
299
+
300
+ it "should add custom attributes" do
301
+ attrs = {txid: 1234}
302
+ logger.logger.attrs = attrs
303
+ logger.info(@msg)
304
+ logger.messages.first.should == attrs.merge(@expected)
305
+ end
306
+
307
+ end
308
+
309
+ context "syslog backend" do
310
+ let(:logger) { ImprovedLogger.new(:syslog) }
311
+ subject { logger }
312
+ its(:sync) { should == true }
313
+ its(:logger) { should be_instance_of(Syslogger) }
314
+ end
315
+
316
+ context "unknown backend" do
317
+ it "should raise for unknown backends " do
318
+ expect { ImprovedLogger.new(:unknown_logger) }.to raise_error(RuntimeError)
319
+ end
320
+ end
321
+
322
+ end
@@ -0,0 +1,27 @@
1
+ require File.dirname(__FILE__) + '/spec_helper'
2
+
3
+ include Liquid::Logging
4
+
5
+ describe MultiLogger do
6
+
7
+ before(:each) do
8
+ @logger = ImprovedLogger.new
9
+ @logger.level = :debug
10
+ @ml = MultiLogger.new(@logger)
11
+ end
12
+
13
+ it "should support attach/detach of loggers" do
14
+ buflog = ImprovedLogger.new(:buffer)
15
+ @ml.attach(buflog)
16
+
17
+ $stderr.should_receive(:write).with(/test1/)
18
+ @ml.info("test1")
19
+ buflog.buffer.should match(/test1/)
20
+
21
+ @ml.detach(buflog)
22
+
23
+ $stderr.should_receive(:write).with(/test2/)
24
+ @ml.info("test2")
25
+ buflog.buffer.should_not match(/test2/)
26
+ end
27
+ end
@@ -0,0 +1,19 @@
1
+ require 'rubygems'
2
+ require 'rspec'
3
+
4
+ require 'simplecov'
5
+ SimpleCov.start
6
+
7
+ $:.unshift(File.dirname(__FILE__) + '/../lib')
8
+ require 'liquid-logging'
9
+
10
+ RSpec.configure do |config|
11
+ # == Mock Framework
12
+ #
13
+ # RSpec uses it's own mocking framework by default. If you prefer to
14
+ # use mocha, flexmock or RR, uncomment the appropriate line:
15
+ #
16
+ # config.mock_with :mocha
17
+ # config.mock_with :flexmock
18
+ # config.mock_with :rr
19
+ end
metadata ADDED
@@ -0,0 +1,69 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: liquid-logging
3
+ version: !ruby/object:Gem::Version
4
+ version: 2.0.0
5
+ platform: ruby
6
+ authors:
7
+ - LiquidM, Inc.
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-11-24 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Advanced logging classes with buffer backend, transactions, multi logger,
14
+ etc
15
+ email:
16
+ - opensource@liquidm.com
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - .gitignore
22
+ - .rspec
23
+ - .travis.yml
24
+ - .yardopts
25
+ - Gemfile
26
+ - LICENSE
27
+ - README.md
28
+ - Rakefile
29
+ - lib/liquid-logging.rb
30
+ - lib/liquid/logging.rb
31
+ - lib/liquid/logging/airbrake.rb
32
+ - lib/liquid/logging/document_logger.rb
33
+ - lib/liquid/logging/improved_io.rb
34
+ - lib/liquid/logging/improved_logger.rb
35
+ - lib/liquid/logging/mask.reek
36
+ - lib/liquid/logging/multi_logger.rb
37
+ - liquid-logging.gemspec
38
+ - spec/improved_logger_spec.rb
39
+ - spec/multi_logger_spec.rb
40
+ - spec/spec_helper.rb
41
+ homepage: https://github.com/liquidm/logging
42
+ licenses: []
43
+ metadata: {}
44
+ post_install_message:
45
+ rdoc_options: []
46
+ require_paths:
47
+ - lib
48
+ required_ruby_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - '>='
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ requirements: []
59
+ rubyforge_project:
60
+ rubygems_version: 2.0.6
61
+ signing_key:
62
+ specification_version: 4
63
+ summary: Advanced logging classes with buffer backend, transactions, multi logger,
64
+ etc
65
+ test_files:
66
+ - spec/improved_logger_spec.rb
67
+ - spec/multi_logger_spec.rb
68
+ - spec/spec_helper.rb
69
+ has_rdoc: