tty-logger 0.1.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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