lumberjack 1.2.7 → 1.4.2

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/ARCHITECTURE.md +244 -0
  3. data/CHANGELOG.md +251 -56
  4. data/README.md +197 -62
  5. data/VERSION +1 -1
  6. data/lib/lumberjack/context.rb +25 -5
  7. data/lib/lumberjack/device/date_rolling_log_file.rb +17 -8
  8. data/lib/lumberjack/device/log_file.rb +14 -7
  9. data/lib/lumberjack/device/multi.rb +8 -7
  10. data/lib/lumberjack/device/null.rb +2 -2
  11. data/lib/lumberjack/device/rolling_log_file.rb +46 -22
  12. data/lib/lumberjack/device/size_rolling_log_file.rb +10 -10
  13. data/lib/lumberjack/device/writer.rb +45 -21
  14. data/lib/lumberjack/device.rb +28 -13
  15. data/lib/lumberjack/formatter/date_time_formatter.rb +5 -5
  16. data/lib/lumberjack/formatter/exception_formatter.rb +4 -4
  17. data/lib/lumberjack/formatter/id_formatter.rb +4 -3
  18. data/lib/lumberjack/formatter/inspect_formatter.rb +1 -1
  19. data/lib/lumberjack/formatter/multiply_formatter.rb +25 -0
  20. data/lib/lumberjack/formatter/object_formatter.rb +1 -1
  21. data/lib/lumberjack/formatter/pretty_print_formatter.rb +7 -5
  22. data/lib/lumberjack/formatter/redact_formatter.rb +23 -0
  23. data/lib/lumberjack/formatter/round_formatter.rb +21 -0
  24. data/lib/lumberjack/formatter/string_formatter.rb +1 -1
  25. data/lib/lumberjack/formatter/strip_formatter.rb +1 -1
  26. data/lib/lumberjack/formatter/structured_formatter.rb +3 -1
  27. data/lib/lumberjack/formatter/tagged_message.rb +39 -0
  28. data/lib/lumberjack/formatter/truncate_formatter.rb +27 -0
  29. data/lib/lumberjack/formatter.rb +96 -28
  30. data/lib/lumberjack/log_entry.rb +90 -19
  31. data/lib/lumberjack/logger.rb +318 -86
  32. data/lib/lumberjack/rack/context.rb +21 -2
  33. data/lib/lumberjack/rack/request_id.rb +8 -4
  34. data/lib/lumberjack/rack/unit_of_work.rb +7 -3
  35. data/lib/lumberjack/rack.rb +4 -4
  36. data/lib/lumberjack/severity.rb +22 -3
  37. data/lib/lumberjack/tag_context.rb +78 -0
  38. data/lib/lumberjack/tag_formatter.rb +124 -25
  39. data/lib/lumberjack/tagged_logger_support.rb +26 -12
  40. data/lib/lumberjack/tagged_logging.rb +1 -1
  41. data/lib/lumberjack/tags.rb +8 -8
  42. data/lib/lumberjack/template.rb +17 -5
  43. data/lib/lumberjack/utils.rb +182 -0
  44. data/lib/lumberjack.rb +64 -35
  45. data/lumberjack.gemspec +18 -15
  46. metadata +23 -54
@@ -0,0 +1,182 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+
5
+ module Lumberjack
6
+ module Utils
7
+ UNDEFINED = Object.new.freeze
8
+ private_constant :UNDEFINED
9
+
10
+ NON_SLUGGABLE_PATTERN = /[^A-Za-z0-9_.-]+/.freeze
11
+ private_constant :NON_SLUGGABLE_PATTERN
12
+
13
+ @deprecations = nil
14
+ @deprecations_lock = nil
15
+ @hostname = UNDEFINED
16
+
17
+ class << self
18
+ # Print warning when deprecated methods are called the first time. This can be disabled
19
+ # by setting the environment variable `LUMBERJACK_NO_DEPRECATION_WARNINGS` to "true".
20
+ # You can see every usage of a deprecated method along with a full stack trace by setting
21
+ # the environment variable `VERBOSE_LUMBERJACK_DEPRECATION_WARNING` to "true".
22
+ #
23
+ # @param method [String] The name of the deprecated method.
24
+ # @param message [String] Optional message to include in the warning.
25
+ # @yield The block to execute after the warning.
26
+ def deprecated(method, message)
27
+ @deprecations_lock ||= Mutex.new
28
+ unless @deprecations&.include?(method)
29
+ @deprecations_lock.synchronize do
30
+ @deprecations ||= {}
31
+ unless @deprecations.include?(method)
32
+ trace = caller[3..-1]
33
+ unless ENV["VERBOSE_LUMBERJACK_DEPRECATION_WARNING"] == "true"
34
+ trace = [trace.first]
35
+ @deprecations[method] = true
36
+ end
37
+ message = "DEPRECATION WARNING: #{message} Called from #{trace.join("\n")}"
38
+ warn(message) unless ENV["LUMBERJACK_NO_DEPRECATION_WARNINGS"] == "true"
39
+ end
40
+ end
41
+ end
42
+
43
+ yield
44
+ end
45
+
46
+ # Get the hostname of the machine. The returned value will be in UTF-8 encoding.
47
+ #
48
+ # @return [String] The hostname of the machine.
49
+ def hostname
50
+ if @hostname.equal?(UNDEFINED)
51
+ @hostname = force_utf8(Socket.gethostname)
52
+ end
53
+ @hostname
54
+ end
55
+
56
+ # Set the hostname to a specific value. If this is not specified, it will use the system hostname.
57
+ #
58
+ # @param hostname [String]
59
+ # @return [void]
60
+ def hostname=(hostname)
61
+ @hostname = force_utf8(hostname)
62
+ end
63
+
64
+ # Generate a global process ID that includes the hostname and process ID.
65
+ #
66
+ # @return [String] The global process ID.
67
+ def global_pid
68
+ if hostname
69
+ "#{hostname}-#{Process.pid}"
70
+ else
71
+ Process.pid.to_s
72
+ end
73
+ end
74
+
75
+ # Generate a global thread ID that includes the global process ID and the thread name.
76
+ #
77
+ # @return [String] The global thread ID.
78
+ def global_thread_id
79
+ "#{global_pid}-#{thread_name}"
80
+ end
81
+
82
+ # Get the name of a thread. The value will be based on the thread's name if it exists.
83
+ # Otherwise a unique id is generated based on the thread's object id. Only alphanumeric
84
+ # characters, underscores, dashes, and periods are kept in thread name.
85
+ #
86
+ # @param thread [Thread] The thread to get the name for. Defaults to the current thread.
87
+ # @return [String] The name of the thread.
88
+ def thread_name(thread = Thread.current)
89
+ thread.name ? slugify(thread.name) : thread.object_id.to_s(36)
90
+ end
91
+
92
+ # Force encode a string to UTF-8. Any invalid byte sequences will be
93
+ # ignored and replaced with an empty string.
94
+ #
95
+ # @param str [String] The string to encode.
96
+ # @return [String] The UTF-8 encoded string.
97
+ def force_utf8(str)
98
+ return nil if str.nil?
99
+
100
+ str.dup.force_encoding("ASCII-8BIT").encode("UTF-8", invalid: :replace, undef: :replace, replace: "")
101
+ end
102
+
103
+ # Flatten a tag hash to a single level hash with dot notation for nested keys.
104
+ #
105
+ # @param tag_hash [Hash] The hash to flatten.
106
+ # @return [Hash<String, Object>] The flattened hash.
107
+ # @example
108
+ # expand_tags(user: {id: 123, name: "Alice"}, action: "login")})
109
+ # # => {"user.id" => 123, "user.name" => "Alice", "action" => "login"}
110
+ def flatten_tags(tag_hash)
111
+ return {} unless tag_hash.is_a?(Hash)
112
+
113
+ flatten_hash_recursive(tag_hash)
114
+ end
115
+
116
+ # Expand a hash of tags that may contain nested hashes or dot notation keys. Dot notation tags
117
+ # will be expanded into nested hashes.
118
+ #
119
+ # @param tags [Hash] The hash of tags to expand.
120
+ # @return [Hash] The expanded hash with dot notation keys.
121
+ #
122
+ # @example
123
+ # expand_tags({"user.id" => 123, "user.name" => "Alice", "action" => "login"})
124
+ # # => {"user" => {"id" => 123, "name" => "Alice"}, "action" => "login"}
125
+ def expand_tags(tags)
126
+ return {} unless tags.is_a?(Hash)
127
+
128
+ expand_dot_notation_hash(tags)
129
+ end
130
+
131
+ private
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
+
144
+ def slugify(str)
145
+ return nil if str.nil?
146
+
147
+ str = str.gsub(NON_SLUGGABLE_PATTERN, "-")
148
+ str.delete_prefix!("-")
149
+ str.chomp!("-")
150
+ str
151
+ end
152
+
153
+ def expand_dot_notation_hash(hash, expanded = {})
154
+ return hash unless hash.is_a?(Hash)
155
+
156
+ hash.each do |key, value|
157
+ key = key.to_s
158
+ if key.include?(".")
159
+ main_key, sub_key = key.split(".", 2)
160
+ main_key_hash = expanded[main_key]
161
+ unless main_key_hash.is_a?(Hash)
162
+ main_key_hash = {}
163
+ expanded[main_key] = main_key_hash
164
+ end
165
+ expand_dot_notation_hash({sub_key => value}, main_key_hash)
166
+ elsif value.is_a?(Hash)
167
+ key_hash = expanded[key]
168
+ unless key_hash.is_a?(Hash)
169
+ key_hash = {}
170
+ expanded[key] = key_hash
171
+ end
172
+ expand_dot_notation_hash(value, key_hash)
173
+ else
174
+ expanded[key] = value
175
+ end
176
+ end
177
+
178
+ expanded
179
+ end
180
+ end
181
+ end
182
+ end
data/lib/lumberjack.rb CHANGED
@@ -1,27 +1,29 @@
1
- # frozen_string_literals: true
1
+ # frozen_string_literal: true
2
2
 
3
- require 'rbconfig'
4
- require 'time'
5
- require 'thread'
6
- require 'securerandom'
7
- require 'logger'
3
+ require "rbconfig"
4
+ require "time"
5
+ require "securerandom"
6
+ require "logger"
7
+ require "fiber"
8
8
 
9
9
  module Lumberjack
10
- LINE_SEPARATOR = (RbConfig::CONFIG['host_os'].match(/mswin/i) ? "\r\n" : "\n")
10
+ LINE_SEPARATOR = ((RbConfig::CONFIG["host_os"] =~ /mswin/i) ? "\r\n" : "\n")
11
11
 
12
- require_relative "lumberjack/severity.rb"
13
- require_relative "lumberjack/formatter.rb"
12
+ require_relative "lumberjack/severity"
13
+ require_relative "lumberjack/formatter"
14
14
 
15
- require_relative "lumberjack/context.rb"
16
- require_relative "lumberjack/log_entry.rb"
17
- require_relative "lumberjack/device.rb"
18
- require_relative "lumberjack/logger.rb"
19
- require_relative "lumberjack/tags.rb"
20
- require_relative "lumberjack/tag_formatter.rb"
21
- require_relative "lumberjack/tagged_logger_support.rb"
22
- require_relative "lumberjack/tagged_logging.rb"
23
- require_relative "lumberjack/template.rb"
24
- require_relative "lumberjack/rack.rb"
15
+ require_relative "lumberjack/context"
16
+ require_relative "lumberjack/log_entry"
17
+ require_relative "lumberjack/device"
18
+ require_relative "lumberjack/logger"
19
+ require_relative "lumberjack/tags"
20
+ require_relative "lumberjack/tag_context"
21
+ require_relative "lumberjack/tag_formatter"
22
+ require_relative "lumberjack/tagged_logger_support"
23
+ require_relative "lumberjack/tagged_logging"
24
+ require_relative "lumberjack/template"
25
+ require_relative "lumberjack/rack"
26
+ require_relative "lumberjack/utils"
25
27
 
26
28
  class << self
27
29
  # Define a unit of work within a block. Within the block supplied to this
@@ -33,20 +35,30 @@ module Lumberjack
33
35
  #
34
36
  # For the common use case of treating a single web request as a unit of work, see the
35
37
  # Lumberjack::Rack::UnitOfWork class.
38
+ #
39
+ # @param id [String] The id for the unit of work.
40
+ # @return [void]
41
+ # @deprecated Use tags instead. This will be removed in version 2.0.
36
42
  def unit_of_work(id = nil)
37
- id ||= SecureRandom.hex(6)
38
- context do
39
- context[:unit_of_work_id] = id
40
- yield
43
+ Lumberjack::Utils.deprecated("Lumberjack.unit_of_work", "Lumberjack.unit_of_work will be removed in version 2.0. Use Lumberjack::Logger#tag(unit_of_work: id) instead.") do
44
+ id ||= SecureRandom.hex(6)
45
+ context do
46
+ context[:unit_of_work_id] = id
47
+ yield
48
+ end
41
49
  end
42
50
  end
43
51
 
44
52
  # Get the UniqueIdentifier for the current unit of work.
53
+ #
54
+ # @return [String, nil] The id for the current unit of work.
55
+ # @deprecated Use tags instead. This will be removed in version 2.0.
45
56
  def unit_of_work_id
46
57
  context[:unit_of_work_id]
47
58
  end
48
59
 
49
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.
50
62
  #
51
63
  # If this method is called with a block, it will set a logging context for the scope of a block.
52
64
  # If there is already a context in scope, a new one will be created that inherits
@@ -54,36 +66,53 @@ module Lumberjack
54
66
  #
55
67
  # Otherwise, it will return the current context. If one doesn't exist, it will return a new one
56
68
  # but that context will not be in any scope.
57
- def context
69
+ #
70
+ # @return [Lumberjack::Context] The current context if called without a block.
71
+ def context(&block)
58
72
  current_context = Thread.current[:lumberjack_context]
59
- if block_given?
60
- Thread.current[:lumberjack_context] = Context.new(current_context)
61
- begin
62
- yield
63
- ensure
64
- Thread.current[:lumberjack_context] = current_context
65
- end
73
+ if block
74
+ use_context(Context.new(current_context), &block)
66
75
  else
67
76
  current_context || Context.new
68
77
  end
69
78
  end
70
-
79
+
80
+ # Set the context to use within a block.
81
+ #
82
+ # @param [Lumberjack::Context] context The context to use within the block.
83
+ # @return [Object] The result of the block.
84
+ def use_context(context, &block)
85
+ current_context = Thread.current[:lumberjack_context]
86
+ begin
87
+ Thread.current[:lumberjack_context] = (context || Context.new)
88
+ yield
89
+ ensure
90
+ Thread.current[:lumberjack_context] = current_context
91
+ end
92
+ end
93
+
71
94
  # Return true if inside a context block.
95
+ #
96
+ # @return [Boolean]
72
97
  def context?
73
98
  !!Thread.current[:lumberjack_context]
74
99
  end
75
100
 
76
101
  # Return the tags from the current context or nil if there are no tags.
102
+ #
103
+ # @return [Hash, nil]
77
104
  def context_tags
78
105
  context = Thread.current[:lumberjack_context]
79
- context.tags if context
106
+ context&.tags
80
107
  end
81
108
 
82
109
  # Set tags on the current context
110
+ #
111
+ # @param [Hash] tags The tags to set.
112
+ # @return [void]
83
113
  def tag(tags)
84
114
  context = Thread.current[:lumberjack_context]
85
- context.tag(tags) if context
115
+ context&.tag(tags)
86
116
  end
87
-
88
117
  end
89
118
  end
data/lumberjack.gemspec CHANGED
@@ -1,16 +1,22 @@
1
1
  Gem::Specification.new do |spec|
2
- spec.name = 'lumberjack'
3
- spec.version = File.read(File.expand_path("../VERSION", __FILE__)).strip
4
- spec.authors = ['Brian Durand']
5
- spec.email = ['bbdurand@gmail.com']
2
+ spec.name = "lumberjack"
3
+ spec.version = File.read(File.join(__dir__, "VERSION")).strip
4
+ spec.authors = ["Brian Durand"]
5
+ spec.email = ["bbdurand@gmail.com"]
6
6
 
7
- spec.summary = "A simple, powerful, and very fast logging utility that can be a drop in replacement for Logger or ActiveSupport::BufferedLogger."
7
+ spec.summary = "A simple, powerful, and fast logging utility with excellent structured logging support that can be a drop in replacement for the standard library Logger."
8
8
  spec.homepage = "https://github.com/bdurand/lumberjack"
9
9
  spec.license = "MIT"
10
10
 
11
+ spec.metadata = {
12
+ "homepage_uri" => spec.homepage,
13
+ "source_code_uri" => spec.homepage,
14
+ "changelog_uri" => "#{spec.homepage}/blob/main/CHANGELOG.md"
15
+ }
16
+
11
17
  # Specify which files should be added to the gem when it is released.
12
18
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
13
- ignore_files = %w(
19
+ ignore_files = %w[
14
20
  .
15
21
  Appraisals
16
22
  Gemfile
@@ -18,17 +24,14 @@ Gem::Specification.new do |spec|
18
24
  Rakefile
19
25
  gemfiles/
20
26
  spec/
21
- )
22
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
23
- `git ls-files -z`.split("\x0").reject{ |f| ignore_files.any?{ |path| f.start_with?(path) } }
27
+ ]
28
+ spec.files = Dir.chdir(__dir__) do
29
+ `git ls-files -z`.split("\x0").reject { |f| ignore_files.any? { |path| f.start_with?(path) } }
24
30
  end
25
31
 
26
- spec.require_paths = ['lib']
32
+ spec.require_paths = ["lib"]
27
33
 
28
- spec.required_ruby_version = '>= 2.3.0'
34
+ spec.required_ruby_version = ">= 2.5.0"
29
35
 
30
- spec.add_development_dependency("rspec", ["~> 3.0"])
31
- spec.add_development_dependency("timecop")
32
- spec.add_development_dependency "rake"
33
- spec.add_development_dependency "appraisal"
36
+ spec.add_development_dependency "bundler"
34
37
  end
metadata CHANGED
@@ -1,59 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lumberjack
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.7
4
+ version: 1.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Durand
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-30 00:00:00.000000000 Z
11
+ date: 2025-09-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: rspec
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '3.0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '3.0'
27
- - !ruby/object:Gem::Dependency
28
- name: timecop
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: rake
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
- - !ruby/object:Gem::Dependency
56
- name: appraisal
14
+ name: bundler
57
15
  requirement: !ruby/object:Gem::Requirement
58
16
  requirements:
59
17
  - - ">="
@@ -66,13 +24,14 @@ dependencies:
66
24
  - - ">="
67
25
  - !ruby/object:Gem::Version
68
26
  version: '0'
69
- description:
27
+ description:
70
28
  email:
71
29
  - bbdurand@gmail.com
72
30
  executables: []
73
31
  extensions: []
74
32
  extra_rdoc_files: []
75
33
  files:
34
+ - ARCHITECTURE.md
76
35
  - CHANGELOG.md
77
36
  - MIT_LICENSE.txt
78
37
  - README.md
@@ -92,11 +51,16 @@ files:
92
51
  - lib/lumberjack/formatter/exception_formatter.rb
93
52
  - lib/lumberjack/formatter/id_formatter.rb
94
53
  - lib/lumberjack/formatter/inspect_formatter.rb
54
+ - lib/lumberjack/formatter/multiply_formatter.rb
95
55
  - lib/lumberjack/formatter/object_formatter.rb
96
56
  - lib/lumberjack/formatter/pretty_print_formatter.rb
57
+ - lib/lumberjack/formatter/redact_formatter.rb
58
+ - lib/lumberjack/formatter/round_formatter.rb
97
59
  - lib/lumberjack/formatter/string_formatter.rb
98
60
  - lib/lumberjack/formatter/strip_formatter.rb
99
61
  - lib/lumberjack/formatter/structured_formatter.rb
62
+ - lib/lumberjack/formatter/tagged_message.rb
63
+ - lib/lumberjack/formatter/truncate_formatter.rb
100
64
  - lib/lumberjack/log_entry.rb
101
65
  - lib/lumberjack/logger.rb
102
66
  - lib/lumberjack/rack.rb
@@ -104,17 +68,22 @@ files:
104
68
  - lib/lumberjack/rack/request_id.rb
105
69
  - lib/lumberjack/rack/unit_of_work.rb
106
70
  - lib/lumberjack/severity.rb
71
+ - lib/lumberjack/tag_context.rb
107
72
  - lib/lumberjack/tag_formatter.rb
108
73
  - lib/lumberjack/tagged_logger_support.rb
109
74
  - lib/lumberjack/tagged_logging.rb
110
75
  - lib/lumberjack/tags.rb
111
76
  - lib/lumberjack/template.rb
77
+ - lib/lumberjack/utils.rb
112
78
  - lumberjack.gemspec
113
79
  homepage: https://github.com/bdurand/lumberjack
114
80
  licenses:
115
81
  - MIT
116
- metadata: {}
117
- post_install_message:
82
+ metadata:
83
+ homepage_uri: https://github.com/bdurand/lumberjack
84
+ source_code_uri: https://github.com/bdurand/lumberjack
85
+ changelog_uri: https://github.com/bdurand/lumberjack/blob/main/CHANGELOG.md
86
+ post_install_message:
118
87
  rdoc_options: []
119
88
  require_paths:
120
89
  - lib
@@ -122,16 +91,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
122
91
  requirements:
123
92
  - - ">="
124
93
  - !ruby/object:Gem::Version
125
- version: 2.3.0
94
+ version: 2.5.0
126
95
  required_rubygems_version: !ruby/object:Gem::Requirement
127
96
  requirements:
128
97
  - - ">="
129
98
  - !ruby/object:Gem::Version
130
99
  version: '0'
131
100
  requirements: []
132
- rubygems_version: 3.0.3
133
- signing_key:
101
+ rubygems_version: 3.4.10
102
+ signing_key:
134
103
  specification_version: 4
135
- summary: A simple, powerful, and very fast logging utility that can be a drop in replacement
136
- for Logger or ActiveSupport::BufferedLogger.
104
+ summary: A simple, powerful, and fast logging utility with excellent structured logging
105
+ support that can be a drop in replacement for the standard library Logger.
137
106
  test_files: []