0
# Logging & Utilities
1
2
Logging configuration, process management, and utility functions for debugging, deployment, and command-line usage of pyftpdlib. These utilities provide essential support functions for production FTP server deployments.
3
4
## Capabilities
5
6
### Logging System
7
8
Comprehensive logging configuration with colored output, multi-process support, and flexible formatting options.
9
10
```python { .api }
11
# Module-level variables
12
logger: logging.Logger # Default pyftpdlib logger instance
13
LEVEL: int = logging.INFO # Default logging level
14
PREFIX: str = '[%(levelname)1.1s %(asctime)s]' # Default log prefix
15
PREFIX_MPROC: str = '[%(levelname)1.1s %(asctime)s %(process)s]' # Multi-process prefix
16
COLOURED: bool # Terminal color support detection
17
TIME_FORMAT: str = "%Y-%m-%d %H:%M:%S" # Timestamp format
18
19
class LogFormatter(logging.Formatter):
20
"""
21
Custom log formatter with color support and robust encoding.
22
Features terminal color detection and proper str/bytes handling.
23
"""
24
25
def __init__(self, *args, **kwargs):
26
"""Initialize formatter with color detection."""
27
28
def format(self, record):
29
"""
30
Format log record with colors and timestamps.
31
32
Parameters:
33
- record: logging.LogRecord instance
34
35
Returns:
36
- Formatted log message string
37
"""
38
39
def config_logging(level=LEVEL, prefix=PREFIX, other_loggers=None):
40
"""
41
Configure pyftpdlib logging system.
42
43
Parameters:
44
- level: logging level (logging.DEBUG, logging.INFO, etc.)
45
- prefix: log message prefix format string
46
- other_loggers: list of other logger names to configure
47
"""
48
49
def debug(s, inst=None):
50
"""
51
Debug logging helper function.
52
53
Parameters:
54
- s: message to log
55
- inst: optional instance for context information
56
"""
57
58
def is_logging_configured():
59
"""
60
Check if logging has been configured.
61
62
Returns:
63
- True if logging is already configured
64
"""
65
```
66
67
### Process Management
68
69
Multi-process server support with worker process forking and automatic restart capabilities.
70
71
```python { .api }
72
def cpu_count():
73
"""
74
Get number of CPU cores available.
75
76
Returns:
77
- Number of CPU cores as integer
78
"""
79
80
def fork_processes(number, max_restarts=100):
81
"""
82
Fork multiple worker processes for multi-process server model.
83
84
Parameters:
85
- number: number of worker processes to fork
86
- max_restarts: maximum automatic restarts per worker (0 = no limit)
87
88
Returns:
89
- Process ID in parent process, None in worker processes
90
91
Note:
92
- Available on POSIX systems only
93
- Handles SIGCHLD for automatic worker restart
94
- Distributes work among processes using SO_REUSEPORT where available
95
"""
96
97
def _reseed_random():
98
"""
99
Internal function to reseed random number generator in child processes.
100
Ensures each worker process has independent random state.
101
"""
102
```
103
104
### Command Line Interface
105
106
Full-featured command-line interface for quick FTP server deployment with extensive configuration options.
107
108
```python { .api }
109
def main(args=None):
110
"""
111
Main entry point for command-line FTP server.
112
Accessible via: python -m pyftpdlib
113
114
Parameters:
115
- args: command line arguments list (None = use sys.argv)
116
117
Command-line options:
118
-i, --interface ADDRESS Bind to specific interface (default: all interfaces)
119
-p, --port PORT Port number (default: 2121)
120
-w, --write Enable write permissions for anonymous
121
-d, --directory FOLDER Directory to serve (default: current directory)
122
-n, --nat-address ADDRESS NAT address to use in PASV responses
123
-r, --range FROM-TO Passive port range (e.g., 8000-9000)
124
-D, --debug Enable debug logging
125
-v, --version Show version and exit
126
-V, --verbose Enable verbose logging
127
-u, --username USER Username for authentication (requires -P)
128
-P, --password PASS Password for authentication (requires -u)
129
"""
130
```
131
132
## Usage Examples
133
134
### Basic Logging Configuration
135
136
```python
137
from pyftpdlib.log import config_logging, debug
138
import logging
139
140
# Configure basic logging
141
config_logging(level=logging.INFO)
142
143
# Configure debug logging with custom prefix
144
config_logging(
145
level=logging.DEBUG,
146
prefix='[%(asctime)s] %(name)s: %(levelname)s - '
147
)
148
149
# Configure logging for other components
150
config_logging(
151
level=logging.WARNING,
152
other_loggers=['paramiko', 'requests']
153
)
154
155
# Use debug helper
156
debug("Connection established", inst=handler_instance)
157
```
158
159
### Multi-Process Server
160
161
```python
162
from pyftpdlib.prefork import fork_processes, cpu_count
163
from pyftpdlib.servers import FTPServer
164
165
def start_worker():
166
"""Worker process main function."""
167
# Setup server in worker process
168
server = FTPServer(("0.0.0.0", 21), handler)
169
server.serve_forever()
170
171
# Fork worker processes
172
num_workers = cpu_count()
173
print(f"Starting {num_workers} worker processes")
174
175
if fork_processes(num_workers) is None:
176
# This is a worker process
177
start_worker()
178
else:
179
# This is the parent process
180
print("All workers started")
181
# Parent can exit or do other work
182
```
183
184
### Command Line Usage
185
186
```bash
187
# Basic FTP server on default port 2121
188
python -m pyftpdlib
189
190
# FTP server with write access for anonymous users
191
python -m pyftpdlib --write
192
193
# Custom port and directory
194
python -m pyftpdlib -p 21 -d /var/ftp/pub
195
196
# Server with authentication
197
python -m pyftpdlib -u ftpuser -P secret123
198
199
# Advanced configuration
200
python -m pyftpdlib \
201
--interface 0.0.0.0 \
202
--port 21 \
203
--directory /home/ftp \
204
--nat-address 203.0.113.10 \
205
--range 50000-50099 \
206
--verbose
207
```
208
209
### Custom Logging Handler
210
211
```python
212
from pyftpdlib.log import LogFormatter
213
import logging
214
import sys
215
216
class CustomFTPHandler(FTPHandler):
217
def __init__(self, conn, server, ioloop=None):
218
super().__init__(conn, server, ioloop)
219
220
# Setup custom logger for this session
221
self.session_logger = logging.getLogger(f'ftp.{self.remote_ip}')
222
handler = logging.StreamHandler(sys.stdout)
223
handler.setFormatter(LogFormatter())
224
self.session_logger.addHandler(handler)
225
self.session_logger.setLevel(logging.INFO)
226
227
def on_login(self, username):
228
self.session_logger.info(f"User {username} logged in")
229
230
def on_file_sent(self, file):
231
self.session_logger.info(f"File sent: {file}")
232
233
def on_logout(self, username):
234
self.session_logger.info(f"User {username} logged out")
235
```
236
237
### Production Logging Setup
238
239
```python
240
import logging
241
import logging.handlers
242
from pyftpdlib.log import LogFormatter
243
244
def setup_production_logging():
245
"""Configure logging for production deployment."""
246
247
# Main application logger
248
logger = logging.getLogger('pyftpdlib')
249
logger.setLevel(logging.INFO)
250
251
# File handler with rotation
252
file_handler = logging.handlers.RotatingFileHandler(
253
'/var/log/pyftpdlib.log',
254
maxBytes=10*1024*1024, # 10MB
255
backupCount=5
256
)
257
file_handler.setFormatter(LogFormatter())
258
logger.addHandler(file_handler)
259
260
# Syslog handler for system integration
261
syslog_handler = logging.handlers.SysLogHandler(address='/dev/log')
262
syslog_formatter = logging.Formatter(
263
'pyftpdlib[%(process)d]: %(levelname)s - %(message)s'
264
)
265
syslog_handler.setFormatter(syslog_formatter)
266
logger.addHandler(syslog_handler)
267
268
# Console handler for interactive debugging
269
console_handler = logging.StreamHandler()
270
console_handler.setFormatter(LogFormatter())
271
console_handler.setLevel(logging.WARNING)
272
logger.addHandler(console_handler)
273
274
setup_production_logging()
275
```
276
277
### Process Management with Restart Logic
278
279
```python
280
import signal
281
import sys
282
import time
283
from pyftpdlib.prefork import fork_processes
284
285
class ProcessManager:
286
def __init__(self, worker_count=None):
287
self.worker_count = worker_count or cpu_count()
288
self.should_exit = False
289
290
def signal_handler(self, signum, frame):
291
"""Handle shutdown signals."""
292
print(f"Received signal {signum}, shutting down...")
293
self.should_exit = True
294
295
def start_master(self):
296
"""Start master process with worker management."""
297
# Setup signal handlers
298
signal.signal(signal.SIGINT, self.signal_handler)
299
signal.signal(signal.SIGTERM, self.signal_handler)
300
301
print(f"Starting master process with {self.worker_count} workers")
302
303
# Fork workers with restart capability
304
if fork_processes(self.worker_count, max_restarts=10) is None:
305
# Worker process
306
self.start_worker()
307
else:
308
# Master process
309
self.monitor_workers()
310
311
def start_worker(self):
312
"""Worker process main loop."""
313
try:
314
server = FTPServer(("0.0.0.0", 21), handler)
315
server.serve_forever()
316
except KeyboardInterrupt:
317
pass
318
except Exception as e:
319
print(f"Worker error: {e}")
320
sys.exit(1)
321
322
def monitor_workers(self):
323
"""Master process monitoring loop."""
324
while not self.should_exit:
325
time.sleep(1)
326
327
print("Master process exiting")
328
329
# Usage
330
manager = ProcessManager(worker_count=4)
331
manager.start_master()
332
```
333
334
### Development vs Production Configuration
335
336
```python
337
import os
338
from pyftpdlib.log import config_logging
339
340
def configure_for_environment():
341
"""Configure logging based on environment."""
342
343
if os.getenv('ENVIRONMENT') == 'development':
344
# Development: verbose console logging
345
config_logging(
346
level=logging.DEBUG,
347
prefix='[%(asctime)s] %(name)s:%(lineno)d - %(levelname)s - '
348
)
349
print("Development logging enabled")
350
351
elif os.getenv('ENVIRONMENT') == 'production':
352
# Production: structured logging to files
353
config_logging(
354
level=logging.INFO,
355
prefix='[%(asctime)s] %(process)d - %(levelname)s - '
356
)
357
print("Production logging enabled")
358
359
else:
360
# Default: standard logging
361
config_logging(level=logging.INFO)
362
363
configure_for_environment()
364
```
365
366
### Integration with systemd
367
368
```python
369
#!/usr/bin/env python3
370
"""
371
Systemd-compatible FTP server script.
372
Save as /usr/local/bin/pyftpd and make executable.
373
"""
374
375
import sys
376
import logging
377
from pyftpdlib.log import config_logging
378
from pyftpdlib.authorizers import DummyAuthorizer
379
from pyftpdlib.handlers import FTPHandler
380
from pyftpdlib.servers import FTPServer
381
382
def main():
383
# Configure logging for systemd (no timestamps, systemd adds them)
384
config_logging(
385
level=logging.INFO,
386
prefix='%(levelname)s - '
387
)
388
389
# Setup FTP server
390
authorizer = DummyAuthorizer()
391
authorizer.add_anonymous('/var/ftp/pub', perm='elr')
392
393
handler = FTPHandler
394
handler.authorizer = authorizer
395
396
server = FTPServer(('0.0.0.0', 21), handler)
397
398
try:
399
logging.info("Starting FTP server")
400
server.serve_forever()
401
except KeyboardInterrupt:
402
logging.info("Received SIGINT, shutting down")
403
except Exception as e:
404
logging.error(f"Server error: {e}")
405
sys.exit(1)
406
finally:
407
server.close_all()
408
logging.info("FTP server stopped")
409
410
if __name__ == '__main__':
411
main()
412
```
413
414
Corresponding systemd service file (`/etc/systemd/system/pyftpd.service`):
415
416
```ini
417
[Unit]
418
Description=Python FTP Server
419
After=network.target
420
421
[Service]
422
Type=simple
423
User=ftp
424
Group=ftp
425
ExecStart=/usr/local/bin/pyftpd
426
Restart=always
427
RestartSec=5
428
429
[Install]
430
WantedBy=multi-user.target
431
```
432
433
## Environment Variables
434
435
pyftpdlib respects several environment variables for configuration:
436
437
- **PYTHONUNBUFFERED**: Disable output buffering for real-time logging
438
- **PYFTPDLIB_DEBUG**: Enable debug logging when set to any value
439
- **PYFTPDLIB_LOG_PREFIX**: Override default log prefix format
440
441
## Platform Support
442
443
- **Process Management**: POSIX systems only (Linux, macOS, BSD)
444
- **Logging**: All platforms with appropriate terminal color detection
445
- **Command Line Interface**: All platforms with cross-platform path handling