summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNejc Habjan <nejc.habjan@siemens.com>2022-07-30 14:47:01 -0700
committerJohn L. Villalovos <john@sodarock.com>2022-07-30 14:47:01 -0700
commitdacf8a3269f2545bf442449da60817bd6a23a7df (patch)
treede31d2638b59e6571cecb24d35c639eb0701a3a0
parent17414f787a70a0d916193ac71bccce0297c4e4e8 (diff)
downloadgitlab-jlvillal/logging-mask-tokens.tar.gz
feat(client): mask tokens by default when loggingjlvillal/logging-mask-tokens
-rw-r--r--docs/api-usage.rst21
-rw-r--r--gitlab/client.py31
-rw-r--r--gitlab/utils.py24
3 files changed, 71 insertions, 5 deletions
diff --git a/docs/api-usage.rst b/docs/api-usage.rst
index 434de38..3c561b1 100644
--- a/docs/api-usage.rst
+++ b/docs/api-usage.rst
@@ -384,6 +384,27 @@ user. For example:
p = gl.projects.create({'name': 'awesome_project'}, sudo='user1')
+Logging
+=======
+
+To enable debug logging from the underlying ``requests`` and ``http.client`` calls,
+you can use ``enable_debug()`` on your ``Gitlab`` instance. For example:
+
+.. code-block:: python
+
+ import os
+ import gitlab
+
+ gl = gitlab.Gitlab(private_token=os.getenv("GITLAB_TOKEN"))
+ gl.enable_debug()
+
+By default, python-gitlab will mask the token used for authentication in logging output.
+If you'd like to debug credentials sent to the API, you can disable masking explicitly:
+
+.. code-block:: python
+
+ gl.enable_debug(mask_credentials=False)
+
.. _object_attributes:
Attributes in updated objects
diff --git a/gitlab/client.py b/gitlab/client.py
index 97ca636..9f99c4f 100644
--- a/gitlab/client.py
+++ b/gitlab/client.py
@@ -524,18 +524,39 @@ class Gitlab:
self.http_username, self.http_password
)
- @staticmethod
- def enable_debug() -> None:
+ def enable_debug(self, mask_credentials: bool = True) -> None:
import logging
- from http.client import HTTPConnection # noqa
+ from http import client
- HTTPConnection.debuglevel = 1
+ client.HTTPConnection.debuglevel = 1
logging.basicConfig()
- logging.getLogger().setLevel(logging.DEBUG)
+ logger = logging.getLogger()
+ logger.setLevel(logging.DEBUG)
+
+ httpclient_log = logging.getLogger("http.client")
+ httpclient_log.propagate = True
+ httpclient_log.setLevel(logging.DEBUG)
+
requests_log = logging.getLogger("requests.packages.urllib3")
requests_log.setLevel(logging.DEBUG)
requests_log.propagate = True
+ # shadow http.client prints to log()
+ # https://stackoverflow.com/a/16337639
+ def print_as_log(*args: Any) -> None:
+ httpclient_log.log(logging.DEBUG, " ".join(args))
+
+ setattr(client, "print", print_as_log)
+
+ if not mask_credentials:
+ return
+
+ token = self.private_token or self.oauth_token or self.job_token
+ handler = logging.StreamHandler()
+ handler.setFormatter(utils.MaskingFormatter(masked=token))
+ logger.handlers.clear()
+ logger.addHandler(handler)
+
def _get_session_opts(self) -> Dict[str, Any]:
return {
"headers": self.headers.copy(),
diff --git a/gitlab/utils.py b/gitlab/utils.py
index f3d97f7..7944f4c 100644
--- a/gitlab/utils.py
+++ b/gitlab/utils.py
@@ -15,6 +15,7 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
+import logging
import pathlib
import traceback
import urllib.parse
@@ -31,6 +32,29 @@ class _StdoutStream:
print(chunk)
+class MaskingFormatter(logging.Formatter):
+ """A logging formatter that can mask credentials"""
+
+ def __init__(
+ self,
+ *args: Any,
+ masked: Optional[str] = None,
+ **kwargs: Any,
+ ) -> None:
+ super().__init__(*args, **kwargs)
+ self.masked = masked
+
+ def _filter(self, entry: str) -> str:
+ if not self.masked:
+ return entry
+
+ return entry.replace(self.masked, "[MASKED]")
+
+ def format(self, record: logging.LogRecord) -> str:
+ original = logging.Formatter.format(self, record)
+ return self._filter(original)
+
+
def response_content(
response: requests.Response,
streamed: bool,