lumberjack 1.3.4 → 1.4.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: db9d72af596912080ab89e900718d2dc1a4d27dda1eb8320d540fa096325b209
4
- data.tar.gz: 0737c0a2bde27fa687af82a6ac65f8ede65abdbe175462bc8c39e515569d3a23
3
+ metadata.gz: c72bca9355bd37fb691a06a4cea4a341b6905b7b5c15429d41061b2b0afd3813
4
+ data.tar.gz: 49a18195fadf1de9b2cf43e3ce95c1f5e49d3d81dd4384a722e8f685187f6bbc
5
5
  SHA512:
6
- metadata.gz: 941b206a2ffcb38760669f04658f9a52ec6eee063e2d76505db7c29c0a2f6b7e2d481fdffa0c88ee6eadc189fcb416e89b0e8490d8eea8513ededefd2ccef21b
7
- data.tar.gz: fa95906ee0eb896b77eb33958223831a4b8f8c43b6f1a84a21df93388ee7f37c974c0fb0bdbc1bda1e9836af00d26aed0c11576409bcb153a3cd7dab3e4373c1
6
+ metadata.gz: 5d58e6244cad6396bf702759c05a130e279d4def0d7bee491817b373fbee79420fa0df776264485e7387657a7dd28bab7e4690b7b0a677d878b98f611432a7f7
7
+ data.tar.gz: d2c2c8f1765230b39df298c68c89bad1db520acbaabfee7da7381d75336de91ff9c3f709e501683fb2502f6b5f7b61eef89c8814f13fda6c2235c3ac43ef5959
data/CHANGELOG.md CHANGED
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 1.4.0
8
+
9
+ ### Changed
10
+
11
+ - Tags are consistently flattened internally to dot notation keys. This makes tag handling more consistent when using nested hashes as tag values. This changes how nested tags are merged, though. Now when new nested tags are set they will be merged into the existing tags rather than replacing them entirely. So `logger.tag(foo: {bar: "baz"})` will now merge the `foo.bar` tag into the existing tags rather than replacing the entire `foo` tag.
12
+ - The `Lumberjack::Logger#context` method can now be called without a block. When called with a block it sets up a new tag context for the block. When called without a block, it returns the current tag context in a `Lumberjack::TagContext` object which can be used to add tags to the current context.
13
+ - Tags in `Lumberjack::LogEntry` are now always stored as a hash of flattened keys. This means that when tags are set on a log entry, they will be automatically flattened to dot notation keys. The `tag` method will return a hash of sub-tags if the tag name is a tag prefix.
14
+
15
+ ### Added
16
+
17
+ - Added `Lumberjack::LogEntry#nested_tags` method to return the tags as a nested hash structure.
18
+
7
19
  ## 1.3.4
8
20
 
9
21
  ### Added
data/README.md CHANGED
@@ -50,7 +50,7 @@ logger.info("request completed", duration: elapsed_time, status: response.status
50
50
  You can also specify tags on a logger that will be included with every log message.
51
51
 
52
52
  ```ruby
53
- logger.tag(host: Socket.gethostname)
53
+ logger.tag_globally(host: Socket.gethostname.force_encoding("UTF-8"))
54
54
  ```
55
55
 
56
56
  You can specify tags that will only be applied to the logger in a block as well.
@@ -64,22 +64,37 @@ end
64
64
  logger.info("there") # Will not include the `thread_id` or `count` tag
65
65
  ```
66
66
 
67
+ The block opens up a new tag context. The context applies only within a block and only for the currently executing thread. You can also open up a new context block without specifying any tags and then add tags to the context within the block.
68
+
69
+ ```ruby
70
+ logger.context do # When a block is given to `context`, it opens a new empty tag context.
71
+ # `context` can be called without a block, to get an TagContext object for manipulating tags.
72
+ logger.context.tag(user_id: current_user.id, username: current_user.username)
73
+ logger.context["user_role"] = current_user.role # You can also use hash syntax to set tags.
74
+ logger.info("user logged in") # Will include the `user_id`, `username`, and `user_role` tags.
75
+ end
76
+ logger.info("no user") # Will not include the tags
77
+ ```
78
+
67
79
  You can also set tags to `Proc` objects that will be evaluated when creating a log entry.
68
80
 
69
81
  ```ruby
70
- logger.tag(thread_id: lambda { Thread.current.object_id })
82
+ logger.tag_globally(thread_id: lambda { Thread.current.object_id })
83
+
71
84
  Thread.new do
72
85
  logger.info("inside thread") # Will include the `thread_id` tag with id of the spawned thread
73
86
  end
87
+
74
88
  logger.info("outside thread") # Will include the `thread_id` tag with id of the main thread
75
89
  ```
76
90
 
77
- Finally, you can specify a logging context with tags that apply within a block to all loggers.
91
+ Finally, you can specify a global logging context that applies to all loggers.
78
92
 
79
93
  ```ruby
80
94
  Lumberjack.context do
81
- Lumberjack.tag(request_id: SecureRandom.hex)
95
+ Lumberjack.tag(request_id: SecureRandom.hex) # add a global context tag
82
96
  logger.info("begin request") # Will include the `request_id` tag
97
+ event_logger.info("http.request") # Will also include the `request_id` tag
83
98
  end
84
99
  logger.info("no requests") # Will not include the `request_id` tag
85
100
  ```
@@ -134,7 +149,7 @@ When combined with structured output devices (like [`lumberjack_json_device`](ht
134
149
 
135
150
  #### Compatibility with ActiveSupport::TaggedLogging
136
151
 
137
- `Lumberjack::Logger` version 1.1.2 or greater is compatible with `ActiveSupport::TaggedLogging`. This is so that other code that expects to have a logger that responds to the `tagged` method will work. Any tags added with the `tagged` method will be appended to an array in the "tagged" tag.
152
+ `Lumberjack::Logger` is compatible with `ActiveSupport::TaggedLogging`. This is so that other code that expects to have a logger that responds to the `tagged` method will work. Any tags added with the `tagged` method will be appended to an array in the "tagged" tag.
138
153
 
139
154
  ```ruby
140
155
  logger.tagged("foo", "bar=1", "other") do
@@ -199,6 +214,7 @@ There are several built in classes you can add as formatters. You can use a symb
199
214
  - `:pretty_print` - `Lumberjack::Formatter::PrettyPrintFormatter` - returns the pretty print format of the object.
200
215
  - `:id` - `Lumberjack::Formatter::IdFormatter` - returns a hash of the object with keys for the id attribute and class.
201
216
  - `:structured` - `Lumberjack::Formatter::StructuredFormatter` - crawls the object and applies the formatter recursively to Enumerable objects found in it (arrays, hashes, etc.).
217
+ - `:truncate` - `Lumberjack::Formatter::TruncateFormatter` - truncates long strings to a specified length.
202
218
 
203
219
  To define your own formatter, either provide a block or an object that responds to `call` with a single argument.
204
220
 
@@ -260,7 +276,7 @@ logger.tag_formatter.add("user.username", &:upcase)
260
276
 
261
277
  #### Templates
262
278
 
263
- If you use the built-in `Lumberjack::Writer` derived devices, you can also customize the Template used to format the LogEntry.
279
+ If you use the built-in `Lumberjack::Device::Writer` derived devices, you can also customize the Template used to format the LogEntry.
264
280
 
265
281
  See `Lumberjack::Template` for a complete list of macros you can use in the template. You can also use a block that receives a `Lumberjack::LogEntry` as a template.
266
282
 
@@ -286,7 +302,7 @@ The logger has hooks for devices that support buffering to potentially increase
286
302
 
287
303
  You can use the `:flush_seconds` option on the logger to periodically flush the log. This is usually a good idea so you can more easily debug hung processes. Without periodic flushing, a process that hangs may never write anything to the log because the messages are sitting in a buffer. By turning on periodic flushing, the logged messages will be written which can greatly aid in debugging the problem.
288
304
 
289
- The built in stream based logging devices use an internal buffer. The size of the buffer (in bytes) can be set with the `:buffer_size` options when initializing a logger. The default behavior is to not to buffer.
305
+ The built in stream based logging devices use an internal buffer. The size of the buffer (in bytes) can be set with the `:buffer_size` options when initializing a logger. The default behavior is to not buffer.
290
306
 
291
307
  ```ruby
292
308
  # Set buffer to flush after 8K has been written to the log.
@@ -361,7 +377,7 @@ To change the log message format to output JSON, you could use this code:
361
377
  config.logger = Lumberjack::Logger.new(log_file_path, :template => lambda{|e| JSON.dump(time: e.time, level: e.severity_label, message: e.message)})
362
378
  ```
363
379
 
364
- To send log messages to syslog instead of to a file, you could use this (require the lumberjack_syslog_device gem):
380
+ To send log messages to syslog instead of to a file, you could use this (requires the lumberjack_syslog_device gem):
365
381
 
366
382
  ```ruby
367
383
  config.logger = Lumberjack::Logger.new(Lumberjack::SyslogDevice.new)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.4
1
+ 1.4.0
@@ -9,6 +9,7 @@ module Lumberjack
9
9
  def initialize(parent_context = nil)
10
10
  @tags = {}
11
11
  @tags.merge!(parent_context.tags) if parent_context
12
+ @tag_context = TagContext.new(@tags)
12
13
  end
13
14
 
14
15
  # Set tags on the context.
@@ -16,9 +17,7 @@ module Lumberjack
16
17
  # @param tags [Hash] The tags to set.
17
18
  # @return [void]
18
19
  def tag(tags)
19
- tags.each do |key, value|
20
- @tags[key.to_s] = value
21
- end
20
+ @tag_context.tag(tags)
22
21
  end
23
22
 
24
23
  # Get a context tag.
@@ -26,7 +25,7 @@ module Lumberjack
26
25
  # @param key [String, Symbol] The tag key.
27
26
  # @return [Object] The tag value.
28
27
  def [](key)
29
- @tags[key.to_s]
28
+ @tag_context[key]
30
29
  end
31
30
 
32
31
  # Set a context tag.
@@ -35,7 +34,15 @@ module Lumberjack
35
34
  # @param value [Object] The tag value.
36
35
  # @return [void]
37
36
  def []=(key, value)
38
- @tags[key.to_s] = value
37
+ @tag_context[key] = value
38
+ end
39
+
40
+ # Remove tags from the context.
41
+ #
42
+ # @param keys [Array<String, Symbol>] The tag keys to remove.
43
+ # @return [void]
44
+ def delete(*keys)
45
+ @tag_context.delete(*keys)
39
46
  end
40
47
 
41
48
  # Clear all the context data.
@@ -64,11 +64,28 @@ module Lumberjack
64
64
  end
65
65
 
66
66
  # Return the tag with the specified name.
67
+ #
68
+ # @param name [String, Symbol] The tag name.
69
+ # @return [Object, nil] The tag value or nil if the tag does not exist.
67
70
  def tag(name)
68
- tags[name.to_s] if tags
71
+ TagContext.new(tags)[name]
72
+ end
73
+
74
+ # Helper method to expand the tags into a nested structure. Tags with dots in the name
75
+ # will be expanded into nested hashes.
76
+ #
77
+ # @return [Hash] The tags expanded into a nested structure.
78
+ #
79
+ # @example
80
+ # entry = Lumberjack::LogEntry.new(Time.now, Logger::INFO, "test", "app", 1500, "a.b.c" => 1, "a.b.d" => 2)
81
+ # entry.nested_tags # => {"a" => {"b" => {"c" => 1, "d" => 2}}}
82
+ def nested_tags
83
+ Utils.expand_tags(tags)
69
84
  end
70
85
 
71
86
  # Return true if the log entry has no message and no tags.
87
+ #
88
+ # @return [Boolean] True if the log entry is empty, false otherwise.
72
89
  def empty?
73
90
  (message.nil? || message == "") && (tags.nil? || tags.empty?)
74
91
  end
@@ -223,6 +223,7 @@ module Lumberjack
223
223
  end
224
224
 
225
225
  progname ||= self.progname
226
+ message_tags = Utils.flatten_tags(message_tags) if message_tags
226
227
 
227
228
  current_tags = self.tags
228
229
  tags = nil unless tags.is_a?(Hash)
@@ -507,16 +508,16 @@ module Lumberjack
507
508
  # @param [Hash] tags The tags to set.
508
509
  # @return [void]
509
510
  def tag(tags, &block)
510
- tags = Tags.stringify_keys(tags)
511
511
  thread_tags = thread_local_value(:lumberjack_logger_tags)
512
512
  if block
513
- merged_tags = (thread_tags ? thread_tags.merge(tags) : tags.dup)
513
+ merged_tags = (thread_tags ? thread_tags.dup : {})
514
+ TagContext.new(merged_tags).tag(tags)
514
515
  push_thread_local_value(:lumberjack_logger_tags, merged_tags, &block)
515
516
  elsif thread_tags
516
- thread_tags.merge!(tags)
517
+ TagContext.new(thread_tags).tag(tags)
517
518
  nil
518
519
  else
519
- Lumberjack::Utils.deprecated("Lumberjack::Logger#tag", "Lumberjack::Logger#tag must be called with a block or inside a context block. In version 2.0 it will no longer be used for setting global tags. Use Lumberjack::Logger#tag_globally instead.") do
520
+ Utils.deprecated("Lumberjack::Logger#tag", "Lumberjack::Logger#tag must be called with a block or inside a context block. In version 2.0 it will no longer be used for setting global tags. Use Lumberjack::Logger#tag_globally instead.") do
520
521
  tag_globally(tags)
521
522
  end
522
523
  end
@@ -525,10 +526,21 @@ module Lumberjack
525
526
  # Set up a context block for the logger. All tags added within the block will be cleared when
526
527
  # the block exits.
527
528
  #
529
+ # @param [Proc] block The block to execute with the tag context.
530
+ # @return [TagContext] If no block is passed, then a Lumberjack::TagContext is returned that can be used
531
+ # to interact with the tags (add, remove, etc.).
532
+ # @yield [TagContext] If a block is passed, it will be yielded a TagContext object that can be used to
533
+ # add or remove tags within the context.
528
534
  def context(&block)
529
- thread_tags = thread_local_value(:lumberjack_logger_tags)&.dup
530
- thread_tags ||= {}
531
- push_thread_local_value(:lumberjack_logger_tags, thread_tags, &block)
535
+ if block
536
+ thread_tags = thread_local_value(:lumberjack_logger_tags)&.dup
537
+ thread_tags ||= {}
538
+ push_thread_local_value(:lumberjack_logger_tags, thread_tags) do
539
+ block.call(TagContext.new(thread_tags))
540
+ end
541
+ else
542
+ TagContext.new(thread_local_value(:lumberjack_logger_tags) || {})
543
+ end
532
544
  end
533
545
 
534
546
  # Add global tags to the logger that will appear on all log entries.
@@ -536,24 +548,19 @@ module Lumberjack
536
548
  # @param [Hash] tags The tags to set.
537
549
  # @return [void]
538
550
  def tag_globally(tags)
539
- tags = Tags.stringify_keys(tags)
540
- @tags.merge!(tags)
551
+ TagContext.new(@tags).tag(tags)
541
552
  nil
542
553
  end
543
554
 
544
- # Remove a tag from the current tag context. If this is called inside a block to a
545
- # call to `tag`, the tags will only be removed for the duration of that block. Otherwise
546
- # they will be removed from the global tags.
555
+ # Remove a tag from the current tag context. If this is called inside a tag context,
556
+ # the tags will only be removed for the duration of that block. Otherwise they will be removed
557
+ # from the global tags.
547
558
  #
548
559
  # @param [Array<String, Symbol>] tag_names The tags to remove.
549
560
  # @return [void]
550
561
  def remove_tag(*tag_names)
551
- thread_tags = thread_local_value(:lumberjack_logger_tags)
552
- if thread_tags
553
- tag_names.each { |name| thread_tags.delete(name.to_s) }
554
- else
555
- tag_names.each { |name| @tags.delete(name.to_s) }
556
- end
562
+ tags = thread_local_value(:lumberjack_logger_tags) || @tags
563
+ TagContext.new(tags).delete(*tag_names)
557
564
  end
558
565
 
559
566
  # Return all tags in scope on the logger including global tags set on the Lumberjack
@@ -575,24 +582,8 @@ module Lumberjack
575
582
  # @param [String, Symbol] name The name of the tag to get.
576
583
  # @return [Object, nil] The value of the tag or nil if the tag does not exist.
577
584
  def tag_value(name)
578
- all_tags = tags
579
- return nil if tags.empty?
580
-
581
585
  name = name.join(".") if name.is_a?(Array)
582
- name = name.to_s
583
- return all_tags[name] if all_tags.include?(name)
584
-
585
- flattened_tags = Lumberjack::Utils.flatten_tags(all_tags)
586
- return flattened_tags[name] if flattened_tags.include?(name)
587
-
588
- flattened_tags.keys.select { |key| key.include?(".") }.each do |key|
589
- parts = key.split(".")
590
- while (subkey = parts.pop)
591
- flattened_tags[parts.join(".")] = {subkey => flattened_tags[(parts + [subkey]).join(".")]}
592
- end
593
- end
594
-
595
- flattened_tags[name]
586
+ TagContext.new(tags)[name]
596
587
  end
597
588
 
598
589
  # Remove all tags on the current logger and logging context within a block.
@@ -653,15 +644,12 @@ module Lumberjack
653
644
  # Merge a tags hash into an existing tags hash.
654
645
  def merge_tags(current_tags, tags)
655
646
  if current_tags.nil? || current_tags.empty?
656
- tags = Tags.stringify_keys(tags) unless tags.nil?
647
+ tags
648
+ elsif tags.nil?
649
+ current_tags
657
650
  else
658
- tags = if tags.nil?
659
- current_tags.dup
660
- else
661
- current_tags.merge(Tags.stringify_keys(tags))
662
- end
651
+ current_tags.merge(tags)
663
652
  end
664
- tags
665
653
  end
666
654
 
667
655
  # Set a local value for a thread tied to this object.
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Lumberjack
4
+ # A tag context provides an interface for manipulating a tag hash.
5
+ class TagContext
6
+ def initialize(tags)
7
+ @tags = tags
8
+ end
9
+
10
+ # Merge new tags into the context tags. Tag values will be flattened using dot notation
11
+ # on the keys. So `{ a: { b: 'c' } }` will become `{ 'a.b' => 'c' }`.
12
+ #
13
+ # If a block is given, then the tags will only be added for the duration of the block.
14
+ #
15
+ # @param tags [Hash] The tags to set.
16
+ # @return [void]
17
+ def tag(tags)
18
+ @tags.merge!(Utils.flatten_tags(tags))
19
+ end
20
+
21
+ # Get a tag value.
22
+ #
23
+ # @param name [String, Symbol] The tag key.
24
+ # @return [Object] The tag value.
25
+ def [](name)
26
+ return nil if @tags.empty?
27
+
28
+ name = name.to_s
29
+ return @tags[name] if @tags.include?(name)
30
+
31
+ # Check for partial matches in dot notation and return the hash representing the partial match.
32
+ prefix_key = "#{name}."
33
+ matching_tags = {}
34
+ @tags.each do |key, value|
35
+ if key.start_with?(prefix_key)
36
+ # Remove the prefix to get the relative key
37
+ relative_key = key[prefix_key.length..-1]
38
+ matching_tags[relative_key] = value
39
+ end
40
+ end
41
+
42
+ return nil if matching_tags.empty?
43
+ matching_tags
44
+ end
45
+
46
+ # Set a tag value.
47
+ #
48
+ # @param name [String, Symbol] The tag name.
49
+ # @param value [Object] The tag value.
50
+ # @return [void]
51
+ def []=(name, value)
52
+ if value.is_a?(Hash)
53
+ @tags.merge!(Utils.flatten_tags(name => value))
54
+ else
55
+ @tags[name.to_s] = value
56
+ end
57
+ end
58
+
59
+ # Remove tags from the context.
60
+ #
61
+ # @param names [Array<String, Symbol>] The tag names to remove.
62
+ # @return [void]
63
+ def delete(*names)
64
+ names.each do |name|
65
+ prefix_key = "#{name}."
66
+ @tags.delete_if { |k, _| k == name.to_s || k.start_with?(prefix_key) }
67
+ end
68
+ nil
69
+ end
70
+
71
+ # Return a copy of the tags as a hash.
72
+ #
73
+ # @return [Hash]
74
+ def to_h
75
+ @tags.dup
76
+ end
77
+ end
78
+ end
@@ -110,15 +110,7 @@ module Lumberjack
110
110
  def flatten_tags(tag_hash)
111
111
  return {} unless tag_hash.is_a?(Hash)
112
112
 
113
- tag_hash.each_with_object({}) do |(key, value), result|
114
- if value.is_a?(Hash)
115
- value.each do |sub_key, sub_value|
116
- result["#{key}.#{sub_key}"] = sub_value
117
- end
118
- else
119
- result[key.to_s] = value
120
- end
121
- end
113
+ flatten_hash_recursive(tag_hash)
122
114
  end
123
115
 
124
116
  # Expand a hash of tags that may contain nested hashes or dot notation keys. Dot notation tags
@@ -138,6 +130,17 @@ module Lumberjack
138
130
 
139
131
  private
140
132
 
133
+ def flatten_hash_recursive(hash, prefix = nil)
134
+ hash.each_with_object({}) do |(key, value), result|
135
+ full_key = prefix ? "#{prefix}.#{key}" : key.to_s
136
+ if value.is_a?(Hash)
137
+ result.merge!(flatten_hash_recursive(value, full_key))
138
+ else
139
+ result[full_key] = value
140
+ end
141
+ end
142
+ end
143
+
141
144
  def slugify(str)
142
145
  return nil if str.nil?
143
146
 
data/lib/lumberjack.rb CHANGED
@@ -17,6 +17,7 @@ module Lumberjack
17
17
  require_relative "lumberjack/device"
18
18
  require_relative "lumberjack/logger"
19
19
  require_relative "lumberjack/tags"
20
+ require_relative "lumberjack/tag_context"
20
21
  require_relative "lumberjack/tag_formatter"
21
22
  require_relative "lumberjack/tagged_logger_support"
22
23
  require_relative "lumberjack/tagged_logging"
@@ -57,6 +58,7 @@ module Lumberjack
57
58
  end
58
59
 
59
60
  # Contexts can be used to store tags that will be attached to all log entries in the block.
61
+ # The context will apply to all Lumberjack loggers that are used within the block.
60
62
  #
61
63
  # If this method is called with a block, it will set a logging context for the scope of a block.
62
64
  # If there is already a context in scope, a new one will be created that inherits
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lumberjack
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.4
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
@@ -68,6 +68,7 @@ files:
68
68
  - lib/lumberjack/rack/request_id.rb
69
69
  - lib/lumberjack/rack/unit_of_work.rb
70
70
  - lib/lumberjack/severity.rb
71
+ - lib/lumberjack/tag_context.rb
71
72
  - lib/lumberjack/tag_formatter.rb
72
73
  - lib/lumberjack/tagged_logger_support.rb
73
74
  - lib/lumberjack/tagged_logging.rb