Skip to content

Commit 852af29

Browse files
committed
Use click for CLI
Replace obsolete self-written logger by logbook
1 parent aa3aceb commit 852af29

File tree

6 files changed

+220
-70
lines changed

6 files changed

+220
-70
lines changed

bin/saltyrtc-server

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
#!/usr/bin/env python3
2+
"""
3+
The command line interface for the SaltyRTC Signalling Server.
4+
"""
5+
import functools
6+
import asyncio
7+
8+
import click
9+
10+
from saltyrtc.server import __version__ as _version
11+
from saltyrtc import server
12+
13+
14+
def aio_run(func, run_forever=False):
15+
func = asyncio.coroutine(func)
16+
17+
def _wrapper(*args, **kwargs):
18+
loop = asyncio.get_event_loop()
19+
task = loop.create_task(func(*args, **kwargs))
20+
loop.run_until_complete(task)
21+
if run_forever:
22+
loop.run_forever()
23+
return task.result()
24+
return functools.update_wrapper(_wrapper, func)
25+
26+
27+
def aio_serve(func):
28+
return aio_run(func, run_forever=True)
29+
30+
31+
@click.group()
32+
@click.pass_context
33+
def cli(ctx):
34+
"""
35+
Command Line Interface. Use --help for details.
36+
"""
37+
ctx.obj = {}
38+
39+
40+
@cli.command(short_help='Show version information.', help="""
41+
Show the current version of the SaltyRTC Signalling Server.
42+
""")
43+
def version():
44+
click.echo('Version: {}'.format(_version))
45+
46+
47+
@cli.command()
48+
@aio_serve
49+
def serve():
50+
yield from server.serve()
51+
52+
53+
if __name__ == '__main__':
54+
with server.logging_handler.applicationbound():
55+
try:
56+
cli()
57+
except Exception as exc:
58+
click.echo('An error occurred:', err=True)
59+
click.echo(exc, err=True)

saltyrtc/server/__init__.py

+35-47
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,24 @@
88

99
import websockets
1010

11-
from . import settings
11+
from . import config
1212
from ._asyncio import gather
1313

14-
from . import exception
14+
from . import exception, util
15+
from .util import get_logging_handler
1516
from .exception import *
1617

1718
__author__ = 'Lennart Grahl <[email protected]>'
1819
__status__ = 'Development'
1920
__version__ = '0.0.1'
2021
__all__ = (
21-
22+
'serve',
23+
'logging_handler',
2224
) + exception.__all__
2325

26+
# Create default logger and handler
27+
logging_handler = util.get_logging_handler()
28+
log = util.get_logger()
2429

2530
# Globals
2631
paths = {}
@@ -151,17 +156,17 @@ def register(self, ws):
151156
raise MessageFlowError('Sent invalid JSON object while registering') from exc
152157

153158
# Check type
154-
type_ = data.get(settings.field_type)
155-
if type_ == settings.type_hello_server:
159+
type_ = data.get(config.field_type)
160+
if type_ == config.type_hello_server:
156161
# Check for key
157-
key = data.get(settings.type_key)
162+
key = data.get(config.type_key)
158163
if key is not None:
159164
# Register connection and set key
160165
role = Role.server
161166
self.set(ws, role, key=key)
162167
else:
163168
raise MessageFlowError('Registered as server but key is missing')
164-
elif type_ == settings.type_hello_client:
169+
elif type_ == config.type_hello_client:
165170
# Register connection
166171
role = Role.client
167172
self.set(ws, role)
@@ -185,8 +190,8 @@ def send_key(self, ws):
185190

186191
# Create key message
187192
message = {
188-
settings.field_type: settings.type_key,
189-
settings.field_data: self.key
193+
config.field_type: config.type_key,
194+
config.field_data: self.key
190195
}
191196

192197
# Send key message
@@ -248,8 +253,8 @@ def handle(self, ws, role, message):
248253
raise MessageFlowError(error) from exc
249254

250255
# Check type
251-
type_ = data.get(settings.field_type)
252-
if type_ == settings.type_reset:
256+
type_ = data.get(config.field_type)
257+
if type_ == config.type_reset:
253258
# Dispatch silently
254259
yield from self.send_reset(ws, role, message)
255260
else:
@@ -260,7 +265,7 @@ def handle(self, ws, role, message):
260265
@asyncio.coroutine
261266
def send_error(self, ws, role):
262267
# Create error message
263-
message = {settings.field_type: settings.type_send_error}
268+
message = {config.field_type: config.type_send_error}
264269

265270
# Send error message
266271
self.logger.info('Sending send error notification to {}', role)
@@ -306,14 +311,14 @@ def keep_alive(logger, ws, role):
306311
logger.debug('Ping to {}', role)
307312
try:
308313
# Send ping
309-
yield from asyncio.wait_for(ws.ping(), settings.ping_timeout)
314+
yield from asyncio.wait_for(ws.ping(), config.ping_timeout)
310315
except asyncio.TimeoutError:
311316
raise PingTimeoutError(role)
312317
else:
313318
logger.debug('Pong from {}', role)
314319

315320
# Wait
316-
yield from asyncio.sleep(settings.ping_interval)
321+
yield from asyncio.sleep(config.ping_interval)
317322
except asyncio.CancelledError:
318323
logger.debug('Ping cancelled {}', role)
319324

@@ -325,7 +330,7 @@ def signaling(ws, path):
325330

326331
try:
327332
# Validate path
328-
if len(path) != settings.path_length:
333+
if len(path) != config.path_length:
329334
raise PathError(len(path))
330335
except PathError as exc:
331336
logging.getLogger('signaling').error(exc)
@@ -365,35 +370,18 @@ def signaling(ws, path):
365370
path.logger.warning('Unreachable section')
366371

367372

368-
def start_server():
369-
server = websockets.serve(signaling, port=8765)
370-
loop = asyncio.get_event_loop()
371-
loop.run_until_complete(server)
372-
loop.run_forever()
373-
374-
375-
def setup_logging():
376-
# Setup formatter and handler
377-
formatter = logging.Formatter(
378-
fmt=settings.logging_formatter,
379-
datefmt=settings.logging_date_formatter,
380-
style=settings.logging_style
381-
)
382-
# handler = logging.color.ColorStreamHandler()
383-
handler = logging.StreamHandler()
384-
handler.setFormatter(formatter)
385-
386-
# Setup websockets logger
387-
ws_logger = logging.getLogger('websockets')
388-
ws_logger.setLevel(settings.logging_level)
389-
ws_logger.addHandler(handler)
390-
391-
# Setup signaling logger
392-
logger = logging.getLogger('signaling')
393-
logger.set_level(settings.logging_level)
394-
logger.add_handler(handler)
395-
396-
397-
if __name__ == '__main__':
398-
setup_logging()
399-
start_server()
373+
@asyncio.coroutine
374+
def serve(port=8765, loop=None):
375+
"""
376+
TODO: Describe.
377+
"""
378+
# Get loop
379+
loop = loop if loop is not None else asyncio.get_event_loop()
380+
381+
# Start server
382+
log.debug('Starting server')
383+
server = yield from websockets.serve(signaling, port=port)
384+
385+
# Return server
386+
log.notice('Listening')
387+
return server

saltyrtc/server/config.py

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
"""
2+
Guess we should override this with some
3+
whatever-configuration-is-in-now config file.
4+
"""
5+
6+
# Logging
7+
logging = True
8+
log_level = 'DEBUG'
9+
10+
# Keep-Alive settings
11+
ping_timeout = 10.0
12+
ping_interval = 60.0
13+
14+
# TODO: This stuff should not be in here as it is constant
15+
# Signalling
16+
path_length = 64
17+
# Signalling messages
18+
field_type = 'type'
19+
field_data = 'data'
20+
type_hello_server = 'hello-server'
21+
type_hello_client = 'hello-client'
22+
type_reset = 'reset'
23+
type_key = 'key'
24+
type_send_error = 'send-error'

saltyrtc/server/settings.py

-22
This file was deleted.

saltyrtc/server/util.py

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
"""
2+
This module provides utility functions for the SaltyRTC Signalling
3+
Server.
4+
"""
5+
from contextlib import contextmanager
6+
7+
from saltyrtc.server import config
8+
9+
__all__ = (
10+
'Logger',
11+
'get_logger',
12+
'get_logging_handler',
13+
)
14+
15+
16+
try:
17+
# noinspection PyPackageRequirements,PyUnresolvedReferences
18+
import logbook
19+
except ImportError:
20+
class Logger(object):
21+
"""
22+
Dummy logger in case :mod:`logbook` is not present.
23+
"""
24+
def __init__(self, name, level=0):
25+
self.name = name
26+
self.level = level
27+
debug = info = warn = warning = notice = error = exception = \
28+
critical = log = lambda *a, **kw: None
29+
else:
30+
class Logger(logbook.Logger):
31+
"""
32+
Disable logger depending on config.
33+
"""
34+
@property
35+
def disabled(self):
36+
return config.logging is False
37+
38+
# Enable debug logging for `asyncio`
39+
import os
40+
# Enable asyncio debug logging
41+
os.environ['PYTHONASYNCIODEBUG'] = '1'
42+
43+
import logbook.compat
44+
import logging
45+
46+
# Redirect asyncio logger
47+
_logger = logging.getLogger('asyncio')
48+
_logger.setLevel(logging.INFO)
49+
_logger.addHandler(logbook.compat.RedirectLoggingHandler())
50+
51+
52+
def _get_log_level():
53+
"""
54+
Return the translated log level in case :mod:`logbook` is present.
55+
"""
56+
try:
57+
# noinspection PyUnresolvedReferences,PyPackageRequirements
58+
from logbook import lookup_level
59+
except ImportError:
60+
return config.log_level
61+
else:
62+
return lookup_level(config.log_level)
63+
64+
65+
def _get_logging_handler():
66+
"""
67+
Return a :class:`~logbook.more.ColorizedStderrHandler` instance
68+
in case :mod:`logbook` is present. Otherwise return a mock handler.
69+
"""
70+
try:
71+
# noinspection PyPackageRequirements,PyUnresolvedReferences
72+
import logbook.more
73+
except ImportError:
74+
class _LoggingHandler:
75+
@contextmanager
76+
def applicationbound(self):
77+
yield
78+
79+
return _LoggingHandler()
80+
else:
81+
return logbook.more.ColorizedStderrHandler()
82+
83+
84+
def get_logger(name=None):
85+
"""
86+
Return the default :class:`Logger` instance of the library.
87+
88+
Arguments:
89+
- `name`: The name of a specific sub-logger.
90+
"""
91+
base_name = 'saltyrtc.server'
92+
name = base_name if name is None else '.'.join((base_name, name))
93+
return Logger(name, level=_get_log_level())
94+
95+
96+
def get_logging_handler():
97+
"""
98+
Return the logging handler.
99+
"""
100+
return _get_logging_handler()

setup.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,11 @@ def get_version():
2121
name='saltyrtc.server',
2222
version=get_version(),
2323
packages=find_packages(),
24-
namespace_packages=['threema'],
24+
namespace_packages=['saltyrtc'],
2525
install_requires=[
2626
'libnacl>=1.4.4',
2727
'click>=6.3',
28+
'logbook>=0.12.5',
2829
'websockets>=3.0',
2930
'asyncio>=3.4.3',
3031
],

0 commit comments

Comments
 (0)