cogger 0.24.1 → 0.26.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
+ NEW_LINE = "\n"
8
+
9
+ SANITIZERS = {
10
+ escape: Sanitizers::Escape.new,
11
+ filter: Sanitizers::Filter,
12
+ format_time: Sanitizers::FormatTime
13
+ }.freeze
14
+
15
+ def initialize sanitizers: SANITIZERS
16
+ @sanitizers = sanitizers
17
+ end
18
+
19
+ def call(*)
20
+ fail NoMethodError,
21
+ "`#{self.class}##{__method__} #{method(__method__).parameters}` must be implemented."
22
+ end
23
+
24
+ protected
25
+
26
+ def sanitize entry, message
27
+ entry.public_send(message).tap do |attributes|
28
+ filter attributes
29
+ attributes.transform_values! { |value| format_time value, format: entry.datetime_format }
30
+ end
31
+ end
32
+
33
+ def escape(...) = sanitizers.fetch(__method__).call(...)
34
+
35
+ def filter(...) = sanitizers.fetch(__method__).call(...)
36
+
37
+ def format_time(...) = sanitizers.fetch(__method__).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!) << NEW_LINE
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) << NEW_LINE
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,25 @@ 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::Position.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
+ parser.call(template, attributes).to_json << NEW_LINE
26
23
  end
27
24
 
28
25
  private
29
26
 
30
- attr_reader :positions, :sanitizer
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"
35
- end
27
+ attr_reader :template, :parser
36
28
  end
37
29
  end
38
30
  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,18 +5,17 @@ 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.
13
+ \.? # Optional precision.
13
14
  (?<width>\d+)? # Optional width.
14
- \.? # Optional precision delimiter.
15
- (?<precision>\d+)? # Optional precision value.
16
15
  < # Reference start.
17
- (?<name>\w+) # Name.
16
+ (?<key>\w+) # Key.
18
17
  : # Delimiter.
19
- (?<color>\w+) # Color.
18
+ (?<directive>\w+) # Directive.
20
19
  > # Reference end.
21
20
  (?<specifier>[ABEGXabcdefgiopsux]) # Specifier.
22
21
  /x
@@ -26,23 +25,24 @@ module Cogger
26
25
  @pattern = pattern
27
26
  end
28
27
 
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
-
28
+ def call template, level
29
+ mutate template, level
40
30
  template
41
31
  end
42
32
 
43
33
  private
44
34
 
45
35
  attr_reader :pattern
36
+
37
+ def mutate template, level
38
+ template.gsub! pattern do |match|
39
+ captures = expressor.last_match.named_captures
40
+ directive = captures["directive"]
41
+ match.sub! ":#{directive}", Core::EMPTY_STRING
42
+
43
+ transform_color match, directive, level
44
+ end
45
+ end
46
46
  end
47
47
  end
48
48
  end
@@ -3,14 +3,14 @@
3
3
  module Cogger
4
4
  module Formatters
5
5
  module Parsers
6
- # Parses template and extracts keys.
7
- class KeyExtractor
6
+ # Parses template and reorders attributes based on template key positions.
7
+ class Position
8
8
  PATTERN = /
9
9
  % # Start.
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
@@ -19,11 +19,20 @@ module Cogger
19
19
  @pattern = pattern
20
20
  end
21
21
 
22
- def call(template) = template.scan(pattern).map { |match| match.first.to_sym }
22
+ # :reek:FeatureEnvy
23
+ def call template, attributes
24
+ return attributes if !template || template.empty?
25
+ return attributes unless template.match? pattern
26
+
27
+ keys = scan template
28
+ attributes.slice(*keys).merge!(attributes.except(*keys))
29
+ end
23
30
 
24
31
  private
25
32
 
26
33
  attr_reader :pattern
34
+
35
+ def scan(template) = template.scan(pattern).map { |match| match.first.to_sym }
27
36
  end
28
37
  end
29
38
  end
@@ -0,0 +1,35 @@
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::Position.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! << NEW_LINE
22
+ end
23
+
24
+ private
25
+
26
+ attr_reader :template, :parser
27
+
28
+ def concat attributes
29
+ parser.call(template, attributes).each.with_object(+"") do |(key, value), line|
30
+ line << key.to_s << "=" << escape(value) << " "
31
+ end
32
+ end
33
+ end
34
+ end
35
+ 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
@@ -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
+ FormatTime = 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
@@ -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!) << NEW_LINE
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 }