logasm-jruby 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,4 @@
1
+ node_modules/
2
+ npm-debug.log
3
+ Gemfile.lock
4
+
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
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
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2
4
+ - 2.3
5
+ - 2.4
6
+ - jruby
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "https://rubygems.org"
2
+
3
+ group :test, :development do
4
+ gem 'pry'
5
+ gem 'rspec'
6
+ end
7
+
8
+ gemspec
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,7 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require "rspec/core/rake_task"
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task ci: :spec
7
+ task default: :spec
@@ -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
+ class NullLogger
3
+ def debug(*)
4
+ end
5
+
6
+ def info(*)
7
+ end
8
+
9
+ def warn(*)
10
+ end
11
+
12
+ def error(*)
13
+ end
14
+
15
+ def fatal(*)
16
+ end
17
+ end
18
+ 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