cogger 0.11.0 → 0.12.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d529b8e4d8dd9fc3197a9e693e17cfcfc9cdadc4a5910f560a0bb50c5b0a5a02
4
- data.tar.gz: 652d74a7778cc22c4219c8051b5dfd24691ed959bdb2108f678282ed279cec41
3
+ metadata.gz: 5f8bcb6e5db91a1e5216dba5864806aead97bd32a9d8fcda1d59a4b22beb079c
4
+ data.tar.gz: a21a6b830568388316d651204f93f9209ba4694b00e6760a677c81224d1e1a3f
5
5
  SHA512:
6
- metadata.gz: 5cff486456ad103849b33360e4e660bb0837626e92a5debca5380977804ad877100878dbd2b8abc4e89840caa0e20445df24883ac79890feac8a8fc367ca7985
7
- data.tar.gz: 841a17616a1aa5fb1ed1c7848d8b2f9237926993cf911910e12fe00b4d7c7b991a86bcc36ac404b976eadd006903a4e65dd94bef96113a296dcf82575ed88200
6
+ metadata.gz: ae3d73862e4cbb63a98223023e77cde2b58cf53a4a5b6a94b7e972215629f47a81b956d196c20384386b31d6833ed33689943e01fb4fb4b60957c98794cfc381
7
+ data.tar.gz: 6d73ec6858638127a168388b23821e77abf7f196a49af51e184ba496084f81295316c148d4d7417d1d3e4b700cc079bc66695b392accc62be47d7cd6e23f3b45
checksums.yaml.gz.sig CHANGED
Binary file
data/README.adoc CHANGED
@@ -21,6 +21,7 @@ toc::[]
21
21
  * Provides customizable templates which leverage the native {format_link}.
22
22
  * Provides customizable formatters for simple, color, JSON, and/or custom output.
23
23
  * Provides multiple streams so you can log the same information to several outputs at once.
24
+ * Provides global and individual log entry tagging.
24
25
  * Provides filtering of sensitive information.
25
26
 
26
27
  == Screenshots
@@ -128,6 +129,7 @@ When creating a new logger, you can configure behavior via the following attribu
128
129
  * `io`: The input/output stream. This can be `STDOUT/$stdout`, a file/path, or `nil`. Default: `$stdout`.
129
130
  * `level`: The severity level you want to log at. Can be `:debug`, `:info`, `:warn`, `:error`, `:fatal`, or `:unknown`. Default: `:info`.
130
131
  * `formatter`: The formatter to use for formatting your log output. Default: `Cogger::Formatter::Color`. See the _Formatters_ section for more info.
132
+ * `tags`: Global tagging for _every_ log entry which _must_ be an array of objects you wish to use for tagging purposes.
131
133
  * `mode`: The binary mode which determines if your logs should be written in binary mode or not. Can be `true` or `false` and is identical to the `binmode` functionality found in the {logger_link} class. Default: `false`.
132
134
  * `age`: The rotation age of your log. This only applies when logging to a file. This is equivalent to the `shift_age` as found with the {logger_link} class. Default: `0`.
133
135
  * `size`: The rotation size of your log. This only applies when logging to a file. This is equivalent to the `shift_size` as found with the {logger_link} class. Default: `1,048,576` (i.e. 1 MB).
@@ -144,6 +146,8 @@ logger = Cogger.new
144
146
  logger = Cogger.new id: :demo,
145
147
  io: "demo.log",
146
148
  level: :debug,
149
+ formatter: :json,
150
+ tags: %w[DEMO DB],
147
151
  mode: false,
148
152
  age: 5,
149
153
  size: 1_000,
@@ -174,7 +178,7 @@ Templates are used by all formatters and adhere to {format_link} as used by `Ker
174
178
  - Use of _reference by name_ is required which means `%<demo>s` is allowed but `%{demo}` is not. This is because _reference by name_ is required for regular expressions and/or {pattern_matching_link}.
175
179
  - Use of the `n$` flag is prohibited because this isn't compatible with the above.
176
180
 
177
- In addition to the above, the {format_link} is further enhanced with the use of _universal_ and _individual_ directives which are primarily used by the _color_ formatter but might prove useful for other formatters. Example:
181
+ In addition to the above, the {format_link} is further enhanced with the use of _universal_ and _individual_ directives which are primarily used by the _color_ formatter but can prove useful for other formatters. Example:
178
182
 
179
183
  [source,ruby]
180
184
  ----
@@ -213,7 +217,7 @@ At this point, you might have gathered that there are specific keys you can use
213
217
 
214
218
  This also means if you pass in these same keys as a log event (example: `logger.info id: :bad, at: Time.now, severity: :bogus`) they will be ignored.
215
219
 
216
- The last key (or keys) is variable and customizable to your needs which is the log event message. Here a couple of examples to illustrate:
220
+ The last key (or keys) is variable and customizable to your needs which is the log event message. The only special key is the `tags` key which is explained later. Here a couple of examples to illustrate:
217
221
 
218
222
  [source,ruby]
219
223
  ----
@@ -247,35 +251,38 @@ Cogger.emojis
247
251
  # }
248
252
  ----
249
253
 
250
- To add an emoji, use:
254
+ The `:emoji` formatter is the default formatter which provides dynamic rendering of emojis based on severity level. Example:
251
255
 
252
256
  [source,ruby]
253
257
  ----
254
- Cogger.add_emoji(:tada, "🎉")
255
- .add_emoji :favorite, "❇️"
258
+ logger = Cogger.new
259
+ logger.info "Demo"
260
+
261
+ # 🟢 Demo
256
262
  ----
257
263
 
258
- By default, the `:emoji` formatter provides dynamic rendering of emojis based on severity level. Example:
264
+ To add one or more emojis, you can chain messages together when registering them:
259
265
 
260
266
  [source,ruby]
261
267
  ----
262
- logger = Cogger.new formatter: :emoji
263
- logger.info "demo"
264
-
265
- # 🟢 demo
268
+ Cogger.add_emoji(:tada, "🎉")
269
+ .add_emoji :favorite, "❇️"
266
270
  ----
267
271
 
268
- If you wanted to use a specific emoji, you could use the color formatter with a specific template:
272
+ If you always want to use the _same_ emoji, you could use the emoji formatter with a specific template:
269
273
 
270
274
  [source,ruby]
271
275
  ----
272
- logger = Cogger.new formatter: Cogger::Formatters::Color.new("%<emoji:tada>s %<message:dynamic>s")
273
- logger.info "demo"
276
+ logger = Cogger.new formatter: Cogger::Formatters::Emoji.new("%<emoji:tada>s %<message:dynamic>s")
274
277
 
275
- # 🎉 demo
278
+ logger.info "Demo"
279
+ logger.warn "Demo"
280
+
281
+ # 🎉 Demo
282
+ # 🎉 Demo
276
283
  ----
277
284
 
278
- Keep in mind that using a specific, non-dynamic, emoji will _always_ display no matter the current severity level.
285
+ As you can see, using a specific and non-dynamic emoji will _always_ display regardless of the current severity level.
279
286
 
280
287
  === Aliases
281
288
 
@@ -300,7 +307,7 @@ Aliases are a powerful way to customize your colors and use short syntax in your
300
307
  "%<message:haze>"
301
308
  ----
302
309
 
303
- Check out the {tone_link} documentation for further examples.
310
+ 💡 These aliases are used by the color and emoji formatters but check out the {tone_link} documentation and _Formatters_ section below for further examples.
304
311
 
305
312
  === Formatters
306
313
 
@@ -320,8 +327,8 @@ Cogger.formatters
320
327
  # "[%<id>s] [%<severity>s] [%<at>s] %<message>s"
321
328
  # ],
322
329
  # :emoji => [
323
- # Cogger::Formatters::Color < Object,
324
- # "%<emoji:dynamic>s% <message:dynamic>s"
330
+ # Cogger::Formatters::Emoji < Cogger::Formatters::Color,
331
+ # nil
325
332
  # ],
326
333
  # :json => [
327
334
  # Cogger::Formatters::JSON < Object,
@@ -342,10 +349,10 @@ You can add a formatter by providing a key, class, and _optional_ template. If a
342
349
 
343
350
  [source,ruby]
344
351
  ----
345
- # Add
352
+ # Registration
346
353
  Cogger.add_formatter :basic, Cogger::Formatters::Simple, "%<severity>s %<message>s"
347
354
 
348
- # Get
355
+ # Usage
349
356
  Cogger.get_formatter :basic
350
357
  # [Cogger::Formatters::Simple, "%<severity>s %<message>s"]
351
358
  ----
@@ -375,18 +382,14 @@ logger = Cogger.new formatter: :rack
375
382
 
376
383
  ==== Color
377
384
 
378
- The color formatter is enabled by default and is the equivalent of initializing with either of the following:
385
+ The color formatter allows you to have color coded logs and can be configured as follows:
379
386
 
380
387
  [source,ruby]
381
388
  ----
382
- logger = Cogger.new
383
- logger = Cogger.new formatter: Cogger::Formatters::Color.new
384
- logger = Cogger.new formatter: Cogger::Formatters::Color.new("%<message:dynamic>s")
389
+ logger = Cogger.new formatter: :color
385
390
  ----
386
391
 
387
- All three of the above examples are identical so you can start to see how different formatters can be used and customized further. Please refer back to the _Templates_ section on how to customize this formatter with more sophisticated templates.
388
-
389
- In addition to template customization, you can customize your color aliases as well. Default colors are provided by {tone_link} which are _aliased_ by log level:
392
+ Please refer back to the _Templates_ section on how to customize this formatter with more sophisticated templates. In addition to template customization, you can customize your color aliases as well. Default colors are provided by {tone_link} which are _aliased_ by log level:
390
393
 
391
394
  [source,ruby]
392
395
  ----
@@ -417,11 +420,56 @@ Once an alias is added, it can be immediately applied via the template of your f
417
420
  logger = Cogger.new formatter: Cogger::Formatters::Color.new("<mystery>%<message>s</mystery>")
418
421
  ----
419
422
 
420
- ℹ️ Much like the simple formatter, any leading or trailing whitespace is automatically after the template has been formatted.
423
+ ℹ️ Much like the simple formatter, any leading or trailing whitespace is automatically removed after the template has been formatted.
424
+
425
+ ==== Emoji
426
+
427
+ The emoji formatter is enabled by default and is the equivalent of initializing with either of the following:
428
+
429
+ [source,ruby]
430
+ ----
431
+ logger = Cogger.new
432
+ logger = Cogger.new formatter: :emoji
433
+ logger = Cogger.new formatter: Cogger::Formatters::Emoji.new("%<emoji:dynamic>s %<message:dynamic>s")
434
+ ----
435
+
436
+ All of the above examples are identical so you can see how different formatters can be used and customized further. The default emojis are registered as follows:
437
+
438
+ [source,ruby]
439
+ ----
440
+ Cogger.emojis
441
+
442
+ # {
443
+ # :debug => "🔎",
444
+ # :info => "🟢",
445
+ # :warn => "⚠️ ",
446
+ # :error => "🛑",
447
+ # :fatal => "🔥",
448
+ # :any => "⚫️"
449
+ # }
450
+ ----
451
+
452
+ This allows an emoji to be dynamically applied based on log severity. You can add or modify aliases as follows:
453
+
454
+ [source,ruby]
455
+ ----
456
+ Cogger.add_emoji :warn, "🟡"
457
+ ----
458
+
459
+ Once an alias is added/updated, it can be immediately applied via the template of your formatter. Example:
460
+
461
+ [source,ruby]
462
+ ----
463
+ logger = Cogger.new
464
+ logger.warn "Demo"
465
+ # 🟡 Demo
466
+ ----
467
+
468
+ ℹ️ Much like the simple and color formatters, any leading or trailing whitespace is automatically removed after the template has been formatted.
421
469
 
422
470
  ==== JSON
423
471
 
424
- This formatter is similar in behavior to the _simple_ formatter except the template allows you to order the layout of your keys only. All other information is ignored. To use:
472
+ This formatter is similar in behavior to the _simple_ formatter except the template allows you to _order_ the layout of your keys. All other information is ignored. To use:
425
473
 
426
474
  [source,ruby]
427
475
  ----
@@ -440,18 +488,31 @@ logger.info verb: "GET", path: "/"
440
488
 
441
489
  Your template can be a full or partial match of keys. If no keys match what is defined in the template, then the original order of the keys will be used instead.
442
490
 
443
- ==== Original
491
+ ==== Native
444
492
 
445
- Should you wish to use the original formatter as provided by original/native {logger_link}, you can get that behavior by specifying it as your preferred formatter. Example:
493
+ Should you wish to use the native formatter as provided by original/native {logger_link}, it will work but not in the manner expected. Example:
446
494
 
447
495
  [source,ruby]
448
496
  ----
449
497
  require "logger"
450
498
 
451
499
  logger = Cogger.new formatter: Logger::Formatter.new
452
- logger.info "demo"
500
+ logger.info "Demo"
501
+
502
+ # I, [2023-10-15T14:32:55.061777 #72801] INFO -- console: #<data Cogger::Entry id="console", severity=:info, at=2023-10-15 14:32:55.061734 -0600, message="Demo", tags=[], payload={}>
503
+ ----
504
+
505
+ While the above doesn't cause an error, you only get a dump of the `Cogger::Entry` which is not what you want. To replicate native {logger_link} functionality, you can do use the simple formatter as follows to produce the rough equivalent:
506
+
507
+ [source,ruby]
508
+ ----
509
+ formatter = Cogger::Formatters::Simple.new(
510
+ "%<severity>s, [%<at>s] %<severity>s -- %<id>s: %<message>s"
511
+ )
512
+ logger = Cogger.new(formatter:)
513
+ logger.info "Demo"
453
514
 
454
- # I, [2023-04-11T19:35:51.175733 #84790] INFO -- console: demo
515
+ # INFO, [2023-10-15 15:07:13 -0600] INFO -- console: Demo
455
516
  ----
456
517
 
457
518
  ==== Custom
@@ -468,7 +529,7 @@ class MyFormatter
468
529
  @sanitizer = sanitizer
469
530
  end
470
531
 
471
- def call(*entry) = "#{format template, sanitizer.call(*entry)}\n"
532
+ def call(*input) = "#{format template, sanitizer.call(*input)}\n"
472
533
 
473
534
  private
474
535
 
@@ -476,7 +537,70 @@ class MyFormatter
476
537
  end
477
538
  ----
478
539
 
479
- There is no restriction on what dependency you might want to initialize your custom formatter with but -- as a bare minimum -- you'll want to provide a default template and inject the sanitizer which sanitizes the raw log entry into a hash you can interact with in your implementation. The only other requirement is that you must implement `#call` which takes a log entry which is an array of positional arguments (i.e. `severity`, `at`, `id`, `message`) and answers back a formatted string. If you need more examples you can either read the link:https://rubyapi.org/o/logger/formatter#method-i-call[Logger::Formatter] documentation or look at any of the formatters provided within this gem.
540
+ There is no restriction on what dependency you might want to initialize your custom formatter with but -- as a bare minimum -- you'll want to provide a default template and inject the sanitizer which sanitizes the raw input into a `Cogger::Entry` object you can interact with in your implementation. The only other requirement is that you must implement `#call` which takes a log entry which is an array of positional arguments (i.e. `severity`, `at`, `id`, `entry`) and answers back a formatted string. If you need more examples you can look at any of the formatters provided within this gem.
541
+
542
+ === Tags
543
+
544
+ Tags allow you to tag your messages at both a global and local (i.e. per message) level. For example, here's what tagging looks like when used globally:
545
+
546
+ [source,ruby]
547
+ ----
548
+ logger = Cogger.new tags: %w[WEB]
549
+ logger.info "Demo"
550
+
551
+ # 🟢 [WEB] Demo
552
+ ----
553
+
554
+ Each tag is wrapped in brackets (i.e. `[]`) and you can use multiple tags:
555
+
556
+ [source,ruby]
557
+ ----
558
+ logger = Cogger.new tags: %w[WEB EXAMPLE]
559
+ logger.info "Demo"
560
+
561
+ # 🟢 [WEB] [EXAMPLE] Demo
562
+ ----
563
+
564
+ You are not limited to string-based tags. Any object will work:
565
+
566
+
567
+ [source,ruby]
568
+ ----
569
+ logger = Cogger.new tags: ["ONE", :two, 3, {four: "FOUR"}, proc { "FIVE" }]
570
+ logger.info "Demo"
571
+
572
+ 🟢 [ONE] [two] [3] [FIVE] [four=FOUR] Demo
573
+ ----
574
+
575
+ With the above, we have string, symbol, integer, hash, and proc tags. With hashes, you'll always get a the key/value pair formatted as: `key=value`. Procs/lambdas allow you to lazy evaluate your tag at time of logging which provides a powerful way to acquire the current thread ID, request ID, etc.
576
+
577
+ In addition to global tags, you can use local tags per log message. Example:
578
+
579
+ [source,ruby]
580
+ ----
581
+ logger = Cogger.new
582
+ logger.info "Demo", tags: ["ONE", :two, 3, {four: "FOUR"}, proc { "FIVE" }]
583
+
584
+ 🟢 [ONE] [two] [3] [FIVE] [four=FOUR] Demo
585
+ ----
586
+
587
+ You can also combine global and local tags:
588
+
589
+ [source,ruby]
590
+ ----
591
+ logger = Cogger.new tags: ["ONE", :two]
592
+ logger.info "Demo", tags: [3, proc { "FOUR" }]
593
+
594
+ # 🟢 [ONE] [two] [3] [FOUR] Demo
595
+ ----
596
+
597
+ As you can see, tags are highly versatile. That said, the following guidelines are worth consideration when using them:
598
+
599
+ * Prefer uppercase tag names to make them visually stand out.
600
+ * Prefer short names, ideally 1-4 characters since long tags defeat the purpose of brevity.
601
+ * Prefer consistent tag names by using tags that are not synonymous or ambiguous.
602
+ * Prefer using tags by feature rather than things like environments. Examples: API, DB, MAILER.
603
+ * Prefer the JSON formatter for structured metadata instead of tags. Logging JSON formatted messages with tags will work but sticking with a traditional hash, instead of tags, will probably serve you better.
480
604
 
481
605
  === Filters
482
606
 
@@ -532,7 +656,7 @@ logger = Cogger.new
532
656
  logger.info "Demo."
533
657
  ----
534
658
 
535
- The above would log the `"Demo."` message to `$stdout` (i.e. the default stream), to the `tmp/demo.log` file, and to `/dev/null`. All of the attributes you would use to construct your default logger apply to any stream. This also means any custom template/formatter can be applied to your streams. Here's another example:
659
+ The above would log the `"Demo."` message to `$stdout` -- the default stream -- to the `tmp/demo.log` file, and to `/dev/null`. All attributes used to construct your default logger apply to all additional streams unless customized further. This means any custom template/formatter can be applied to your streams. Example:
536
660
 
537
661
  [source,ruby]
538
662
  ----
@@ -542,6 +666,25 @@ logger.info "Demo."
542
666
 
543
667
  In this situation, you'd get colorized output to `$stdout` and JSON output to the `tmp/demo.log` file.
544
668
 
669
+ There is a lot you can do with streams. For example, if you wanted to experiment with the same message formatted by multiple formatters, you could add a stream per format. Example:
670
+
671
+ [source,ruby]
672
+ ----
673
+ cogger = Cogger.new
674
+ .add_stream(formatter: :color)
675
+ .add_stream(formatter: :detail)
676
+ .add_stream(formatter: :json)
677
+ .add_stream(formatter: :simple)
678
+
679
+ cogger.info "Demo"
680
+
681
+ # 🟢 Demo
682
+ # Demo
683
+ # [console] [INFO] [2023-10-15 15:17:27 -0600] Demo
684
+ # {"id":"console","severity":"INFO","at":"2023-10-15 15:17:27 -0600","message":"Demo"}
685
+ # Demo
686
+ ----
687
+
545
688
  === Defaults
546
689
 
547
690
  Should you ever need quick access to the defaults, you can use:
data/cogger.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "cogger"
5
- spec.version = "0.11.0"
5
+ spec.version = "0.12.0"
6
6
  spec.authors = ["Brooke Kuhlmann"]
7
7
  spec.email = ["brooke@alchemists.io"]
8
8
  spec.homepage = "https://alchemists.io/projects/cogger"
@@ -9,20 +9,24 @@ module Cogger
9
9
  :io,
10
10
  :level,
11
11
  :formatter,
12
+ :tags,
12
13
  :mode,
13
14
  :age,
14
15
  :size,
15
16
  :suffix,
17
+ :entry,
16
18
  :logger
17
19
  ) do
18
20
  def initialize id: Program.call,
19
21
  io: $stdout,
20
22
  level: Logger.const_get(ENV.fetch("LOG_LEVEL", "INFO")),
21
- formatter: Formatters::Color.new,
23
+ formatter: Formatters::Emoji.new,
24
+ tags: [],
22
25
  mode: false,
23
26
  age: 0,
24
27
  size: 1_048_576,
25
28
  suffix: "%Y-%m-%d",
29
+ entry: Entry,
26
30
  logger: Logger
27
31
  super
28
32
  end
@@ -40,9 +44,9 @@ module Cogger
40
44
 
41
45
  def inspect
42
46
  "#<#{self.class} @id=#{id}, @io=#{io.class}, @level=#{level}, " \
43
- "@formatter=#{formatter.class}, " \
47
+ "@formatter=#{formatter.class}, @tags=#{tags.inspect}, " \
44
48
  "@mode=#{mode}, @age=#{age}, @size=#{size}, @suffix=#{suffix.inspect}, " \
45
- "@logger=#{logger}>"
49
+ "@entry=#{entry}, @logger=#{logger}>"
46
50
  end
47
51
  end
48
52
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cogger
4
+ # Defines a log entry which can be formatted for output.
5
+ Entry = Data.define :id, :severity, :at, :message, :tags, :payload do
6
+ def self.for(message = nil, **payload)
7
+ new id: (payload.delete(:id) || Program.call),
8
+ severity: (payload.delete(:severity) || "INFO").upcase,
9
+ message: (block_given? ? yield : message),
10
+ tags: Array(payload.delete(:tags)),
11
+ payload:
12
+ end
13
+
14
+ def self.for_crash message, error, id:
15
+ new id:,
16
+ severity: "FATAL",
17
+ message:,
18
+ payload: {
19
+ error_message: error.message,
20
+ error_class: error.class,
21
+ backtrace: error.backtrace
22
+ }
23
+ end
24
+
25
+ def initialize id: Program.call,
26
+ severity: "INFO",
27
+ at: Time.now,
28
+ message: nil,
29
+ tags: [],
30
+ payload: {}
31
+ super
32
+ end
33
+
34
+ def attributes = {id:, severity:, at:, message:, **payload}
35
+
36
+ def tagged_attributes tagger: Tag
37
+ computed_tags = tagger.for(*tags)
38
+
39
+ return attributes if computed_tags.empty?
40
+
41
+ {id:, severity:, at:, message:, tags: computed_tags.to_a, **payload}
42
+ end
43
+
44
+ def tagged tagger: Tag
45
+ attributes.tap do |pairs|
46
+ computed_tags = tagger.for(*tags)
47
+ pairs[:message] = "#{computed_tags} #{pairs[:message]}" unless computed_tags.empty?
48
+ end
49
+ end
50
+ end
51
+ end
@@ -11,8 +11,8 @@ module Cogger
11
11
  @processor = processor
12
12
  end
13
13
 
14
- def call(*entry)
15
- updated_template, attributes = processor.call(template, *entry)
14
+ def call(*input)
15
+ updated_template, attributes = processor.call(template, *input)
16
16
  "#{format(updated_template, **attributes).tap(&:strip!)}\n"
17
17
  end
18
18
 
@@ -16,8 +16,8 @@ module Cogger
16
16
  @processor = processor
17
17
  end
18
18
 
19
- def call(*entry)
20
- updated_template, attributes = processor.call(template, *entry)
19
+ def call(*input)
20
+ updated_template, attributes = processor.call(template, *input)
21
21
  attributes[:backtrace] = %( #{attributes[:backtrace].join "\n "})
22
22
  "#{format(updated_template, **attributes)}\n"
23
23
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cogger
4
+ module Formatters
5
+ # Formats by emoji and color.
6
+ class Emoji < Color
7
+ TEMPLATE = "%<emoji:dynamic>s %<message:dynamic>s"
8
+
9
+ def initialize(template = TEMPLATE, ...)
10
+ super
11
+ end
12
+ end
13
+ end
14
+ end
@@ -10,21 +10,19 @@ module Cogger
10
10
 
11
11
  def initialize template = TEMPLATE,
12
12
  parser: Parsers::Individual.new,
13
- sanitizer: Kit::Sanitizer.new
14
- @template = template
15
- @parser = parser
13
+ sanitizer: Kit::Sanitizer
14
+ @positions = parser.call(template).last.keys
16
15
  @sanitizer = sanitizer
17
16
  end
18
17
 
19
- def call(*entry)
20
- positions = parser.call(template).last.keys
21
- attributes = sanitizer.call(*entry).tap(&:compact!)
18
+ def call(*input)
19
+ attributes = sanitizer.call(*input).tagged_attributes.tap(&:compact!)
22
20
  "#{attributes.slice(*positions).merge!(attributes.except(*positions)).to_json}\n"
23
21
  end
24
22
 
25
23
  private
26
24
 
27
- attr_reader :template, :parser, :sanitizer
25
+ attr_reader :positions, :sanitizer
28
26
  end
29
27
  end
30
28
  end
@@ -3,34 +3,13 @@
3
3
  module Cogger
4
4
  module Formatters
5
5
  module Kit
6
- # Transforms a positional log entry into a hash entry for template parsing and formatting.
7
- class Sanitizer
8
- def initialize filters: Cogger.filters
9
- @filters = filters
10
- end
6
+ # Ensures log entry is filtered of sensitive data.
7
+ Sanitizer = lambda do |*input, filters: Cogger.filters|
8
+ *, entry = input
9
+ payload = entry.payload
11
10
 
12
- # :reek:FeatureEnvy
13
- def call(*entry)
14
- severity, at, id, message = entry
15
-
16
- attributes = if message.is_a? Hash
17
- {id:, severity:, at:, message: nil, **message.except(:id, :severity, :at)}
18
- else
19
- {id:, severity:, at:, message:}
20
- end
21
-
22
- filter attributes
23
- end
24
-
25
- private
26
-
27
- attr_reader :filters
28
-
29
- # :reek:FeatureEnvy
30
- def filter attributes
31
- filters.each { |key| attributes[key] = "[FILTERED]" if attributes.key? key }
32
- attributes
33
- end
11
+ filters.each { |key| payload[key] = "[FILTERED]" if payload.key? key }
12
+ entry
34
13
  end
35
14
  end
36
15
  end
@@ -45,7 +45,7 @@ module Cogger
45
45
  # :reek:FeatureEnvy
46
46
  # :reek:TooManyStatements
47
47
  def sanitize_and_extract template, attributes
48
- template.dup.gsub pattern do
48
+ template.gsub pattern do
49
49
  captures = Regexp.last_match.named_captures
50
50
  attributes[captures["key"].to_sym] = captures["directive"]
51
51
 
@@ -8,15 +8,15 @@ module Cogger
8
8
  # Processes emojis and colors.
9
9
  class Color
10
10
  def initialize parser: Parsers::Dynamic.new,
11
- kit: {sanitizer: Kit::Sanitizer.new, colorizer: Kit::Colorizer},
11
+ kit: {sanitizer: Kit::Sanitizer, colorizer: Kit::Colorizer},
12
12
  registry: Cogger
13
13
  @parser = parser
14
14
  @kit = kit
15
15
  @registry = registry
16
16
  end
17
17
 
18
- def call(template, *entry)
19
- attributes = sanitizer.call(*entry)
18
+ def call(template, *input)
19
+ attributes = sanitizer.call(*input).tagged
20
20
 
21
21
  case parser.call template
22
22
  in [String => body, String => style] then universal body, style, **attributes
@@ -6,12 +6,12 @@ module Cogger
6
6
  class Simple
7
7
  TEMPLATE = "%<message>s"
8
8
 
9
- def initialize template = TEMPLATE, sanitizer: Kit::Sanitizer.new
9
+ def initialize template = TEMPLATE, sanitizer: Kit::Sanitizer
10
10
  @template = template
11
11
  @sanitizer = sanitizer
12
12
  end
13
13
 
14
- def call(*entry) = "#{format(template, sanitizer.call(*entry)).tap(&:strip!)}\n"
14
+ def call(*input) = "#{format(template, sanitizer.call(*input).tagged).tap(&:strip!)}\n"
15
15
 
16
16
  private
17
17
 
data/lib/cogger/hub.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "forwardable"
3
4
  require "logger"
4
5
  require "refinements/hashes"
5
6
  require "refinements/loggers"
@@ -8,12 +9,33 @@ module Cogger
8
9
  # Loads configuration and simultaneously sends messages to multiple streams.
9
10
  # :reek:TooManyInstanceVariables
10
11
  class Hub
12
+ extend Forwardable
13
+
11
14
  using Refinements::Loggers
12
15
  using Refinements::Hashes
13
16
 
14
- def initialize(registry: Cogger, model: Configuration.new, **attributes)
17
+ delegate %i[
18
+ close
19
+ reopen
20
+ debug!
21
+ debug?
22
+ info!
23
+ info?
24
+ warn!
25
+ warn?
26
+ error!
27
+ error?
28
+ fatal!
29
+ fatal?
30
+ formatter
31
+ formatter=
32
+ level
33
+ level=
34
+ ] => :primary
35
+
36
+ def initialize(registry: Cogger, model: Configuration, **attributes)
15
37
  @registry = registry
16
- @configuration = model.with(**find_formatter(attributes))
38
+ @configuration = model[**find_formatter(attributes)]
17
39
  @primary = configuration.to_logger
18
40
  @streams = [@primary]
19
41
  @mutex = Mutex.new
@@ -25,19 +47,19 @@ module Cogger
25
47
  self
26
48
  end
27
49
 
28
- def debug(...) = log(__method__, ...)
50
+ def debug(message = nil, **payload, &) = log(__method__, message, **payload, &)
29
51
 
30
- def info(...) = log(__method__, ...)
52
+ def info(message = nil, **payload, &) = log(__method__, message, **payload, &)
31
53
 
32
- def warn(...) = log(__method__, ...)
54
+ def warn(message = nil, **payload, &) = log(__method__, message, **payload, &)
33
55
 
34
- def error(...) = log(__method__, ...)
56
+ def error(message = nil, **payload, &) = log(__method__, message, **payload, &)
35
57
 
36
- def fatal(...) = log(__method__, ...)
58
+ def fatal(message = nil, **payload, &) = log(__method__, message, **payload, &)
37
59
 
38
- def unknown(...) = log(__method__, ...)
60
+ def any(message = nil, **payload, &) = log(__method__, message, **payload, &)
39
61
 
40
- alias any unknown
62
+ alias unknown any
41
63
 
42
64
  def reread = primary.reread
43
65
 
@@ -60,17 +82,30 @@ module Cogger
60
82
  )
61
83
  end
62
84
 
63
- # :reek:TooManyStatements
64
- def log(severity, message = nil, &)
65
- mutex.synchronize { streams.each { |logger| logger.public_send(severity, message, &) } }
66
- true
85
+ def log(severity, message, **payload, &)
86
+ dispatch(severity, message, **payload, &)
67
87
  rescue StandardError => error
68
- configuration.with(id: "Cogger", io: $stdout, formatter: Formatters::Crash.new)
88
+ crash message, error
89
+ end
90
+
91
+ def dispatch(severity, message, **payload, &)
92
+ entry = configuration.entry.for(
93
+ message,
94
+ id: configuration.id,
95
+ severity:,
96
+ tags: configuration.tags + Array(payload.delete(:tags)),
97
+ **payload,
98
+ &
99
+ )
100
+
101
+ mutex.synchronize { streams.each { |logger| logger.public_send severity, entry } }
102
+ true
103
+ end
104
+
105
+ def crash message, error
106
+ configuration.with(id: :cogger, io: $stdout, formatter: Formatters::Crash.new)
69
107
  .to_logger
70
- .fatal message:,
71
- error_message: error.message,
72
- error_class: error.class,
73
- backtrace: error.backtrace
108
+ .fatal configuration.entry.for_crash(message, error, id: configuration.id)
74
109
  true
75
110
  end
76
111
  end
@@ -27,11 +27,7 @@ module Cogger
27
27
  Cogger::Formatters::Simple,
28
28
  "[%<id>s] [%<severity>s] [%<at>s] %<message>s"
29
29
  )
30
- .add_formatter(
31
- :emoji,
32
- Cogger::Formatters::Color,
33
- "%<emoji:dynamic>s %<message:dynamic>s"
34
- )
30
+ .add_formatter(:emoji, Cogger::Formatters::Emoji)
35
31
  .add_formatter(:json, Cogger::Formatters::JSON)
36
32
  .add_formatter(:simple, Cogger::Formatters::Simple)
37
33
  .add_formatter :rack,
data/lib/cogger/tag.rb ADDED
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cogger
4
+ # Models a tag which may consist of an array and/or hash.
5
+ Tag = Data.define :singles, :pairs do
6
+ def self.for(*bag)
7
+ bag.each.with_object new do |item, tag|
8
+ value = item.is_a?(Proc) ? item.call : item
9
+ value.is_a?(Hash) ? tag.pairs.merge!(value) : tag.singles.append(value)
10
+ end
11
+ end
12
+
13
+ def initialize singles: [], pairs: {}
14
+ super
15
+ end
16
+
17
+ def empty? = singles.empty? && pairs.empty?
18
+
19
+ def to_a
20
+ return [] if empty?
21
+
22
+ pairs.map { |key, value| "#{key}=#{value}" }
23
+ .prepend(*singles.map(&:to_s))
24
+ end
25
+
26
+ def to_s = empty? ? "" : "#{format_singles} #{format_pairs}".tap(&:strip!)
27
+
28
+ private
29
+
30
+ def format_singles
31
+ singles.map { |value| "[#{value}]" }
32
+ .join " "
33
+ end
34
+
35
+ def format_pairs
36
+ pairs.map { |key, value| "[#{key}=#{value}]" }
37
+ .join(" ")
38
+ end
39
+ end
40
+ end
data/lib/cogger.rb CHANGED
@@ -15,10 +15,5 @@ module Cogger
15
15
 
16
16
  def self.loader(registry = Zeitwerk::Registry) = registry.loader_for __FILE__
17
17
 
18
- def self.init(...)
19
- warn "#{self}##{__method__} is deprecated, use `.new` instead.", category: :deprecated
20
- Client.new(...)
21
- end
22
-
23
18
  def self.new(...) = Hub.new(...)
24
19
  end
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cogger
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brooke Kuhlmann
@@ -35,7 +35,7 @@ cert_chain:
35
35
  3n5C8/6Zh9DYTkpcwPSuIfAga6wf4nXc9m6JAw8AuMLaiWN/r/2s4zJsUHYERJEu
36
36
  gZGm4JqtuSg8pYjPeIJxS960owq+SfuC+jxqmRA54BisFCv/0VOJi7tiJVY=
37
37
  -----END CERTIFICATE-----
38
- date: 2023-10-01 00:00:00.000000000 Z
38
+ date: 2023-10-15 00:00:00.000000000 Z
39
39
  dependencies:
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: refinements
@@ -92,10 +92,11 @@ files:
92
92
  - README.adoc
93
93
  - cogger.gemspec
94
94
  - lib/cogger.rb
95
- - lib/cogger/client.rb
96
95
  - lib/cogger/configuration.rb
96
+ - lib/cogger/entry.rb
97
97
  - lib/cogger/formatters/color.rb
98
98
  - lib/cogger/formatters/crash.rb
99
+ - lib/cogger/formatters/emoji.rb
99
100
  - lib/cogger/formatters/json.rb
100
101
  - lib/cogger/formatters/kit/colorizer.rb
101
102
  - lib/cogger/formatters/kit/sanitizer.rb
@@ -107,6 +108,7 @@ files:
107
108
  - lib/cogger/hub.rb
108
109
  - lib/cogger/program.rb
109
110
  - lib/cogger/registry.rb
111
+ - lib/cogger/tag.rb
110
112
  homepage: https://alchemists.io/projects/cogger
111
113
  licenses:
112
114
  - Hippocratic-2.1
metadata.gz.sig CHANGED
Binary file
data/lib/cogger/client.rb DELETED
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "forwardable"
4
- require "logger"
5
- require "refinements/loggers"
6
- require "tone"
7
-
8
- module Cogger
9
- # Provides the primary client for colorized logging.
10
- class Client
11
- extend Forwardable
12
-
13
- using Refinements::Loggers
14
-
15
- delegate %i[formatter level progname debug info warn error fatal unknown] => :logger
16
-
17
- # :reek:TooManyStatements
18
- def initialize logger = Logger.new($stdout), color: Cogger.color, **attributes
19
- warn "#{self.class}##{__method__} is deprecated, use `Cogger.new` instead.",
20
- category: :deprecated
21
-
22
- @logger = logger
23
- @color = color
24
- @attributes = attributes
25
-
26
- configure
27
- yield logger if block_given?
28
- end
29
-
30
- def any(...) = logger.unknown(...)
31
-
32
- def reread = logger.reread
33
-
34
- private
35
-
36
- attr_reader :logger, :color, :attributes
37
-
38
- # rubocop:disable Metrics/AbcSize
39
- def configure
40
- logger.datetime_format = attributes.fetch :datetime_format, logger.datetime_format
41
- logger.level = attributes.fetch :level, default_level
42
- logger.progname = attributes.fetch :progname, logger.progname
43
- logger.formatter = attributes.fetch :formatter, default_formatter
44
- end
45
- # rubocop:enable Metrics/AbcSize
46
-
47
- def default_level = logger.class.const_get ENV.fetch("LOG_LEVEL", "INFO")
48
-
49
- def default_formatter
50
- -> severity, _at, _name, message { "#{color[message, severity.downcase]}\n" }
51
- end
52
- end
53
- end