| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 2a2404f commit 1611d78
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -406,6 +406,27 @@ user. For example: | |||
| 406 | 406 | ||
| 407 | 407 | p = gl.projects.create({'name': 'awesome_project'}, sudo='user1') | |
| 408 | 408 | ||
| 409 | + Logging | ||
| 410 | + ======= | ||
| 411 | + | ||
| 412 | + To enable debug logging from the underlying ``requests`` and ``http.client`` calls, | ||
| 413 | + you can use ``enable_debug()`` on your ``Gitlab`` instance. For example: | ||
| 414 | + | ||
| 415 | + .. code-block:: python | ||
| 416 | + | ||
| 417 | + import os | ||
| 418 | + import gitlab | ||
| 419 | + | ||
| 420 | + gl = gitlab.Gitlab(private_token=os.getenv("GITLAB_TOKEN")) | ||
| 421 | + gl.enable_debug() | ||
| 422 | + | ||
| 423 | + By default, python-gitlab will mask the token used for authentication in logging output. | ||
| 424 | + If you'd like to debug credentials sent to the API, you can disable masking explicitly: | ||
| 425 | + | ||
| 426 | + .. code-block:: python | ||
| 427 | + | ||
| 428 | + gl.enable_debug(mask_credentials=False) | ||
| 429 | + | ||
| 409 | 430 | .. _object_attributes: | |
| 410 | 431 | ||
| 411 | 432 | Attributes in updated objects | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -517,18 +517,39 @@ def _set_auth_info(self) -> None: | |||
| 517 | 517 | self.http_username, self.http_password | |
| 518 | 518 | ) | |
| 519 | 519 | ||
| 520 | - @staticmethod | ||
| 521 | - def enable_debug() -> None: | ||
| 520 | + def enable_debug(self, mask_credentials: bool = True) -> None: | ||
| 522 | 521 | import logging | |
| 523 | - from http.client import HTTPConnection # noqa | ||
| 522 | + from http import client | ||
| 524 | 523 | ||
| 525 | - HTTPConnection.debuglevel = 1 | ||
| 524 | + client.HTTPConnection.debuglevel = 1 | ||
| 526 | 525 | logging.basicConfig() | |
| 527 | - logging.getLogger().setLevel(logging.DEBUG) | ||
| 526 | + logger = logging.getLogger() | ||
| 527 | + logger.setLevel(logging.DEBUG) | ||
| 528 | + | ||
| 529 | + httpclient_log = logging.getLogger("http.client") | ||
| 530 | + httpclient_log.propagate = True | ||
| 531 | + httpclient_log.setLevel(logging.DEBUG) | ||
| 532 | + | ||
| 528 | 533 | requests_log = logging.getLogger("requests.packages.urllib3") | |
| 529 | 534 | requests_log.setLevel(logging.DEBUG) | |
| 530 | 535 | requests_log.propagate = True | |
| 531 | 536 | ||
| 537 | + # shadow http.client prints to log() | ||
| 538 | + # https://stackoverflow.com/a/16337639 | ||
| 539 | + def print_as_log(*args: Any) -> None: | ||
| 540 | + httpclient_log.log(logging.DEBUG, " ".join(args)) | ||
| 541 | + | ||
| 542 | + setattr(client, "print", print_as_log) | ||
| 543 | + | ||
| 544 | + if not mask_credentials: | ||
| 545 | + return | ||
| 546 | + | ||
| 547 | + token = self.private_token or self.oauth_token or self.job_token | ||
| 548 | + handler = logging.StreamHandler() | ||
| 549 | + handler.setFormatter(utils.MaskingFormatter(masked=token)) | ||
| 550 | + logger.handlers.clear() | ||
| 551 | + logger.addHandler(handler) | ||
| 552 | + | ||
| 532 | 553 | def _get_session_opts(self) -> Dict[str, Any]: | |
| 533 | 554 | return { | |
| 534 | 555 | "headers": self.headers.copy(), | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1,9 +1,10 @@ | |||
| 1 | 1 | import email.message | |
| 2 | + import logging | ||
| 2 | 3 | import pathlib | |
| 3 | 4 | import traceback | |
| 4 | 5 | import urllib.parse | |
| 5 | 6 | import warnings | |
| 6 | - from typing import Any, Callable, Dict, Iterator, Optional, Tuple, Type, Union | ||
| 7 | + from typing import Any, Callable, Dict, Iterator, Literal, Optional, Tuple, Type, Union | ||
| 7 | 8 | ||
| 8 | 9 | import requests | |
| 9 | 10 | ||
@@ -22,6 +23,31 @@ def get_content_type(content_type: Optional[str]) -> str: | |||
| 22 | 23 | return message.get_content_type() | |
| 23 | 24 | ||
| 24 | 25 | ||
| 26 | + class MaskingFormatter(logging.Formatter): | ||
| 27 | + """A logging formatter that can mask credentials""" | ||
| 28 | + | ||
| 29 | + def __init__( | ||
| 30 | + self, | ||
| 31 | + fmt: Optional[str] = logging.BASIC_FORMAT, | ||
| 32 | + datefmt: Optional[str] = None, | ||
| 33 | + style: Literal["%", "{", "$"] = "%", | ||
| 34 | + validate: bool = True, | ||
| 35 | + masked: Optional[str] = None, | ||
| 36 | + ) -> None: | ||
| 37 | + super().__init__(fmt, datefmt, style, validate) | ||
| 38 | + self.masked = masked | ||
| 39 | + | ||
| 40 | + def _filter(self, entry: str) -> str: | ||
| 41 | + if not self.masked: | ||
| 42 | + return entry | ||
| 43 | + | ||
| 44 | + return entry.replace(self.masked, "[MASKED]") | ||
| 45 | + | ||
| 46 | + def format(self, record: logging.LogRecord) -> str: | ||
| 47 | + original = logging.Formatter.format(self, record) | ||
| 48 | + return self._filter(original) | ||
| 49 | + | ||
| 50 | + | ||
| 25 | 51 | def response_content( | |
| 26 | 52 | response: requests.Response, | |
| 27 | 53 | streamed: bool, | |
| Back | FazBrowse Home | New Git URL |
0 commit comments