cogger 0.24.1 → 0.25.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cogger
4
+ module Formatters
5
+ # An abstract class with common/shared functionality.
6
+ class Abstract
7
+ SANITIZERS = {
8
+ datetime: Sanitizers::DateTime,
9
+ escape: Sanitizers::Escape.new,
10
+ filter: Sanitizers::Filter
11
+ }.freeze
12
+
13
+ def initialize sanitizers: SANITIZERS
14
+ @sanitizers = sanitizers
15
+ end
16
+
17
+ def call(*)
18
+ fail NoMethodError,
19
+ "`#{self.class}##{__method__} #{method(__method__).parameters}` must be implemented."
20
+ end
21
+
22
+ protected
23
+
24
+ def sanitize entry, message
25
+ entry.public_send(message).tap do |attributes|
26
+ function = -> value { sanitize_datetime value, format: entry.datetime_format }
27
+
28
+ filter attributes
29
+ attributes.transform_values!(&function)
30
+ end
31
+ end
32
+
33
+ def escape(...) = sanitizers.fetch(__method__).call(...)
34
+
35
+ def filter(...) = sanitizers.fetch(__method__).call(...)
36
+
37
+ def sanitize_datetime(...) = sanitizers.fetch(:datetime).call(...)
38
+
39
+ private
40
+
41
+ attr_reader :sanitizers
42
+ end
43
+ end
44
+ end
@@ -3,22 +3,27 @@
3
3
  module Cogger
4
4
  module Formatters
5
5
  # Formats by color.
6
- class Color
6
+ class Color < Abstract
7
7
  TEMPLATE = "<dynamic>[%<id>s]</dynamic> %<message:dynamic>s"
8
8
 
9
- def initialize template = TEMPLATE, processor: Processors::Color.new
9
+ def initialize template = TEMPLATE, parser: Parsers::Combined.new
10
+ super()
10
11
  @template = template
11
- @processor = processor
12
+ @parser = parser
12
13
  end
13
14
 
14
15
  def call(*input)
15
- updated_template, attributes = processor.call(template, *input)
16
- "#{format(updated_template, **attributes).tap(&:strip!)}\n"
16
+ *, entry = input
17
+ attributes = sanitize entry, :tagged
18
+
19
+ "#{format(parse(attributes[:level]), attributes).tap(&:strip!)}\n"
17
20
  end
18
21
 
19
22
  private
20
23
 
21
- attr_reader :template, :processor
24
+ attr_reader :template, :parser
25
+
26
+ def parse(level) = parser.call template, level
22
27
  end
23
28
  end
24
29
  end
@@ -3,7 +3,7 @@
3
3
  module Cogger
4
4
  module Formatters
5
5
  # Formats fatal crashes.
6
- class Crash
6
+ class Crash < Abstract
7
7
  TEMPLATE = <<~CONTENT
8
8
  <dynamic>[%<id>s] [%<level>s] [%<at>s] Crash!
9
9
  %<message>s
@@ -11,20 +11,25 @@ module Cogger
11
11
  %<backtrace>s</dynamic>
12
12
  CONTENT
13
13
 
14
- def initialize template = TEMPLATE, processor: Processors::Color.new
14
+ def initialize template = TEMPLATE, parser: Parsers::Combined.new
15
+ super()
15
16
  @template = template
16
- @processor = processor
17
+ @parser = parser
17
18
  end
18
19
 
19
20
  def call(*input)
20
- updated_template, attributes = processor.call(template, *input)
21
+ *, entry = input
22
+ attributes = sanitize entry, :tagged
21
23
  attributes[:backtrace] = %( #{attributes[:backtrace].join "\n "})
22
- "#{format(updated_template, **attributes)}\n"
24
+
25
+ "#{format(parse(attributes[:level]), attributes)}\n"
23
26
  end
24
27
 
25
28
  private
26
29
 
27
- attr_reader :template, :processor
30
+ attr_reader :template, :parser
31
+
32
+ def parse(level) = parser.call template, level
28
33
  end
29
34
  end
30
35
  end
@@ -6,33 +6,35 @@ require "json"
6
6
  module Cogger
7
7
  module Formatters
8
8
  # Formats as JSON output.
9
- class JSON
9
+ class JSON < Abstract
10
10
  TEMPLATE = nil
11
11
 
12
- def initialize template = TEMPLATE,
13
- parser: Parsers::KeyExtractor.new,
14
- sanitizer: Kit::Sanitizer
15
- @positions = template ? parser.call(template) : Core::EMPTY_ARRAY
16
- @sanitizer = sanitizer
12
+ def initialize template = TEMPLATE, parser: Parsers::KeyExtractor.new
13
+ super()
14
+ @template = template
15
+ @parser = parser
17
16
  end
18
17
 
19
18
  def call(*input)
20
- attributes = sanitizer.call(*input).tagged_attributes.tap(&:compact!)
21
- format_date_time attributes
19
+ *, entry = input
20
+ attributes = sanitize(entry, :tagged_attributes).tap(&:compact!)
22
21
 
23
- return "#{attributes.to_json}\n" if positions.empty?
24
-
25
- "#{attributes.slice(*positions).merge!(attributes.except(*positions)).to_json}\n"
22
+ %(#{reorder(attributes).to_json}\n)
26
23
  end
27
24
 
28
25
  private
29
26
 
30
- attr_reader :positions, :sanitizer
27
+ attr_reader :template, :parser
28
+
29
+ def reorder attributes
30
+ positions = positions_for template
31
31
 
32
- # :reek:UtilityFunction
33
- def format_date_time attributes
34
- attributes[:at] = attributes[:at].utc.strftime "%Y-%m-%dT%H:%M:%S.%L%:z"
32
+ return attributes if positions.empty?
33
+
34
+ attributes.slice(*positions).merge!(attributes.except(*positions))
35
35
  end
36
+
37
+ def positions_for(template) = template ? parser.call(template) : Core::EMPTY_ARRAY
36
38
  end
37
39
  end
38
40
  end
@@ -3,11 +3,13 @@
3
3
  module Cogger
4
4
  module Formatters
5
5
  module Parsers
6
- # An abstrct class with default functionality.
6
+ # An abstract class with common functionality.
7
7
  class Abstract
8
- def initialize registry: Cogger, colorizer: Kit::Colorizer, expressor: Regexp
8
+ TRANSFORMERS = {color: Transformers::Color.new, emoji: Transformers::Emoji.new}.freeze
9
+
10
+ def initialize registry: Cogger, transformers: TRANSFORMERS, expressor: Regexp
9
11
  @registry = registry
10
- @colorizer = colorizer
12
+ @transformers = transformers
11
13
  @expressor = expressor
12
14
  end
13
15
 
@@ -18,7 +20,11 @@ module Cogger
18
20
 
19
21
  protected
20
22
 
21
- attr_reader :registry, :colorizer, :expressor
23
+ attr_reader :registry, :transformers, :expressor
24
+
25
+ def transform_color(...) = transformers.fetch(:color).call(...)
26
+
27
+ def transform_emoji(...) = transformers.fetch(:emoji).call(...)
22
28
  end
23
29
  end
24
30
  end
@@ -5,14 +5,14 @@ module Cogger
5
5
  module Parsers
6
6
  # Parses template literals, emojis, and keys for specific and dynamic colors.
7
7
  class Combined
8
- STEPS = [Element.new, Emoji.new, Specific.new].freeze
8
+ STEPS = [Element.new, Emoji.new, Key.new].freeze # Order matters.
9
9
 
10
10
  def initialize steps: STEPS
11
11
  @steps = steps
12
12
  end
13
13
 
14
- def call(template, **attributes)
15
- steps.reduce(template.dup) { |modification, step| step.call modification, **attributes }
14
+ def call template, level
15
+ steps.reduce(template.dup) { |modification, step| step.call modification, level }
16
16
  end
17
17
 
18
18
  private
@@ -6,13 +6,13 @@ module Cogger
6
6
  # Parses template elements for specific and dynamic colors.
7
7
  class Element < Abstract
8
8
  PATTERN = %r(
9
- < # Tag open start.
10
- (?<name>\w+) # Tag open name.
11
- > # Tag open end.
12
- (?<content>.+?) # Content.
13
- </ # Tag close start.
14
- \w+ # Tag close.
15
- > # Tag close end.
9
+ < # Tag open start.
10
+ (?<directive>\w+) # Tag open name.
11
+ > # Tag open end.
12
+ (?<content>.+?) # Content.
13
+ </ # Tag close start.
14
+ \w+ # Tag close.
15
+ > # Tag close end.
16
16
  )mx
17
17
 
18
18
  def initialize pattern: PATTERN
@@ -20,19 +20,21 @@ module Cogger
20
20
  @pattern = pattern
21
21
  end
22
22
 
23
- def call(template, **)
24
- template.gsub! pattern do
25
- captures = expressor.last_match.named_captures
26
- color = colorizer.call(captures["name"], **)
27
- registry.color[captures["content"], color]
28
- end
29
-
23
+ def call template, level
24
+ mutate template, level
30
25
  template
31
26
  end
32
27
 
33
28
  private
34
29
 
35
30
  attr_reader :pattern
31
+
32
+ def mutate template, level
33
+ template.gsub! pattern do
34
+ captures = expressor.last_match.named_captures
35
+ transform_color captures["content"], captures["directive"], level
36
+ end
37
+ end
36
38
  end
37
39
  end
38
40
  end
@@ -6,11 +6,11 @@ module Cogger
6
6
  # Parses template emojis for specific and dynamic colors.
7
7
  class Emoji < Abstract
8
8
  PATTERN = /
9
- %< # Start.
10
- emoji # Name.
11
- : # Delimiter.
12
- (?<color>\w+) # Color.
13
- >s # End.
9
+ %< # Start.
10
+ (?<key>emoji) # Key.
11
+ : # Delimiter.
12
+ (?<directive>\w+) # Directive.
13
+ >s # End.
14
14
  /x
15
15
 
16
16
  def initialize pattern: PATTERN
@@ -18,20 +18,21 @@ module Cogger
18
18
  @pattern = pattern
19
19
  end
20
20
 
21
- def call(template, **)
22
- template.gsub! pattern do
23
- captures = expressor.last_match.named_captures
24
- color = colorizer.call(captures["color"], **)
25
-
26
- registry.get_emoji color
27
- end
28
-
21
+ def call template, level
22
+ mutate template, level
29
23
  template
30
24
  end
31
25
 
32
26
  private
33
27
 
34
28
  attr_reader :pattern
29
+
30
+ def mutate template, level
31
+ template.gsub! pattern do
32
+ captures = expressor.last_match.named_captures
33
+ transform_emoji captures["key"], captures["directive"], level
34
+ end
35
+ end
35
36
  end
36
37
  end
37
38
  end
@@ -5,8 +5,8 @@ require "core"
5
5
  module Cogger
6
6
  module Formatters
7
7
  module Parsers
8
- # Parses template for specific and dynamic string format specifiers.
9
- class Specific < Abstract
8
+ # Parses template for specific and dynamic keys (i.e. string format specifiers).
9
+ class Key < Abstract
10
10
  PATTERN = /
11
11
  % # Start.
12
12
  (?<flag>[\s#+-0*])? # Optional flag.
@@ -14,9 +14,9 @@ module Cogger
14
14
  \.? # Optional precision delimiter.
15
15
  (?<precision>\d+)? # Optional precision value.
16
16
  < # Reference start.
17
- (?<name>\w+) # Name.
17
+ (?<key>\w+) # Key.
18
18
  : # Delimiter.
19
- (?<color>\w+) # Color.
19
+ (?<directive>\w+) # Directive.
20
20
  > # Reference end.
21
21
  (?<specifier>[ABEGXabcdefgiopsux]) # Specifier.
22
22
  /x
@@ -26,23 +26,24 @@ module Cogger
26
26
  @pattern = pattern
27
27
  end
28
28
 
29
- # :reek:TooManyStatements
30
- def call(template, **)
31
- template.gsub! pattern do |match|
32
- captures = expressor.last_match.named_captures
33
- original_color = captures["color"]
34
- color = colorizer.call(original_color, **)
35
-
36
- match.sub! ":#{original_color}", Core::EMPTY_STRING
37
- registry.color[match, color]
38
- end
39
-
29
+ def call template, level
30
+ mutate template, level
40
31
  template
41
32
  end
42
33
 
43
34
  private
44
35
 
45
36
  attr_reader :pattern
37
+
38
+ def mutate template, level
39
+ template.gsub! pattern do |match|
40
+ captures = expressor.last_match.named_captures
41
+ directive = captures["directive"]
42
+ match.sub! ":#{directive}", Core::EMPTY_STRING
43
+
44
+ transform_color match, directive, level
45
+ end
46
+ end
46
47
  end
47
48
  end
48
49
  end
@@ -10,7 +10,7 @@ module Cogger
10
10
  ? # Flag, width, or precision.
11
11
  < # Reference start.
12
12
  (?<name>\w+) # Name.
13
- (?::[\w]+)? # Optional delimiter and or color.
13
+ (?::[\w]+)? # Optional delimiter and directive.
14
14
  > # Reference end.
15
15
  ? # Specifier.
16
16
  /x
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "core"
4
+
5
+ module Cogger
6
+ module Formatters
7
+ # Formats as key=value output.
8
+ class Property < Abstract
9
+ TEMPLATE = nil
10
+
11
+ def initialize template = TEMPLATE, parser: Parsers::KeyExtractor.new
12
+ super()
13
+ @template = template
14
+ @parser = parser
15
+ end
16
+
17
+ def call(*input)
18
+ *, entry = input
19
+ attributes = sanitize(entry, :tagged_attributes).tap(&:compact!)
20
+
21
+ concat(attributes).chop! << "\n"
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :template, :parser
27
+
28
+ def concat attributes
29
+ reorder(attributes).each.with_object(+"") do |(key, value), line|
30
+ line << key.to_s << "=" << escape(value) << " "
31
+ end
32
+ end
33
+
34
+ def reorder attributes
35
+ positions = positions_for template
36
+
37
+ return attributes if positions.empty?
38
+
39
+ attributes.slice(*positions).merge!(attributes.except(*positions))
40
+ end
41
+
42
+ def positions_for(template) = template ? parser.call(template) : Core::EMPTY_ARRAY
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "date"
4
+
5
+ module Cogger
6
+ module Formatters
7
+ module Sanitizers
8
+ # Sanitizes/formats date/time value.
9
+ DateTime = lambda do |value, format: Cogger::DATETIME_FORMAT|
10
+ return value unless value.is_a?(::Time) || value.is_a?(Date) || value.is_a?(::DateTime)
11
+
12
+ value.strftime format
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cogger
4
+ module Formatters
5
+ module Sanitizers
6
+ # Sanitizes value as fully quoted string for emojis, spaces, and control characters.
7
+ class Escape
8
+ PATTERN = /
9
+ \A # Search string start.
10
+ .* # Match zero or more characters.
11
+ ( # Conditional start.
12
+ (?!\p{Number}) # Look ahead and ignore unicode numbers.
13
+ \p{Emoji} # Match unicode emoji only.
14
+ | # Or.
15
+ [[:space:]] # Match spaces, tabs, and new lines.
16
+ | # Or.
17
+ [[:cntrl:]] # Match control characters.
18
+ ) # Conditional end.
19
+ .* # Match zero or more characters.
20
+ \z # Search string end.
21
+ /xu
22
+
23
+ def initialize pattern: PATTERN
24
+ @pattern = pattern
25
+ end
26
+
27
+ def call value
28
+ return dump value unless value.is_a? Array
29
+
30
+ value.reduce(+"") { |text, item| text << dump(item) << ", " }
31
+ .then { |text| %([#{text.delete_suffix ", "}]).dump }
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :pattern
37
+
38
+ def dump(value) = value.to_s.gsub(pattern, &:dump)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cogger
4
+ module Formatters
5
+ module Sanitizers
6
+ # Sanitizes/removes sensitive values.
7
+ Filter = lambda do |attributes, filters: Cogger.filters|
8
+ filters.each { |key| attributes[key] = "[FILTERED]" if attributes.key? key }
9
+ attributes
10
+ end
11
+ end
12
+ end
13
+ end
@@ -2,20 +2,25 @@
2
2
 
3
3
  module Cogger
4
4
  module Formatters
5
- # Formats simple templates that require no additional processing.
6
- class Simple
5
+ # Formats simple templates that require minimal processing.
6
+ class Simple < Abstract
7
7
  TEMPLATE = "[%<id>s] %<message>s"
8
8
 
9
- def initialize template = TEMPLATE, sanitizer: Kit::Sanitizer
9
+ def initialize template = TEMPLATE
10
+ super()
10
11
  @template = template
11
- @sanitizer = sanitizer
12
12
  end
13
13
 
14
- def call(*input) = "#{format(template, sanitizer.call(*input).tagged).tap(&:strip!)}\n"
14
+ def call(*input)
15
+ *, entry = input
16
+ attributes = sanitize entry, :tagged
17
+
18
+ "#{format(template, attributes).tap(&:strip!)}\n"
19
+ end
15
20
 
16
21
  private
17
22
 
18
- attr_reader :template, :sanitizer
23
+ attr_reader :template, :processor
19
24
  end
20
25
  end
21
26
  end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cogger
4
+ module Formatters
5
+ module Transformers
6
+ # Transforms target into colorized string.
7
+ class Color
8
+ def initialize emoji: Emoji::KEY, key_transformer: Key, registry: Cogger
9
+ @emoji = emoji
10
+ @key_transformer = key_transformer
11
+ @registry = registry
12
+ end
13
+
14
+ def call target, directive, level
15
+ return target if !target.is_a?(String) || target == emoji
16
+
17
+ key = key_transformer.call directive, level
18
+
19
+ return client.encode target, key if aliases.key?(key) || defaults.key?(key)
20
+
21
+ target
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :emoji, :key_transformer, :registry
27
+
28
+ def aliases = registry.aliases
29
+
30
+ def defaults = client.defaults
31
+
32
+ def client = registry.color
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cogger
4
+ module Formatters
5
+ module Transformers
6
+ # Transforms target into emoji.
7
+ class Emoji
8
+ KEY = "emoji"
9
+
10
+ def initialize key = KEY, key_transformer: Key, registry: Cogger
11
+ @key = key
12
+ @key_transformer = key_transformer
13
+ @registry = registry
14
+ end
15
+
16
+ def call target, directive, level
17
+ return target unless target == key
18
+
19
+ key = key_transformer.call directive, level
20
+
21
+ registry.aliases.key?(key) ? registry.get_emoji(key) : target
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :key, :key_transformer, :registry
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cogger
4
+ module Formatters
5
+ module Transformers
6
+ # Transforms directive, based on log level, into a key for color or emoji lookup.
7
+ Key = -> directive, level { (directive == "dynamic" ? level.downcase : directive).to_sym }
8
+ end
9
+ end
10
+ end
data/lib/cogger/hub.rb CHANGED
@@ -100,12 +100,14 @@ module Cogger
100
100
  crash message, error
101
101
  end
102
102
 
103
+ # rubocop:todo Metrics/MethodLength
103
104
  def dispatch(level, message, **payload, &)
104
105
  entry = configuration.entry.for(
105
106
  message,
106
107
  id: configuration.id,
107
108
  level:,
108
109
  tags: configuration.entag(payload.delete(:tags)),
110
+ datetime_format: configuration.datetime_format,
109
111
  **payload,
110
112
  &
111
113
  )
@@ -113,6 +115,7 @@ module Cogger
113
115
  mutex.synchronize { streams.each { |logger| logger.public_send level, entry } }
114
116
  true
115
117
  end
118
+ # rubocop:enable Metrics/MethodLength
116
119
 
117
120
  def crash message, error
118
121
  configuration.with(id: :cogger, io: $stdout, formatter: Formatters::Crash.new)
@@ -2,7 +2,7 @@
2
2
 
3
3
  require "pathname"
4
4
 
5
- # Provides a function for computing the default program name based on current file.
5
+ # Computes default program name based on current file name.
6
6
  module Cogger
7
7
  Program = lambda do |name = $PROGRAM_NAME|
8
8
  Pathname(name).then { |path| path.basename(path.extname).to_s }