summaryrefslogtreecommitdiffstats
path: root/lib/puppet/util/log.rb
blob: e841c7addc28449165f991b74b3d9c42b6b7afe1 (plain)
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
require 'puppet/util/tagging'
require 'puppet/util/classgen'

# Pass feedback to the user.  Log levels are modeled after syslog's, and it is
# expected that that will be the most common log destination.  Supports
# multiple destinations, one of which is a remote server.
class Puppet::Util::Log
    include Puppet::Util
    extend Puppet::Util::ClassGen
    include Puppet::Util::Tagging

    @levels = [:debug,:info,:notice,:warning,:err,:alert,:emerg,:crit]
    @loglevel = 2

    @desttypes = {}

    # Create a new destination type.
    def self.newdesttype(name, options = {}, &block)

                    dest = genclass(
                name, :parent => Puppet::Util::Log::Destination, :prefix => "Dest",
            :block => block,
            :hash => @desttypes,
        
            :attributes => options
        )
        dest.match(dest.name)

        dest
    end

    require 'puppet/util/log/destination'
    require 'puppet/util/log/destinations'

    @destinations = {}

    @queued = []

    class << self
        include Puppet::Util
        include Puppet::Util::ClassGen

        attr_reader :desttypes
    end

    # Reset log to basics.  Basically just flushes and closes files and
    # undefs other objects.
    def Log.close(destination)
        if @destinations.include?(destination)
            @destinations[destination].flush if @destinations[destination].respond_to?(:flush)
            @destinations[destination].close if @destinations[destination].respond_to?(:close)
            @destinations.delete(destination)
        end
    end

    def self.close_all
        destinations.keys.each { |dest|
            close(dest)
        }
    end

    # Flush any log destinations that support such operations.
    def Log.flush
        @destinations.each { |type, dest|
            dest.flush if dest.respond_to?(:flush)
        }
    end

    # Create a new log message.  The primary role of this method is to
    # avoid creating log messages below the loglevel.
    def Log.create(hash)
        raise Puppet::DevError, "Logs require a level" unless hash.include?(:level)
        raise Puppet::DevError, "Invalid log level #{hash[:level]}" unless @levels.index(hash[:level])
        @levels.index(hash[:level]) >= @loglevel ? Puppet::Util::Log.new(hash) : nil
    end

    def Log.destinations
        @destinations
    end

    # Yield each valid level in turn
    def Log.eachlevel
        @levels.each { |level| yield level }
    end

    # Return the current log level.
    def Log.level
        @levels[@loglevel]
    end

    # Set the current log level.
    def Log.level=(level)
        level = level.intern unless level.is_a?(Symbol)

        raise Puppet::DevError, "Invalid loglevel #{level}" unless @levels.include?(level)

        @loglevel = @levels.index(level)
    end

    def Log.levels
        @levels.dup
    end

    # Create a new log destination.
    def Log.newdestination(dest)
        # Each destination can only occur once.
        if @destinations.find { |name, obj| obj.name == dest }
            return
        end

        name, type = @desttypes.find do |name, klass|
            klass.match?(dest)
        end

        raise Puppet::DevError, "Unknown destination type #{dest}" unless type

        begin
            if type.instance_method(:initialize).arity == 1
                @destinations[dest] = type.new(dest)
            else
                @destinations[dest] = type.new()
            end
            flushqueue
            @destinations[dest]
        rescue => detail
            puts detail.backtrace if Puppet[:debug]

            # If this was our only destination, then add the console back in.
            newdestination(:console) if @destinations.empty? and (dest != :console and dest != "console")
        end
    end

    # Route the actual message. FIXME There are lots of things this method
    # should do, like caching and a bit more.  It's worth noting that there's
    # a potential for a loop here, if the machine somehow gets the destination set as
    # itself.
    def Log.newmessage(msg)
        return if @levels.index(msg.level) < @loglevel

        queuemessage(msg) if @destinations.length == 0

        @destinations.each do |name, dest|
            threadlock(dest) do
                dest.handle(msg)
            end
        end
    end

    def Log.queuemessage(msg)
        @queued.push(msg)
    end

    def Log.flushqueue
        return unless @destinations.size >= 1
        @queued.each do |msg|
            Log.newmessage(msg)
        end
        @queued.clear
    end

    def Log.sendlevel?(level)
        @levels.index(level) >= @loglevel
    end

    # Reopen all of our logs.
    def Log.reopen
        Puppet.notice "Reopening log files"
        types = @destinations.keys
        @destinations.each { |type, dest|
            dest.close if dest.respond_to?(:close)
        }
        @destinations.clear
        # We need to make sure we always end up with some kind of destination
        begin
            types.each { |type|
                Log.newdestination(type)
            }
        rescue => detail
            if @destinations.empty?
                Log.newdestination(:syslog)
                Puppet.err detail.to_s
            end
        end
    end

    # Is the passed level a valid log level?
    def self.validlevel?(level)
        @levels.include?(level)
    end

    attr_accessor :time, :remote, :file, :line, :version, :source
    attr_reader :level, :message

    def initialize(args)
        self.level = args[:level]
        self.message = args[:message]
        self.source = args[:source] || "Puppet"

        @time = Time.now

        if tags = args[:tags]
            tags.each { |t| self.tag(t) }
        end

        [:file, :line, :version].each do |attr|
            next unless value = args[attr]
            send(attr.to_s + "=", value)
        end

        Log.newmessage(self)
    end

    def message=(msg)
        raise ArgumentError, "Puppet::Util::Log requires a message" unless msg
        @message = msg.to_s
    end

    def level=(level)
        raise ArgumentError, "Puppet::Util::Log requires a log level" unless level
        @level = level.to_sym
        raise ArgumentError, "Invalid log level #{@level}" unless self.class.validlevel?(@level)

        # Tag myself with my log level
        tag(level)
    end

    # If they pass a source in to us, we make sure it is a string, and
    # we retrieve any tags we can.
    def source=(source)
        if source.respond_to?(:source_descriptors)
            descriptors = source.source_descriptors
            @source = descriptors[:path]

            descriptors[:tags].each { |t| tag(t) }

            [:file, :line, :version].each do |param|
                next unless descriptors[param]
                send(param.to_s + "=", descriptors[param])
            end
        else
            @source = source.to_s
        end
    end

    def to_report
        "#{time} #{source} (#{level}): #{to_s}"
    end

    def to_s
        message
    end
end

# This is for backward compatibility from when we changed the constant to Puppet::Util::Log
# because the reports include the constant name.  Apparently the alias was created in
# March 2007, should could probably be removed soon.
Puppet::Log = Puppet::Util::Log