diff --git a/.env b/.env new file mode 100644 index 0000000..3d00200 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +USAGE_LOGGERS_DISABLE=False +USAGE_LOGGERS_URL="http://localhost:7701/message" diff --git a/.env.example b/.env.example index 404527d..3d00200 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,2 @@ USAGE_LOGGERS_DISABLE=False -USAGE_LOGGERS_URL="http://localhost:4001/message" +USAGE_LOGGERS_URL="http://localhost:7701/message" diff --git a/.github/workflows/crossplatform_tests.yaml b/.github/workflows/crossplatform_tests.yaml new file mode 100644 index 0000000..ca4055c --- /dev/null +++ b/.github/workflows/crossplatform_tests.yaml @@ -0,0 +1,26 @@ +name: Crossplatform Tests + +on: + - push + - pull_request + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] + python-version: ["3.6", "3.7", "3.8", "3.9"] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox tox-gh-actions + - name: Test with tox + run: tox diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml deleted file mode 100644 index 3ae2d74..0000000 --- a/.github/workflows/pytest.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Unit test the usagelogger - -on: - push: - branches: - - master - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - run: | - git fetch --prune --unshallow - - name: Set up Python 3.7 - uses: actions/setup-python@v1 - with: - python-version: 3.7 - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements_dev.txt - - name: Run pytest - run: | - pytest diff --git a/README.md b/README.md index ec7452e..1c3b65a 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ pip3 install --upgrade usagelogger ```python from aiohttp import web -from usagelogger.aiohttp import HttpLoggerForAIOHTTP +from usagelogger.middleware.aiohttp import HttpLoggerForAIOHTTP async def test(request): return web.Response(text="Hello") @@ -48,7 +48,7 @@ async def test(request): app = web.Application( middlewares=[ HttpLoggerForAIOHTTP( - url="http://localhost:4001/message", rules="include debug" + url="http://localhost:7701/message", rules="include debug" ) ] ) @@ -66,7 +66,7 @@ First edit `settings.py` to register middleware, like this: ```python MIDDLEWARE = [ - "usagelogger.django.HttpLoggerForDjango", # Always on the top + "usagelogger.middleware.django.HttpLoggerForDjango", # Always on the top "django.middleware...", ] ``` @@ -75,7 +75,7 @@ Then add a new section to `settings.py` for logging configuration, like this: ```python USAGELOGGER = { - 'url': 'http://localhost:4001/message', + 'url': 'http://localhost:7701/message', 'rules': 'include debug' } ``` @@ -86,12 +86,12 @@ USAGELOGGER = { ```python from flask import Flask -from usagelogger.flask import HttpLoggerForFlask +from usagelogger.middleware.flask import HttpLoggerForFlask app = Flask(__name__) app.wsgi_app = HttpLoggerForFlask( # type: ignore - app=app.wsgi_app, url="http://localhost:4001/message", rules="include debug" + app=app.wsgi_app, url="http://localhost:7701/message", rules="include debug" ) @app.route("/") @@ -108,7 +108,7 @@ app.run(debug=True) ```python from usagelogger import resurface -s = resurface.Session(url="http://localhost:4001/message", rules="include debug") +s = resurface.Session(url="http://localhost:7701/message", rules="include debug") s.get(...) ``` diff --git a/pyproject.toml b/pyproject.toml index bea69ff..71b195b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,6 +17,9 @@ exclude = ''' ''' [tool.pytest.ini_options] +testpaths = [ + "tests", +] log_cli = true log_cli_level = "CRITICAL" log_cli_format = "%(message)s" @@ -25,3 +28,8 @@ log_file = "test_debug.log" log_file_level = "DEBUG" log_file_format = "%(asctime)s [%(levelname)8s] %(message)s (%(filename)s:%(lineno)s)" log_file_date_format = "%Y-%m-%d %H:%M:%S" + +[build-system] +requires = ["setuptools>=42.0", "wheel"] +build-backend = "setuptools.build_meta" + diff --git a/requirements_dev.txt b/requirements_dev.txt index d5b8c6f..ef13a93 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -11,3 +11,4 @@ pytest==6.2.2 Werkzeug==1.0.1 django==3.1.12 aiohttp==3.7.4 +tox==3.24.3 diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/setup.cfg @@ -0,0 +1 @@ + diff --git a/setup.py b/setup.py index bcdc09f..811e3e3 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,6 @@ -from setuptools import find_packages, setup +from distutils import util + +from setuptools import setup def read_file(name): @@ -6,9 +8,12 @@ def read_file(name): return fd.read() +middleware = util.convert_path("usagelogger/middleware") +utils = util.convert_path("usagelogger/utils") + setup( name="usagelogger", - version="2.2.6", + version="3.0.0", description="Logging usage of Python-based services, with user privacy by design.", long_description=read_file("DESCRIPTIONS.md"), long_description_content_type="text/markdown", @@ -27,9 +32,15 @@ def read_file(name): "Programming Language :: Python :: 3.9", ], keywords="logging resurface", - packages=find_packages(exclude=["tests"]), + package_dir={ + "usagelogger": "usagelogger", + "usagelogger.middleware": middleware, + "usagelogger.utils": utils, + }, + packages=["usagelogger", "usagelogger.middleware", "usagelogger.utils"], + # packages=find_packages(exclude=["tests"]), python_requires=">=3.7, <4", - install_requires=read_file("requirements.txt").splitlines(), + install_requires=["requests>=2"], include_package_data=True, tests_require=["pytest"], project_urls={ diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..5004758 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,5 @@ +import os +import sys + +sys.path.append("/usagelogger") +os.environ.setdefault("DEBUG", "True") diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..6a49b51 --- /dev/null +++ b/tox.ini @@ -0,0 +1,29 @@ +[tox] +minversion = 3.8.0 +envlist = py37, py38, py39, flake8, mypy +isolated_build = true + +[gh-actions] +python = + 3.7: py37 + 3.8: py38, mypy, flake8 + 3.9: py39 + +[testenv] +setenv = + PYTHONPATH = {toxinidir} +deps = + -r{toxinidir}/requirements_dev.txt +commands = + pytest --basetemp={envtmpdir} + +; [testenv:flake8] +; basepython = python3.8 +; deps = flake8 +; commands = flake8 usagelogger + +; [testenv:mypy] +; basepython = python3.8 +; deps = +; -r{toxinidir}/requirements_dev.txt +; commands = mypy usagelogger diff --git a/usagelogger/__init__.py b/usagelogger/__init__.py index 452e39c..2b11bd2 100644 --- a/usagelogger/__init__.py +++ b/usagelogger/__init__.py @@ -1,3 +1,4 @@ +from . import middleware # noqa from .base_logger import BaseLogger # noqa from .http_logger import HttpLogger # noqa from .http_message import HttpMessage # noqa @@ -16,4 +17,10 @@ "BaseLogger", "HttpLogger", "HttpMessage", + "resurface", + "middleware", + # Depricating soon + "flask", + "django", + "aiohttp", ] diff --git a/usagelogger/base_logger.py b/usagelogger/base_logger.py index 6c6d10c..f51ac35 100644 --- a/usagelogger/base_logger.py +++ b/usagelogger/base_logger.py @@ -1,9 +1,11 @@ # coding: utf-8 # © 2016-2021 Resurface Labs Inc. +import json import os import socket import threading import zlib +from queue import Queue from typing import Dict, List, Optional from urllib.parse import urlsplit @@ -13,6 +15,8 @@ from .usage_loggers import UsageLoggers +enclosure_queue: Queue = Queue() + class BaseLogger: """Basic usage logger to embed or extend.""" @@ -25,7 +29,7 @@ def __init__( url: Optional[str] = None, skip_compression: bool = False, skip_submission: bool = False, - conn=requests.Session(), + conn: requests.Session = requests.Session(), ) -> None: self.agent = agent @@ -46,11 +50,10 @@ def __init__( self._url = None elif url is not None and isinstance(url, str): try: - if urlsplit(url).scheme in {"http", "https"}: - self._url_scheme: str = urlsplit(url).scheme - self._url = url - else: + if urlsplit(url).scheme not in {"http", "https"}: raise TypeError("incorrect URL scheme") + self._url_scheme: str = urlsplit(url).scheme + self._url = url except TypeError: self._enabled = False self._url = None @@ -86,49 +89,88 @@ def enabled(self) -> bool: def queue(self) -> Optional[List[str]]: return self._queue - def submit(self, msg: str) -> None: + def __internal_submission(self, q: Queue): + try: + headers: Dict[str, str] = { + "Connection": "keep-alive", + "Content-Type": "application/ndjson; charset=UTF-8", + "User-Agent": "Resurface/" + + usagelogger.__version__ + + " (" + + self.agent + + ")", + } + + to_submit = [] + + cnt = 0 + + while not q.empty() or cnt == 100: + payload = q.get() + payload["msg"] = json.dumps(payload["msg"], separators=(",", ":")) + if not payload["skip_compression"]: + body = payload["msg"] + else: + headers["Content-Encoding"] = "deflated" + body = zlib.compress(payload["msg"]) + + to_submit.append(body) + cnt += 1 + q.task_done() + + # print(f"submitting {len(to_submit)} at once") + ndjson_payload = ("\n".join(to_submit)).encode("utf-8") + response = self.conn.post( + payload["url"], data=ndjson_payload, headers=headers + ) + if response.status_code == 204: + with self._submit_successes_lock: + self._submit_successes += 1 + else: + with self._submit_failures_lock: + self._submit_failures += 1 + # http errors + except (requests.exceptions.RequestException, IOError, OSError): + with self._submit_failures_lock: + self._submit_failures += 1 + # JSON errors + except (OverflowError, TypeError, ValueError): + with self._submit_failures_lock: + self._submit_failures += 1 + + def submit(self, msg: list) -> None: """Submits JSON message to intended destination.""" - if msg is None or self.skip_submission is True or self.enabled is False: + if ( + not msg + or msg is None + or self.skip_submission is True + or self.enabled is False + ): pass elif self._queue is not None: - self._queue.append(msg) + self._queue.append(json.dumps(msg, separators=(",", ":"))) with self._submit_successes_lock: self._submit_successes += 1 else: - try: - headers: Dict[str, str] = { - "Connection": "keep-alive", - "Content-Type": "application/json; charset=UTF-8", - "User-Agent": "Resurface/" - + usagelogger.__version__ - + " (" - + self.agent - + ")", - } - - if not self.skip_compression: - body = msg.encode("utf-8") - else: - headers["Content-Encoding"] = "deflated" - body = zlib.compress(msg.encode("utf-8")) - - response = self.conn.post(self.url, data=body, headers=headers) - if response.status_code == 204: - with self._submit_successes_lock: - self._submit_successes += 1 - else: - with self._submit_failures_lock: - self._submit_failures += 1 - - # http errors - except (requests.exceptions.RequestException, IOError, OSError): - with self._submit_failures_lock: - self._submit_failures += 1 - # JSON errors - except (OverflowError, TypeError, ValueError): - with self._submit_failures_lock: - self._submit_failures += 1 + payload = { + "url": self.url, + "msg": msg, + "skip_compression": self.skip_compression, + } + enclosure_queue.put(payload) + if "submission_thread" not in [x.name for x in threading.enumerate()]: + worker = threading.Thread( + target=self.__internal_submission, + args=(enclosure_queue,), + ) + worker.name = "submission_thread" + worker.setDaemon(True) + worker.start() + if os.environ.get("DEBUG") == "True": + worker.join() # Not a good practice but required for that success and failure counts + + # enclosure_queue.join() # We don't have to block our main thread. @property def submit_failures(self) -> int: diff --git a/usagelogger/http_logger.py b/usagelogger/http_logger.py index 8deb1a7..1ecd627 100644 --- a/usagelogger/http_logger.py +++ b/usagelogger/http_logger.py @@ -1,12 +1,10 @@ # coding: utf-8 # © 2016-2021 Resurface Labs Inc. - -import json -from usagelogger.warnings_utils import ResurfaceWarning -from typing import List, Optional +from typing import Dict, List, Optional from .base_logger import BaseLogger from .http_rules import HttpRules +from .utils.resurface_utils import ResurfaceWarning class HttpLogger(BaseLogger): @@ -45,6 +43,7 @@ def __init__( # apply configuration rules self.skip_compression = self._rules.skip_compression self.skip_submission = self._rules.skip_submission + if ( self._enabled and url is not None @@ -61,14 +60,26 @@ def __init__( def rules(self) -> HttpRules: return self._rules - def submit_if_passing(self, details: List[List[str]]) -> None: + def submit_if_passing( + self, details: List[List[str]], custom_fields: Optional[Dict[str, str]] + ) -> None: # apply active rules details = self._rules.apply(details) # type: ignore if details is None: return + # add custom fields + if custom_fields: + details.extend( + [ + [k, v] + for k, v in custom_fields.items() + if k not in [k for k, v in details] + ] + ) + # finalize message details.append(["host", self.host]) # let's do this thing - self.submit(json.dumps(details, separators=(",", ":"))) + self.submit(details) diff --git a/usagelogger/http_message.py b/usagelogger/http_message.py index beb2a0f..b0f7944 100644 --- a/usagelogger/http_message.py +++ b/usagelogger/http_message.py @@ -1,9 +1,8 @@ # coding: utf-8 # © 2016-2021 Resurface Labs Inc. - from re import match from time import time -from typing import List, Optional +from typing import Dict, List, Optional from urllib import parse from .http_logger import HttpLogger @@ -20,7 +19,8 @@ def send( request_body: Optional[str] = None, now=None, interval=None, - ) -> None: # TODO: missing type hints + custom_fields: Optional[Dict[str, str]] = None, + ) -> None: if not logger.enabled: return @@ -51,42 +51,21 @@ def send( if interval is not None: message.append(["interval", interval]) - logger.submit_if_passing(message) + logger.submit_if_passing(message, custom_fields) @classmethod - def build( + def build( # noqa: C901 cls, request, response, response_body: Optional[str] = None, request_body: Optional[str] = None, - ) -> List[List[str]]: + ) -> List[List[str]]: # sourcery no-metrics message: List[List[str]] = [] + address_in_header: bool = False - if request.__class__.__name__ == "WSGIRequest": - message = [] - if request.method: - message.append(["request_method", request.method]) - url = request.build_absolute_uri() - if url: - message.append(["request_url", url]) - if response.status_code: - message.append(["response_code", str(response.status_code)]) - for k, v in request.headers.items(): - message.append([f"request_header:{k}".lower(), v]) - message.append(["request_body", request.body.decode()]) - if request.method == "GET": - for k, v in request.GET.items(): - message.append([f"request_param:{k}".lower(), v]) - elif request.method == "POST": - for k, v in request.POST.items(): - message.append([f"request_param:{k}".lower(), v]) - for k, v in response.items(): - message.append([f"response_header:{k}".lower(), v]) - message.append(["response_body", response.content.decode("utf8")]) - - elif request.__class__.__name__ == "HttpRequestImpl": + if request.__class__.__name__ == "HttpRequestImpl": message = [] if request.method: message.append(["request_method", request.method]) @@ -94,12 +73,41 @@ def build( message.append(["request_url", request.url]) if response.status: message.append(["response_code", str(response.status)]) + + if request.remote_addr: + message.append( + ["request_header: x-forwarded-for ", request.remote_addr] + ) + address_in_header = True + for k, v in request.headers.items(): - message.append([f"request_header:{k}".lower(), v]) - for k, v in request.params.items(): - message.append([f"request_param:{k}".lower(), v]) - for k, v in response.headers.items(): - message.append([f"response_header:{k}".lower(), v]) + k = k.lower() + message.append([f"request_header:{k}", v]) + if not address_in_header and k in [ + "x-forwarded-for", + "forwarded-for", + "forwarded", + "cf-connecting-ip", + "fastly-client-ip", + "true-client-ip", + ]: + address_in_header = True + elif k in [ + "x-forwarded", + "x-client-ip", + "x-real-ip", + "x-cluster-client-ip", + ]: + message.append([f"request_header: x-forwarded-for {k}", v]) + + message.extend( + [f"request_param:{k}".lower(), v] for k, v in request.params.items() + ) + + message.extend( + [f"response_header:{k}".lower(), v] for k, v in response.headers.items() + ) + final_request_body = ( request_body if (request_body is not None) else request.body ) @@ -121,20 +129,25 @@ def build( message.append(["request_url", url]) if response.status_code: message.append(["response_code", str(response.status_code)]) - for k, v in request.headers.items(): - message.append([f"request_header:{k}".lower(), v]) + message.extend( + [f"request_header:{k}".lower(), v] for k, v in request.headers.items() + ) parsed_url = parse.parse_qs(parse.urlparse(url).query) - for k, v in parsed_url.items(): - message.append([f"request_param:{k}".lower(), v[0]]) + message.extend( + [f"request_param:{k}".lower(), v[0]] for k, v in parsed_url.items() + ) + if request.body: body_ = request.body if isinstance(body_, bytes): body_ = body_.decode() message.append(["request_body", body_]) - for k, v in response.headers.items(): - message.append([f"response_header:{k}".lower(), v]) + message.extend( + [f"response_header:{k}".lower(), v] for k, v in response.headers.items() + ) + message.append(["response_body", response.content.decode("utf8")]) return message diff --git a/usagelogger/http_request_impl.py b/usagelogger/http_request_impl.py index 7e927fd..0b185bf 100644 --- a/usagelogger/http_request_impl.py +++ b/usagelogger/http_request_impl.py @@ -12,9 +12,11 @@ def __init__( headers: Optional[Dict[str, str]] = None, params: Optional[Dict[str, str]] = None, body: Optional[str] = None, + remote_addr: Optional[str] = None, ) -> None: self.method = method self.url = url self.headers = {} if headers is None else headers self.params = {} if params is None else params self.body = body + self.remote_addr = remote_addr diff --git a/usagelogger/http_rules.py b/usagelogger/http_rules.py index 7206730..ab0ad23 100644 --- a/usagelogger/http_rules.py +++ b/usagelogger/http_rules.py @@ -396,7 +396,7 @@ def apply(self, details: List[List[str]]) -> Optional[List[List[str]]]: # remove any details with empty values details = [x for x in details if x[1] != ""] - if len(details) == 0: + if not details: return None # mask sensitive details based on replace rules if configured @@ -407,7 +407,7 @@ def apply(self, details: List[List[str]]) -> Optional[List[List[str]]]: # remove any details with empty values details = [x for x in details if x[1] != ""] - return None if len(details) == 0 else details + return None if not details else details __REGEX_ALLOW_HTTP_URL: Pattern = re.compile(r"^\s*allow_http_url\s*(#.*)?$") __REGEX_BLANK_OR_COMMENT: Pattern = re.compile(r"^\s*([#].*)*$") diff --git a/usagelogger/middleware/__init__.py b/usagelogger/middleware/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/usagelogger/aiohttp.py b/usagelogger/middleware/aiohttp.py similarity index 91% rename from usagelogger/aiohttp.py rename to usagelogger/middleware/aiohttp.py index 2796878..bc4809c 100644 --- a/usagelogger/aiohttp.py +++ b/usagelogger/middleware/aiohttp.py @@ -6,7 +6,7 @@ from usagelogger import HttpLogger, HttpMessage from usagelogger.http_request_impl import HttpRequestImpl from usagelogger.http_response_impl import HttpResponseImpl -from usagelogger.multipart_utils import decode_multipart +from usagelogger.utils.multipart_decoder import decode_multipart def HttpLoggerForAIOHTTP(url: Optional[str] = None, rules: Optional[str] = None): @@ -29,6 +29,7 @@ async def resurface_logger_middleware(request, handler): params=request.query, method=request.method, body=decode_multipart(data__) if is_multipart else data__.decode(), + remote_addr=request.remote_addr or None, ), response=HttpResponseImpl( status=response.status, diff --git a/usagelogger/django.py b/usagelogger/middleware/django.py similarity index 91% rename from usagelogger/django.py rename to usagelogger/middleware/django.py index ecf0526..3eb05a9 100644 --- a/usagelogger/django.py +++ b/usagelogger/middleware/django.py @@ -6,7 +6,7 @@ from django.http.request import RawPostDataException from usagelogger import HttpLogger, HttpMessage, HttpRequestImpl, HttpResponseImpl -from usagelogger.multipart_utils import decode_multipart +from usagelogger.utils.multipart_decoder import decode_multipart def __read_settings__(key): @@ -66,6 +66,9 @@ def __call__(self, request): headers=request.headers, params=request.POST if method == "POST" else request.GET, body=request_body, + remote_addr=request.META.get("HTTP_X_FORWARDED_FOR") + or request.META.get("REMOTE_ADDR") + or None, ), response=HttpResponseImpl( status=response.status_code, diff --git a/usagelogger/flask.py b/usagelogger/middleware/flask.py similarity index 94% rename from usagelogger/flask.py rename to usagelogger/middleware/flask.py index f328fc4..e93f314 100644 --- a/usagelogger/flask.py +++ b/usagelogger/middleware/flask.py @@ -1,7 +1,5 @@ # coding: utf-8 # © 2016-2021 Resurface Labs Inc. - - import time from io import BytesIO from typing import Dict, Iterable, List, Optional, Tuple @@ -11,7 +9,7 @@ from werkzeug.wsgi import ClosingIterator from usagelogger import HttpLogger, HttpMessage, HttpRequestImpl, HttpResponseImpl -from usagelogger.multipart_utils import decode_multipart +from usagelogger.utils.multipart_decoder import decode_multipart class HttpLoggerForFlask: @@ -33,9 +31,7 @@ def start_response( def finish_response(self, response: ClosingIterator) -> List[bytes]: self.interval = 1000.0 * (time.time() - self.start_time) - stored_response_chunks: List[bytes] = [] - for line in response: - stored_response_chunks.append(line) + stored_response_chunks: List[bytes] = list(response) self.response = stored_response_chunks return stored_response_chunks @@ -85,6 +81,7 @@ def _start_response(status, response_headers, *args): headers=dict(request.headers), params=params, body=body__, + remote_addr=request.remote_addr or None, ), response=HttpResponseImpl( status=self.status, diff --git a/usagelogger/resurface.py b/usagelogger/resurface.py index e0c33eb..2cabbb7 100644 --- a/usagelogger/resurface.py +++ b/usagelogger/resurface.py @@ -8,9 +8,9 @@ from requests.sessions import Session as RequestsSession from requests.utils import default_headers -from usagelogger import HttpLogger, HttpMessage - -from ._adapter import MiddlewareHTTPAdapter +from .http_logger import HttpLogger +from .http_message import HttpMessage +from .utils import _adapter class Session(RequestsSession): @@ -41,7 +41,7 @@ def __init__(self, url: Optional[str] = None, rules: Optional[str] = None) -> No middlewares = [ResurfaceHTTPAdapter(url=url, rules=rules)] - adapter = MiddlewareHTTPAdapter(middlewares) + adapter = _adapter.MiddlewareHTTPAdapter(middlewares) self.mount("https://", adapter) self.mount("http://", adapter) diff --git a/usagelogger/utils/__init__.py b/usagelogger/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/usagelogger/_adapter.py b/usagelogger/utils/_adapter.py similarity index 100% rename from usagelogger/_adapter.py rename to usagelogger/utils/_adapter.py diff --git a/usagelogger/multipart_utils.py b/usagelogger/utils/multipart_decoder.py similarity index 100% rename from usagelogger/multipart_utils.py rename to usagelogger/utils/multipart_decoder.py diff --git a/usagelogger/warnings_utils.py b/usagelogger/utils/resurface_utils.py similarity index 70% rename from usagelogger/warnings_utils.py rename to usagelogger/utils/resurface_utils.py index 547f089..4f1f78f 100644 --- a/usagelogger/warnings_utils.py +++ b/usagelogger/utils/resurface_utils.py @@ -1,6 +1,3 @@ -# coding: utf-8 -# © 2016-2021 Resurface Labs Inc. - import warnings @@ -9,13 +6,17 @@ def __init__(self, warning_type, environment_var=None, required_type=None): self.warning_type = warning_type self.env_var = environment_var self.required_type = required_type + def __str__(self): if self.warning_type == "argtype": - warn = f"Invalid type for {self.env_var} " +\ - f"(argument should be a {self.required_type}). " +\ - "Logger won't be enabled." + warn = ( + f"Invalid type for {self.env_var} " + + f"(argument should be a {self.required_type}). " + + "Logger won't be enabled." + ) elif self.warning_type == "nologger": warn = "Logger is not enabled." return warn + def warn(self): warnings.warn(self, stacklevel=2)