logasm-jruby 1.2.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 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