Add option (--log-file) to redirect logs to a file
This commit is contained in:
parent
fcfbec4760
commit
a5a0149402
10
README.md
10
README.md
|
@ -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.
|
||||
|
|
16
proxy.py
16
proxy.py
|
@ -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
116
tests.py
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue