serverengine 1.5.5 → 1.5.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|