Add option (--log-file) to redirect logs to a file

This commit is contained in:
Abhinav Singh 2019-08-26 09:37:25 -07:00
parent fcfbec4760
commit a5a0149402
3 changed files with 101 additions and 41 deletions

View File

@ -1,6 +1,6 @@
[![Proxy.Py](ProxyPy.png)](https://github.com/abhinavsingh/proxy.py)
[![alt text](https://travis-ci.org/abhinavsingh/proxy.py.svg?branch=develop "Build Status")](https://travis-ci.org/abhinavsingh/proxy.py/) [![Coverage Status](https://coveralls.io/repos/github/abhinavsingh/proxy.py/badge.svg?branch=develop)](https://coveralls.io/github/abhinavsingh/proxy.py?branch=develop)
[![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![alt text](https://travis-ci.org/abhinavsingh/proxy.py.svg?branch=develop "Build Status")](https://travis-ci.org/abhinavsingh/proxy.py/) [![Coverage Status](https://coveralls.io/repos/github/abhinavsingh/proxy.py/badge.svg?branch=develop)](https://coveralls.io/github/abhinavsingh/proxy.py?branch=develop)
Features
--------
@ -35,7 +35,8 @@ usage: proxy.py [-h] [--backlog BACKLOG] [--basic-auth BASIC_AUTH]
[--client-recvbuf-size CLIENT_RECVBUF_SIZE]
[--hostname HOSTNAME] [--ipv4] [--enable-http-proxy]
[--enable-web-server] [--log-level LOG_LEVEL]
[--log-format LOG_FORMAT] [--num-workers NUM_WORKERS]
[--log-file LOG_FILE] [--log-format LOG_FORMAT]
[--num-workers NUM_WORKERS]
[--open-file-limit OPEN_FILE_LIMIT] [--pac-file PAC_FILE]
[--pac-file-url-path PAC_FILE_URL_PATH] [--plugins PLUGINS]
[--port PORT] [--server-recvbuf-size SERVER_RECVBUF_SIZE]
@ -51,7 +52,7 @@ optional arguments:
Default: No authentication. Specify colon separated
user:password to enable basic authentication.
--client-recvbuf-size CLIENT_RECVBUF_SIZE
Default: 8 KB. Maximum amount of data received from
Default: 1 MB. Maximum amount of data received from
the client in a single recv() operation. Bump this
value for faster uploads at the expense of increased
RAM.
@ -67,6 +68,7 @@ optional arguments:
CRITICAL. Both upper and lowercase values are
allowed.You may also simply use the leading character
e.g. --log-level d
--log-file LOG_FILE Default: sys.stdout. Log file destination.
--log-format LOG_FORMAT
Log format for Python logger.
--num-workers NUM_WORKERS
@ -81,7 +83,7 @@ optional arguments:
--plugins PLUGINS Comma separated plugins
--port PORT Default: 8899. Server port.
--server-recvbuf-size SERVER_RECVBUF_SIZE
Default: 8 KB. Maximum amount of data received from
Default: 1 MB. Maximum amount of data received from
the server in a single recv() operation. Bump this
value for faster downloads at the expense of increased
RAM.

View File

@ -60,6 +60,7 @@ DEFAULT_NUM_WORKERS = 0
DEFAULT_PLUGINS = {}
DEFAULT_VERSION = False
DEFAULT_LOG_FORMAT = '%(asctime)s - %(levelname)s - pid:%(process)d - %(funcName)s:%(lineno)d - %(message)s'
DEFAULT_LOG_FILE = None
# Set to True if under test
UNDER_TEST = False
@ -1124,7 +1125,7 @@ def init_parser() -> argparse.ArgumentParser:
help='Default: No authentication. Specify colon separated user:password '
'to enable basic authentication.')
parser.add_argument('--client-recvbuf-size', type=int, default=DEFAULT_CLIENT_RECVBUF_SIZE,
help='Default: 8 KB. Maximum amount of data received from the '
help='Default: 1 MB. Maximum amount of data received from the '
'client in a single recv() operation. Bump this '
'value for faster uploads at the expense of '
'increased RAM.')
@ -1141,6 +1142,8 @@ def init_parser() -> argparse.ArgumentParser:
help='Valid options: DEBUG, INFO (default), WARNING, ERROR, CRITICAL. '
'Both upper and lowercase values are allowed.'
'You may also simply use the leading character e.g. --log-level d')
parser.add_argument('--log-file', type=str, default=DEFAULT_LOG_FILE,
help='Default: sys.stdout. Log file destination.')
parser.add_argument('--log-format', type=str, default=DEFAULT_LOG_FORMAT,
help='Log format for Python logger.')
parser.add_argument('--num-workers', type=int, default=DEFAULT_NUM_WORKERS,
@ -1157,7 +1160,7 @@ def init_parser() -> argparse.ArgumentParser:
parser.add_argument('--port', type=int, default=DEFAULT_PORT,
help='Default: 8899. Server port.')
parser.add_argument('--server-recvbuf-size', type=int, default=DEFAULT_SERVER_RECVBUF_SIZE,
help='Default: 8 KB. Maximum amount of data received from the '
help='Default: 1 MB. Maximum amount of data received from the '
'server in a single recv() operation. Bump this '
'value for faster downloads at the expense of '
'increased RAM.')
@ -1186,14 +1189,17 @@ def main(args):
sys.exit(0)
try:
logging.basicConfig(level=getattr(
log_level = getattr(
logging,
{'D': 'DEBUG',
'I': 'INFO',
'W': 'WARNING',
'E': 'ERROR',
'C': 'CRITICAL'}[args.log_level.upper()[0]]),
format=args.log_format)
'C': 'CRITICAL'}[args.log_level.upper()[0]])
if args.log_file:
logging.basicConfig(filename=args.log_file, filemode='a', level=log_level, format=args.log_format)
else:
logging.basicConfig(level=log_level, format=args.log_format)
set_open_file_limit(args.open_file_limit)

116
tests.py
View File

@ -15,6 +15,8 @@ import os
import socket
import time
import unittest
import errno
import proxy
from contextlib import closing
from http.server import HTTPServer, BaseHTTPRequestHandler
from threading import Thread
@ -23,8 +25,6 @@ from unittest import mock
if os.name != 'nt':
import resource
import proxy
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s')
@ -36,6 +36,58 @@ def get_available_port():
return port
class TestTcpConnection(unittest.TestCase):
def testHandlesIOError(self):
self.conn = proxy.TcpConnection(proxy.TcpConnection.types.CLIENT)
_conn = mock.MagicMock()
_conn.recv.side_effect = IOError()
self.conn.conn = _conn
with mock.patch('proxy.logger') as mock_logger:
self.conn.recv()
mock_logger.exception.assert_called()
logging.info(mock_logger.exception.call_args[0][0].startswith('Exception while receiving from connection'))
def testHandlesConnReset(self):
self.conn = proxy.TcpConnection(proxy.TcpConnection.types.CLIENT)
_conn = mock.MagicMock()
e = IOError()
e.errno = errno.ECONNRESET
_conn.recv.side_effect = e
self.conn.conn = _conn
with mock.patch('proxy.logger') as mock_logger:
self.conn.recv()
mock_logger.exception.assert_not_called()
mock_logger.debug.assert_called()
self.assertEqual(mock_logger.debug.call_args[0][0], '%r' % e)
def testClosesIfNotClosed(self):
self.conn = proxy.TcpConnection(proxy.TcpConnection.types.CLIENT)
_conn = mock.MagicMock()
self.conn.conn = _conn
self.conn.close()
_conn.close.assert_called()
self.assertTrue(self.conn.closed)
def testNoOpIfAlreadyClosed(self):
self.conn = proxy.TcpConnection(proxy.TcpConnection.types.CLIENT)
_conn = mock.MagicMock()
self.conn.conn = _conn
self.conn.closed = True
self.conn.close()
_conn.close.assert_not_called()
self.assertTrue(self.conn.closed)
@mock.patch('socket.create_connection')
def testTcpServerClosesConnOnGC(self, mock_create_connection):
conn = mock.MagicMock()
mock_create_connection.return_value = conn
self.conn = proxy.TcpServerConnection(proxy.DEFAULT_IPV4_HOSTNAME, proxy.DEFAULT_PORT)
self.conn.connect()
del self.conn
conn.close.assert_called()
@unittest.skipIf(os.getenv('TESTING_ON_TRAVIS', 0), 'Opening sockets not allowed on Travis')
class TestTcpServer(unittest.TestCase):
ipv4_port = None
@ -774,6 +826,34 @@ class TestWorker(unittest.TestCase):
self.assertTrue(mock_http_proxy.called)
class TestHttpRequestRejected(unittest.TestCase):
def setUp(self):
self.request = proxy.HttpParser(proxy.HttpParser.types.REQUEST_PARSER)
def test_empty_response(self):
e = proxy.HttpRequestRejected()
self.assertEqual(e.response(self.request), None)
def test_status_code_response(self):
e = proxy.HttpRequestRejected(status_code=b'200 OK')
self.assertEqual(e.response(self.request), proxy.CRLF.join([
b'HTTP/1.1 200 OK',
proxy.PROXY_AGENT_HEADER,
proxy.CRLF
]))
def test_body_response(self):
e = proxy.HttpRequestRejected(status_code=b'404 NOT FOUND', body=b'Nothing here')
self.assertEqual(e.response(self.request), proxy.CRLF.join([
b'HTTP/1.1 404 NOT FOUND',
proxy.PROXY_AGENT_HEADER,
b'Content-Length: 12',
proxy.CRLF,
b'Nothing here'
]))
class TestMain(unittest.TestCase):
@mock.patch('proxy.HttpProtocolConfig')
@ -809,7 +889,7 @@ class TestMain(unittest.TestCase):
mock_set_open_file_limit.assert_not_called()
mock_config.assert_not_called()
@unittest.skipIf(True, 'For some reason this test passes when running in Intellij but fails via CLI :(')
@unittest.skipIf(False, 'For some reason this test passes when running with Intellij but fails via CLI :(')
@mock.patch('builtins.print')
@mock.patch('proxy.HttpProtocolConfig')
@mock.patch('proxy.set_open_file_limit')
@ -821,7 +901,7 @@ class TestMain(unittest.TestCase):
with self.assertRaises(SystemExit):
proxy.main([])
mock_version.assert_called()
logging.info(mock_print.call_args.startswith('DEPRECATION'))
self.assertTrue(mock_print.call_args.startswith('DEPRECATION'))
mock_multicore_dispatcher.assert_not_called()
mock_set_open_file_limit.assert_not_called()
mock_config.assert_not_called()
@ -878,34 +958,6 @@ class TestMain(unittest.TestCase):
mock_set_rlimit.assert_not_called()
class TestHttpRequestRejected(unittest.TestCase):
def setUp(self):
self.request = proxy.HttpParser(proxy.HttpParser.types.REQUEST_PARSER)
def test_empty_response(self):
e = proxy.HttpRequestRejected()
self.assertEqual(e.response(self.request), None)
def test_status_code_response(self):
e = proxy.HttpRequestRejected(status_code=b'200 OK')
self.assertEqual(e.response(self.request), proxy.CRLF.join([
b'HTTP/1.1 200 OK',
proxy.PROXY_AGENT_HEADER,
proxy.CRLF
]))
def test_body_response(self):
e = proxy.HttpRequestRejected(status_code=b'404 NOT FOUND', body=b'Nothing here')
self.assertEqual(e.response(self.request), proxy.CRLF.join([
b'HTTP/1.1 404 NOT FOUND',
proxy.PROXY_AGENT_HEADER,
b'Content-Length: 12',
proxy.CRLF,
b'Nothing here'
]))
if __name__ == '__main__':
proxy.UNDER_TEST = True
unittest.main()