cogger 0.24.1 → 0.25.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,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 }