bluepill 0.1.3 → 0.1.4
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 +5 -5
- data/CHANGELOG.md +820 -0
- data/CONTRIBUTING.md +31 -0
- data/LICENSE +1 -1
- data/README.md +64 -29
- data/bin/bluepill +1 -1
- data/bluepill.gemspec +17 -6
- data/lib/bluepill/application.rb +309 -41
- data/lib/bluepill/condition_watch.rb +103 -8
- data/lib/bluepill/controller.rb +124 -13
- data/lib/bluepill/dsl/app_proxy.rb +102 -3
- data/lib/bluepill/dsl/process_factory.rb +183 -60
- data/lib/bluepill/dsl/process_proxy.rb +132 -14
- data/lib/bluepill/dsl.rb +27 -5
- data/lib/bluepill/group.rb +142 -9
- data/lib/bluepill/logger.rb +120 -5
- data/lib/bluepill/process.rb +571 -200
- data/lib/bluepill/process_commands.rb +262 -0
- data/lib/bluepill/process_conditions/always_true.rb +18 -4
- data/lib/bluepill/process_conditions/cpu_usage.rb +34 -0
- data/lib/bluepill/process_conditions/file_time.rb +40 -4
- data/lib/bluepill/process_conditions/http.rb +81 -23
- data/lib/bluepill/process_conditions/mem_usage.rb +61 -2
- data/lib/bluepill/process_conditions/process_condition.rb +43 -0
- data/lib/bluepill/process_conditions/running_time.rb +36 -1
- data/lib/bluepill/process_conditions/zombie_process.rb +26 -5
- data/lib/bluepill/process_conditions.rb +15 -1
- data/lib/bluepill/process_journal.rb +256 -104
- data/lib/bluepill/process_statistics.rb +33 -3
- data/lib/bluepill/socket.rb +63 -16
- data/lib/bluepill/system/commands.rb +286 -0
- data/lib/bluepill/system.rb +192 -228
- data/lib/bluepill/trigger.rb +110 -14
- data/lib/bluepill/triggers/flapping.rb +84 -3
- data/lib/bluepill/util/rotational_array.rb +24 -1
- data/lib/bluepill/version.rb +71 -19
- data/lib/bluepill.rb +25 -2
- data/sig/bluepill.rbs +926 -0
- data/sig/manifest.yaml +7 -0
- metadata +16 -34
- data/bin/sample_forking_server +0 -66
- data/lib/bluepill/application/client.rb +0 -6
- data/lib/bluepill/application/server.rb +0 -21
|
@@ -1,41 +1,159 @@
|
|
|
1
1
|
module Bluepill
|
|
2
|
+
# The self of a process block in a config file
|
|
3
|
+
#
|
|
4
|
+
# Any of the process's attributes can be set with `name = value`,
|
|
5
|
+
# `name value`, or `name!` for true, and read back with `name`. See
|
|
6
|
+
# {Process::CONFIGURABLE_ATTRIBUTES} for the attributes a process has.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# app.process('web') do |process|
|
|
10
|
+
# process.start_command = '/usr/bin/my_server'
|
|
11
|
+
# process.daemonize!
|
|
12
|
+
# process.checks :mem_usage, every: 10.seconds, below: 100.megabytes
|
|
13
|
+
# end
|
|
14
|
+
#
|
|
15
|
+
# @api public
|
|
2
16
|
class ProcessProxy
|
|
3
|
-
|
|
17
|
+
# Return the attributes that the block set, by name
|
|
18
|
+
#
|
|
19
|
+
# @return [Hash{Symbol => Object}] the attributes
|
|
20
|
+
#
|
|
21
|
+
# @api private
|
|
22
|
+
attr_reader :attributes
|
|
23
|
+
|
|
24
|
+
# Return the checks that the block added, with their options, by name
|
|
25
|
+
#
|
|
26
|
+
# @return [Hash{Symbol => Hash{Symbol => Object}}] the checks
|
|
27
|
+
#
|
|
28
|
+
# @api private
|
|
29
|
+
attr_reader :watches
|
|
30
|
+
|
|
31
|
+
# Return the process's name
|
|
32
|
+
#
|
|
33
|
+
# @return [String] the name
|
|
34
|
+
#
|
|
35
|
+
# @api private
|
|
36
|
+
attr_reader :name
|
|
37
|
+
|
|
38
|
+
# Create the proxy of a process, and run its block
|
|
39
|
+
#
|
|
40
|
+
# @param [String] process_name the process's name
|
|
41
|
+
# @param [Hash{Symbol => Object}] attributes the attributes the process
|
|
42
|
+
# starts with, which the block adds to
|
|
43
|
+
# @param [Proc] process_block the block that configures the process
|
|
44
|
+
#
|
|
45
|
+
# @return [void]
|
|
46
|
+
#
|
|
47
|
+
# @api private
|
|
4
48
|
def initialize(process_name, attributes, process_block)
|
|
5
49
|
@name = process_name
|
|
6
50
|
@attributes = attributes
|
|
7
51
|
@watches = {}
|
|
8
52
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
else
|
|
12
|
-
instance_exec(self, &process_block)
|
|
13
|
-
end
|
|
53
|
+
# instance_eval also passes the proxy to a block that takes an argument
|
|
54
|
+
instance_eval(&process_block)
|
|
14
55
|
end
|
|
15
56
|
|
|
57
|
+
# Set or read an attribute of the process
|
|
58
|
+
#
|
|
59
|
+
# A name ending in `=`, or one given a value, sets the attribute. A name
|
|
60
|
+
# ending in `!` sets it to true. A name alone reads an attribute that's
|
|
61
|
+
# set.
|
|
62
|
+
#
|
|
63
|
+
# @example
|
|
64
|
+
# process.start_command = '/usr/bin/my_server'
|
|
65
|
+
# process.stop_grace_time 10.seconds
|
|
66
|
+
# process.daemonize!
|
|
67
|
+
# process.start_command # => "/usr/bin/my_server"
|
|
68
|
+
#
|
|
69
|
+
# @param [Symbol] name the attribute's name
|
|
70
|
+
# @param [Array<Object>] args the value, if it's set
|
|
71
|
+
#
|
|
72
|
+
# @return [Object] the value that was set or read
|
|
73
|
+
#
|
|
74
|
+
# @raise [NoMethodError] for an attribute that isn't set, or more than one
|
|
75
|
+
# value
|
|
76
|
+
#
|
|
77
|
+
# @api public
|
|
16
78
|
def method_missing(name, *args)
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
79
|
+
attribute = name.to_s
|
|
80
|
+
case args.size
|
|
81
|
+
when 1
|
|
82
|
+
value, = args
|
|
83
|
+
@attributes[attribute.chomp('=').to_sym] = value
|
|
84
|
+
when 0
|
|
85
|
+
if attribute.end_with?('!')
|
|
86
|
+
@attributes[attribute.chomp('!').to_sym] = true
|
|
87
|
+
elsif @attributes.key?(name)
|
|
88
|
+
@attributes.fetch(name)
|
|
89
|
+
else
|
|
90
|
+
super
|
|
91
|
+
end
|
|
25
92
|
else
|
|
26
93
|
super
|
|
27
94
|
end
|
|
28
95
|
end
|
|
29
96
|
|
|
97
|
+
# Return whether the proxy answers a name through method_missing
|
|
98
|
+
#
|
|
99
|
+
# Ruby turns a String name into a Symbol before calling this.
|
|
100
|
+
#
|
|
101
|
+
# @param [Symbol] name the method's name
|
|
102
|
+
# @param [Boolean, nil] _include_private ignored
|
|
103
|
+
#
|
|
104
|
+
# @return [Boolean] true for a setter, a `name!`, or an attribute that's
|
|
105
|
+
# set
|
|
106
|
+
#
|
|
107
|
+
# @api private
|
|
108
|
+
def respond_to_missing?(name, _include_private)
|
|
109
|
+
['=', '!'].include?(name[-1]) || @attributes.key?(name)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Add a check of the process, or a trigger such as :flapping
|
|
113
|
+
#
|
|
114
|
+
# @example
|
|
115
|
+
# process.checks :cpu_usage, every: 10.seconds, below: 50, times: [3, 5]
|
|
116
|
+
# process.checks :flapping, times: 2, within: 30.seconds, retry_in: 7.seconds
|
|
117
|
+
#
|
|
118
|
+
# @param [Symbol] name the check's name, such as :mem_usage
|
|
119
|
+
# @param [Hash{Symbol => Object}] options the check's options, such as
|
|
120
|
+
# :every, :below and :times
|
|
121
|
+
#
|
|
122
|
+
# @return [Hash{Symbol => Object}] the options
|
|
123
|
+
#
|
|
124
|
+
# @api public
|
|
30
125
|
def checks(name, options = {})
|
|
31
126
|
@watches[name] = options
|
|
32
127
|
end
|
|
33
128
|
|
|
129
|
+
# Monitor the process's children too
|
|
130
|
+
#
|
|
131
|
+
# The block configures each child the way a process block configures a
|
|
132
|
+
# process. It must set a stop_command.
|
|
133
|
+
#
|
|
134
|
+
# @example
|
|
135
|
+
# process.monitor_children do |child|
|
|
136
|
+
# child.stop_command = 'kill -QUIT {{PID}}'
|
|
137
|
+
# child.checks :mem_usage, every: 10.seconds, below: 200.megabytes
|
|
138
|
+
# end
|
|
139
|
+
#
|
|
140
|
+
# @yield [child] configures each child
|
|
141
|
+
# @yieldparam [ProcessProxy] child the child's proxy, which is also self
|
|
142
|
+
# @yieldreturn [void]
|
|
143
|
+
#
|
|
144
|
+
# @return [Proc, nil] the block
|
|
145
|
+
#
|
|
146
|
+
# @api public
|
|
34
147
|
def monitor_children(&child_process_block)
|
|
35
148
|
@attributes[:monitor_children] = true
|
|
36
149
|
@attributes[:child_process_block] = child_process_block
|
|
37
150
|
end
|
|
38
151
|
|
|
152
|
+
# Create the process that the block described
|
|
153
|
+
#
|
|
154
|
+
# @return [Process] the process
|
|
155
|
+
#
|
|
156
|
+
# @api private
|
|
39
157
|
def to_process
|
|
40
158
|
Process.new(@name, @watches, @attributes)
|
|
41
159
|
end
|
data/lib/bluepill/dsl.rb
CHANGED
|
@@ -1,11 +1,33 @@
|
|
|
1
1
|
module Bluepill
|
|
2
|
+
# Load an application from a config file
|
|
3
|
+
#
|
|
4
|
+
# The block describes the application's processes. It runs with self set
|
|
5
|
+
# to an {AppProxy}, and also gets the proxy as an argument. Bluepill then
|
|
6
|
+
# loads the application, which starts its daemon.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# Bluepill.application(:my_app, log_file: '/var/log/bluepill.log') do |app|
|
|
10
|
+
# app.process('web') do |process|
|
|
11
|
+
# process.start_command = '/usr/bin/my_server'
|
|
12
|
+
# process.pid_file = '/var/run/my_server.pid'
|
|
13
|
+
# end
|
|
14
|
+
# end
|
|
15
|
+
#
|
|
16
|
+
# @param [#to_s] app_name the application's name
|
|
17
|
+
# @param [Hash{Symbol => Object}] options the application's options, such
|
|
18
|
+
# as :log_file, :foreground and :base_dir
|
|
19
|
+
#
|
|
20
|
+
# @yield [app] describes the application's processes
|
|
21
|
+
# @yieldparam [AppProxy] app the application's proxy, which is also self
|
|
22
|
+
# @yieldreturn [void]
|
|
23
|
+
#
|
|
24
|
+
# @return [void]
|
|
25
|
+
#
|
|
26
|
+
# @api public
|
|
2
27
|
def self.application(app_name, options = {}, &block)
|
|
3
28
|
app_proxy = AppProxy.new(app_name, options)
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
else
|
|
7
|
-
app_proxy.instance_exec(app_proxy, &block)
|
|
8
|
-
end
|
|
29
|
+
# instance_eval also passes the proxy to a block that takes an argument
|
|
30
|
+
app_proxy.instance_eval(&block)
|
|
9
31
|
app_proxy.app.load
|
|
10
32
|
end
|
|
11
33
|
end
|
data/lib/bluepill/group.rb
CHANGED
|
@@ -1,30 +1,149 @@
|
|
|
1
1
|
module Bluepill
|
|
2
|
+
# The processes of an application that share a group name
|
|
3
|
+
#
|
|
4
|
+
# A config puts a process in a group with `process.group = "name"`.
|
|
5
|
+
# Processes without one are in the application's nameless group. A
|
|
6
|
+
# command such as `bluepill restart name` acts on every process in the
|
|
7
|
+
# group.
|
|
8
|
+
#
|
|
9
|
+
# @api private
|
|
2
10
|
class Group
|
|
3
|
-
|
|
11
|
+
# Return the group's name
|
|
12
|
+
#
|
|
13
|
+
# @return [String, nil] the name, or nil for the application's nameless
|
|
14
|
+
# group
|
|
15
|
+
#
|
|
16
|
+
# @api private
|
|
17
|
+
attr_accessor :name
|
|
18
|
+
|
|
19
|
+
# Return the group's processes
|
|
20
|
+
#
|
|
21
|
+
# @return [Array<Process>] the processes
|
|
22
|
+
#
|
|
23
|
+
# @api private
|
|
24
|
+
attr_accessor :processes
|
|
25
|
+
|
|
26
|
+
# Return the group's logger, which its processes' loggers prefix
|
|
27
|
+
#
|
|
28
|
+
# @return [Logger] the logger
|
|
29
|
+
#
|
|
30
|
+
# @api private
|
|
31
|
+
attr_accessor :logger
|
|
32
|
+
|
|
33
|
+
# Return a logger for processes, which bluepill doesn't use
|
|
34
|
+
#
|
|
35
|
+
# @return [Object] whatever was set
|
|
36
|
+
#
|
|
37
|
+
# @api private
|
|
4
38
|
attr_accessor :process_logger
|
|
5
39
|
|
|
40
|
+
# Create an empty group
|
|
41
|
+
#
|
|
42
|
+
# @param [String, nil] name the group's name, or nil for the nameless
|
|
43
|
+
# group
|
|
44
|
+
# @param [Hash{Symbol => Object}] options the group's options, with
|
|
45
|
+
# :logger
|
|
46
|
+
#
|
|
47
|
+
# @return [void]
|
|
48
|
+
#
|
|
49
|
+
# @api private
|
|
6
50
|
def initialize(name, options = {})
|
|
7
51
|
self.name = name
|
|
8
52
|
self.processes = []
|
|
9
|
-
|
|
53
|
+
logger = options[:logger] #: Logger
|
|
54
|
+
self.logger = logger
|
|
10
55
|
end
|
|
11
56
|
|
|
57
|
+
# Add a process, giving it a logger prefixed with its name
|
|
58
|
+
#
|
|
59
|
+
# @param [Process] process the process
|
|
60
|
+
#
|
|
61
|
+
# @return [Array<Process>] the group's processes
|
|
62
|
+
#
|
|
63
|
+
# @api private
|
|
12
64
|
def add_process(process)
|
|
13
65
|
process.logger = logger.prefix_with(process.name)
|
|
14
66
|
processes << process
|
|
15
67
|
end
|
|
16
68
|
|
|
69
|
+
# Tick every process in the group
|
|
70
|
+
#
|
|
71
|
+
# @return [void]
|
|
72
|
+
#
|
|
73
|
+
# @api private
|
|
17
74
|
def tick
|
|
18
75
|
processes.each(&:tick)
|
|
19
76
|
end
|
|
20
77
|
|
|
78
|
+
# Set each process's state from whether it's running
|
|
79
|
+
#
|
|
80
|
+
# Bluepill does this once, when it loads the config.
|
|
81
|
+
#
|
|
82
|
+
# @return [void]
|
|
83
|
+
#
|
|
84
|
+
# @api private
|
|
21
85
|
def determine_initial_state
|
|
22
86
|
processes.each(&:determine_initial_state)
|
|
23
87
|
end
|
|
24
88
|
|
|
25
|
-
#
|
|
89
|
+
# @!method start(process_name = nil)
|
|
90
|
+
# Start the group's processes, or one of them
|
|
91
|
+
#
|
|
92
|
+
# Each process runs the command on a thread of its own. Unless it's
|
|
93
|
+
# set to run it without blocking (group_start_noblock), the group
|
|
94
|
+
# waits for it before going on to the next process.
|
|
95
|
+
#
|
|
96
|
+
# @param [String, nil] process_name the process to start, or nil for
|
|
97
|
+
# every process in the group
|
|
98
|
+
#
|
|
99
|
+
# @return [Array<String>] the processes it started, as group:process
|
|
100
|
+
#
|
|
101
|
+
# @api private
|
|
102
|
+
#
|
|
103
|
+
# @!method unmonitor(process_name = nil)
|
|
104
|
+
# Stop monitoring the group's processes, or one of them
|
|
105
|
+
#
|
|
106
|
+
# Each process runs the command on a thread of its own. Unless it's
|
|
107
|
+
# set to run it without blocking (group_unmonitor_noblock), the group
|
|
108
|
+
# waits for it before going on to the next process.
|
|
109
|
+
#
|
|
110
|
+
# @param [String, nil] process_name the process to unmonitor, or nil
|
|
111
|
+
# for every process in the group
|
|
112
|
+
#
|
|
113
|
+
# @return [Array<String>] the processes it unmonitored, as
|
|
114
|
+
# group:process
|
|
115
|
+
#
|
|
116
|
+
# @api private
|
|
117
|
+
#
|
|
118
|
+
# @!method stop(process_name = nil)
|
|
119
|
+
# Stop the group's processes, or one of them
|
|
120
|
+
#
|
|
121
|
+
# Each process runs the command on a thread of its own. Unless it's
|
|
122
|
+
# set to run it without blocking (group_stop_noblock), the group waits
|
|
123
|
+
# for it before going on to the next process.
|
|
124
|
+
#
|
|
125
|
+
# @param [String, nil] process_name the process to stop, or nil for
|
|
126
|
+
# every process in the group
|
|
127
|
+
#
|
|
128
|
+
# @return [Array<String>] the processes it stopped, as group:process
|
|
129
|
+
#
|
|
130
|
+
# @api private
|
|
131
|
+
#
|
|
132
|
+
# @!method restart(process_name = nil)
|
|
133
|
+
# Restart the group's processes, or one of them
|
|
134
|
+
#
|
|
135
|
+
# Each process runs the command on a thread of its own. Unless it's
|
|
136
|
+
# set to run it without blocking (group_restart_noblock), the group
|
|
137
|
+
# waits for it before going on to the next process.
|
|
138
|
+
#
|
|
139
|
+
# @param [String, nil] process_name the process to restart, or nil for
|
|
140
|
+
# every process in the group
|
|
141
|
+
#
|
|
142
|
+
# @return [Array<String>] the processes it restarted, as group:process
|
|
143
|
+
#
|
|
144
|
+
# @api private
|
|
26
145
|
[:start, :unmonitor, :stop, :restart].each do |event|
|
|
27
|
-
class_eval <<-
|
|
146
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
28
147
|
def #{event}(process_name = nil)
|
|
29
148
|
threads = []
|
|
30
149
|
affected = []
|
|
@@ -44,18 +163,31 @@ module Bluepill
|
|
|
44
163
|
threads.each { |t| t.join } unless threads.nil?
|
|
45
164
|
affected
|
|
46
165
|
end
|
|
47
|
-
|
|
166
|
+
RUBY
|
|
48
167
|
end
|
|
49
168
|
|
|
169
|
+
# Return the status lines that bluepill status shows
|
|
170
|
+
#
|
|
171
|
+
# Without a process name, it's a line for each process (and each of its
|
|
172
|
+
# children, for one with monitor_children), under the group's name. For
|
|
173
|
+
# one process, it's its line and its event history.
|
|
174
|
+
#
|
|
175
|
+
# @param [String, nil] process_name the process to show, or nil for
|
|
176
|
+
# every process in the group
|
|
177
|
+
#
|
|
178
|
+
# @return [Array<String>] the lines, ending with an empty one
|
|
179
|
+
#
|
|
180
|
+
# @api private
|
|
50
181
|
def status(process_name = nil)
|
|
51
|
-
lines = []
|
|
182
|
+
lines = [] #: Array[String]
|
|
52
183
|
if process_name.nil?
|
|
53
|
-
prefix =
|
|
184
|
+
prefix = (' ' if name)
|
|
54
185
|
lines << "#{name}:" if name
|
|
55
186
|
|
|
56
187
|
processes.each do |process|
|
|
57
188
|
lines << format('%s%s(pid:%s): %s', prefix, process.name, process.actual_pid, process.state)
|
|
58
189
|
next unless process.monitor_children?
|
|
190
|
+
|
|
59
191
|
process.children.each do |child|
|
|
60
192
|
lines << format(' %s%s: %s', prefix, child.name, child.state)
|
|
61
193
|
end
|
|
@@ -63,8 +195,9 @@ module Bluepill
|
|
|
63
195
|
|
|
64
196
|
else
|
|
65
197
|
processes.each do |process|
|
|
66
|
-
next
|
|
67
|
-
|
|
198
|
+
next unless process_name.eql?(process.name)
|
|
199
|
+
|
|
200
|
+
lines << format('%s(pid:%s): %s', process.name, process.actual_pid, process.state)
|
|
68
201
|
lines << process.statistics.to_s
|
|
69
202
|
end
|
|
70
203
|
end
|
data/lib/bluepill/logger.rb
CHANGED
|
@@ -1,7 +1,27 @@
|
|
|
1
1
|
module Bluepill
|
|
2
|
+
# Write bluepill's log, to syslog or a file, with prefixes
|
|
3
|
+
#
|
|
4
|
+
# A logger made with prefix_with writes through its parent, which adds
|
|
5
|
+
# its own prefix, so a process's messages are tagged with its app's name
|
|
6
|
+
# and its own, as in "[app:web] Going from up => down".
|
|
7
|
+
#
|
|
8
|
+
# @api private
|
|
2
9
|
class Logger
|
|
10
|
+
# The syslog priorities, each a method that logs a message
|
|
11
|
+
#
|
|
12
|
+
# @api private
|
|
3
13
|
LOG_METHODS = [:emerg, :alert, :crit, :err, :warning, :notice, :info, :debug].freeze
|
|
4
14
|
|
|
15
|
+
# Make a logger
|
|
16
|
+
#
|
|
17
|
+
# @param [Hash{Symbol => Object}] options :logger, a parent logger to
|
|
18
|
+
# write through, :prefix, :stdout to echo messages there too,
|
|
19
|
+
# :log_file to write to instead of syslog, and :identity, the name
|
|
20
|
+
# syslog shows
|
|
21
|
+
#
|
|
22
|
+
# @return [void]
|
|
23
|
+
#
|
|
24
|
+
# @api private
|
|
5
25
|
def initialize(options = {})
|
|
6
26
|
@options = options
|
|
7
27
|
@logger = options[:logger] || create_logger
|
|
@@ -10,8 +30,56 @@ module Bluepill
|
|
|
10
30
|
@prefixes = {}
|
|
11
31
|
end
|
|
12
32
|
|
|
33
|
+
# @!method emerg(msg, prefix = [])
|
|
34
|
+
# Log a message at the emergency priority
|
|
35
|
+
# @param [String] msg the message
|
|
36
|
+
# @param [Array<String, nil>] prefix the prefixes of the loggers below
|
|
37
|
+
# @return [void]
|
|
38
|
+
# @api private
|
|
39
|
+
# @!method alert(msg, prefix = [])
|
|
40
|
+
# Log a message at the alert priority
|
|
41
|
+
# @param [String] msg the message
|
|
42
|
+
# @param [Array<String, nil>] prefix the prefixes of the loggers below
|
|
43
|
+
# @return [void]
|
|
44
|
+
# @api private
|
|
45
|
+
# @!method crit(msg, prefix = [])
|
|
46
|
+
# Log a message at the critical priority
|
|
47
|
+
# @param [String] msg the message
|
|
48
|
+
# @param [Array<String, nil>] prefix the prefixes of the loggers below
|
|
49
|
+
# @return [void]
|
|
50
|
+
# @api private
|
|
51
|
+
# @!method err(msg, prefix = [])
|
|
52
|
+
# Log a message at the error priority
|
|
53
|
+
# @param [String] msg the message
|
|
54
|
+
# @param [Array<String, nil>] prefix the prefixes of the loggers below
|
|
55
|
+
# @return [void]
|
|
56
|
+
# @api private
|
|
57
|
+
# @!method warning(msg, prefix = [])
|
|
58
|
+
# Log a message at the warning priority
|
|
59
|
+
# @param [String] msg the message
|
|
60
|
+
# @param [Array<String, nil>] prefix the prefixes of the loggers below
|
|
61
|
+
# @return [void]
|
|
62
|
+
# @api private
|
|
63
|
+
# @!method notice(msg, prefix = [])
|
|
64
|
+
# Log a message at the notice priority
|
|
65
|
+
# @param [String] msg the message
|
|
66
|
+
# @param [Array<String, nil>] prefix the prefixes of the loggers below
|
|
67
|
+
# @return [void]
|
|
68
|
+
# @api private
|
|
69
|
+
# @!method info(msg, prefix = [])
|
|
70
|
+
# Log a message at the info priority
|
|
71
|
+
# @param [String] msg the message
|
|
72
|
+
# @param [Array<String, nil>] prefix the prefixes of the loggers below
|
|
73
|
+
# @return [void]
|
|
74
|
+
# @api private
|
|
75
|
+
# @!method debug(msg, prefix = [])
|
|
76
|
+
# Log a message at the debug priority
|
|
77
|
+
# @param [String] msg the message
|
|
78
|
+
# @param [Array<String, nil>] prefix the prefixes of the loggers below
|
|
79
|
+
# @return [void]
|
|
80
|
+
# @api private
|
|
13
81
|
LOG_METHODS.each do |method|
|
|
14
|
-
class_eval <<-
|
|
82
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
|
15
83
|
def #{method}(msg, prefix = [])
|
|
16
84
|
if @logger.is_a?(self.class)
|
|
17
85
|
@logger.#{method}(msg, [@prefix] + prefix)
|
|
@@ -24,16 +92,31 @@ module Bluepill
|
|
|
24
92
|
@logger.#{method}("\#{s_prefix}\#{msg}")
|
|
25
93
|
end
|
|
26
94
|
end
|
|
27
|
-
|
|
95
|
+
RUBY
|
|
28
96
|
end
|
|
29
97
|
|
|
98
|
+
# Return a logger that adds a prefix to this one's messages
|
|
99
|
+
#
|
|
100
|
+
# It's made once for each prefix, and kept.
|
|
101
|
+
#
|
|
102
|
+
# @param [String] prefix the prefix, such as a process's name
|
|
103
|
+
#
|
|
104
|
+
# @return [Logger] the prefixed logger
|
|
105
|
+
#
|
|
106
|
+
# @api private
|
|
30
107
|
def prefix_with(prefix)
|
|
31
108
|
@prefixes[prefix] ||= self.class.new(logger: self, prefix: prefix)
|
|
32
109
|
end
|
|
33
110
|
|
|
111
|
+
# Open the log file or syslog again, such as after log rotation
|
|
112
|
+
#
|
|
113
|
+
# @return [void]
|
|
114
|
+
#
|
|
115
|
+
# @api private
|
|
34
116
|
def reopen
|
|
35
|
-
|
|
36
|
-
|
|
117
|
+
logger = @logger
|
|
118
|
+
if logger.instance_of?(self.class)
|
|
119
|
+
logger.reopen
|
|
37
120
|
else
|
|
38
121
|
@logger = create_logger
|
|
39
122
|
end
|
|
@@ -41,21 +124,53 @@ module Bluepill
|
|
|
41
124
|
|
|
42
125
|
protected
|
|
43
126
|
|
|
127
|
+
# Open the log file, or syslog if there isn't one
|
|
128
|
+
#
|
|
129
|
+
# @return [LoggerAdapter, Syslog] where the messages go
|
|
130
|
+
#
|
|
131
|
+
# @api private
|
|
44
132
|
def create_logger
|
|
45
133
|
if @options[:log_file]
|
|
46
|
-
LoggerAdapter.new(@options
|
|
134
|
+
LoggerAdapter.new(@options.fetch(:log_file))
|
|
47
135
|
else
|
|
136
|
+
require_syslog
|
|
48
137
|
Syslog.close if Syslog.opened? # need to explictly close it before reopening it
|
|
49
138
|
Syslog.open(@options[:identity] || 'bluepilld', Syslog::LOG_PID, Syslog::LOG_LOCAL6)
|
|
50
139
|
end
|
|
51
140
|
end
|
|
52
141
|
|
|
142
|
+
# Load syslog, which is only loaded when it's used
|
|
143
|
+
#
|
|
144
|
+
# Ruby 3.4 and newer don't include it by default, so apps that log to a
|
|
145
|
+
# file don't need it in their Gemfile.
|
|
146
|
+
#
|
|
147
|
+
# @return [void]
|
|
148
|
+
#
|
|
149
|
+
# @raise [LoadError] with how to fix it, if syslog can't be loaded
|
|
150
|
+
#
|
|
151
|
+
# @api private
|
|
152
|
+
def require_syslog
|
|
153
|
+
require 'syslog'
|
|
154
|
+
rescue LoadError
|
|
155
|
+
message = 'Bluepill logs to syslog unless log_file is set, but the syslog library ' \
|
|
156
|
+
"could not be loaded. Since Ruby 3.4 it's a separate gem, so add gem 'syslog' " \
|
|
157
|
+
'to your Gemfile or set log_file.'
|
|
158
|
+
raise LoadError, message
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# A ::Logger that also answers to the syslog priorities' names
|
|
162
|
+
#
|
|
163
|
+
# @api private
|
|
53
164
|
class LoggerAdapter < ::Logger
|
|
165
|
+
# The ::Logger method for each syslog priority
|
|
166
|
+
#
|
|
167
|
+
# @api private
|
|
54
168
|
LOGGER_EQUIVALENTS =
|
|
55
169
|
{debug: :debug, err: :error, warning: :warn, info: :info, emerg: :fatal, alert: :warn, crit: :fatal, notice: :info}.freeze
|
|
56
170
|
|
|
57
171
|
LOG_METHODS.each do |method|
|
|
58
172
|
next if method == LOGGER_EQUIVALENTS[method]
|
|
173
|
+
|
|
59
174
|
alias_method method, LOGGER_EQUIVALENTS[method]
|
|
60
175
|
end
|
|
61
176
|
end
|