tty-logger 0.1.0 → 0.6.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.
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTY
4
+ class Logger
5
+ class DataFilter
6
+ FILTERED = "[FILTERED]"
7
+ DOT = "."
8
+
9
+ attr_reader :filters, :compiled_filters, :mask
10
+
11
+ # Create a data filter instance with filters.
12
+ #
13
+ # @example
14
+ # TTY::Logger::DataFilter.new(%w[foo], mask: "<SECRET>")
15
+ #
16
+ # @param [String] mask
17
+ # the mask to replace object with. Defaults to `"[FILTERED]"`
18
+ #
19
+ # @api private
20
+ def initialize(filters = [], mask: nil)
21
+ @mask = mask || FILTERED
22
+ @filters = filters
23
+ @compiled_filters = compile(filters)
24
+ end
25
+
26
+ # Filter object for keys matching provided filters.
27
+ #
28
+ # @example
29
+ # data_filter = TTY::Logger::DataFilter.new(%w[foo])
30
+ # data_filter.filter({"foo" => "bar"})
31
+ # # => {"foo" => "[FILTERED]"}
32
+ #
33
+ # @param [Object] obj
34
+ # the object to filter
35
+ #
36
+ # @api public
37
+ def filter(obj)
38
+ return obj if filters.empty?
39
+
40
+ obj.each_with_object({}) do |(k, v), acc|
41
+ acc[k] = filter_val(k, v)
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def compile(filters)
48
+ compiled = {
49
+ regexps: [],
50
+ nested_regexps: [],
51
+ blocks: []
52
+ }
53
+ strings = []
54
+ nested_strings = []
55
+
56
+ filters.each do |filter|
57
+ case filter
58
+ when Proc
59
+ compiled[:blocks] << filter
60
+ when Regexp
61
+ if filter.to_s.include?(DOT)
62
+ compiled[:nested_regexps] << filter
63
+ else
64
+ compiled[:regexps] << filter
65
+ end
66
+ else
67
+ exp = Regexp.escape(filter)
68
+ if exp.include?(DOT)
69
+ nested_strings << exp
70
+ else
71
+ strings << exp
72
+ end
73
+ end
74
+ end
75
+
76
+ if !strings.empty?
77
+ compiled[:regexps] << /^(#{strings.join("|")})$/
78
+ end
79
+
80
+ if !nested_strings.empty?
81
+ compiled[:nested_regexps] << /^(#{nested_strings.join("|")})$/
82
+ end
83
+
84
+ compiled
85
+ end
86
+
87
+ def filter_val(key, val, composite = [])
88
+ return mask if filtered?(key, composite)
89
+
90
+ case val
91
+ when Hash then filter_obj(key, val, composite << key)
92
+ when Array then filter_arr(key, val, composite)
93
+ else val
94
+ end
95
+ end
96
+
97
+ def filtered?(key, composite)
98
+ composite_key = composite + [key]
99
+ joined_key = composite_key.join(DOT)
100
+ @compiled_filters[:regexps].any? { |reg| !!reg.match(key.to_s) } ||
101
+ @compiled_filters[:nested_regexps].any? { |reg| !!reg.match(joined_key) } ||
102
+ @compiled_filters[:blocks].any? { |block| block.(composite_key.dup) }
103
+ end
104
+
105
+ def filter_obj(_key, obj, composite)
106
+ obj.each_with_object({}) do |(k, v), acc|
107
+ acc[k] = filter_val(k, v, composite)
108
+ end
109
+ end
110
+
111
+ def filter_arr(key, obj, composite)
112
+ obj.reduce([]) do |acc, v|
113
+ acc << filter_val(key, v, composite)
114
+ end
115
+ end
116
+ end # DataFilter
117
+ end # Logger
118
+ end # TTY
@@ -11,7 +11,7 @@ module TTY
11
11
 
12
12
  attr_reader :backtrace
13
13
 
14
- def initialize(message, fields, metadata)
14
+ def initialize(message, fields = {}, metadata = {})
15
15
  @message = message
16
16
  @fields = fields
17
17
  @metadata = metadata
@@ -29,7 +29,7 @@ module TTY
29
29
  @message.each do |msg|
30
30
  case msg
31
31
  when Exception
32
- @backtrace = msg.backtrace
32
+ @backtrace = msg.backtrace if msg.backtrace
33
33
  else
34
34
  msg
35
35
  end
@@ -20,7 +20,7 @@ module TTY
20
20
  def dump(obj, max_bytes: 2**12, max_depth: 3)
21
21
  bytesize = 0
22
22
 
23
- hash = obj.reduce({}) do |acc, (k, v)|
23
+ hash = obj.each_with_object({}) do |(k, v), acc|
24
24
  str = (k.to_json + v.to_json)
25
25
  items = acc.keys.size - 1
26
26
 
@@ -31,7 +31,6 @@ module TTY
31
31
  bytesize += str.bytesize
32
32
  acc[k] = dump_val(v, max_depth)
33
33
  end
34
- acc
35
34
  end
36
35
  ::JSON.generate(hash)
37
36
  end
@@ -50,13 +49,13 @@ module TTY
50
49
  def enc_obj(obj, depth)
51
50
  return ELLIPSIS if depth.zero?
52
51
 
53
- obj.reduce({}) { |acc, (k, v)| acc[k] = dump_val(v, depth); acc }
52
+ obj.each_with_object({}) { |(k, v), acc| acc[k] = dump_val(v, depth) }
54
53
  end
55
54
 
56
55
  def enc_arr(obj, depth)
57
56
  return ELLIPSIS if depth.zero?
58
57
 
59
- obj.reduce([]) { |acc, v| acc << dump_val(v, depth); acc }
58
+ obj.each_with_object([]) { |v, acc| acc << dump_val(v, depth) }
60
59
  end
61
60
  end # JSON
62
61
  end # Formatters
@@ -32,12 +32,13 @@ module TTY
32
32
  def dump(obj, max_bytes: 2**12, max_depth: 3)
33
33
  bytesize = 0
34
34
 
35
- line = obj.reduce([]) do |acc, (k, v)|
35
+ line = obj.each_with_object([]) do |(k, v), acc|
36
36
  str = "#{dump_key(k)}=#{dump_val(v, max_depth)}"
37
37
  items = acc.size - 1
38
38
 
39
39
  if bytesize + str.bytesize + items > max_bytes
40
- if bytesize + items + (acc[-1].bytesize - ELLIPSIS.bytesize) > max_bytes
40
+ if bytesize + items +
41
+ (acc[-1].bytesize - ELLIPSIS.bytesize) > max_bytes
41
42
  acc.pop
42
43
  end
43
44
  acc << ELLIPSIS
@@ -46,7 +47,6 @@ module TTY
46
47
  bytesize += str.bytesize
47
48
  acc << str
48
49
  end
49
- acc
50
50
  end
51
51
  line.join(SPACE)
52
52
  end
@@ -86,8 +86,8 @@ module TTY
86
86
  return LBRACE + ELLIPSIS + RBRACE if depth.zero?
87
87
 
88
88
  LBRACE +
89
- obj.map { |k, v| "#{dump_key(k)}=#{dump_val(v, depth)}" }.join(SPACE) +
90
- RBRACE
89
+ obj.map { |k, v| "#{dump_key(k)}=#{dump_val(v, depth)}" }
90
+ .join(SPACE) + RBRACE
91
91
  end
92
92
 
93
93
  def enc_arr(array, depth)
@@ -113,7 +113,8 @@ module TTY
113
113
  case str
114
114
  when SINGLE_QUOTE_REGEX
115
115
  str.inspect
116
- when ESCAPE_STR_REGEX, LITERAL_TRUE, LITERAL_FALSE, LITERAL_NIL, NUM_REGEX
116
+ when ESCAPE_STR_REGEX, LITERAL_TRUE, LITERAL_FALSE,
117
+ LITERAL_NIL, NUM_REGEX
117
118
  ESCAPE_DOUBLE_QUOTE + str.inspect[1..-2] + ESCAPE_DOUBLE_QUOTE
118
119
  else
119
120
  str
@@ -4,6 +4,19 @@ module TTY
4
4
  class Logger
5
5
  module Handlers
6
6
  module Base
7
+ # Change current log level for the duration of the block
8
+ #
9
+ # @param [String] tmp_level
10
+ # the temporary log level
11
+ #
12
+ # @api public
13
+ def log_at(tmp_level)
14
+ old_level, @level = level, tmp_level
15
+ yield
16
+ ensure
17
+ @level = old_level
18
+ end
19
+
7
20
  # Coerce formatter name into constant
8
21
  #
9
22
  # @api private
@@ -55,23 +55,44 @@ module TTY
55
55
  color: :cyan,
56
56
  levelpad: 0
57
57
  }
58
- }
58
+ }.freeze
59
59
 
60
+ TEXT_REGEXP = /([{}()\[\]])?(["']?)(\S+?)(["']?=)/.freeze
61
+ JSON_REGEXP = /\"([^,]+?)\"(?=:)/.freeze
62
+
63
+ COLOR_PATTERNS = {
64
+ text: [TEXT_REGEXP, ->(c) { "\\1\\2" + c.("\\3") + "\\4" }],
65
+ json: [JSON_REGEXP, ->(c) { "\"" + c.("\\1") + "\"" }]
66
+ }.freeze
67
+
68
+ # The output stream
69
+ # @api private
60
70
  attr_reader :output
61
71
 
72
+ # The configuration options
73
+ # @api private
62
74
  attr_reader :config
63
75
 
76
+ # The logging level
77
+ # @api private
64
78
  attr_reader :level
65
79
 
80
+ # The format for the message
81
+ # @api private
82
+ attr_reader :message_format
83
+
66
84
  def initialize(output: $stderr, formatter: nil, config: nil, level: nil,
67
- styles: {})
85
+ styles: {}, enable_color: nil, message_format: "%-25s")
68
86
  @output = Array[output].flatten
69
87
  @formatter = coerce_formatter(formatter || config.formatter).new
88
+ @formatter_name = @formatter.class.name.split("::").last.downcase
89
+ @color_pattern = COLOR_PATTERNS[@formatter_name.to_sym]
70
90
  @config = config
71
91
  @styles = styles
72
92
  @level = level || @config.level
73
93
  @mutex = Mutex.new
74
- @pastel = Pastel.new
94
+ @pastel = Pastel.new(enabled: enable_color)
95
+ @message_format = message_format
75
96
  end
76
97
 
77
98
  # Handle log event output in format
@@ -104,14 +125,16 @@ module TTY
104
125
  end
105
126
  end
106
127
  fmt << ARROW unless config.metadata.empty?
107
- fmt << color.(style[:symbol])
108
- fmt << color.(style[:label]) + (" " * style[:levelpad])
109
- fmt << "%-25s" % event.message.join(" ")
128
+ unless style.empty?
129
+ fmt << color.(style[:symbol])
130
+ fmt << color.(style[:label]) + (" " * style[:levelpad])
131
+ end
132
+ fmt << message_format % event.message.join(" ")
110
133
  unless event.fields.empty?
134
+ pattern, replacement = *@color_pattern
111
135
  fmt << @formatter.dump(event.fields, max_bytes: config.max_bytes,
112
- max_depth: config.max_depth).
113
- gsub(/(\S+)(?=\=)/, color.("\\1")).
114
- gsub(/\"([^,]+?)\"(?=:)/, "\"" + color.("\\1") + "\"")
136
+ max_depth: config.max_depth)
137
+ .gsub(pattern, replacement.(color))
115
138
  end
116
139
  unless event.backtrace.empty?
117
140
  fmt << "\n" + format_backtrace(event)
@@ -138,11 +161,11 @@ module TTY
138
161
  #
139
162
  # @api private
140
163
  def configure_styles(event)
141
- style = STYLES[event.metadata[:name].to_sym].dup
142
- (@styles[event.metadata[:name].to_sym] || {}).each do |k, v|
143
- style[k] = v
144
- end
145
- style
164
+ return {} if event.metadata[:name].nil?
165
+
166
+ STYLES.fetch(event.metadata[:name].to_sym, {})
167
+ .dup
168
+ .merge!(@styles[event.metadata[:name].to_sym] || {})
146
169
  end
147
170
 
148
171
  def configure_color(style)
@@ -10,41 +10,57 @@ module TTY
10
10
  FATAL_LEVEL = 4
11
11
 
12
12
  LEVEL_NAMES = {
13
- DEBUG_LEVEL => :debug,
14
- INFO_LEVEL => :info,
15
- WARN_LEVEL => :warn,
16
- ERROR_LEVEL => :error,
17
- FATAL_LEVEL => :fatal
18
- }
13
+ debug: DEBUG_LEVEL,
14
+ info: INFO_LEVEL,
15
+ warn: WARN_LEVEL,
16
+ error: ERROR_LEVEL,
17
+ fatal: FATAL_LEVEL
18
+ }.freeze
19
19
 
20
+ # All the default level names
21
+ #
22
+ # @return [Array[Symbol]]
23
+ #
24
+ # @api private
20
25
  def level_names
21
- [:debug, :info, :warn, :error, :fatal]
26
+ LEVEL_NAMES.keys
22
27
  end
23
28
 
29
+ # Convert level name to level number
30
+ #
31
+ # @param [Symbol] level
32
+ #
33
+ # @return [Integer]
34
+ #
24
35
  # @api private
25
36
  def level_to_number(level)
26
- case level.to_s.downcase
27
- when "debug" then DEBUG_LEVEL
28
- when "info" then INFO_LEVEL
29
- when "warn" then WARN_LEVEL
30
- when "error" then ERROR_LEVEL
31
- when "fatal" then FATAL_LEVEL
32
- else
33
- raise ArgumentError, "Invalid level #{level.inspect}"
34
- end
37
+ LEVEL_NAMES[level.to_s.downcase.to_sym] ||
38
+ raise(ArgumentError, "Invalid level #{level.inspect}")
35
39
  end
36
40
 
41
+ # Convert level number to level name
42
+ #
43
+ # @param [Integer] number
44
+ #
45
+ # @return [Symbol]
46
+ #
37
47
  # @api private
38
- def number_to_level(level)
39
- LEVEL_NAMES[level]
48
+ def number_to_level(number)
49
+ LEVEL_NAMES.key(number)
40
50
  end
41
51
 
52
+ # Compares two levels by name or number
53
+ #
54
+ # @return [Symbol]
55
+ # either :lt, :gt or :eq
56
+ #
42
57
  # @api private
43
58
  def compare_levels(left, right)
44
59
  left = left.is_a?(Integer) ? left : level_to_number(left)
45
60
  right = right.is_a?(Integer) ? right : level_to_number(right)
46
61
 
47
62
  return :eq if left == right
63
+
48
64
  left < right ? :lt : :gt
49
65
  end
50
66
  end # Levels
@@ -2,6 +2,6 @@
2
2
 
3
3
  module TTY
4
4
  class Logger
5
- VERSION = "0.1.0"
6
- end
7
- end
5
+ VERSION = "0.6.0"
6
+ end # Logger
7
+ end # TTY
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tty-logger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Murach
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-21 00:00:00.000000000 Z
11
+ date: 2020-12-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pastel
@@ -16,28 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: 0.7.0
19
+ version: '0.8'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: 0.7.0
27
- - !ruby/object:Gem::Dependency
28
- name: bundler
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '1.5'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '1.5'
26
+ version: '0.8'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: rake
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -56,36 +42,33 @@ dependencies:
56
42
  name: rspec
57
43
  requirement: !ruby/object:Gem::Requirement
58
44
  requirements:
59
- - - "~>"
45
+ - - ">="
60
46
  - !ruby/object:Gem::Version
61
47
  version: '3.0'
62
48
  type: :development
63
49
  prerelease: false
64
50
  version_requirements: !ruby/object:Gem::Requirement
65
51
  requirements:
66
- - - "~>"
52
+ - - ">="
67
53
  - !ruby/object:Gem::Version
68
54
  version: '3.0'
69
55
  description: Readable, structured and beautiful terminal logging
70
56
  email:
71
- - me@piotrmurach.com
57
+ - piotr@piotrmurach.com
72
58
  executables: []
73
59
  extensions: []
74
- extra_rdoc_files: []
60
+ extra_rdoc_files:
61
+ - README.md
62
+ - CHANGELOG.md
63
+ - LICENSE.txt
75
64
  files:
76
65
  - CHANGELOG.md
77
66
  - LICENSE.txt
78
67
  - README.md
79
- - Rakefile
80
- - examples/console.rb
81
- - examples/error.rb
82
- - examples/handler.rb
83
- - examples/output.rb
84
- - examples/override.rb
85
- - examples/stream.rb
86
68
  - lib/tty-logger.rb
87
69
  - lib/tty/logger.rb
88
70
  - lib/tty/logger/config.rb
71
+ - lib/tty/logger/data_filter.rb
89
72
  - lib/tty/logger/event.rb
90
73
  - lib/tty/logger/formatters/json.rb
91
74
  - lib/tty/logger/formatters/text.rb
@@ -95,27 +78,7 @@ files:
95
78
  - lib/tty/logger/handlers/stream.rb
96
79
  - lib/tty/logger/levels.rb
97
80
  - lib/tty/logger/version.rb
98
- - spec/spec_helper.rb
99
- - spec/unit/add_handler_spec.rb
100
- - spec/unit/config_spec.rb
101
- - spec/unit/event_spec.rb
102
- - spec/unit/exception_spec.rb
103
- - spec/unit/formatter_spec.rb
104
- - spec/unit/formatters/json_spec.rb
105
- - spec/unit/formatters/text_spec.rb
106
- - spec/unit/handler_spec.rb
107
- - spec/unit/handlers/custom_spec.rb
108
- - spec/unit/handlers/null_spec.rb
109
- - spec/unit/handlers/stream_spec.rb
110
- - spec/unit/levels_spec.rb
111
- - spec/unit/log_metadata_spec.rb
112
- - spec/unit/log_spec.rb
113
- - spec/unit/output_spec.rb
114
- - tasks/console.rake
115
- - tasks/coverage.rake
116
- - tasks/spec.rake
117
- - tty-logger.gemspec
118
- homepage: https://piotrmurach.github.io/tty
81
+ homepage: https://ttytoolkit.org
119
82
  licenses:
120
83
  - MIT
121
84
  metadata:
@@ -123,7 +86,7 @@ metadata:
123
86
  bug_tracker_uri: https://github.com/piotrmurach/tty-logger/issues
124
87
  changelog_uri: https://github.com/piotrmurach/tty-logger/blob/master/CHANGELOG.md
125
88
  documentation_uri: https://www.rubydoc.info/gems/tty-logger
126
- homepage_uri: https://piotrmurach.github.io/tty
89
+ homepage_uri: https://ttytoolkit.org
127
90
  source_code_uri: https://github.com/piotrmurach/tty-logger
128
91
  post_install_message:
129
92
  rdoc_options: []
@@ -140,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
140
103
  - !ruby/object:Gem::Version
141
104
  version: '0'
142
105
  requirements: []
143
- rubygems_version: 3.0.3
106
+ rubygems_version: 3.1.2
144
107
  signing_key:
145
108
  specification_version: 4
146
109
  summary: Readable, structured and beautiful terminal logging