logasm-jruby 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/Gemfile +8 -0
- data/README.md +121 -0
- data/Rakefile +7 -0
- data/benchmark/whitelisting.rb +53 -0
- data/lib/logasm.rb +74 -0
- data/lib/logasm/adapters.rb +22 -0
- data/lib/logasm/adapters/stdout_adapter.rb +29 -0
- data/lib/logasm/adapters/stdout_json_adapter.rb +44 -0
- data/lib/logasm/null_logger.rb +18 -0
- data/lib/logasm/preprocessors.rb +18 -0
- data/lib/logasm/preprocessors/blacklist.rb +61 -0
- data/lib/logasm/preprocessors/json_pointer_trie.rb +40 -0
- data/lib/logasm/preprocessors/strategies/mask.rb +43 -0
- data/lib/logasm/preprocessors/strategies/prune.rb +44 -0
- data/lib/logasm/preprocessors/whitelist.rb +56 -0
- data/lib/logasm/utils.rb +91 -0
- data/logasm.gemspec +36 -0
- data/spec/adapters/stdout_adapter_spec.rb +48 -0
- data/spec/adapters/stdout_json_adapter_spec.rb +43 -0
- data/spec/logasm_spec.rb +142 -0
- data/spec/preprocessors/blacklist_spec.rb +131 -0
- data/spec/preprocessors/json_pointer_trie.rb +36 -0
- data/spec/preprocessors/whitelist_spec.rb +335 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/support/implement_interface.rb +11 -0
- data/spec/utils_spec.rb +84 -0
- metadata +168 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d979fabe79cb012160eae9358e7ebb15122ee58715d36c2d19d3a983413f1962
|
4
|
+
data.tar.gz: fd5391fc22692e4a82528bc43863a9ce4a93fa854e0826fcfb56371c0b6c178d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 575d5e5f7ea14be165321542d33561d8405be7b3b653f22c3a88e6be359a29fcf4916ce0c0fbe6a2324edd02e7c659a3f8d663ffef6f79cdbfedc0c356a63fba
|
7
|
+
data.tar.gz: a9b53672c3b49f420fe55327fdd1ece358580cb6787ae0bfddddcaa98cb6f076b0ab4d72d814b010f0a0494299214c1513ba5fa0bf8cadd6aec096febfa1873e
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
logasm
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.4.1
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
Logasm
|
2
|
+
================
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
|
6
|
+
### Creating a new Logasm logger in Ruby
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
Logasm.build(application_name, logger_config)
|
10
|
+
```
|
11
|
+
|
12
|
+
<b>logger_config</b> is a hash with logger types and their configuration.
|
13
|
+
|
14
|
+
#### Configuration
|
15
|
+
|
16
|
+
```
|
17
|
+
loggers:
|
18
|
+
stdout:
|
19
|
+
level: 'debug'
|
20
|
+
```
|
21
|
+
Supported log levels:
|
22
|
+
|
23
|
+
1. fatal
|
24
|
+
2. error
|
25
|
+
3. warn
|
26
|
+
4. info
|
27
|
+
5. debug
|
28
|
+
|
29
|
+
For example level: 'warn' will log everything with warn and above.
|
30
|
+
|
31
|
+
#### Examples
|
32
|
+
|
33
|
+
Creating a new stdout logger
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
require 'logasm'
|
37
|
+
|
38
|
+
logasm = Logasm.build('myApp', stdout: nil)
|
39
|
+
```
|
40
|
+
|
41
|
+
When no loggers are specified, it creates a stdout logger by default.
|
42
|
+
|
43
|
+
## Preprocessors
|
44
|
+
|
45
|
+
Preprocessors allow modification of log messages, prior to sending of the message to the configured logger(s).
|
46
|
+
|
47
|
+
### Blacklist
|
48
|
+
|
49
|
+
Excludes or masks defined fields of the passed hash object.
|
50
|
+
You can specify the name of the field and which action to take on it.
|
51
|
+
Nested hashes of any level are preprocessed as well.
|
52
|
+
|
53
|
+
Available actions:
|
54
|
+
|
55
|
+
* `prune` (default) - fully excludes the field and its value from the hash.
|
56
|
+
* `mask` - replaces every character from the original value with `*`.
|
57
|
+
In case of `array`, `hash` or `boolean` value is replaced with one `*`.
|
58
|
+
|
59
|
+
#### Configuration
|
60
|
+
|
61
|
+
```yaml
|
62
|
+
preprocessors:
|
63
|
+
blacklist:
|
64
|
+
fields:
|
65
|
+
- key: password
|
66
|
+
- key: phone
|
67
|
+
action: mask
|
68
|
+
```
|
69
|
+
|
70
|
+
#### Usage
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
logger = Logasm.build(application_name, logger_config, preprocessors)
|
74
|
+
|
75
|
+
input = {password: 'password', info: {phone: '+12055555555'}}
|
76
|
+
|
77
|
+
logger.debug("Received request", input)
|
78
|
+
```
|
79
|
+
|
80
|
+
Logger output:
|
81
|
+
|
82
|
+
```
|
83
|
+
Received request {"info":{"phone":"************"}}
|
84
|
+
```
|
85
|
+
|
86
|
+
### Whitelist
|
87
|
+
|
88
|
+
Prunes or masks all the fields except those whitelisted in the configuration using [JSON Pointer](https://tools.ietf.org/html/rfc6901).
|
89
|
+
Only simple values(`string`, `number`, `boolean`) can be whitelisted.
|
90
|
+
Whitelisting array and hash elements can be done using wildcard symbol `~`.
|
91
|
+
|
92
|
+
Available actions:
|
93
|
+
|
94
|
+
* `mask` (default) - replaces every character from the original value with `*`.
|
95
|
+
In case of `array`, `hash` or `boolean` value is replaced with one `*`.
|
96
|
+
* `prune` - fully excludes the field and its value from the hash.
|
97
|
+
|
98
|
+
#### Configuration
|
99
|
+
|
100
|
+
```yaml
|
101
|
+
preprocessors:
|
102
|
+
whitelist:
|
103
|
+
pointers: ['/info/phone', '/addresses/~/host']
|
104
|
+
action: prune
|
105
|
+
```
|
106
|
+
|
107
|
+
#### Usage
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
logger = Logasm.build(application_name, logger_config, preprocessors)
|
111
|
+
|
112
|
+
input = {password: 'password', info: {phone: '+12055555555'}, addresses: [{host: 'example.com', path: 'info'}]}
|
113
|
+
|
114
|
+
logger.debug("Received request", input)
|
115
|
+
```
|
116
|
+
|
117
|
+
Logger output:
|
118
|
+
|
119
|
+
```
|
120
|
+
Received request {password: "********", "info": {"phone": "+12055555555"}, "addresses": [{"host": "example.com","path": "****"}]}
|
121
|
+
```
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'bundler/setup'
|
2
|
+
require 'logasm/preprocessors/whitelist'
|
3
|
+
require 'benchmark/ips'
|
4
|
+
|
5
|
+
pointers = %w[
|
6
|
+
/scalar
|
7
|
+
/flat_hash/~
|
8
|
+
/nested_hash/~/deep_hash/~
|
9
|
+
/flat_array/~
|
10
|
+
/nested_array/~/deep_array/~
|
11
|
+
]
|
12
|
+
|
13
|
+
%w[prune mask].each do |action|
|
14
|
+
preprocessor = Logasm::Preprocessors::Whitelist.new(pointers: pointers, action: action)
|
15
|
+
|
16
|
+
Benchmark.ips do |x|
|
17
|
+
x.config(time: 5, warmup: 2)
|
18
|
+
|
19
|
+
x.report("Scalar value whitelisting (action=#{action})") do
|
20
|
+
preprocessor.process(scalar: 'value', bad_scalar: 'value', hash: {})
|
21
|
+
end
|
22
|
+
|
23
|
+
x.report("Flat hash whitelisting (action=#{action})") do
|
24
|
+
preprocessor.process(flat_hash: { scalar: 'value', array: [1, 2], hash: {} })
|
25
|
+
end
|
26
|
+
|
27
|
+
x.report("Nested hash whitelisting (action=#{action})") do
|
28
|
+
preprocessor.process(
|
29
|
+
nested_hash: {
|
30
|
+
next_level_hash: {
|
31
|
+
deep_hash: { scalar: 'value', array: [1, 2] }
|
32
|
+
},
|
33
|
+
next_level_hash2: {
|
34
|
+
deep_hash: { scalar: 'value', array: [1, 2] }
|
35
|
+
},
|
36
|
+
next_level_hash3: {
|
37
|
+
deep_hash: { scalar: 'value', array: [1, 2] }
|
38
|
+
}
|
39
|
+
}
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
x.report("Flat array whitelisting (action=#{action})") do
|
44
|
+
preprocessor.process(
|
45
|
+
nested_array: [
|
46
|
+
{ deep_array: [1, 2, 3] },
|
47
|
+
{ deep_array: [1, 2, 3] },
|
48
|
+
{ deep_array: [1, 2, 3] }
|
49
|
+
]
|
50
|
+
)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
data/lib/logasm.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require_relative 'logasm/adapters'
|
3
|
+
require_relative 'logasm/utils'
|
4
|
+
require_relative 'logasm/null_logger'
|
5
|
+
require_relative 'logasm/preprocessors'
|
6
|
+
|
7
|
+
LOG_LEVEL_QUERY_METHODS = [:debug?, :info?, :warn?, :error?, :fatal?]
|
8
|
+
|
9
|
+
class Logasm
|
10
|
+
def self.build(service_name, loggers_config, preprocessors_config = {})
|
11
|
+
loggers_config ||= {stdout: nil}
|
12
|
+
preprocessors = preprocessors_config.map do |type, arguments|
|
13
|
+
Preprocessors.get(type.to_s, arguments || {})
|
14
|
+
end
|
15
|
+
adapters = loggers_config.map do |type, arguments|
|
16
|
+
Adapters.get(type.to_s, service_name, arguments || {})
|
17
|
+
end
|
18
|
+
new(adapters, preprocessors)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(adapters, preprocessors)
|
22
|
+
@adapters = adapters
|
23
|
+
@preprocessors = preprocessors
|
24
|
+
end
|
25
|
+
|
26
|
+
def debug(*args, &block)
|
27
|
+
log :debug, *args, &block
|
28
|
+
end
|
29
|
+
|
30
|
+
def info(*args, &block)
|
31
|
+
log :info, *args, &block
|
32
|
+
end
|
33
|
+
|
34
|
+
def warn(*args, &block)
|
35
|
+
log :warn, *args, &block
|
36
|
+
end
|
37
|
+
|
38
|
+
def error(*args, &block)
|
39
|
+
log :error, *args, &block
|
40
|
+
end
|
41
|
+
|
42
|
+
def fatal(*args, &block)
|
43
|
+
log :fatal, *args, &block
|
44
|
+
end
|
45
|
+
|
46
|
+
LOG_LEVEL_QUERY_METHODS.each do |method|
|
47
|
+
define_method(method) do
|
48
|
+
@adapters.any? {|adapter| adapter.public_send(method) }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def log(level, *args, &block)
|
55
|
+
data = parse_log_data(*args, &block)
|
56
|
+
processed_data = preprocess(data)
|
57
|
+
|
58
|
+
@adapters.each do |adapter|
|
59
|
+
adapter.log(level, processed_data)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def preprocess(data)
|
64
|
+
@preprocessors.inject(data) do |data_to_process, preprocessor|
|
65
|
+
preprocessor.process(data_to_process)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def parse_log_data(message = nil, metadata = {}, &block)
|
70
|
+
return message if message.is_a?(Hash)
|
71
|
+
|
72
|
+
(metadata || {}).merge(message: block ? block.call : message)
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Logasm
|
2
|
+
module Adapters
|
3
|
+
LOG_LEVELS = %w(debug info warn error fatal unknown).freeze
|
4
|
+
|
5
|
+
def self.get(type, service_name, arguments)
|
6
|
+
adapter =
|
7
|
+
if type == 'stdout'
|
8
|
+
if arguments.fetch(:json, false)
|
9
|
+
require_relative 'adapters/stdout_json_adapter'
|
10
|
+
StdoutJsonAdapter
|
11
|
+
else
|
12
|
+
require_relative 'adapters/stdout_adapter'
|
13
|
+
StdoutAdapter
|
14
|
+
end
|
15
|
+
else
|
16
|
+
raise "Unsupported logger: #{type}"
|
17
|
+
end
|
18
|
+
level = LOG_LEVELS.index(arguments.fetch(:level, 'debug'))
|
19
|
+
adapter.new(level, service_name, arguments)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
class Logasm
|
4
|
+
module Adapters
|
5
|
+
class StdoutAdapter
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
attr_reader :logger
|
9
|
+
|
10
|
+
def_delegators :@logger, :debug?, :info?, :warn?, :error?, :fatal?
|
11
|
+
|
12
|
+
def initialize(level, *)
|
13
|
+
@logger = Logger.new(STDOUT)
|
14
|
+
@logger.level = level
|
15
|
+
end
|
16
|
+
|
17
|
+
def log(level, metadata = {})
|
18
|
+
message = metadata[:message]
|
19
|
+
data = metadata.select { |key, value| key != :message }
|
20
|
+
log_data = [
|
21
|
+
message,
|
22
|
+
data.empty? ? nil : Utils.generate_json(data)
|
23
|
+
].compact.join(' ')
|
24
|
+
|
25
|
+
@logger.public_send level, log_data
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class Logasm
|
2
|
+
module Adapters
|
3
|
+
class StdoutJsonAdapter
|
4
|
+
def initialize(level, service_name, *)
|
5
|
+
@level = level
|
6
|
+
@service_name = service_name
|
7
|
+
@application_name = Utils.application_name(service_name)
|
8
|
+
end
|
9
|
+
|
10
|
+
def log(level, metadata = {})
|
11
|
+
if meets_threshold?(level)
|
12
|
+
message = Utils.build_event(metadata, level, @application_name)
|
13
|
+
STDOUT.puts(Utils.generate_json(message))
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def debug?
|
18
|
+
meets_threshold?(:debug)
|
19
|
+
end
|
20
|
+
|
21
|
+
def info?
|
22
|
+
meets_threshold?(:info)
|
23
|
+
end
|
24
|
+
|
25
|
+
def warn?
|
26
|
+
meets_threshold?(:warn)
|
27
|
+
end
|
28
|
+
|
29
|
+
def error?
|
30
|
+
meets_threshold?(:error)
|
31
|
+
end
|
32
|
+
|
33
|
+
def fatal?
|
34
|
+
meets_threshold?(:fatal)
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def meets_threshold?(level)
|
40
|
+
LOG_LEVELS.index(level.to_s) >= @level
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Logasm
|
2
|
+
module Preprocessors
|
3
|
+
def self.get(type, arguments)
|
4
|
+
preprocessor =
|
5
|
+
case type.to_s
|
6
|
+
when 'blacklist'
|
7
|
+
require_relative 'preprocessors/blacklist'
|
8
|
+
Preprocessors::Blacklist
|
9
|
+
when 'whitelist'
|
10
|
+
require_relative 'preprocessors/whitelist'
|
11
|
+
Preprocessors::Whitelist
|
12
|
+
else
|
13
|
+
raise "Unknown preprocessor: #{type}"
|
14
|
+
end
|
15
|
+
preprocessor.new(arguments)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class Logasm
|
2
|
+
module Preprocessors
|
3
|
+
class Blacklist
|
4
|
+
|
5
|
+
DEFAULT_ACTION = 'prune'
|
6
|
+
MASK_SYMBOL = '*'
|
7
|
+
MASKED_VALUE = MASK_SYMBOL * 5
|
8
|
+
|
9
|
+
class UnsupportedActionException < Exception
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(config = {})
|
13
|
+
@fields_to_process = config[:fields].inject({}) do |mem, field|
|
14
|
+
key = field.delete(:key)
|
15
|
+
options = {action: DEFAULT_ACTION}.merge(field)
|
16
|
+
validate_action_supported(options[:action])
|
17
|
+
mem.merge(key => options)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def process(data)
|
22
|
+
if data.is_a? Hash
|
23
|
+
data.inject({}) do |mem, (key, val)|
|
24
|
+
if (field = @fields_to_process[key.to_s])
|
25
|
+
self.send(action_method(field[:action]), mem, key, val)
|
26
|
+
else
|
27
|
+
mem.merge(key => process(val))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
elsif data.is_a? Array
|
31
|
+
data.inject([]) do |mem, val|
|
32
|
+
mem + [process(val)]
|
33
|
+
end
|
34
|
+
else
|
35
|
+
data
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def action_method(action)
|
42
|
+
"#{action}_field"
|
43
|
+
end
|
44
|
+
|
45
|
+
def validate_action_supported(action)
|
46
|
+
unless self.respond_to?(action_method(action).to_sym, true)
|
47
|
+
raise UnsupportedActionException.new("Action: #{action} is not supported")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def mask_field(data, key, val)
|
52
|
+
data.merge(key => MASKED_VALUE)
|
53
|
+
end
|
54
|
+
|
55
|
+
def prune_field(data, *)
|
56
|
+
data
|
57
|
+
end
|
58
|
+
alias_method :exclude_field, :prune_field
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|