teelogger 0.4.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +6 -0
- data/features/filter.feature +80 -0
- data/features/formatter.feature +49 -0
- data/features/logger.feature +20 -0
- data/features/step_definitions/filter_steps.rb +70 -0
- data/features/step_definitions/formatter_steps.rb +30 -0
- data/features/step_definitions/{steps.rb → logger_steps.rb} +18 -8
- data/features/support/env.rb +6 -0
- data/lib/teelogger.rb +89 -95
- data/lib/teelogger/extensions.rb +47 -0
- data/lib/teelogger/filter.rb +215 -0
- data/lib/teelogger/filters/assignment.rb +42 -0
- data/lib/teelogger/filters/cli.rb +45 -0
- data/lib/teelogger/filters/recursive.rb +55 -0
- data/lib/teelogger/formatter.rb +108 -0
- data/lib/teelogger/levels.rb +41 -0
- data/lib/teelogger/version.rb +1 -1
- data/teelogger.gemspec +3 -0
- metadata +47 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 374fbb37a9d2c70a80c83f6c52ec70501fef456a
|
4
|
+
data.tar.gz: 43ff68bc53641a1805f21e76916ec5241b0e5549
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 704c17de207b1a44bb1886ea9291c27c77c67b8e38140dcca144f21009705dae5e58c50a8ce056e877195320be3413985915d2d96651a7e691042846126ad0b8
|
7
|
+
data.tar.gz: 0797bc600ca9c252c61ec27289cc61103143ab7df846760b025f0039f5222d98aa0fd13db267fd6042053fc614bcad71e7e2bb005e97ea50f8a198d5f663bd2e
|
data/.travis.yml
CHANGED
@@ -0,0 +1,80 @@
|
|
1
|
+
@filter
|
2
|
+
Feature: Filter
|
3
|
+
As a user of the teelogger gem
|
4
|
+
When I use the Filter class
|
5
|
+
I expect it to work as documented.
|
6
|
+
|
7
|
+
@filter_01
|
8
|
+
Scenario Outline: Replace patterns in Strings
|
9
|
+
Given I create a TeeLogger for testing filters
|
10
|
+
And I write a log message containing the word "<word>"
|
11
|
+
Then I expect the log message to <condition> the word "<word>"
|
12
|
+
|
13
|
+
Examples:
|
14
|
+
| word | condition |
|
15
|
+
| hello | contain |
|
16
|
+
| hello=123 | contain |
|
17
|
+
| --hello=123 | contain |
|
18
|
+
| hello: 123 | contain |
|
19
|
+
| password=123 | not contain |
|
20
|
+
| password: 123 | not contain |
|
21
|
+
| --password=123 | not contain |
|
22
|
+
|
23
|
+
@filter_02
|
24
|
+
Scenario Outline: Replace patterns in Strings in Arrays
|
25
|
+
Given I create a TeeLogger for testing filters
|
26
|
+
And I write a log message containing the word "<word>" in an Array
|
27
|
+
Then I expect the log message to <condition> the word "<word>"
|
28
|
+
|
29
|
+
Examples:
|
30
|
+
| word | condition |
|
31
|
+
| hello | contain |
|
32
|
+
| hello=123 | contain |
|
33
|
+
| --hello=123 | contain |
|
34
|
+
| hello: 123 | contain |
|
35
|
+
| password=123 | not contain |
|
36
|
+
| password: 123 | not contain |
|
37
|
+
| --password=123 | not contain |
|
38
|
+
|
39
|
+
@filter_03
|
40
|
+
Scenario Outline: Replace patterns in Strings in Hashes
|
41
|
+
Given I create a TeeLogger for testing filters
|
42
|
+
And I write a log message containing the value "<word>" for the key "<key>" in a Hash
|
43
|
+
Then I expect the log message to <condition> the word "<word>"
|
44
|
+
|
45
|
+
Examples:
|
46
|
+
| key | word | condition |
|
47
|
+
| hello | MUST REMAIN | contain |
|
48
|
+
| password | TO BE HIDDEN | not contain |
|
49
|
+
|
50
|
+
@filter_04
|
51
|
+
Scenario Outline: Replace patterns in CLI-like arrays
|
52
|
+
Given I create a TeeLogger for testing filters
|
53
|
+
And I write a log message containing the word sequence "<word1>", "<word2>"
|
54
|
+
Then I expect the log message to <condition> the word "<word2>"
|
55
|
+
|
56
|
+
Examples:
|
57
|
+
| word1 | word2 | condition |
|
58
|
+
| hello | MUST REMAIN | contain |
|
59
|
+
| password | TO BE HIDDEN | not contain |
|
60
|
+
|
61
|
+
@filter_05
|
62
|
+
Scenario Outline: Ensure custom filter words work
|
63
|
+
Given I create a TeeLogger for testing filters
|
64
|
+
And I set filter words to include "<filter>"
|
65
|
+
And I write a log message containing the word "<word>"
|
66
|
+
Then I expect the log message to <condition> the word "<word>"
|
67
|
+
|
68
|
+
Examples:
|
69
|
+
| filter | word | condition |
|
70
|
+
| foo | foo=123 | not contain |
|
71
|
+
| bar | foo=123 | contain |
|
72
|
+
| foo | bar=123 | contain |
|
73
|
+
| bar | bar=123 | not contain |
|
74
|
+
|
75
|
+
@filter_06
|
76
|
+
Scenario: Custom filter
|
77
|
+
Given I create a TeeLogger for testing filters
|
78
|
+
And I register a custom filter
|
79
|
+
And I write a log message containing the word "foo"
|
80
|
+
Then I expect the log message to not contain the word "foo"
|
@@ -0,0 +1,49 @@
|
|
1
|
+
@formatter
|
2
|
+
Feature: Formatter
|
3
|
+
As a user of the teelogger gem
|
4
|
+
When I use the Formatter class
|
5
|
+
I expect it to work as documented.
|
6
|
+
|
7
|
+
@formatter_01
|
8
|
+
Scenario Outline: Placeholders
|
9
|
+
Given I create a Formatter with "{<placeholder>}" in the format string
|
10
|
+
And I call it with parameters "<severity>", "<time>", "<progname>" and "<message>"
|
11
|
+
Then I expect the result to match "<result>"
|
12
|
+
|
13
|
+
Examples:
|
14
|
+
| placeholder | severity | time | progname | message | result |
|
15
|
+
| severity | INFO | 2015-07-03T12:10:57Z | STDOUT | test message | INFO |
|
16
|
+
| severity | InfO | 2015-07-03T12:10:57Z | STDOUT | test message | INFO |
|
17
|
+
| short_severity | iNFO | 2015-07-03T12:10:57Z | STDOUT | test message | I |
|
18
|
+
| short_severity | infO | 2015-07-03T12:10:57Z | STDOUT | test message | I |
|
19
|
+
| logger_timestamp | INFO | 2015-07-03T12:10:57Z | STDOUT | test message | 2015-07-03T12:10:57.000000 |
|
20
|
+
| logger_timestamp | InfO | 2015-07-03T12:10:57Z | STDOUT | test message | 2015-07-03T12:10:57.000000 |
|
21
|
+
| logger_timestamp | InfO | 2015-07-03T12:10:57+0100 | STDOUT | test message | 2015-07-03T11:10:57.000000 |
|
22
|
+
| iso8601_timestamp | INFO | 2015-07-03T12:10:57Z | STDOUT | test message | 2015-07-03T12:10:57\\\\+0000 |
|
23
|
+
| iso8601_timestamp | InfO | 2015-07-03T12:10:57Z | STDOUT | test message | 2015-07-03T12:10:57\\\\+0000 |
|
24
|
+
| iso8601_timestamp | InfO | 2015-07-03T12:10:57+0100 | STDOUT | test message | 2015-07-03T11:10:57\\\\+0000 |
|
25
|
+
| iso8601_timestamp_utc | INFO | 2015-07-03T12:10:57Z | STDOUT | test message | 2015-07-03T12:10:57Z |
|
26
|
+
| iso8601_timestamp_utc | InfO | 2015-07-03T12:10:57Z | STDOUT | test message | 2015-07-03T12:10:57Z |
|
27
|
+
| iso8601_timestamp_utc | InfO | 2015-07-03T12:10:57+0100 | STDOUT | test message | 2015-07-03T11:10:57Z |
|
28
|
+
| tai64n_timestamp | INFO | 2015-07-03T12:10:57Z | STDOUT | test message | @4000000055967bdb000001f4 |
|
29
|
+
| tai64n_timestamp | InfO | 2015-07-03T12:10:57Z | STDOUT | test message | @4000000055967bdb000001f4 |
|
30
|
+
| tai64n_timestamp | InfO | 2015-07-03T12:10:57+0100 | STDOUT | test message | @4000000055966dcb000001f4 |
|
31
|
+
| logger | iNFO | 2015-07-03T12:10:57Z | sTDouT | test message | sTDouT |
|
32
|
+
| message | iNFO | 2015-07-03T12:10:57Z | STDOUT | teSt mESsage | teSt mESsage |
|
33
|
+
| pid | iNFO | 2015-07-03T12:10:57Z | STDOUT | teSt mESsage | \\\\d+ |
|
34
|
+
|
35
|
+
|
36
|
+
# Note 1: need four \ to escape a special character in the regex field
|
37
|
+
|
38
|
+
@formatter_02
|
39
|
+
Scenario Outline: Format strings
|
40
|
+
Given I create a Formatter with the "<format>" format string
|
41
|
+
And I call it with parameters "<severity>", "<time>", "<progname>" and "<message>"
|
42
|
+
Then I expect the result to match "<result>"
|
43
|
+
|
44
|
+
Examples:
|
45
|
+
| format | severity | time | progname | message | result |
|
46
|
+
| FORMAT_LOGGER | INFO | 2015-07-03T12:10:57Z | STDOUT | test message | I, \\\\[2015-07-03T12:10:57.000000 #\\\\d+\\\\] INFO -- STDOUT: test message |
|
47
|
+
| FORMAT_DEFAULT | INFO | 2015-07-03T12:10:57Z | STDOUT | test message | I, \\\\[2015-07-03T12:10:57\\\\+0000 #\\\\d+\\\\] STDOUT: test message |
|
48
|
+
| FORMAT_SHORT | INFO | 2015-07-03T12:10:57Z | STDOUT | test message | I, \\\\[2015-07-03T12:10:57\\\\+0000\\\\] test message |
|
49
|
+
| FORMAT_DJB | INFO | 2015-07-03T12:10:57Z | STDOUT | test message | @4000000055967bdb000001f4 INFO: test message |
|
data/features/logger.feature
CHANGED
@@ -89,4 +89,24 @@ Feature: Logger
|
|
89
89
|
| ERROR | appear |
|
90
90
|
| FATAL | not appear |
|
91
91
|
|
92
|
+
@logger_07
|
93
|
+
Scenario Outline: Bad log levels
|
94
|
+
Given I create a TeeLogger with default parameters
|
95
|
+
And I set the log level to "<initial>"
|
96
|
+
And I set the log level to "<level>"
|
97
|
+
Then I expect this to <raise> an exception
|
98
|
+
And I expect the log level to be "<result>"
|
99
|
+
|
100
|
+
Examples:
|
101
|
+
| initial | level | raise | result |
|
102
|
+
| fatal | DeBuG | not raise | debug |
|
103
|
+
| fatal | debugFOO | raise | fatal |
|
104
|
+
| fatal | InfO | not raise | info |
|
105
|
+
| fatal | infoFOO | raise | fatal |
|
106
|
+
| fatal | wARn | not raise | warn |
|
107
|
+
| fatal | warnFOO | raise | fatal |
|
108
|
+
| fatal | eRrOR | not raise | error |
|
109
|
+
| fatal | errorFOO | raise | fatal |
|
110
|
+
| debug | faTAl | not raise | fatal |
|
111
|
+
| debug | fatalFOO | raise | debug |
|
92
112
|
|
@@ -0,0 +1,70 @@
|
|
1
|
+
io = nil
|
2
|
+
logger = nil
|
3
|
+
|
4
|
+
Given(/^I create a TeeLogger for testing filters$/) do
|
5
|
+
io = StringIO.new
|
6
|
+
logger = TeeLogger::TeeLogger.new io
|
7
|
+
assert [TeeLogger::DEFAULT_FLUSH_INTERVAL] == logger.flush_interval, "Flush interval is not default: #{logger.flush_interval}"
|
8
|
+
end
|
9
|
+
|
10
|
+
Given(/^I write a log message containing the word "([^"]*)"$/) do |word|
|
11
|
+
# Log a string
|
12
|
+
logger.error(word)
|
13
|
+
end
|
14
|
+
|
15
|
+
Given(/^I write a log message containing the word "([^"]*)" in an Array$/) do |word|
|
16
|
+
# Log an Array
|
17
|
+
logger.error([1, word, 3])
|
18
|
+
end
|
19
|
+
|
20
|
+
Given(/^I write a log message containing the value "([^"]*)" for the key "([^"]*)" in a Hash$/) do |value, key|
|
21
|
+
# Log a Hash
|
22
|
+
val = {
|
23
|
+
1 => 2,
|
24
|
+
2 => {
|
25
|
+
key => value
|
26
|
+
},
|
27
|
+
3 => [
|
28
|
+
'a', "#{key}=#{value}", 'b',
|
29
|
+
],
|
30
|
+
}
|
31
|
+
logger.error(val)
|
32
|
+
end
|
33
|
+
|
34
|
+
Given(/^I set filter words to include "([^"]*)"$/) do |filter_word|
|
35
|
+
logger.filter_words = [filter_word]
|
36
|
+
end
|
37
|
+
|
38
|
+
Given(/^I register a custom filter$/) do
|
39
|
+
class MyFilter < TeeLogger::Filter::FilterBase
|
40
|
+
FILTER_TYPES = [String]
|
41
|
+
WINDOW_SIZE = 1
|
42
|
+
def process(*args)
|
43
|
+
# Nil everything
|
44
|
+
args.each_with_index do |dummy, idx|
|
45
|
+
args[idx] = nil
|
46
|
+
end
|
47
|
+
return args
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
logger.register_filter(MyFilter)
|
52
|
+
end
|
53
|
+
|
54
|
+
Given(/^I write a log message containing the word sequence "([^"]*)", "([^"]*)"$/) do |word1, word2|
|
55
|
+
logger.error(word1, word2)
|
56
|
+
end
|
57
|
+
|
58
|
+
Then(/^I expect the log message to ([^ ]* ?)contain the word "([^"]*)"$/) do |mod, word|
|
59
|
+
mod = mod.strip
|
60
|
+
message = io.string
|
61
|
+
|
62
|
+
case mod
|
63
|
+
when "not"
|
64
|
+
assert !message.include?(word), "Log message contains '#{word}' when it must not!:\n#{message}"
|
65
|
+
else
|
66
|
+
assert message.include?(word), "Log message does not contain '#{word}' when it should!:\n#{message}"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
formatter = nil
|
2
|
+
result = nil
|
3
|
+
|
4
|
+
Given(/^I create a Formatter with "([^"]*)" in the format string$/) do |format|
|
5
|
+
formatter = ::TeeLogger::Formatter.new(format)
|
6
|
+
end
|
7
|
+
|
8
|
+
Given(/^I create a Formatter with the "([^"]*)" format string$/) do |constant|
|
9
|
+
format = ::TeeLogger::Formatter.const_get(constant)
|
10
|
+
formatter = ::TeeLogger::Formatter.new(format)
|
11
|
+
end
|
12
|
+
|
13
|
+
Given(/^I call it with parameters "([^"]*)", "([^"]*)", "([^"]*)" and "([^"]*)"$/) do |severity, time, progname, message|
|
14
|
+
# The time needs to be parsed to make some kind of sense; anything else
|
15
|
+
# can just be passed through. For time parsing, we need to temporarily force
|
16
|
+
# the timezone to UTC, otherwise the tests will run differently on different
|
17
|
+
# machines.
|
18
|
+
zone = ENV["TZ"]
|
19
|
+
ENV["TZ"] = "UTC"
|
20
|
+
t = Time.parse(time)
|
21
|
+
ENV["TZ"] = zone
|
22
|
+
|
23
|
+
result = formatter.call(severity, t, progname, message)
|
24
|
+
end
|
25
|
+
|
26
|
+
Then(/^I expect the result to match "([^"]*)"$/) do |expected|
|
27
|
+
regex = Regexp.new("^#{expected}$")
|
28
|
+
assert regex.match(result), "Expected to match '#{regex}' (#{expected}), but got '#{result}'"
|
29
|
+
end
|
30
|
+
|
@@ -1,9 +1,3 @@
|
|
1
|
-
begin
|
2
|
-
require 'test/unit/assertions'
|
3
|
-
rescue LoadError
|
4
|
-
require 'minitest/assertions'
|
5
|
-
end
|
6
|
-
|
7
1
|
message = "test message"
|
8
2
|
io = nil
|
9
3
|
logger = nil
|
@@ -19,9 +13,14 @@ Given(/^I set the flush_interval to "([^"]*)"$/) do |interval|
|
|
19
13
|
assert [i] == logger.flush_interval, "Setting flush interval did not take: #{logger.flush_interval}"
|
20
14
|
end
|
21
15
|
|
22
|
-
|
16
|
+
level_set_exception = nil
|
23
17
|
Given(/^I set the log level to "(.*?)"$/) do |level|
|
24
|
-
|
18
|
+
begin
|
19
|
+
logger.level = level
|
20
|
+
level_set_exception = nil
|
21
|
+
rescue StandardError => err
|
22
|
+
level_set_exception = err
|
23
|
+
end
|
25
24
|
end
|
26
25
|
|
27
26
|
Given(/^I write a log message at log level "(.*?)"$/) do |level|
|
@@ -33,6 +32,17 @@ Then(/^I expect the log message to appear on the screen$/) do
|
|
33
32
|
puts "Can't test this; please check manually"
|
34
33
|
end
|
35
34
|
|
35
|
+
Then(/^I expect this to ([^ ]*?) ?raise an exception$/) do |mod|
|
36
|
+
if mod.strip != "not"
|
37
|
+
assert !level_set_exception.nil?, "Expected an exception, but none was raised."
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
Then(/^I expect the log level to be "([^"]*)"$/) do |result|
|
42
|
+
expected = TeeLogger::TeeLogger.convert_level(result.strip)
|
43
|
+
assert logger.level == [expected], "Expected numeric level #{expected} but got #{logger.level[0]}."
|
44
|
+
end
|
45
|
+
|
36
46
|
Then(/^I expect the log level "(.*?)" to (.*?) taken hold$/) do |level, condition|
|
37
47
|
meth = "#{level.downcase}?".to_sym
|
38
48
|
res = logger.send(meth)
|
data/features/support/env.rb
CHANGED
data/lib/teelogger.rb
CHANGED
@@ -6,53 +6,16 @@
|
|
6
6
|
# All rights reserved.
|
7
7
|
#
|
8
8
|
require "teelogger/version"
|
9
|
+
require "teelogger/extensions"
|
10
|
+
require "teelogger/levels"
|
11
|
+
require "teelogger/formatter"
|
12
|
+
require "teelogger/filter"
|
9
13
|
|
10
14
|
require "logger"
|
11
15
|
|
12
16
|
module TeeLogger
|
13
17
|
DEFAULT_FLUSH_INTERVAL = 2000
|
14
18
|
|
15
|
-
##
|
16
|
-
# Extensions for the ruby logger
|
17
|
-
module LoggerExtensions
|
18
|
-
attr_accessor :teelogger_io
|
19
|
-
attr_accessor :flush_interval
|
20
|
-
|
21
|
-
##
|
22
|
-
# Flush ruby and OS buffers for this logger
|
23
|
-
def flush
|
24
|
-
if @teelogger_io.nil?
|
25
|
-
raise "TeeLogger logger without IO object, can't do anything"
|
26
|
-
end
|
27
|
-
|
28
|
-
@teelogger_io.flush
|
29
|
-
begin
|
30
|
-
@teelogger_io.fsync
|
31
|
-
rescue NotImplementedError, Errno::EINVAL
|
32
|
-
# pass
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
|
-
|
37
|
-
##
|
38
|
-
# This function invokes flush if it's been invoked more often than
|
39
|
-
# flush_interval.
|
40
|
-
def auto_flush
|
41
|
-
if @written.nil?
|
42
|
-
@written = 0
|
43
|
-
end
|
44
|
-
|
45
|
-
@written += 1
|
46
|
-
|
47
|
-
if @written >= self.flush_interval
|
48
|
-
self.flush
|
49
|
-
@written = 0
|
50
|
-
end
|
51
|
-
end
|
52
|
-
end # module LoggerExtensions
|
53
|
-
|
54
|
-
|
55
|
-
|
56
19
|
##
|
57
20
|
# Logger that writes to multiple outputs. Behaves just like Ruby's Logger,
|
58
21
|
# and like a hash of String => Logger.
|
@@ -73,44 +36,22 @@ module TeeLogger
|
|
73
36
|
# end
|
74
37
|
# end
|
75
38
|
class TeeLogger
|
39
|
+
# Extends and includes
|
40
|
+
extend ::TeeLogger::Levels
|
41
|
+
include ::TeeLogger::Levels
|
42
|
+
include ::TeeLogger::Filter
|
43
|
+
|
44
|
+
# Properties
|
76
45
|
@default_level
|
46
|
+
@formatter
|
77
47
|
@loggers
|
78
48
|
@ios
|
79
49
|
|
80
|
-
##
|
81
|
-
# Convert a log level to its string name
|
82
|
-
def self.string_level(level)
|
83
|
-
if level.is_a? String
|
84
|
-
return level
|
85
|
-
end
|
86
|
-
|
87
|
-
Logger::Severity.constants.each do |const|
|
88
|
-
if level == Logger.const_get(const)
|
89
|
-
return const
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
return nil
|
94
|
-
end
|
95
|
-
|
96
|
-
##
|
97
|
-
# Convert a string log level to its constant value
|
98
|
-
def self.convert_level(val)
|
99
|
-
if val.is_a? String
|
100
|
-
begin
|
101
|
-
val = Logger.const_get(val.upcase)
|
102
|
-
rescue NameError
|
103
|
-
val = Logger::Severity::WARN
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
return val
|
108
|
-
end
|
109
50
|
|
110
51
|
private
|
111
52
|
##
|
112
53
|
# Define log functions as strings, for internal re-use
|
113
|
-
LOG_FUNCTIONS = Logger::Severity.constants.map { |level|
|
54
|
+
LOG_FUNCTIONS = Logger::Severity.constants.map { |level| string_level(level.to_s).downcase }
|
114
55
|
|
115
56
|
public
|
116
57
|
|
@@ -129,26 +70,40 @@ public
|
|
129
70
|
logger = Logger.new(io)
|
130
71
|
|
131
72
|
# Initialize logger
|
132
|
-
io.write "Logging to '#{arg}' initialized with level #{
|
133
|
-
logger.level =
|
73
|
+
io.write "Logging to '#{arg}' initialized with level #{string_level(@default_level)}.\n"
|
74
|
+
logger.level = convert_level(@default_level)
|
134
75
|
else
|
135
76
|
# We have some other object - let's hope it's an IO object
|
136
|
-
key =
|
77
|
+
key = nil
|
78
|
+
case arg
|
79
|
+
when STDOUT
|
80
|
+
key = 'STDOUT'
|
81
|
+
when STDERR
|
82
|
+
key = 'STDERR'
|
83
|
+
else
|
84
|
+
key = arg.to_s
|
85
|
+
end
|
137
86
|
|
138
87
|
# Try to create the logger.
|
139
88
|
io = arg
|
140
89
|
logger = Logger.new(io)
|
141
90
|
|
142
91
|
# Initialize logger
|
143
|
-
io.write "Logging to #{key} initialized with level #{
|
144
|
-
logger.level =
|
92
|
+
io.write "Logging to #{key} initialized with level #{string_level(@default_level)}.\n"
|
93
|
+
logger.level = convert_level(@default_level)
|
145
94
|
end
|
146
95
|
|
96
|
+
# Set the logger formatter
|
97
|
+
logger.formatter = @formatter
|
98
|
+
|
147
99
|
# Extend logger instances with extra functionality
|
148
|
-
logger.extend(LoggerExtensions)
|
100
|
+
logger.extend(::TeeLogger::LoggerExtensions)
|
149
101
|
logger.teelogger_io = io
|
150
102
|
logger.flush_interval = DEFAULT_FLUSH_INTERVAL
|
151
103
|
|
104
|
+
# Flush the "Logging to..." line
|
105
|
+
logger.flush
|
106
|
+
|
152
107
|
if not key.nil? and not logger.nil? and not io.nil?
|
153
108
|
@loggers[key] = logger
|
154
109
|
@ios[key] = io
|
@@ -166,9 +121,13 @@ public
|
|
166
121
|
|
167
122
|
# Initialization
|
168
123
|
@default_level = Logger::Severity::INFO
|
124
|
+
@formatter = ::TeeLogger::Formatter.new
|
169
125
|
@loggers = {}
|
170
126
|
@ios = {}
|
171
127
|
|
128
|
+
# Load built-in filters
|
129
|
+
load_filters(*args)
|
130
|
+
|
172
131
|
# Create logs for all arguments
|
173
132
|
args.each do |arg|
|
174
133
|
add_logger(arg)
|
@@ -180,7 +139,7 @@ public
|
|
180
139
|
# Set log level; override this to also accept strings
|
181
140
|
def level=(val)
|
182
141
|
# Convert strings to the constant value
|
183
|
-
val =
|
142
|
+
val = convert_level(val)
|
184
143
|
|
185
144
|
# Update the default log level
|
186
145
|
@default_level = val
|
@@ -192,6 +151,19 @@ public
|
|
192
151
|
end
|
193
152
|
|
194
153
|
|
154
|
+
##
|
155
|
+
# Set the formatter
|
156
|
+
def formatter=(formatter)
|
157
|
+
# Update the default formatter
|
158
|
+
@formatter = formatter
|
159
|
+
|
160
|
+
# Set all loggers' formatters
|
161
|
+
@loggers.each do |key, logger|
|
162
|
+
logger.formatter = formatter
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
|
195
167
|
##
|
196
168
|
# Log an exception
|
197
169
|
def exception(message, ex)
|
@@ -254,6 +226,7 @@ public
|
|
254
226
|
end
|
255
227
|
|
256
228
|
def method_missing(meth, *args, &block)
|
229
|
+
puts "MISSING #{meth}"
|
257
230
|
dispatch(meth, *args, &block)
|
258
231
|
end
|
259
232
|
|
@@ -261,12 +234,36 @@ public
|
|
261
234
|
|
262
235
|
|
263
236
|
def dispatch(meth, *args, &block)
|
264
|
-
meth_name = meth.to_s
|
265
|
-
|
266
237
|
if @loggers.nil? or @loggers.empty?
|
267
238
|
raise "No loggers created, can't do anything."
|
268
239
|
end
|
269
240
|
|
241
|
+
# Try dispatching the call, with preprocessing based on whether it
|
242
|
+
# is a log function or not.
|
243
|
+
meth_name = meth.to_s
|
244
|
+
|
245
|
+
ret = []
|
246
|
+
if LOG_FUNCTIONS.include? meth_name
|
247
|
+
ret = dispatch_log(meth_name, *args)
|
248
|
+
else
|
249
|
+
ret = dispatch_other(meth_name, *args, &block)
|
250
|
+
end
|
251
|
+
|
252
|
+
# Some double checking on the return value(s).
|
253
|
+
if not ret.empty?
|
254
|
+
return ret
|
255
|
+
end
|
256
|
+
|
257
|
+
# If the method wasn't from the loggers, we'll try to send it to the
|
258
|
+
# hash.
|
259
|
+
return @loggers.send(meth_name, *args, &block)
|
260
|
+
end
|
261
|
+
|
262
|
+
|
263
|
+
def dispatch_log(meth_name, *args)
|
264
|
+
# Filter all arguments
|
265
|
+
args = apply_filters(*args)
|
266
|
+
|
270
267
|
# Compose message
|
271
268
|
msg = args.map do |arg|
|
272
269
|
if arg.is_a? String
|
@@ -281,26 +278,23 @@ public
|
|
281
278
|
ret = []
|
282
279
|
@loggers.each do |key, logger|
|
283
280
|
if logger.respond_to? meth_name
|
284
|
-
|
285
|
-
|
286
|
-
message
|
287
|
-
end
|
288
|
-
else
|
289
|
-
ret << logger.send(meth_name, *args, &block)
|
281
|
+
ret << logger.send(meth_name, key) do
|
282
|
+
message
|
290
283
|
end
|
291
284
|
end
|
292
285
|
end
|
286
|
+
return ret
|
287
|
+
end
|
293
288
|
|
294
|
-
# Some double checking on the return value(s).
|
295
|
-
if not ret.empty?
|
296
|
-
return ret
|
297
|
-
end
|
298
289
|
|
299
|
-
|
300
|
-
|
301
|
-
|
290
|
+
def dispatch_other(meth_name, *args, &block)
|
291
|
+
ret = []
|
292
|
+
@loggers.each do |key, logger|
|
293
|
+
if logger.respond_to? meth_name
|
294
|
+
ret << logger.send(meth_name, *args, &block)
|
295
|
+
end
|
296
|
+
end
|
297
|
+
return ret
|
302
298
|
end
|
303
|
-
|
304
299
|
end
|
305
|
-
|
306
300
|
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#
|
2
|
+
# TeeLogger
|
3
|
+
# https://github.com/spriteCloud/teelogger
|
4
|
+
#
|
5
|
+
# Copyright (c) 2014-2015 spriteCloud B.V. and other TeeLogger contributors.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
module TeeLogger
|
9
|
+
##
|
10
|
+
# Extensions for the ruby logger
|
11
|
+
module LoggerExtensions
|
12
|
+
attr_accessor :teelogger_io
|
13
|
+
attr_accessor :flush_interval
|
14
|
+
|
15
|
+
##
|
16
|
+
# Flush ruby and OS buffers for this logger
|
17
|
+
def flush
|
18
|
+
if @teelogger_io.nil?
|
19
|
+
raise "TeeLogger logger without IO object, can't do anything"
|
20
|
+
end
|
21
|
+
|
22
|
+
@teelogger_io.flush
|
23
|
+
begin
|
24
|
+
@teelogger_io.fsync
|
25
|
+
rescue NotImplementedError, Errno::EINVAL
|
26
|
+
# pass
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
##
|
32
|
+
# This function invokes flush if it's been invoked more often than
|
33
|
+
# flush_interval.
|
34
|
+
def auto_flush
|
35
|
+
if @written.nil?
|
36
|
+
@written = 0
|
37
|
+
end
|
38
|
+
|
39
|
+
@written += 1
|
40
|
+
|
41
|
+
if @written >= self.flush_interval
|
42
|
+
self.flush
|
43
|
+
@written = 0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end # module LoggerExtensions
|
47
|
+
end # module TeeLogger
|
@@ -0,0 +1,215 @@
|
|
1
|
+
#
|
2
|
+
# TeeLogger
|
3
|
+
# https://github.com/spriteCloud/teelogger
|
4
|
+
#
|
5
|
+
# Copyright (c) 2014,2015 spriteCloud B.V. and other TeeLogger contributors.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
require 'require_all'
|
9
|
+
|
10
|
+
module TeeLogger
|
11
|
+
module Filter
|
12
|
+
##
|
13
|
+
# The default words to filter. It's up to each individual filter to decide
|
14
|
+
# what to do when they encounter a word, but these are the words the filters
|
15
|
+
# should process.
|
16
|
+
# Note that they can be strings or regular expressions. Regular expressions
|
17
|
+
# should by and large not be anchored to the beginning or end of strings.
|
18
|
+
DEFAULT_FILTER_WORDS = [
|
19
|
+
/password[a-z\-_]*/,
|
20
|
+
/salt[a-z\-_]*/,
|
21
|
+
]
|
22
|
+
|
23
|
+
##
|
24
|
+
# Filter words
|
25
|
+
def filter_words
|
26
|
+
@filter_words ||= DEFAULT_FILTER_WORDS
|
27
|
+
return @filter_words
|
28
|
+
end
|
29
|
+
|
30
|
+
def filter_words=(arg)
|
31
|
+
# Coerce into array
|
32
|
+
begin
|
33
|
+
arr = []
|
34
|
+
arg.each do |item|
|
35
|
+
arr << item
|
36
|
+
end
|
37
|
+
@filter_words = arr
|
38
|
+
rescue NameError, NoMethodError
|
39
|
+
raise "Can't set filter words, not iterable: #{arg}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
##
|
45
|
+
# Load all built-in filters.
|
46
|
+
def load_filters(*args)
|
47
|
+
require_rel 'filters'
|
48
|
+
::TeeLogger::Filter.constants.collect {|const_sym|
|
49
|
+
::TeeLogger::Filter.const_get(const_sym)
|
50
|
+
}.each do |filter|
|
51
|
+
begin
|
52
|
+
register_filter(filter)
|
53
|
+
if not ENV['TEELOGGER_VERBOSE'].nil? and ENV['TEELOGGER_VERBOSE'].to_i > 0
|
54
|
+
puts "Registered filter #{filter}."
|
55
|
+
end
|
56
|
+
rescue StandardError => err
|
57
|
+
if not ENV['TEELOGGER_VERBOSE'].nil? and ENV['TEELOGGER_VERBOSE'].to_i > 0
|
58
|
+
puts "Not registering filter: #{err}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
##
|
65
|
+
# Returns all registered filters.
|
66
|
+
def registered_filters
|
67
|
+
# Initialize if it doesn't exist
|
68
|
+
@filters ||= {}
|
69
|
+
return @filters
|
70
|
+
end
|
71
|
+
|
72
|
+
##
|
73
|
+
# Expects a class, registers the class for use by the filter function
|
74
|
+
def register_filter(filter)
|
75
|
+
# Sanity checks/register filter
|
76
|
+
if filter.class != Class
|
77
|
+
raise "Ignoring '#{filter}', not a class."
|
78
|
+
end
|
79
|
+
|
80
|
+
if not filter < FilterBase
|
81
|
+
raise "Class '#{filter}' is not derived from FilterBase."
|
82
|
+
end
|
83
|
+
|
84
|
+
begin
|
85
|
+
window = filter::WINDOW_SIZE.to_i
|
86
|
+
window_filters = registered_filters.fetch(window, {})
|
87
|
+
|
88
|
+
filter::FILTER_TYPES.each do |type|
|
89
|
+
type_filters = window_filters.fetch(type, [])
|
90
|
+
type_filters.push(filter) unless type_filters.include?(filter)
|
91
|
+
window_filters[type] = type_filters
|
92
|
+
end
|
93
|
+
|
94
|
+
registered_filters[window] = window_filters
|
95
|
+
rescue NameError, NoMethodError
|
96
|
+
raise "Class '#{filter}' is missing a FILTER_TYPES Array or a WINDOW_SIZE Integer."
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
##
|
101
|
+
# Applies all registered filters.
|
102
|
+
def apply_filters(*args)
|
103
|
+
# Pre-process filter words: we need to have regular expressions everywhere
|
104
|
+
words = []
|
105
|
+
filter_words.each do |word|
|
106
|
+
if word.is_a? Regexp
|
107
|
+
words << word
|
108
|
+
else
|
109
|
+
words << Regexp.new(word.to_s)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# We instanciate each filter once per application, and store the instnaces
|
114
|
+
# in a cache for that duration.
|
115
|
+
filter_cache = {}
|
116
|
+
|
117
|
+
# Pass state on to apply_filters_internal
|
118
|
+
state = {
|
119
|
+
:words => words,
|
120
|
+
:filter_cache => filter_cache,
|
121
|
+
:filters => self,
|
122
|
+
}
|
123
|
+
return apply_filters_internal(state, *args)
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
##
|
128
|
+
# Implementation of apply_filters that doesn't initialize state, but carries
|
129
|
+
# it over. Used internally only.
|
130
|
+
def apply_filters_internal(state, *args)
|
131
|
+
filtered_args = args
|
132
|
+
|
133
|
+
# Iterate through filters
|
134
|
+
registered_filters.each do |window, window_filters|
|
135
|
+
# Determine actual window size
|
136
|
+
window_size = [window, filtered_args.size].min
|
137
|
+
|
138
|
+
# Process each window so that elements are updated in-place. This
|
139
|
+
# means we'll start at index 0 and process up to window_size elements.
|
140
|
+
idx = 0
|
141
|
+
while (idx + window_size - 1) < filtered_args.size
|
142
|
+
# We need to use *one* argument to determine whether the filter
|
143
|
+
# type applies. The current strategy is to match the first argument
|
144
|
+
# only, and let the filter cast to other types if necessary.
|
145
|
+
first_arg = filtered_args[idx]
|
146
|
+
|
147
|
+
window_filters.each do |class_match, type_filters|
|
148
|
+
# We process with these type filters if first_arg matches the
|
149
|
+
# class_match.
|
150
|
+
if not first_arg.is_a? class_match
|
151
|
+
next
|
152
|
+
end
|
153
|
+
|
154
|
+
# Now process with the given filters.
|
155
|
+
type_filters.each do |filter|
|
156
|
+
# XXX Do not turn this into a one-liner, or we'll instanciate
|
157
|
+
# filters without using them.
|
158
|
+
filter_instance = state[:filter_cache].fetch(filter, nil)
|
159
|
+
if filter_instance.nil?
|
160
|
+
filter_instance = filter.new(state)
|
161
|
+
state[:filter_cache][filter] = filter_instance
|
162
|
+
end
|
163
|
+
|
164
|
+
# Single item windows need to be processed a bit differently from
|
165
|
+
# multi-item windows.
|
166
|
+
tuple = filtered_args[idx..idx + window_size - 1]
|
167
|
+
filtered = filter_instance.process(*tuple)
|
168
|
+
|
169
|
+
# Sanity check result
|
170
|
+
if filtered.size != tuple.size
|
171
|
+
raise "Filter #{filter} added or removed items to the log; don't know how to process!"
|
172
|
+
end
|
173
|
+
|
174
|
+
filtered.each_with_index do |item, offset|
|
175
|
+
filtered_args[idx + offset] = item
|
176
|
+
end
|
177
|
+
end # type_filters.each
|
178
|
+
end # window_filters.each
|
179
|
+
|
180
|
+
# Advance to the next window
|
181
|
+
idx += 1
|
182
|
+
end # each window
|
183
|
+
end # all registered filters
|
184
|
+
|
185
|
+
return filtered_args
|
186
|
+
end
|
187
|
+
|
188
|
+
|
189
|
+
##
|
190
|
+
# Any filter implementations must derive from this
|
191
|
+
class FilterBase
|
192
|
+
# Define FILTER_TYPES = [class, class] to declare what types this filter
|
193
|
+
# applies to.
|
194
|
+
# Define WINDOW_SIZE = int to declare how many parameters the filter
|
195
|
+
# processes at a time. It will be a sliding window of arguments.
|
196
|
+
# Note that filters may receive fewer arguments if there are less than
|
197
|
+
# WINDOW_SIZE in total.
|
198
|
+
|
199
|
+
##
|
200
|
+
# Initialize with filter words
|
201
|
+
attr_accessor :run_data
|
202
|
+
|
203
|
+
def initialize(run_data)
|
204
|
+
@run_data = run_data
|
205
|
+
end
|
206
|
+
|
207
|
+
##
|
208
|
+
# Base filter leaves the argument untouched.
|
209
|
+
def process(*args)
|
210
|
+
args
|
211
|
+
end
|
212
|
+
|
213
|
+
end # class FilterBase
|
214
|
+
end # end module Filter
|
215
|
+
end # end module TeeLogger
|
@@ -0,0 +1,42 @@
|
|
1
|
+
#
|
2
|
+
# TeeLogger
|
3
|
+
# https://github.com/spriteCloud/teelogger
|
4
|
+
#
|
5
|
+
# Copyright (c) 2014,2015 spriteCloud B.V. and other TeeLogger contributors.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
require 'teelogger/filter'
|
9
|
+
|
10
|
+
module TeeLogger
|
11
|
+
module Filter
|
12
|
+
##
|
13
|
+
# The Assignment filter takes strings of the form <prefix><word>=<value> and
|
14
|
+
# obfuscates the value.
|
15
|
+
class Assignment < FilterBase
|
16
|
+
FILTER_TYPES = [String]
|
17
|
+
WINDOW_SIZE = 1
|
18
|
+
|
19
|
+
def initialize(*args)
|
20
|
+
super(*args)
|
21
|
+
|
22
|
+
# We create more complex matches out of the filter words passed.
|
23
|
+
@matches = []
|
24
|
+
run_data[:words].each do |word|
|
25
|
+
@matches << /(-{0,2}#{word} *[=:] *)(.*)/i
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def process(*args)
|
30
|
+
# Note that due to the window size of one, args is only an element long.
|
31
|
+
args.each do |arg|
|
32
|
+
@matches.each do |match|
|
33
|
+
# Modify the matching arguments in place
|
34
|
+
arg.gsub!(match, '\1[REDACTED]')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
return args
|
39
|
+
end
|
40
|
+
end # class Assignment
|
41
|
+
end # module Filter
|
42
|
+
end # module TeeLogger
|
@@ -0,0 +1,45 @@
|
|
1
|
+
#
|
2
|
+
# TeeLogger
|
3
|
+
# https://github.com/spriteCloud/teelogger
|
4
|
+
#
|
5
|
+
# Copyright (c) 2014,2015 spriteCloud B.V. and other TeeLogger contributors.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
require 'teelogger/filter'
|
9
|
+
|
10
|
+
module TeeLogger
|
11
|
+
module Filter
|
12
|
+
##
|
13
|
+
# The CLI filter takes sequences of strings of the form ["word", "value"]
|
14
|
+
# and obfuscates the value if the word matches.
|
15
|
+
class CLI < FilterBase
|
16
|
+
FILTER_TYPES = [String]
|
17
|
+
WINDOW_SIZE = 2
|
18
|
+
|
19
|
+
def initialize(*args)
|
20
|
+
super(*args)
|
21
|
+
|
22
|
+
# We create more complex matches out of the filter words passed.
|
23
|
+
@matches = []
|
24
|
+
run_data[:words].each do |word|
|
25
|
+
@matches << /(-{0,2}#{word})(.*)/i
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def process(*args)
|
30
|
+
# In case the window is too small, ignore it
|
31
|
+
if args.size < 2
|
32
|
+
return args
|
33
|
+
end
|
34
|
+
|
35
|
+
# Otherwise, if the first argument matches, we'll redact the second.
|
36
|
+
@matches.each do |word|
|
37
|
+
if word.match(args[0])
|
38
|
+
args[1] = '[REDACTED]'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
return args
|
42
|
+
end
|
43
|
+
end # class CLI
|
44
|
+
end # module Filter
|
45
|
+
end # module TeeLogger
|
@@ -0,0 +1,55 @@
|
|
1
|
+
#
|
2
|
+
# TeeLogger
|
3
|
+
# https://github.com/spriteCloud/teelogger
|
4
|
+
#
|
5
|
+
# Copyright (c) 2014,2015 spriteCloud B.V. and other TeeLogger contributors.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
require 'teelogger/filter'
|
9
|
+
|
10
|
+
module TeeLogger
|
11
|
+
module Filter
|
12
|
+
##
|
13
|
+
# The Recursive filter takes Hashes or Arrays, and recursively applies the
|
14
|
+
# other filters to their values.
|
15
|
+
class Recursive < FilterBase
|
16
|
+
FILTER_TYPES = [Enumerable]
|
17
|
+
WINDOW_SIZE = 1
|
18
|
+
|
19
|
+
def process(*args)
|
20
|
+
# For each argument, recurse processing. Note that due to the window
|
21
|
+
# size of one, args is only an element long - but let's write this out
|
22
|
+
# properly.
|
23
|
+
args.each do |arg|
|
24
|
+
# Since we're matching enumerabls, the argument must respond to .each
|
25
|
+
arg.each do |expanded|
|
26
|
+
# The expanded variable can be a single item or a list of items.
|
27
|
+
# If expanded is itself an Enumarable, the first item is a key, the remainder
|
28
|
+
# values. We need to recursively process the values.
|
29
|
+
if expanded.is_a? Enumerable
|
30
|
+
# If the key matches any of the filter words, we'll just skip
|
31
|
+
# the value entirely.
|
32
|
+
key = expanded[0]
|
33
|
+
redacted = false
|
34
|
+
run_data[:words].each do |word|
|
35
|
+
if word.match(key.to_s)
|
36
|
+
arg[key] = '[REDACTED]'
|
37
|
+
redacted = true
|
38
|
+
break
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
if not redacted
|
43
|
+
arg[key] = run_data[:filters].apply_filters_internal(run_data, *expanded[1..-1])
|
44
|
+
end
|
45
|
+
else
|
46
|
+
arg = run_data[:filters].apply_filters_internal(run_data, expanded)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
return args
|
52
|
+
end
|
53
|
+
end # class Recursive
|
54
|
+
end # module Filter
|
55
|
+
end # module TeeLogger
|
@@ -0,0 +1,108 @@
|
|
1
|
+
#
|
2
|
+
# TeeLogger
|
3
|
+
# https://github.com/spriteCloud/teelogger
|
4
|
+
#
|
5
|
+
# Copyright (c) 2014-2015 spriteCloud B.V. and other TeeLogger contributors.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
require 'tai64'
|
9
|
+
|
10
|
+
module TeeLogger
|
11
|
+
##
|
12
|
+
# Placeholders for the formatter take a single argument, and convert it to
|
13
|
+
# a string argument using placeholder specific rules.
|
14
|
+
module FormatterPlaceholders
|
15
|
+
def severity(severity, time, progname, message)
|
16
|
+
severity.to_s.upcase
|
17
|
+
end
|
18
|
+
|
19
|
+
def short_severity(severity, time, progname, message)
|
20
|
+
severity.to_s.upcase[0..0]
|
21
|
+
end
|
22
|
+
|
23
|
+
def logger_timestamp(severity, time, progname, message)
|
24
|
+
time.strftime("%Y-%m-%dT%H:%M:%S.") << "%06d" % time.usec
|
25
|
+
end
|
26
|
+
|
27
|
+
def iso8601_timestamp(severity, time, progname, message)
|
28
|
+
time.strftime("%Y-%m-%dT%H:%M:%S%z")
|
29
|
+
end
|
30
|
+
|
31
|
+
def iso8601_timestamp_utc(severity, time, progname, message)
|
32
|
+
time.dup.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
33
|
+
end
|
34
|
+
|
35
|
+
def tai64n_timestamp(severity, time, progname, message)
|
36
|
+
Tai64::Time.new(time).to_label.to_s
|
37
|
+
end
|
38
|
+
|
39
|
+
def logger(severity, time, progname, message)
|
40
|
+
progname.to_s
|
41
|
+
end
|
42
|
+
|
43
|
+
def message(severity, time, progname, message)
|
44
|
+
message.to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
def pid(severity, time, progname, message)
|
48
|
+
$$.to_s
|
49
|
+
end
|
50
|
+
|
51
|
+
extend self
|
52
|
+
end # module FormatterPlaceholders
|
53
|
+
|
54
|
+
##
|
55
|
+
# The formatter class accepts a format string, but in a different format from
|
56
|
+
# Kernel#sprintf. Instead, placeholders enclosed in {} (but without the Ruby-
|
57
|
+
# typical #, so not #{}) will get replaced with the output of the functions
|
58
|
+
# defined in FormatterPlaceholders.
|
59
|
+
#
|
60
|
+
# The class also defines a few example format strings as constants.
|
61
|
+
class Formatter
|
62
|
+
# Valid placeholder to use in the format string
|
63
|
+
PLACEHOLDERS = ::TeeLogger::FormatterPlaceholders.instance_methods
|
64
|
+
|
65
|
+
##
|
66
|
+
# Some format strings defined
|
67
|
+
|
68
|
+
# Format string most similar to the Ruby logger
|
69
|
+
FORMAT_LOGGER = "{short_severity}, [{logger_timestamp} \#{pid}] {severity} -- {logger}: {message}\n"
|
70
|
+
|
71
|
+
# Default format string
|
72
|
+
FORMAT_DEFAULT = "{short_severity}, [{iso8601_timestamp} \#{pid}] {logger}: {message}\n"
|
73
|
+
|
74
|
+
# Shorter format string
|
75
|
+
FORMAT_SHORT = "{short_severity}, [{iso8601_timestamp}] {message}\n"
|
76
|
+
|
77
|
+
# DJB format using Tai64N labels
|
78
|
+
FORMAT_DJB = "{tai64n_timestamp} {severity}: {message}\n"
|
79
|
+
|
80
|
+
##
|
81
|
+
# Implementation
|
82
|
+
def initialize(format = FORMAT_DEFAULT)
|
83
|
+
@format = format
|
84
|
+
end
|
85
|
+
|
86
|
+
def call(*args) # shortern *args; the same pattern as placeholders is used
|
87
|
+
# Formatting the message means replacing each placeholder with results
|
88
|
+
# from the placeholder function. We're caching results to save some time.
|
89
|
+
cache = {}
|
90
|
+
message = @format.dup
|
91
|
+
|
92
|
+
PLACEHOLDERS.each do |placeholder|
|
93
|
+
value = nil
|
94
|
+
begin
|
95
|
+
value = cache.fetch(placeholder,
|
96
|
+
::TeeLogger::FormatterPlaceholders.send(placeholder.to_sym, *args))
|
97
|
+
cache[placeholder] = value
|
98
|
+
rescue NoMethodError
|
99
|
+
raise "Invalid formatter placeholder used in format string: #{placeholder}"
|
100
|
+
end
|
101
|
+
|
102
|
+
message.gsub!(/{#{placeholder}}/, value)
|
103
|
+
end
|
104
|
+
|
105
|
+
return message
|
106
|
+
end
|
107
|
+
end # class Formatter
|
108
|
+
end # module TeeLogger
|
@@ -0,0 +1,41 @@
|
|
1
|
+
#
|
2
|
+
# TeeLogger
|
3
|
+
# https://github.com/spriteCloud/teelogger
|
4
|
+
#
|
5
|
+
# Copyright (c) 2014-2015 spriteCloud B.V. and other TeeLogger contributors.
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
module TeeLogger
|
9
|
+
module Levels
|
10
|
+
##
|
11
|
+
# Convert a log level to its string name
|
12
|
+
def string_level(level)
|
13
|
+
if level.is_a? String
|
14
|
+
return level
|
15
|
+
end
|
16
|
+
|
17
|
+
Logger::Severity.constants.each do |const|
|
18
|
+
if level == Logger.const_get(const)
|
19
|
+
return const
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
return nil
|
24
|
+
end
|
25
|
+
|
26
|
+
##
|
27
|
+
# Convert a string log level to its constant value
|
28
|
+
def convert_level(val)
|
29
|
+
if val.is_a? String
|
30
|
+
begin
|
31
|
+
val = Logger.const_get(val.upcase)
|
32
|
+
rescue NameError
|
33
|
+
raise "Invalid log level '#{val}' specified."
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
return val
|
38
|
+
end
|
39
|
+
|
40
|
+
end # module Levels
|
41
|
+
end # module TeeLogger
|
data/lib/teelogger/version.rb
CHANGED
data/teelogger.gemspec
CHANGED
@@ -22,4 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_development_dependency "rake"
|
23
23
|
spec.add_development_dependency "cucumber"
|
24
24
|
spec.add_development_dependency "minitest"
|
25
|
+
|
26
|
+
spec.add_dependency "tai64", "~> 0.0"
|
27
|
+
spec.add_dependency "require_all", "~> 1.3"
|
25
28
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: teelogger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jens Finkhaeuser
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-07-06 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -66,6 +66,34 @@ dependencies:
|
|
66
66
|
- - '>='
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: tai64
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ~>
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0.0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ~>
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0.0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: require_all
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ~>
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.3'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ~>
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.3'
|
69
97
|
description: Mini wrapper around Ruby Logger for logging to multiple destinations.
|
70
98
|
email:
|
71
99
|
- foss@spritecloud.com
|
@@ -79,10 +107,21 @@ files:
|
|
79
107
|
- LICENSE
|
80
108
|
- README.md
|
81
109
|
- Rakefile
|
110
|
+
- features/filter.feature
|
111
|
+
- features/formatter.feature
|
82
112
|
- features/logger.feature
|
83
|
-
- features/step_definitions/
|
113
|
+
- features/step_definitions/filter_steps.rb
|
114
|
+
- features/step_definitions/formatter_steps.rb
|
115
|
+
- features/step_definitions/logger_steps.rb
|
84
116
|
- features/support/env.rb
|
85
117
|
- lib/teelogger.rb
|
118
|
+
- lib/teelogger/extensions.rb
|
119
|
+
- lib/teelogger/filter.rb
|
120
|
+
- lib/teelogger/filters/assignment.rb
|
121
|
+
- lib/teelogger/filters/cli.rb
|
122
|
+
- lib/teelogger/filters/recursive.rb
|
123
|
+
- lib/teelogger/formatter.rb
|
124
|
+
- lib/teelogger/levels.rb
|
86
125
|
- lib/teelogger/version.rb
|
87
126
|
- teelogger.gemspec
|
88
127
|
homepage: https://github.com/spriteCloud/teelogger
|
@@ -110,6 +149,10 @@ signing_key:
|
|
110
149
|
specification_version: 4
|
111
150
|
summary: Mini wrapper around Ruby Logger for logging to multiple destinations.
|
112
151
|
test_files:
|
152
|
+
- features/filter.feature
|
153
|
+
- features/formatter.feature
|
113
154
|
- features/logger.feature
|
114
|
-
- features/step_definitions/
|
155
|
+
- features/step_definitions/filter_steps.rb
|
156
|
+
- features/step_definitions/formatter_steps.rb
|
157
|
+
- features/step_definitions/logger_steps.rb
|
115
158
|
- features/support/env.rb
|