cogger 0.11.0 → 0.12.0

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