File: logging.rb

package info (click to toggle)
puppet-agent 7.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 19,092 kB
  • sloc: ruby: 245,074; sh: 456; makefile: 38; xml: 33
file content (300 lines) | stat: -rw-r--r-- 11,740 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
# A module to make logging a bit easier.
require_relative '../../puppet/util/log'
require_relative '../../puppet/error'

module Puppet::Util
module Logging

  def send_log(level, message)
    Puppet::Util::Log.create({:level => level, :source => log_source, :message => message}.merge(log_metadata))
  end

  # Create a method for each log level.
  Puppet::Util::Log.eachlevel do |level|
    # handle debug a special way for performance reasons
    next if level == :debug
    define_method(level) do |args|
      args = args.join(" ") if args.is_a?(Array)
      send_log(level, args)
    end
  end

  # Output a debug log message if debugging is on (but only then)
  # If the output is anything except a static string, give the debug
  # a block - it will be called with all other arguments, and is expected
  # to return the single string result.
  #
  # Use a block at all times for increased performance.
  #
  # @example This takes 40% of the time compared to not using a block
  #  Puppet.debug { "This is a string that interpolated #{x} and #{y} }"
  #
  def debug(*args)
    return nil unless Puppet::Util::Log.level == :debug
    if block_given?
      send_log(:debug, yield(*args))
    else
      send_log(:debug, args.join(" "))
    end
  end

  # Log an exception via Puppet.err.  Will also log the backtrace if Puppet[:trace] is set.
  # Parameters:
  # [exception] an Exception to log
  # [message] an optional String overriding the message to be logged; by default, we log Exception.message.
  #    If you pass a String here, your string will be logged instead.  You may also pass nil if you don't
  #    wish to log a message at all; in this case it is likely that you are only calling this method in order
  #    to take advantage of the backtrace logging.
  def log_exception(exception, message = :default, options = {})
    level = options[:level] || :err
    combined_trace = Puppet[:trace] || options[:trace]
    puppet_trace = Puppet[:puppet_trace] || options[:puppet_trace]

    if message == :default && exception.is_a?(Puppet::ParseErrorWithIssue)
      # Retain all detailed info and keep plain message and stacktrace separate
      backtrace = build_exception_trace(exception, combined_trace, puppet_trace)
      Puppet::Util::Log.create({
          :level => level,
          :source => log_source,
          :message => exception.basic_message,
          :issue_code => exception.issue_code,
          :backtrace => backtrace.empty? ? nil : backtrace,
          :file => exception.file,
          :line => exception.line,
          :pos => exception.pos,
          :environment => exception.environment,
          :node => exception.node
        }.merge(log_metadata))
    else
      send_log(level, format_exception(exception, message, combined_trace, puppet_trace))
    end
  end

  def build_exception_trace(exception, combined_trace = true, puppet_trace = false)
    built_trace = format_backtrace(exception, combined_trace, puppet_trace)

    if exception.respond_to?(:original)
      original =  exception.original
      unless original.nil?
        built_trace << _('Wrapped exception:')
        built_trace << original.message
        built_trace += build_exception_trace(original, combined_trace, puppet_trace)
      end
    end

    built_trace
  end
  private :build_exception_trace

  def format_exception(exception, message = :default, combined_trace = true, puppet_trace = false)
    arr = []
    case message
    when :default
      arr << exception.message
    when nil
      # don't log anything if they passed a nil; they are just calling for the optional backtrace logging
    else
      arr << message
    end

    arr += format_backtrace(exception, combined_trace, puppet_trace)

    if exception.respond_to?(:original) and exception.original
      arr << _("Wrapped exception:")
      arr << format_exception(exception.original, :default, combined_trace, puppet_trace)
    end

    arr.flatten.join("\n")
  end

  def format_backtrace(exception, combined_trace, puppet_trace)
    puppetstack = exception.respond_to?(:puppetstack) ? exception.puppetstack : []

    if combined_trace and exception.backtrace
      Puppet::Util.format_backtrace_array(exception.backtrace, puppetstack)
    elsif puppet_trace && !puppetstack.empty?
      Puppet::Util.format_backtrace_array(puppetstack)
    else
      []
    end
  end

  def log_and_raise(exception, message)
    log_exception(exception, message)
    raise exception, message + "\n" + exception.to_s, exception.backtrace
  end

  class DeprecationWarning < Exception; end

  # Logs a warning indicating that the Ruby code path is deprecated.  Note that
  # this method keeps track of the offending lines of code that triggered the
  # deprecation warning, and will only log a warning once per offending line of
  # code.  It will also stop logging deprecation warnings altogether after 100
  # unique deprecation warnings have been logged.  Finally, if
  # Puppet[:disable_warnings] includes 'deprecations', it will squelch all
  # warning calls made via this method.
  #
  # @param message [String] The message to log (logs via warning)
  # @param key [String] Optional key to mark the message as unique. If not
  #   passed in, the originating call line will be used instead.
  def deprecation_warning(message, key = nil)
    issue_deprecation_warning(message, key, nil, nil, true)
  end

  # Logs a warning whose origin comes from Puppet source rather than somewhere
  # internal within Puppet.  Otherwise the same as deprecation_warning()
  #
  # @param message [String] The message to log (logs via warning)
  # @param options [Hash]
  # @option options [String] :file File we are warning from
  # @option options [Integer] :line Line number we are warning from
  # @option options [String] :key (:file + :line) Alternative key used to mark
  #   warning as unique
  #
  # Either :file and :line and/or :key must be passed.
  def puppet_deprecation_warning(message, options = {})
    key = options[:key]
    file = options[:file]
    line = options[:line]
    #TRANSLATORS the literals ":file", ":line", and ":key" should not be translated
    raise Puppet::DevError, _("Need either :file and :line, or :key") if (key.nil?) && (file.nil? || line.nil?)

    key ||= "#{file}:#{line}"
    issue_deprecation_warning(message, key, file, line, false)
  end

  # Logs a (non deprecation) warning once for a given key.
  #
  # @param kind [String] The kind of warning. The
  #   kind must be one of the defined kinds for the Puppet[:disable_warnings] setting.
  # @param message [String] The message to log (logs via warning)
  # @param key [String] Key used to make this warning unique
  # @param file [String,:default,nil] the File related to the warning
  # @param line [Integer,:default,nil] the Line number related to the warning
  #   warning as unique
  # @param level [Symbol] log level to use, defaults to :warning
  #
  # Either :file and :line and/or :key must be passed.
  def warn_once(kind, key, message, file = nil, line = nil, level = :warning)
    return if Puppet[:disable_warnings].include?(kind)
    $unique_warnings ||= {}
    if $unique_warnings.length < 100 then
      if (! $unique_warnings.has_key?(key)) then
        $unique_warnings[key] = message
        call_trace = if file == :default and line == :default
                       # Suppress the file and line number output
                       ''
                     else
                       error_location_str = Puppet::Util::Errors.error_location(file, line)
                       if error_location_str.empty?
                         "\n   " + _('(file & line not available)')
                       else
                         "\n   %{error_location}" % { error_location: error_location_str }
                       end
                     end
        send_log(level, "#{message}#{call_trace}")
      end
    end
  end

  def get_deprecation_offender()
    # we have to put this in its own method to simplify testing; we need to be able to mock the offender results in
    # order to test this class, and our framework does not appear to enjoy it if you try to mock Kernel.caller
    #
    # let's find the offending line;  we need to jump back up the stack a few steps to find the method that called
    #  the deprecated method
    if Puppet[:trace]
      caller(3)
    else
      [caller(3, 1).first]
    end
  end

  def clear_deprecation_warnings
    $unique_warnings.clear if $unique_warnings
    $deprecation_warnings.clear if $deprecation_warnings
  end

  # TODO: determine whether there might be a potential use for adding a puppet configuration option that would
  # enable this deprecation logging.

  # utility method that can be called, e.g., from spec_helper config.after, when tracking down calls to deprecated
  # code.
  # Parameters:
  # [deprecations_file] relative or absolute path of a file to log the deprecations to
  # [pattern] (default nil) if specified, will only log deprecations whose message matches the provided pattern
  def log_deprecations_to_file(deprecations_file, pattern = nil)
    # this method may get called lots and lots of times (e.g., from spec_helper config.after) without the global
    # list of deprecation warnings being cleared out.  We don't want to keep logging the same offenders over and over,
    # so, we need to keep track of what we've logged.
    #
    # It'd be nice if we could just clear out the list of deprecation warnings, but then the very next spec might
    # find the same offender, and we'd end up logging it again.
    $logged_deprecation_warnings ||= {}

    # Deprecation messages are UTF-8 as they are produced by Ruby
    Puppet::FileSystem.open(deprecations_file, nil, "a:UTF-8") do |f|
      if ($deprecation_warnings) then
        $deprecation_warnings.each do |offender, message|
          if (! $logged_deprecation_warnings.has_key?(offender)) then
            $logged_deprecation_warnings[offender] = true
            if ((pattern.nil?) || (message =~ pattern)) then
              f.puts(message)
              f.puts(offender)
              f.puts()
            end
          end
        end
      end
    end
  end

  # Sets up Facter logging.
  # This method causes Facter output to be forwarded to Puppet.
  def self.setup_facter_logging!
    Puppet.runtime[:facter]
    true
  end

  private

  def issue_deprecation_warning(message, key, file, line, use_caller)
    return if Puppet[:disable_warnings].include?('deprecations')
    $deprecation_warnings ||= {}
    if $deprecation_warnings.length < 100
      key ||= (offender = get_deprecation_offender)
      unless $deprecation_warnings.has_key?(key)
        $deprecation_warnings[key] = message
        # split out to allow translation
        call_trace = if use_caller
                       _("(location: %{location})") % { location: (offender || get_deprecation_offender).join('; ') }
                     else
                       Puppet::Util::Errors.error_location_with_unknowns(file, line)
                     end
        warning("%{message}\n   %{call_trace}" % { message: message, call_trace: call_trace })
      end
    end
  end

  def is_resource?
    defined?(Puppet::Type) && is_a?(Puppet::Type)
  end

  def is_resource_parameter?
    defined?(Puppet::Parameter) && is_a?(Puppet::Parameter)
  end

  def log_metadata
    [:file, :line, :tags].inject({}) do |result, attr|
      result[attr] = send(attr) if respond_to?(attr)
      result
    end
  end

  def log_source
    # We need to guard the existence of the constants, since this module is used by the base Puppet module.
    (is_resource? or is_resource_parameter?) and respond_to?(:path) and return path.to_s
    to_s
  end
end
end