teelogger 0.4.1 → 0.5.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/.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
|