madvertise-logging 1.2.1-java

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,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
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 1.9.2
5
+ - ruby-head
6
+ - jruby-head
@@ -0,0 +1 @@
1
+ --no-private
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'syslogger', :require => false
6
+
7
+ group :development, :test do
8
+ gem 'rake'
9
+ gem 'reek'
10
+ gem 'rspec'
11
+ gem 'simplecov'
12
+ gem 'yard'
13
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 madvertise Mobile Advertising GmbH
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,33 @@
1
+ # Madvertise::Logging
2
+
3
+ The madvertise-logging gem is a collection of classes for logging related
4
+ tasks. 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/madvertise/logging.png)](http://travis-ci.org/madvertise/logging)
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ gem 'madvertise-logging'
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install madvertise-logging
22
+
23
+ ## Usage
24
+
25
+ Please refer to the [API documentation](http://rubydoc.info/gems/madvertise-logging/frames).
26
+
27
+ ## Contributing
28
+
29
+ 1. Fork it
30
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
31
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
32
+ 4. Push to the branch (`git push origin my-new-feature`)
33
+ 5. Create new Pull Request
@@ -0,0 +1,3 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ Dir['tasks/**/*.rake'].each { |t| load t }
@@ -0,0 +1 @@
1
+ require 'madvertise/logging'
@@ -0,0 +1,5 @@
1
+ require 'madvertise/logging/improved_logger'
2
+ require 'madvertise/logging/multi_logger'
3
+
4
+ ImprovedLogger = Madvertise::Logging::ImprovedLogger
5
+ MultiLogger = Madvertise::Logging::MultiLogger
@@ -0,0 +1,21 @@
1
+ require 'airbrake'
2
+
3
+ module Madvertise
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 Madvertise
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 Madvertise
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,457 @@
1
+ require 'logger'
2
+ require 'stringio'
3
+ require 'benchmark'
4
+
5
+ require 'madvertise/logging/improved_io'
6
+ require 'madvertise/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 Madvertise
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 madvertise-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)\/madvertise-logging/
231
+ end
232
+ end
233
+
234
+ private
235
+
236
+ # Return the first callee outside the madvertise-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_backend
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
+ case RUBY_PLATFORM
319
+ when 'java'
320
+ create_log4j_logger
321
+ else
322
+ create_ruby_logger(@logfile)
323
+ end
324
+ end
325
+
326
+ def create_log4j_logger
327
+ begin
328
+ require 'log4j'
329
+ require 'log4jruby'
330
+ Log4jruby::Logger.get($0).tap do |logger|
331
+ @backend = :log4j
332
+ configure_log4j(logger)
333
+ end
334
+ rescue LoadError
335
+ self.logger = :ruby
336
+ error("Couldn't load log4jruby gem, falling back to pure ruby Logger")
337
+ end
338
+ end
339
+
340
+ def configure_log4j(logger)
341
+ return unless @backend == :log4j
342
+
343
+ @console = org.apache.log4j.ConsoleAppender.new
344
+ @console.setLayout(org.apache.log4j.PatternLayout.new(Formatter.log4j_format))
345
+ @console.setThreshold(org.apache.log4j.Level.const_get(self.class.severities.key(logger.level).to_s.upcase.to_sym))
346
+ @console.activateOptions
347
+
348
+ org.apache.log4j.Logger.getRootLogger.tap do |root|
349
+ root.getLoggerRepository.resetConfiguration
350
+ root.addAppender(@console)
351
+ end
352
+ end
353
+
354
+ def create_ruby_logger(io)
355
+ Logger.new(io).tap do |logger|
356
+ logger.formatter = Formatter.new
357
+ logger.progname = progname
358
+ end
359
+ end
360
+
361
+ ##
362
+ # The Formatter class is responsible for formatting log messages. The
363
+ # default format is:
364
+ #
365
+ # YYYY:MM:DD HH:MM:SS.MS daemon_name(pid) level: message
366
+ #
367
+ class Formatter
368
+
369
+ @format = "%{time} %{progname}(%{pid}) [%{severity}] %{msg}\n"
370
+ @log4j_format = "%d %c(%t) [%p] %m%n"
371
+ @time_format = "%Y-%m-%d %H:%M:%S.%N"
372
+
373
+ class << self
374
+ # Format string for log messages.
375
+ attr_accessor :format
376
+ attr_accessor :log4j_format
377
+
378
+ # Format string for timestamps in log messages.
379
+ attr_accessor :time_format
380
+ end
381
+
382
+ RUBY2SYSLOG = {
383
+ :debug => 7,
384
+ :info => 6,
385
+ :warn => 4,
386
+ :error => 3,
387
+ :fatal => 2,
388
+ :unknown => 3,
389
+ }
390
+
391
+ # @private
392
+ def call(severity, time, progname, msg)
393
+ self.class.format % {
394
+ :time => time.strftime(self.class.time_format),
395
+ :progname => progname,
396
+ :pid => $$,
397
+ :severity => severity,
398
+ :syslog_severity => RUBY2SYSLOG[severity.downcase.to_sym],
399
+ :msg => msg.to_s,
400
+ }
401
+ end
402
+ end
403
+
404
+ # @private
405
+ module IOCompat
406
+ def close_read
407
+ nil
408
+ end
409
+
410
+ def close_write
411
+ close
412
+ end
413
+
414
+ def closed?
415
+ raise NotImplementedError
416
+ end
417
+
418
+ def sync
419
+ @backend != :buffer
420
+ end
421
+
422
+ def sync=(value)
423
+ raise NotImplementedError, "#{self} cannot change sync mode"
424
+ end
425
+
426
+ # ImprovedLogger is write-only
427
+ def _raise_write_only
428
+ raise IOError, "#{self} is a buffer-less, write-only, non-seekable stream."
429
+ end
430
+
431
+ [
432
+ :bytes,
433
+ :chars,
434
+ :codepoints,
435
+ :lines,
436
+ :eof?,
437
+ :getbyte,
438
+ :getc,
439
+ :gets,
440
+ :pos,
441
+ :pos=,
442
+ :read,
443
+ :readlines,
444
+ :readpartial,
445
+ :rewind,
446
+ :seek,
447
+ :ungetbyte,
448
+ :ungetc
449
+ ].each do |meth|
450
+ alias_method meth, :_raise_write_only
451
+ end
452
+ end
453
+
454
+ include IOCompat
455
+ end
456
+ end
457
+ end