tlogger 0.6.0 → 0.22.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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +3 -0
- data/README.md +91 -9
- data/Rakefile +9 -1
- data/bin/console +4 -4
- data/lib/tlogger.rb +11 -309
- data/lib/tlogger/logger_group.rb +105 -0
- data/lib/tlogger/tlogger.rb +380 -0
- data/lib/tlogger/version.rb +1 -1
- data/samples/basic.rb +64 -35
- data/samples/group.rb +54 -0
- data/tlogger.gemspec +12 -27
- metadata +21 -74
- data/Gemfile.lock +0 -31
- data/LICENSE.txt +0 -21
- data/release.job +0 -53
- data/samples/basic2.rb +0 -25
@@ -0,0 +1,105 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Tlogger
|
4
|
+
|
5
|
+
# LoggerGroup is yet at wrapper around the Tlogger
|
6
|
+
# This class can be used as Tlogger replacement as it is
|
7
|
+
# delegated to the Tlogger upon method_missing method triggered.
|
8
|
+
#
|
9
|
+
# However this allow configuration of multiple loggers into
|
10
|
+
# single class.
|
11
|
+
#
|
12
|
+
# When operation such as 'debug' is called on this class
|
13
|
+
# all the registered logger shall be called the 'debug' method each therefore
|
14
|
+
# it will be logged to all registered loggers
|
15
|
+
class LoggerGroup
|
16
|
+
|
17
|
+
def initialize
|
18
|
+
@loggers = { }
|
19
|
+
end
|
20
|
+
|
21
|
+
##
|
22
|
+
# Create and add the logger into the group and registerd it with the given +key+
|
23
|
+
#
|
24
|
+
# *params shall be passed to underlying Tlogger new method
|
25
|
+
#
|
26
|
+
# Returns created Tlogger object
|
27
|
+
def create_logger(key, *params)
|
28
|
+
@loggers[key] = Tlogger.new(*params)
|
29
|
+
@loggers[key]
|
30
|
+
end # #create_logger
|
31
|
+
|
32
|
+
# Delete this logger from the group.
|
33
|
+
#
|
34
|
+
# delete_logger different from detach_logger as delete_logger shall close and set the logger to nil.
|
35
|
+
#
|
36
|
+
# detach_logger however just remove the logger from the group and it is up to the applicaation to close it.
|
37
|
+
def delete_logger(key)
|
38
|
+
logger = @loggers[key]
|
39
|
+
if not logger.nil?
|
40
|
+
logger.close
|
41
|
+
logger = nil
|
42
|
+
end
|
43
|
+
@loggers.delete(key)
|
44
|
+
end # #delete logger
|
45
|
+
|
46
|
+
##
|
47
|
+
# Detach the logger from the group, but not close the logger
|
48
|
+
#
|
49
|
+
# Detach the logger return the object to the caller and remove it from the internal group
|
50
|
+
#
|
51
|
+
# The effect is after detach operation, any logging done to this group would not include that particular logger and
|
52
|
+
# application is free to use the logger to log messages
|
53
|
+
#
|
54
|
+
def detach_logger(key)
|
55
|
+
@loggers.delete(key)
|
56
|
+
end # # detach_logger
|
57
|
+
|
58
|
+
##
|
59
|
+
# Add an existing logger instance to this group
|
60
|
+
#
|
61
|
+
# Noted that this logger object not necessary to be Tlogger object. It can be any object as long as it has the method that
|
62
|
+
# response to the usage.
|
63
|
+
#
|
64
|
+
# This is due to this class just a thin wrapper around the 'logger' object that any method call unknown to local shall be
|
65
|
+
# redirected to the 'logger' class.
|
66
|
+
#
|
67
|
+
# In another way, it is perfectly ok to call say_hello() on LoggerGroup if the 'logger' given response to method say_hello() or else
|
68
|
+
# NoMethodException shall be thrown. It is that simple.
|
69
|
+
def add_logger(key, logger)
|
70
|
+
@loggers[key] = logger
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns the logger that registered to the +key+
|
74
|
+
def get_log(key)
|
75
|
+
@loggers[key]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Close the logger group, effectively close all registered loggers
|
79
|
+
def close
|
80
|
+
@loggers.each do |k,v|
|
81
|
+
v.close
|
82
|
+
v = nil
|
83
|
+
end
|
84
|
+
@loggers.clear
|
85
|
+
end # # close
|
86
|
+
|
87
|
+
# Delegate unknown method to the underlying logger
|
88
|
+
def method_missing(mtd,*args,&block)
|
89
|
+
|
90
|
+
#hit = false
|
91
|
+
@loggers.each do |k,v|
|
92
|
+
begin
|
93
|
+
v.send(mtd,*args,&block)
|
94
|
+
#hit = true
|
95
|
+
rescue Exception => ex
|
96
|
+
STDERR.puts ex.message
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
#super if not hit
|
101
|
+
|
102
|
+
end # # method_missing
|
103
|
+
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,380 @@
|
|
1
|
+
|
2
|
+
require 'logger'
|
3
|
+
require 'openssl'
|
4
|
+
|
5
|
+
module Tlogger
|
6
|
+
|
7
|
+
##
|
8
|
+
# Tlogger class is meant to be a thin wrapper around the Ruby Logger class (by default) to provide contextual logging capabilities to the logging system.
|
9
|
+
#
|
10
|
+
# Contextual logging allow developers:
|
11
|
+
# * Structure the log message not only by the severity (debug, info, error, warning) but also another key that is useful for the application
|
12
|
+
# during troublehshooting purposes. This can provide more info to the log message being logged. For example can automatically tag the log
|
13
|
+
# message with number of 1789 loops in the program
|
14
|
+
# * Subsequent from this context info, developer now can selectively turn on / off certain logging message to get the clarity needed. Relying
|
15
|
+
# on the above example, if we are only interested in loop 1232, we can disabled all other log message EXCEPT the tag show loop_1232.
|
16
|
+
# This will drastically reduce the effort required in reading all the 1231 lines of log.
|
17
|
+
#
|
18
|
+
# I found it rather helpful especially in managing the print out of log AFTER it has been logging from everywhere in my programs.
|
19
|
+
# I also found that just filtering by log level (debug, info, error and warn) is not sufficient sometime to lessen the effort of log reading
|
20
|
+
# for a specific issue especially the only way to debug is turned on the debug level and ALL debug messages now sprang to life!
|
21
|
+
# It is really not an easy task to look for specific info especially when it was an old project.
|
22
|
+
#
|
23
|
+
# Now you can just disable all and only turn on the specific tag around the issue area to investigate the issue. You will now have better focus
|
24
|
+
# in the issue on hand instead of hunting the line that you need from long list of debug output.
|
25
|
+
class Tlogger
|
26
|
+
|
27
|
+
# +tag+ is the tag that is being set for this logger session.
|
28
|
+
#
|
29
|
+
# One session shall only have one specific tag value, which is the default tag for this logger session.
|
30
|
+
#
|
31
|
+
# If multiple tags are required, use the method tdebug, terror, twarn, tinfo or #with_tag block to create a new tag
|
32
|
+
#
|
33
|
+
# Note that the tag can be in symbol or string, however shall convert to symbol when processing
|
34
|
+
attr_accessor :tag
|
35
|
+
# +include_caller+ (true/false) tell the logger to print the caller together with the tag
|
36
|
+
attr_accessor :include_caller
|
37
|
+
# +logger+ it is the actual logger instance of this Tlogger
|
38
|
+
attr_reader :logger
|
39
|
+
|
40
|
+
def initialize(*args , &block)
|
41
|
+
# default to console
|
42
|
+
if args.length == 0
|
43
|
+
args << STDOUT
|
44
|
+
end
|
45
|
+
|
46
|
+
@opts = {}
|
47
|
+
if args[-1].is_a?(Hash)
|
48
|
+
@opts = opts
|
49
|
+
@opts = { } if @opts.nil?
|
50
|
+
args = args[0..-2]
|
51
|
+
end
|
52
|
+
|
53
|
+
@logger = @opts[:logger_instance] || Logger.new(*args,&block)
|
54
|
+
@disabled = []
|
55
|
+
@dHistory = {}
|
56
|
+
@include_caller = false
|
57
|
+
@tag = nil
|
58
|
+
|
59
|
+
@genable = true
|
60
|
+
@exception = []
|
61
|
+
end # initialize
|
62
|
+
|
63
|
+
#
|
64
|
+
# :method: with_tag
|
65
|
+
#
|
66
|
+
# Tag all log inside the block with the given tag value
|
67
|
+
#
|
68
|
+
# Useful to tag multiple lines of log under single tag
|
69
|
+
#
|
70
|
+
def with_tag(tag,&block)
|
71
|
+
if block and not tag.nil?
|
72
|
+
log = self.clone
|
73
|
+
log.tag = tag
|
74
|
+
block.call(log)
|
75
|
+
end
|
76
|
+
end # with_tag
|
77
|
+
|
78
|
+
#
|
79
|
+
# :method: off_tag
|
80
|
+
#
|
81
|
+
# Turn off a tag. After turning off, the log message that tie to this tag shall not be printed out
|
82
|
+
#
|
83
|
+
# Do note that all tags by default are turned on.
|
84
|
+
#
|
85
|
+
def off_tag(*tags)
|
86
|
+
tags.each do |tag|
|
87
|
+
if not (tag.nil? or tag.empty? or @disabled.include?(tag))
|
88
|
+
@disabled << tag.to_sym
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end # off_tag
|
92
|
+
alias_method :tag_off, :off_tag
|
93
|
+
|
94
|
+
#
|
95
|
+
# :method: on_tag
|
96
|
+
#
|
97
|
+
# Turn on a tag.
|
98
|
+
#
|
99
|
+
# Note that by default all tags are turned on. This only affect whatever tags that has been turned off via
|
100
|
+
# the method #off_tag. It doesn't work as adding a tag. Adding a tag message should use tdebug, terror, tinfo,
|
101
|
+
# twarn or #with_tag
|
102
|
+
def on_tag(*tags)
|
103
|
+
tags.each do |tag|
|
104
|
+
@disabled.delete(tag.to_sym) if not (tag.nil? or tag.empty?)
|
105
|
+
end
|
106
|
+
end # on_tag
|
107
|
+
alias_method :tag_on, :on_tag
|
108
|
+
|
109
|
+
#
|
110
|
+
# :method: off_all_tags
|
111
|
+
#
|
112
|
+
# All log messages with tag out of your face!
|
113
|
+
#
|
114
|
+
def off_all_tags
|
115
|
+
@genable = false
|
116
|
+
clear_exceptions
|
117
|
+
end
|
118
|
+
alias_method :all_tags_off, :off_all_tags
|
119
|
+
alias_method :tags_all_off, :off_all_tags
|
120
|
+
|
121
|
+
#
|
122
|
+
# :method: on_all_tags
|
123
|
+
#
|
124
|
+
# All log messages now come down on you! RUN!
|
125
|
+
#
|
126
|
+
# No wait, you need to read that before you run away...
|
127
|
+
#
|
128
|
+
def on_all_tags
|
129
|
+
@genable = true
|
130
|
+
clear_exceptions
|
131
|
+
end
|
132
|
+
alias_method :all_tags_on, :on_all_tags
|
133
|
+
alias_method :tags_all_on, :on_all_tags
|
134
|
+
|
135
|
+
#
|
136
|
+
# :method: off_all_tags_except
|
137
|
+
#
|
138
|
+
# Turn off all tags EXCEPT the tags given.
|
139
|
+
#
|
140
|
+
# Note the parameters can be a list (multiple tags with ',' separator)
|
141
|
+
#
|
142
|
+
def off_all_tags_except(*tags)
|
143
|
+
off_all_tags
|
144
|
+
clear_exceptions
|
145
|
+
@exception.concat tags.map(&:to_sym)
|
146
|
+
end
|
147
|
+
alias_method :off_all_except, :off_all_tags_except
|
148
|
+
alias_method :all_off_except, :off_all_tags_except
|
149
|
+
|
150
|
+
#
|
151
|
+
# :method: on_all_tags_except
|
152
|
+
#
|
153
|
+
# Turn on all tags EXCEPT the tags given
|
154
|
+
#
|
155
|
+
# Note the parameters can be a list (multiple tags with ',' separator)
|
156
|
+
#
|
157
|
+
def on_all_tags_except(*tags)
|
158
|
+
on_all_tags
|
159
|
+
clear_exceptions
|
160
|
+
@exception.concat tags.map(&:to_sym)
|
161
|
+
end
|
162
|
+
alias_method :on_all_except, :on_all_tags_except
|
163
|
+
alias_method :all_on_except, :on_all_tags_except
|
164
|
+
|
165
|
+
#
|
166
|
+
# :method: clear_exceptions
|
167
|
+
#
|
168
|
+
# Clear the exception list. All exampted tags given either by #off_all_tags_except or #on_all_tags_except
|
169
|
+
# shall be reset
|
170
|
+
#
|
171
|
+
def clear_exceptions
|
172
|
+
@exception.clear
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# :method: remove_from_exception
|
177
|
+
#
|
178
|
+
# Remote a set of tags from the exception list
|
179
|
+
#
|
180
|
+
def remove_from_exception(*tags)
|
181
|
+
@exception.delete_if { |e| tags.include?(e) }
|
182
|
+
end
|
183
|
+
|
184
|
+
#
|
185
|
+
# :method: method_missing
|
186
|
+
#
|
187
|
+
# This is where the delegation to the Logger object happen or no_method_exception shall be thrown
|
188
|
+
#
|
189
|
+
def method_missing(mtd, *args, &block)
|
190
|
+
if [:debug, :error, :info, :warn].include?(mtd)
|
191
|
+
|
192
|
+
if args.length > 0 and args[0].is_a?(Symbol)
|
193
|
+
tag = args[0]
|
194
|
+
args = args[1..-1]
|
195
|
+
else
|
196
|
+
tag = @tag
|
197
|
+
end
|
198
|
+
|
199
|
+
if is_genabled?(tag) and not tag_disabled?(tag)
|
200
|
+
|
201
|
+
if block
|
202
|
+
if not (tag.nil? or tag.empty?) and args.length == 0
|
203
|
+
args = [ format_message(tag) ]
|
204
|
+
end
|
205
|
+
|
206
|
+
out = block
|
207
|
+
else
|
208
|
+
if not (tag.nil? or tag.empty?)
|
209
|
+
str = args[0]
|
210
|
+
args = [ format_message(tag) ]
|
211
|
+
out = Proc.new { str }
|
212
|
+
else
|
213
|
+
out = block
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
@logger.send(mtd, *args, &out)
|
218
|
+
|
219
|
+
end # if not disabled
|
220
|
+
|
221
|
+
|
222
|
+
elsif [:tdebug, :terror, :tinfo, :twarn].include?(mtd)
|
223
|
+
|
224
|
+
key = args[0]
|
225
|
+
|
226
|
+
if is_genabled?(key) and not tag_disabled?(key.to_sym)
|
227
|
+
if block
|
228
|
+
out = Proc.new { block.call }
|
229
|
+
args = [ format_message(key) ]
|
230
|
+
else
|
231
|
+
str = args[1]
|
232
|
+
out = Proc.new { str }
|
233
|
+
args = [ format_message(key) ]
|
234
|
+
end
|
235
|
+
|
236
|
+
mtd = mtd.to_s[1..-1].to_sym
|
237
|
+
@logger.send(mtd, *args, &out)
|
238
|
+
end
|
239
|
+
|
240
|
+
elsif [:odebug, :oerror, :oinfo, :owarn].include?(mtd)
|
241
|
+
|
242
|
+
key = args[0]
|
243
|
+
|
244
|
+
if is_genabled?(key) and not tag_disabled?(key)
|
245
|
+
|
246
|
+
if block
|
247
|
+
out = Proc.new { block.call }
|
248
|
+
args = [ format_message(key) ]
|
249
|
+
else
|
250
|
+
str = args[1]
|
251
|
+
out = Proc.new { str }
|
252
|
+
args = [ format_message(key) ]
|
253
|
+
end
|
254
|
+
|
255
|
+
msg = out.call
|
256
|
+
if not (msg.nil? or msg.empty?)
|
257
|
+
if not already_shown_or_add(key,msg)
|
258
|
+
mtd = mtd.to_s[1..-1].to_sym
|
259
|
+
@logger.send(mtd, *args, &out)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
end
|
264
|
+
|
265
|
+
elsif [:ifdebug, :iferror, :ifinfo, :ifwarn].include?(mtd)
|
266
|
+
|
267
|
+
cond = args[0]
|
268
|
+
key = args[1]
|
269
|
+
|
270
|
+
if cond.is_a?(Proc)
|
271
|
+
cond = cond.call
|
272
|
+
end
|
273
|
+
|
274
|
+
if is_genabled?(key) and not tag_disabled?(key) and cond
|
275
|
+
|
276
|
+
if block
|
277
|
+
out = Proc.new { block.call }
|
278
|
+
args = [ format_message(key) ]
|
279
|
+
else
|
280
|
+
str = args[2]
|
281
|
+
out = Proc.new { str }
|
282
|
+
args = [ format_message(key) ]
|
283
|
+
end
|
284
|
+
|
285
|
+
msg = out.call
|
286
|
+
if not (msg.nil? or msg.empty?)
|
287
|
+
mtd = mtd.to_s[2..-1].to_sym
|
288
|
+
@logger.send(mtd, *args, &out)
|
289
|
+
end
|
290
|
+
|
291
|
+
end
|
292
|
+
|
293
|
+
elsif @logger.respond_to?(mtd)
|
294
|
+
@logger.send(mtd, *args, &block)
|
295
|
+
else
|
296
|
+
super
|
297
|
+
end
|
298
|
+
end # method_missing
|
299
|
+
|
300
|
+
#
|
301
|
+
# :method: tag_disabled?
|
302
|
+
#
|
303
|
+
# Check if the tag is disabled
|
304
|
+
#
|
305
|
+
def tag_disabled?(tag)
|
306
|
+
if tag.nil? or tag.empty?
|
307
|
+
false
|
308
|
+
else
|
309
|
+
@disabled.include?(tag.to_sym)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
#
|
314
|
+
# :method: show_source
|
315
|
+
# Helper setting the flag include_caller
|
316
|
+
#
|
317
|
+
def show_source
|
318
|
+
@include_caller = true
|
319
|
+
end
|
320
|
+
|
321
|
+
private
|
322
|
+
def format_message(key)
|
323
|
+
# returning args array
|
324
|
+
if @include_caller
|
325
|
+
"[#{key}] #{find_caller} "
|
326
|
+
else
|
327
|
+
"[#{key}] "
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
def is_genabled?(key)
|
332
|
+
if key.nil?
|
333
|
+
true
|
334
|
+
else
|
335
|
+
(@genable and not @exception.include?(key.to_sym)) or (not @genable and @exception.include?(key.to_sym))
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
def already_shown_or_add(key,msg)
|
340
|
+
smsg = Digest::SHA256.hexdigest(msg)
|
341
|
+
if @dHistory[key.to_sym].nil?
|
342
|
+
add_to_history(key,smsg)
|
343
|
+
false
|
344
|
+
else
|
345
|
+
res = @dHistory[key.to_sym].include?(smsg)
|
346
|
+
add_to_history(key,smsg) if not res
|
347
|
+
res
|
348
|
+
end
|
349
|
+
end # already_shown_or_add
|
350
|
+
|
351
|
+
def add_to_history(key,dgt)
|
352
|
+
@dHistory[key.to_sym] = [] if @dHistory[key.to_sym].nil?
|
353
|
+
@dHistory[key.to_sym] << dgt if not @dHistory[key.to_sym].include?(dgt)
|
354
|
+
end # add_to_history
|
355
|
+
|
356
|
+
def find_caller
|
357
|
+
caller.each do |c|
|
358
|
+
next if c =~ /tlogger.rb/
|
359
|
+
@cal = c
|
360
|
+
break
|
361
|
+
end
|
362
|
+
|
363
|
+
if @cal.nil? or @cal.empty?
|
364
|
+
@cal = caller[0]
|
365
|
+
end
|
366
|
+
|
367
|
+
# reduce it down to last two folder?
|
368
|
+
sp = @cal.split(File::SEPARATOR)
|
369
|
+
if sp.length > 1
|
370
|
+
msg = "/#{sp[-2]}/#{sp[-1]}"
|
371
|
+
else
|
372
|
+
msg = sp[-1]
|
373
|
+
end
|
374
|
+
|
375
|
+
msg
|
376
|
+
|
377
|
+
end # find_caller
|
378
|
+
|
379
|
+
end
|
380
|
+
end
|