serverengine 1.5.5 → 1.5.6
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.
- data/.rspec +2 -0
- data/.travis.yml +0 -1
- data/Changelog +6 -0
- data/README.md +49 -45
- data/Rakefile +0 -1
- data/lib/serverengine/config_loader.rb +17 -13
- data/lib/serverengine/daemon_logger.rb +189 -48
- data/lib/serverengine/server.rb +25 -0
- data/lib/serverengine/version.rb +1 -1
- data/spec/daemon_logger_spec.rb +81 -28
- metadata +6 -3
data/.rspec
ADDED
data/.travis.yml
CHANGED
data/Changelog
CHANGED
data/README.md
CHANGED
@@ -57,33 +57,35 @@ se = ServerEngine.create(nil, MyWorker, {
|
|
57
57
|
se.run
|
58
58
|
```
|
59
59
|
|
60
|
-
Send `TERM` signal to kill the daemon. See also **Signals** section bellow.
|
60
|
+
Send `TERM` signal to kill the daemon. See also **Signals** section bellow for details.
|
61
61
|
|
62
62
|
|
63
63
|
### Multiprocess server
|
64
64
|
|
65
|
-
Simply set **process** or **thread**
|
65
|
+
Simply set **worker_type=process** or **worker_type=thread** parameter, and set number of workers to `workers` parameter.
|
66
66
|
|
67
67
|
```ruby
|
68
68
|
se = ServerEngine.create(nil, MyWorker, {
|
69
69
|
:daemonize => true,
|
70
70
|
:log => 'myserver.log',
|
71
71
|
:pid_path => 'myserver.pid',
|
72
|
-
:workers => 4,
|
73
72
|
:worker_type => 'process',
|
73
|
+
:workers => 4,
|
74
74
|
})
|
75
75
|
se.run
|
76
76
|
```
|
77
77
|
|
78
78
|
See also **Worker types** section bellow.
|
79
79
|
|
80
|
-
#### Multiprocess TCP server
|
81
80
|
|
82
|
-
|
81
|
+
### Multiprocess TCP server
|
82
|
+
|
83
|
+
One of the typical implementation styles of TCP servers is that a parent process listens socket and child processes accept connections from clients.
|
83
84
|
|
84
|
-
|
85
|
+
ServerEngine allows you to optionally implement a server module to control the parent process:
|
85
86
|
|
86
87
|
```ruby
|
88
|
+
# Server module controls the parent process
|
87
89
|
module MyServer
|
88
90
|
def before_run
|
89
91
|
@sock = TCPServer.new(config[:bind], config[:port])
|
@@ -92,6 +94,7 @@ module MyServer
|
|
92
94
|
attr_reader :sock
|
93
95
|
end
|
94
96
|
|
97
|
+
# Worker module controls child processes
|
95
98
|
module MyWorker
|
96
99
|
def run
|
97
100
|
until @stop
|
@@ -111,8 +114,8 @@ se = ServerEngine.create(MyServer, MyWorker, {
|
|
111
114
|
:daemonize => true,
|
112
115
|
:log => 'myserver.log',
|
113
116
|
:pid_path => 'myserver.pid',
|
114
|
-
:workers => 4,
|
115
117
|
:worker_type => 'process',
|
118
|
+
:workers => 4,
|
116
119
|
:bind => '0.0.0.0',
|
117
120
|
:port => 9071,
|
118
121
|
})
|
@@ -141,7 +144,7 @@ See also **Configuration** section bellow.
|
|
141
144
|
|
142
145
|
Server programs running 24x7 hours need to survive even if a process stalled because of unexpected memory swapping or network errors.
|
143
146
|
|
144
|
-
Supervisor
|
147
|
+
Supervisor process runs as the parent process of the server process and monitor it to restart automatically.
|
145
148
|
|
146
149
|
```ruby
|
147
150
|
se = ServerEngine.create(nil, MyWorker, {
|
@@ -152,10 +155,11 @@ se = ServerEngine.create(nil, MyWorker, {
|
|
152
155
|
se.run
|
153
156
|
```
|
154
157
|
|
158
|
+
|
155
159
|
### Live restart
|
156
160
|
|
157
|
-
You can restart a server process without waiting for completion of
|
158
|
-
This feature
|
161
|
+
You can restart a server process without waiting for completion of all workers using `INT` signal (`supervisor=true` and `enable_detach=true` parameters must be enabled).
|
162
|
+
This feature allows you to minimize downtime where workers take long time to complete a task.
|
159
163
|
|
160
164
|
```
|
161
165
|
# 1. starts server
|
@@ -187,7 +191,7 @@ This feature is useful to minimize downtime where workers take long time to comp
|
|
187
191
|
+----------+ +-----------+
|
188
192
|
```
|
189
193
|
|
190
|
-
Note that network servers (which listen sockets) shouldn't use live restart because it causes "Address already in use" error. Instead, simply use `worker_type=process` configuration and send `USR1` to restart
|
194
|
+
Note that network servers (which listen sockets) shouldn't use live restart because it causes "Address already in use" error at the server process. Instead, simply use `worker_type=process` configuration and send `USR1` to restart workers instead of the server. It restarts a worker without waiting for shutdown of the other workers. This way doesn't cause downtime because server process doesn't close listening sockets and keeps accepting new clients (See also `restart_server_process` parameter if necessary).
|
191
195
|
|
192
196
|
|
193
197
|
### Dynamic configuration reloading
|
@@ -322,43 +326,43 @@ Graceful shutdown and restart call `Worker#stop` method and wait for completion
|
|
322
326
|
## Configuration
|
323
327
|
|
324
328
|
- Daemon
|
325
|
-
- **daemonize** enables daemonize (default: false)
|
326
|
-
- **pid_path** sets the path to pid file (default: don't create pid file)
|
327
|
-
- **supervisor** enables supervisor if it's true (default: false)
|
328
|
-
- **daemon_process_name** changes process name ($0) of server or supervisor process
|
329
|
-
- **chuser** changes execution user
|
330
|
-
- **chgroup** changes execution group
|
331
|
-
- **chumask** changes umask
|
332
|
-
- **daemonize_error_exit_code** exit code when daemonize, changing user or changing group fails (default: 1)
|
329
|
+
- **daemonize** enables daemonize (default: false)
|
330
|
+
- **pid_path** sets the path to pid file (default: don't create pid file)
|
331
|
+
- **supervisor** enables supervisor if it's true (default: false)
|
332
|
+
- **daemon_process_name** changes process name ($0) of server or supervisor process
|
333
|
+
- **chuser** changes execution user
|
334
|
+
- **chgroup** changes execution group
|
335
|
+
- **chumask** changes umask
|
336
|
+
- **daemonize_error_exit_code** exit code when daemonize, changing user or changing group fails (default: 1)
|
333
337
|
- Supervisor: available only when `supervisor` parameters is true
|
334
|
-
- **server_process_name** changes process name ($0) of server process
|
335
|
-
- **restart_server_process** restarts server process when it receives USR1 or HUP signal. (default: false)
|
336
|
-
- **enable_detach** enables INT signal (default: true)
|
337
|
-
- **exit_on_detach** exits supervisor after detaching server process instead of restarting it (default: false)
|
338
|
-
- **disable_reload** disables USR2 signal (default: false)
|
339
|
-
- **server_restart_wait** sets wait time before restarting server after last restarting (default: 1.0)
|
340
|
-
- **server_detach_wait** sets wait time before starting live restart (default: 10.0)
|
338
|
+
- **server_process_name** changes process name ($0) of server process
|
339
|
+
- **restart_server_process** restarts server process when it receives USR1 or HUP signal. (default: false)
|
340
|
+
- **enable_detach** enables INT signal (default: true)
|
341
|
+
- **exit_on_detach** exits supervisor after detaching server process instead of restarting it (default: false)
|
342
|
+
- **disable_reload** disables USR2 signal (default: false)
|
343
|
+
- **server_restart_wait** sets wait time before restarting server after last restarting (default: 1.0) [dynamic reloadable]
|
344
|
+
- **server_detach_wait** sets wait time before starting live restart (default: 10.0) [dynamic reloadable]
|
341
345
|
- Multithread server and multiprocess server: available only when `worker_type` is thread or process
|
342
|
-
- **workers** sets number of workers (default: 1)
|
343
|
-
- **start_worker_delay** sets wait time before starting a new worker (default: 0)
|
344
|
-
- **start_worker_delay_rand** randomizes start_worker_delay at this ratio (default: 0.2)
|
345
|
-
- Multiprocess server: available only when `worker_type` is "process"
|
346
|
-
- **worker_process_name** changes process name ($0) of workers
|
347
|
-
- **worker_heartbeat_interval** sets interval of heartbeats in seconds (default: 1.0)
|
348
|
-
- **worker_heartbeat_timeout** sets timeout of heartbeat in seconds (default: 180)
|
349
|
-
- **worker_graceful_kill_interval** sets the first interval of TERM signals in seconds (default: 15)
|
350
|
-
- **worker_graceful_kill_interval_increment** sets increment of TERM signal interval in seconds (default: 10)
|
351
|
-
- **worker_graceful_kill_timeout** sets promotion timeout from TERM to QUIT signal in seconds. -1 means no timeout (default: 600)
|
352
|
-
- **worker_immediate_kill_interval** sets the first interval of QUIT signals in seconds (default: 10)
|
353
|
-
- **worker_immediate_kill_interval_increment** sets increment of QUIT signal interval in seconds (default: 10)
|
354
|
-
- **worker_immediate_kill_timeout** sets promotion timeout from QUIT to KILL signal in seconds. -1 means no timeout (default: 600)
|
346
|
+
- **workers** sets number of workers (default: 1) [dynamic reloadable]
|
347
|
+
- **start_worker_delay** sets wait time before starting a new worker (default: 0) [dynamic reloadable]
|
348
|
+
- **start_worker_delay_rand** randomizes start_worker_delay at this ratio (default: 0.2) [dynamic reloadable]
|
349
|
+
- Multiprocess server: available only when `worker_type` is "process" [dynamic reloadable]
|
350
|
+
- **worker_process_name** changes process name ($0) of workers [dynamic reloadable]
|
351
|
+
- **worker_heartbeat_interval** sets interval of heartbeats in seconds (default: 1.0) [dynamic reloadable]
|
352
|
+
- **worker_heartbeat_timeout** sets timeout of heartbeat in seconds (default: 180) [dynamic reloadable]
|
353
|
+
- **worker_graceful_kill_interval** sets the first interval of TERM signals in seconds (default: 15) [dynamic reloadable]
|
354
|
+
- **worker_graceful_kill_interval_increment** sets increment of TERM signal interval in seconds (default: 10) [dynamic reloadable]
|
355
|
+
- **worker_graceful_kill_timeout** sets promotion timeout from TERM to QUIT signal in seconds. -1 means no timeout (default: 600) [dynamic reloadable]
|
356
|
+
- **worker_immediate_kill_interval** sets the first interval of QUIT signals in seconds (default: 10) [dynamic reloadable]
|
357
|
+
- **worker_immediate_kill_interval_increment** sets increment of QUIT signal interval in seconds (default: 10) [dynamic reloadable]
|
358
|
+
- **worker_immediate_kill_timeout** sets promotion timeout from QUIT to KILL signal in seconds. -1 means no timeout (default: 600) [dynamic reloadable]
|
355
359
|
- Logger
|
356
|
-
- **log** sets path to log file. Set "-" for STDOUT (default: STDERR)
|
357
|
-
- **log_level** log level: debug, info, warn, error or fatal. (default: debug)
|
358
|
-
- **log_rotate_age** generations to keep rotated log files (default: 5)
|
359
|
-
- **log_rotate_size** sets the size to rotate log files (default: 1048576)
|
360
|
-
- **log_stdout** hooks STDOUT to log file (default: true)
|
361
|
-
- **log_stderr** hooks STDERR to log file (default: true)
|
360
|
+
- **log** sets path to log file. Set "-" for STDOUT (default: STDERR) [dynamic reloadable]
|
361
|
+
- **log_level** log level: trace, debug, info, warn, error or fatal. (default: debug) [dynamic reloadable]
|
362
|
+
- **log_rotate_age** generations to keep rotated log files (default: 5)
|
363
|
+
- **log_rotate_size** sets the size to rotate log files (default: 1048576)
|
364
|
+
- **log_stdout** hooks STDOUT to log file (default: true)
|
365
|
+
- **log_stderr** hooks STDERR to log file (default: true)
|
362
366
|
- **logger_class** class of the logger instance (default: ServerEngine::DaemonLogger)
|
363
367
|
|
364
368
|
---
|
data/Rakefile
CHANGED
@@ -40,20 +40,13 @@ module ServerEngine
|
|
40
40
|
|
41
41
|
@logger_class = @config[:logger_class] || DaemonLogger
|
42
42
|
|
43
|
-
case c = @config[:log]
|
44
|
-
when nil # default
|
45
|
-
@log_dev = STDERR
|
46
|
-
when "-"
|
47
|
-
@log_dev = STDOUT
|
48
|
-
else
|
49
|
-
@log_dev = c
|
50
|
-
end
|
51
|
-
|
52
43
|
if @logger
|
53
|
-
|
54
|
-
|
44
|
+
logdev = logdev_from_config(@config)
|
45
|
+
unless logdev.is_a?(IO)
|
46
|
+
# Here doesn't allow to change logdev to IO dynamically
|
47
|
+
# because Server#start_io_logging_thread can't follow it.
|
48
|
+
@logger.logdev = io
|
55
49
|
end
|
56
|
-
|
57
50
|
@logger.level = @config[:log_level] || 'debug'
|
58
51
|
end
|
59
52
|
|
@@ -68,7 +61,18 @@ module ServerEngine
|
|
68
61
|
return
|
69
62
|
end
|
70
63
|
|
71
|
-
@logger = @logger_class.new(@
|
64
|
+
@logger = @logger_class.new(logdev_from_config(@config), @config)
|
65
|
+
end
|
66
|
+
|
67
|
+
def logdev_from_config(config)
|
68
|
+
case c = @config[:log]
|
69
|
+
when nil # default
|
70
|
+
return STDERR
|
71
|
+
when "-"
|
72
|
+
return STDOUT
|
73
|
+
else
|
74
|
+
return c
|
75
|
+
end
|
72
76
|
end
|
73
77
|
end
|
74
78
|
|
@@ -20,42 +20,74 @@ module ServerEngine
|
|
20
20
|
require 'logger'
|
21
21
|
|
22
22
|
class DaemonLogger < Logger
|
23
|
-
def initialize(
|
24
|
-
@hook_stdout = config.fetch(:log_stdout, true)
|
25
|
-
@hook_stderr = config.fetch(:log_stderr, true)
|
23
|
+
def initialize(logdev, config={})
|
26
24
|
rotate_age = config[:log_rotate_age] || 5
|
27
25
|
rotate_size = config[:log_rotate_size] || 1048576
|
28
26
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
@file_dev = MultiprocessFileLogDevice.new(nil,
|
28
|
+
shift_age: rotate_age, shift_size: rotate_size)
|
29
|
+
|
30
|
+
super(nil)
|
31
|
+
|
32
|
+
self.level = config[:log_level] || 'debug'
|
33
|
+
self.logdev = logdev
|
34
|
+
end
|
35
|
+
|
36
|
+
def logdev=(logdev)
|
37
|
+
# overwrites Logger's @logdev variable
|
38
|
+
if logdev.respond_to?(:write) and logdev.respond_to?(:close)
|
39
|
+
# IO
|
40
|
+
@logdev = logdev
|
41
|
+
@logdev.sync = true if @logdev.respond_to?(:sync=)
|
42
|
+
@file_dev.path = nil
|
33
43
|
else
|
34
|
-
|
44
|
+
# path string
|
45
|
+
@file_dev.path = logdev
|
46
|
+
@logdev = @file_dev
|
35
47
|
end
|
48
|
+
logdev
|
49
|
+
end
|
36
50
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
51
|
+
# override add method
|
52
|
+
def add(severity, message = nil, progname = nil, &block)
|
53
|
+
if severity < @level
|
54
|
+
return true
|
55
|
+
end
|
56
|
+
if message.nil?
|
57
|
+
if block_given?
|
58
|
+
message = yield
|
59
|
+
else
|
60
|
+
message = progname
|
61
|
+
progname = nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
progname ||= @progname
|
65
|
+
self << format_message(SEVERITY_FORMATS_[severity+1], Time.now, progname, message)
|
66
|
+
true
|
67
|
+
end
|
41
68
|
|
42
|
-
|
69
|
+
module Severity
|
70
|
+
include Logger::Severity
|
71
|
+
TRACE = -1
|
43
72
|
end
|
73
|
+
include Severity
|
44
74
|
|
45
|
-
|
75
|
+
SEVERITY_FORMATS_ = %w(TRACE DEBUG INFO WARN ERROR FATAL ANY)
|
46
76
|
|
47
77
|
def level=(expr)
|
48
78
|
case expr.to_s
|
49
|
-
when 'fatal',
|
50
|
-
e =
|
51
|
-
when 'error',
|
52
|
-
e =
|
53
|
-
when 'warn',
|
54
|
-
e =
|
55
|
-
when 'info',
|
56
|
-
e =
|
57
|
-
when 'debug',
|
58
|
-
e =
|
79
|
+
when 'fatal', FATAL.to_s
|
80
|
+
e = FATAL
|
81
|
+
when 'error', ERROR.to_s
|
82
|
+
e = ERROR
|
83
|
+
when 'warn', WARN.to_s
|
84
|
+
e = WARN
|
85
|
+
when 'info', INFO.to_s
|
86
|
+
e = INFO
|
87
|
+
when 'debug', DEBUG.to_s
|
88
|
+
e = DEBUG
|
89
|
+
when 'trace', TRACE.to_s
|
90
|
+
e = TRACE
|
59
91
|
else
|
60
92
|
raise ArgumentError, "invalid log level: #{expr}"
|
61
93
|
end
|
@@ -63,29 +95,10 @@ module ServerEngine
|
|
63
95
|
super(e)
|
64
96
|
end
|
65
97
|
|
66
|
-
def
|
67
|
-
STDOUT.sync = true
|
68
|
-
@hook_stdout = true
|
69
|
-
|
70
|
-
STDOUT.reopen(@io) if @io != STDOUT
|
71
|
-
self
|
72
|
-
end
|
73
|
-
|
74
|
-
def hook_stderr!
|
75
|
-
STDERR.sync = true
|
76
|
-
@hook_stderr = true
|
77
|
-
|
78
|
-
STDERR.reopen(@io) if @io != STDERR
|
79
|
-
self
|
80
|
-
end
|
98
|
+
def trace?; @level <= TRACE; end
|
81
99
|
|
82
100
|
def reopen!
|
83
|
-
|
84
|
-
@io.reopen(@path, "a")
|
85
|
-
@io.sync = true
|
86
|
-
hook_stdout! if @hook_stdout
|
87
|
-
hook_stderr! if @hook_stderr
|
88
|
-
end
|
101
|
+
@file_dev.reopen!
|
89
102
|
nil
|
90
103
|
end
|
91
104
|
|
@@ -100,11 +113,139 @@ module ServerEngine
|
|
100
113
|
end
|
101
114
|
|
102
115
|
def close
|
103
|
-
|
104
|
-
@io.close unless @io.closed?
|
105
|
-
end
|
116
|
+
@file_dev.close
|
106
117
|
nil
|
107
118
|
end
|
119
|
+
|
120
|
+
class MultiprocessFileLogDevice
|
121
|
+
def initialize(path, opts={})
|
122
|
+
@shift_age = opts[:shift_age] || 7
|
123
|
+
@shift_size = opts[:shift_size] || 1024*1024
|
124
|
+
@mutex = Mutex.new
|
125
|
+
self.path = path
|
126
|
+
end
|
127
|
+
|
128
|
+
def write(data)
|
129
|
+
# it's hard to remove this synchronize because IO#write raises
|
130
|
+
# Errno::ENOENT if IO#reopen is running concurrently.
|
131
|
+
@mutex.synchronize do
|
132
|
+
unless @file
|
133
|
+
return nil
|
134
|
+
end
|
135
|
+
log_rotate_or_reopen
|
136
|
+
@file.write(data)
|
137
|
+
end
|
138
|
+
rescue Exception => e
|
139
|
+
warn "log writing failed: #{e}"
|
140
|
+
end
|
141
|
+
|
142
|
+
def path=(path)
|
143
|
+
@mutex.synchronize do
|
144
|
+
old_file = @file
|
145
|
+
file = open_logfile(path)
|
146
|
+
begin
|
147
|
+
@file = file
|
148
|
+
@path = path
|
149
|
+
file = old_file
|
150
|
+
ensure
|
151
|
+
file.close if file
|
152
|
+
end
|
153
|
+
end
|
154
|
+
return path
|
155
|
+
end
|
156
|
+
|
157
|
+
def close
|
158
|
+
@mutex.synchronize do
|
159
|
+
@file.close
|
160
|
+
@file = nil
|
161
|
+
end
|
162
|
+
nil
|
163
|
+
end
|
164
|
+
|
165
|
+
attr_reader :path
|
166
|
+
|
167
|
+
def reopen!
|
168
|
+
@mutex.synchronize do
|
169
|
+
if @file
|
170
|
+
@file.reopen(@path, 'a')
|
171
|
+
@file.sync = true
|
172
|
+
end
|
173
|
+
end
|
174
|
+
true
|
175
|
+
end
|
176
|
+
|
177
|
+
# for compatibility with Logger::LogDevice
|
178
|
+
def dev
|
179
|
+
@file
|
180
|
+
end
|
181
|
+
|
182
|
+
# for compatibility with Logger::LogDevice
|
183
|
+
def filename
|
184
|
+
@path
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def open_logfile(path)
|
190
|
+
return nil unless path
|
191
|
+
file = File.open(path, 'a')
|
192
|
+
file.sync = true
|
193
|
+
return file
|
194
|
+
end
|
195
|
+
|
196
|
+
def log_rotate_or_reopen
|
197
|
+
stat = @file.stat
|
198
|
+
if stat.size <= @shift_size
|
199
|
+
return
|
200
|
+
end
|
201
|
+
|
202
|
+
# inter-process locking
|
203
|
+
retry_limit = 8
|
204
|
+
retry_sleep = 0.1
|
205
|
+
begin
|
206
|
+
# 1) other process is log-rotating now
|
207
|
+
# 2) other process log rotated
|
208
|
+
# 3) no active processes
|
209
|
+
lock = File.open(@path, File::WRONLY | File::APPEND)
|
210
|
+
begin
|
211
|
+
lock.flock(File::LOCK_EX)
|
212
|
+
ino = lock.stat.ino
|
213
|
+
if ino == File.stat(@path).ino
|
214
|
+
# 3)
|
215
|
+
log_rotate
|
216
|
+
else
|
217
|
+
reopen!
|
218
|
+
end
|
219
|
+
rescue
|
220
|
+
lock.close
|
221
|
+
end
|
222
|
+
rescue Errno::ENOENT => e
|
223
|
+
raise e if retry_limit <= 0
|
224
|
+
sleep retry_sleep
|
225
|
+
retry_limit -= 1
|
226
|
+
retry_sleep *= 2
|
227
|
+
retry
|
228
|
+
end
|
229
|
+
|
230
|
+
rescue => e
|
231
|
+
warn "log rotation inter-process lock failed: #{e}"
|
232
|
+
end
|
233
|
+
|
234
|
+
def log_rotate
|
235
|
+
(@shift_age-2).downto(0) do |i|
|
236
|
+
old_path = "#{@path}.#{i}"
|
237
|
+
shift_path = "#{@path}.#{i+1}"
|
238
|
+
if FileTest.exist?(old_path)
|
239
|
+
File.rename(old_path, shift_path)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
File.rename(@path, "#{@path}.0")
|
243
|
+
@file.reopen(@path, 'a')
|
244
|
+
@file.sync = true
|
245
|
+
rescue => e
|
246
|
+
warn "log rotation failed: #{e}"
|
247
|
+
end
|
248
|
+
end
|
108
249
|
end
|
109
250
|
|
110
251
|
end
|
data/lib/serverengine/server.rb
CHANGED
@@ -26,6 +26,11 @@ module ServerEngine
|
|
26
26
|
@stop = false
|
27
27
|
|
28
28
|
super(load_config_proc, &block)
|
29
|
+
|
30
|
+
@log_stdout = !!@config.fetch(:log_stdout, true)
|
31
|
+
@log_stderr = !!@config.fetch(:log_stderr, true)
|
32
|
+
@log_stdout = false if logdev_from_config(@config) == STDOUT
|
33
|
+
@log_stderr = false if logdev_from_config(@config) == STDERR
|
29
34
|
end
|
30
35
|
|
31
36
|
def before_run
|
@@ -70,6 +75,10 @@ module ServerEngine
|
|
70
75
|
def main
|
71
76
|
create_logger unless @logger
|
72
77
|
|
78
|
+
# start threads to transfer logs from STDOUT/ERR to the logger
|
79
|
+
start_io_logging_thread(STDOUT) if @log_stdout
|
80
|
+
start_io_logging_thread(STDERR) if @log_stderr
|
81
|
+
|
73
82
|
before_run
|
74
83
|
|
75
84
|
begin
|
@@ -93,6 +102,22 @@ module ServerEngine
|
|
93
102
|
w.instance_eval { initialize }
|
94
103
|
w
|
95
104
|
end
|
105
|
+
|
106
|
+
def start_io_logging_thread(io)
|
107
|
+
r, w = IO.pipe
|
108
|
+
io.reopen(w)
|
109
|
+
w.close
|
110
|
+
|
111
|
+
Thread.new do
|
112
|
+
begin
|
113
|
+
while line = r.gets
|
114
|
+
@logger << line
|
115
|
+
end
|
116
|
+
rescue => e
|
117
|
+
ServerEngine.dump_uncaught_error(e)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
96
121
|
end
|
97
122
|
|
98
123
|
end
|
data/lib/serverengine/version.rb
CHANGED
data/spec/daemon_logger_spec.rb
CHANGED
@@ -1,40 +1,26 @@
|
|
1
|
+
require 'stringio'
|
1
2
|
|
2
3
|
describe ServerEngine::DaemonLogger do
|
3
4
|
before { FileUtils.mkdir_p("tmp") }
|
4
5
|
before { FileUtils.rm_f("tmp/se1.log") }
|
6
|
+
before { FileUtils.rm_f Dir["tmp/se1.log.**"] }
|
5
7
|
before { FileUtils.rm_f("tmp/se2.log") }
|
6
8
|
|
7
|
-
subject { DaemonLogger.new("tmp/se1.log",
|
9
|
+
subject { DaemonLogger.new("tmp/se1.log", level: 'trace') }
|
8
10
|
|
9
11
|
it 'reopen' do
|
10
|
-
subject.
|
11
|
-
|
12
|
-
subject.warn "
|
13
|
-
|
14
|
-
File.read('tmp/se2.log').should =~ /test$/
|
15
|
-
end
|
16
|
-
|
17
|
-
it 'stderr hook 1' do
|
18
|
-
subject.hook_stderr!
|
19
|
-
STDERR.puts "test"
|
20
|
-
|
21
|
-
File.read('tmp/se1.log').should == "test\n"
|
22
|
-
end
|
12
|
+
subject.warn "ABCDEF"
|
13
|
+
File.open('tmp/se1.log', "w") {|f| }
|
14
|
+
subject.warn "test2"
|
23
15
|
|
24
|
-
|
25
|
-
log = DaemonLogger.new("tmp/se1.log", log_stdout: false, log_stderr: true)
|
26
|
-
STDERR.puts "test"
|
27
|
-
|
28
|
-
File.read('tmp/se1.log').should == "test\n"
|
16
|
+
File.read('tmp/se1.log').should_not =~ /ABCDEF/
|
29
17
|
end
|
30
18
|
|
31
|
-
it '
|
32
|
-
subject.
|
33
|
-
subject.
|
34
|
-
subject.reopen!
|
35
|
-
STDERR.puts "test"
|
19
|
+
it 'reset path' do
|
20
|
+
subject.logdev = 'tmp/se2.log'
|
21
|
+
subject.warn "test"
|
36
22
|
|
37
|
-
File.read('tmp/se2.log').should
|
23
|
+
File.read('tmp/se2.log').should =~ /test$/
|
38
24
|
end
|
39
25
|
|
40
26
|
it 'default level is debug' do
|
@@ -45,18 +31,57 @@ describe ServerEngine::DaemonLogger do
|
|
45
31
|
it 'level set by int' do
|
46
32
|
subject.level = Logger::FATAL
|
47
33
|
subject.level.should == Logger::FATAL
|
34
|
+
subject.trace?.should == false
|
35
|
+
subject.debug?.should == false
|
36
|
+
subject.info?.should == false
|
37
|
+
subject.warn?.should == false
|
38
|
+
subject.error?.should == false
|
39
|
+
subject.fatal?.should == true
|
48
40
|
|
49
41
|
subject.level = Logger::ERROR
|
50
42
|
subject.level.should == Logger::ERROR
|
43
|
+
subject.trace?.should == false
|
44
|
+
subject.debug?.should == false
|
45
|
+
subject.info?.should == false
|
46
|
+
subject.warn?.should == false
|
47
|
+
subject.error?.should == true
|
48
|
+
subject.fatal?.should == true
|
51
49
|
|
52
50
|
subject.level = Logger::WARN
|
53
51
|
subject.level.should == Logger::WARN
|
52
|
+
subject.trace?.should == false
|
53
|
+
subject.debug?.should == false
|
54
|
+
subject.info?.should == false
|
55
|
+
subject.warn?.should == true
|
56
|
+
subject.error?.should == true
|
57
|
+
subject.fatal?.should == true
|
54
58
|
|
55
59
|
subject.level = Logger::INFO
|
56
60
|
subject.level.should == Logger::INFO
|
61
|
+
subject.trace?.should == false
|
62
|
+
subject.debug?.should == false
|
63
|
+
subject.info?.should == true
|
64
|
+
subject.warn?.should == true
|
65
|
+
subject.error?.should == true
|
66
|
+
subject.fatal?.should == true
|
57
67
|
|
58
68
|
subject.level = Logger::DEBUG
|
59
69
|
subject.level.should == Logger::DEBUG
|
70
|
+
subject.trace?.should == false
|
71
|
+
subject.debug?.should == true
|
72
|
+
subject.info?.should == true
|
73
|
+
subject.warn?.should == true
|
74
|
+
subject.error?.should == true
|
75
|
+
subject.fatal?.should == true
|
76
|
+
|
77
|
+
subject.level = DaemonLogger::TRACE
|
78
|
+
subject.level.should == DaemonLogger::TRACE
|
79
|
+
subject.trace?.should == true
|
80
|
+
subject.debug?.should == true
|
81
|
+
subject.info?.should == true
|
82
|
+
subject.warn?.should == true
|
83
|
+
subject.error?.should == true
|
84
|
+
subject.fatal?.should == true
|
60
85
|
end
|
61
86
|
|
62
87
|
it 'level set by string' do
|
@@ -74,15 +99,43 @@ describe ServerEngine::DaemonLogger do
|
|
74
99
|
|
75
100
|
subject.level = 'debug'
|
76
101
|
subject.level.should == Logger::DEBUG
|
102
|
+
|
103
|
+
subject.level = 'trace'
|
104
|
+
subject.level.should == DaemonLogger::TRACE
|
77
105
|
end
|
78
106
|
|
79
107
|
it 'unknown level' do
|
80
108
|
lambda { subject.level = 'unknown' }.should raise_error(ArgumentError)
|
81
109
|
end
|
82
110
|
|
83
|
-
it '
|
84
|
-
|
85
|
-
log
|
111
|
+
it 'rotation' do
|
112
|
+
log = DaemonLogger.new("tmp/se1.log", level: 'trace', log_rotate_age: 3, log_rotate_size: 10)
|
113
|
+
log.warn "test1"
|
114
|
+
File.exist?("tmp/se1.log").should == true
|
115
|
+
File.exist?("tmp/se1.log.0").should == false
|
116
|
+
|
117
|
+
log.warn "test2"
|
118
|
+
File.exist?("tmp/se1.log").should == true
|
119
|
+
File.exist?("tmp/se1.log.0").should == true
|
120
|
+
File.read("tmp/se1.log.0") =~ /test2$/
|
121
|
+
|
122
|
+
log.warn "test3"
|
123
|
+
log.warn "test4"
|
124
|
+
File.exist?("tmp/se1.log").should == true
|
125
|
+
File.exist?("tmp/se1.log.2").should == true
|
126
|
+
File.exist?("tmp/se1.log.3").should == false
|
127
|
+
|
128
|
+
log.warn "test5"
|
129
|
+
File.read("tmp/se1.log.0") =~ /test5$/
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'IO logger' do
|
133
|
+
io = StringIO.new
|
134
|
+
io.should_receive(:write)
|
135
|
+
io.should_not_receive(:reopen)
|
136
|
+
|
137
|
+
log = DaemonLogger.new(io)
|
86
138
|
log.debug "stdout logging test"
|
139
|
+
log.reopen!
|
87
140
|
end
|
88
141
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: serverengine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.
|
4
|
+
version: 1.5.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-10-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: sigdump
|
@@ -67,6 +67,7 @@ extensions: []
|
|
67
67
|
extra_rdoc_files: []
|
68
68
|
files:
|
69
69
|
- .gitignore
|
70
|
+
- .rspec
|
70
71
|
- .travis.yml
|
71
72
|
- Changelog
|
72
73
|
- Gemfile
|
@@ -118,6 +119,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
118
119
|
- - ! '>='
|
119
120
|
- !ruby/object:Gem::Version
|
120
121
|
version: '0'
|
122
|
+
segments:
|
123
|
+
- 0
|
124
|
+
hash: -2266789879425924886
|
121
125
|
requirements: []
|
122
126
|
rubyforge_project:
|
123
127
|
rubygems_version: 1.8.23
|
@@ -133,4 +137,3 @@ test_files:
|
|
133
137
|
- spec/signal_thread_spec.rb
|
134
138
|
- spec/spec_helper.rb
|
135
139
|
- spec/supervisor_spec.rb
|
136
|
-
has_rdoc: false
|