diff options
| author | Josh Steadmon <steadmon@google.com> | 2022-03-08 10:24:43 -0800 |
|---|---|---|
| committer | Josh Steadmon <steadmon@google.com> | 2022-03-16 17:33:07 +0000 |
| commit | 244c9a71a689743acea97c6a07ff4dfce4dc6dab (patch) | |
| tree | 2a52943292478722db03174e577f05ab4ffc1eb7 /git_trace2_event_log.py | |
| parent | b308db1e2a982ae4158cb6fedd23d3d547bd09b0 (diff) | |
| download | git-repo-244c9a71a689743acea97c6a07ff4dfce4dc6dab.tar.gz | |
trace: allow writing traces to a socket
Git can write trace2 events to a Unix domain socket [1]. This can be
specified via Git's `trace2.eventTarget` config option, which we read to
determine where to log our own trace2 events. Currently, if the Git
config specifies a socket as the trace2 target, we fail to log any
traces.
Fix this by adding support for writing to a Unix domain socket,
following the same specification that Git supports.
[1]: https://git-scm.com/docs/api-trace2#_enabling_a_target
Change-Id: I928bc22ba04fba603a9132eb055141845fa48ab2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/332339
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Josh Steadmon <steadmon@google.com>
Diffstat (limited to 'git_trace2_event_log.py')
| -rw-r--r-- | git_trace2_event_log.py | 86 |
1 files changed, 72 insertions, 14 deletions
diff --git a/git_trace2_event_log.py b/git_trace2_event_log.py index 0e5e9089..7426aba9 100644 --- a/git_trace2_event_log.py +++ b/git_trace2_event_log.py | |||
| @@ -29,8 +29,10 @@ https://git-scm.com/docs/api-trace2#_the_event_format_target | |||
| 29 | 29 | ||
| 30 | 30 | ||
| 31 | import datetime | 31 | import datetime |
| 32 | import errno | ||
| 32 | import json | 33 | import json |
| 33 | import os | 34 | import os |
| 35 | import socket | ||
| 34 | import sys | 36 | import sys |
| 35 | import tempfile | 37 | import tempfile |
| 36 | import threading | 38 | import threading |
| @@ -218,20 +220,39 @@ class EventLog(object): | |||
| 218 | retval, p.stderr), file=sys.stderr) | 220 | retval, p.stderr), file=sys.stderr) |
| 219 | return path | 221 | return path |
| 220 | 222 | ||
| 223 | def _WriteLog(self, write_fn): | ||
| 224 | """Writes the log out using a provided writer function. | ||
| 225 | |||
| 226 | Generate compact JSON output for each item in the log, and write it using | ||
| 227 | write_fn. | ||
| 228 | |||
| 229 | Args: | ||
| 230 | write_fn: A function that accepts byts and writes them to a destination. | ||
| 231 | """ | ||
| 232 | |||
| 233 | for e in self._log: | ||
| 234 | # Dump in compact encoding mode. | ||
| 235 | # See 'Compact encoding' in Python docs: | ||
| 236 | # https://docs.python.org/3/library/json.html#module-json | ||
| 237 | write_fn(json.dumps(e, indent=None, separators=(',', ':')).encode('utf-8') + b'\n') | ||
| 238 | |||
| 221 | def Write(self, path=None): | 239 | def Write(self, path=None): |
| 222 | """Writes the log out to a file. | 240 | """Writes the log out to a file or socket. |
| 223 | 241 | ||
| 224 | Log is only written if 'path' or 'git config --get trace2.eventtarget' | 242 | Log is only written if 'path' or 'git config --get trace2.eventtarget' |
| 225 | provide a valid path to write logs to. | 243 | provide a valid path (or socket) to write logs to. |
| 226 | 244 | ||
| 227 | Logging filename format follows the git trace2 style of being a unique | 245 | Logging filename format follows the git trace2 style of being a unique |
| 228 | (exclusive writable) file. | 246 | (exclusive writable) file. |
| 229 | 247 | ||
| 230 | Args: | 248 | Args: |
| 231 | path: Path to where logs should be written. | 249 | path: Path to where logs should be written. The path may have a prefix of |
| 250 | the form "af_unix:[{stream|dgram}:]", in which case the path is | ||
| 251 | treated as a Unix domain socket. See | ||
| 252 | https://git-scm.com/docs/api-trace2#_enabling_a_target for details. | ||
| 232 | 253 | ||
| 233 | Returns: | 254 | Returns: |
| 234 | log_path: Path to the log file if log is written, otherwise None | 255 | log_path: Path to the log file or socket if log is written, otherwise None |
| 235 | """ | 256 | """ |
| 236 | log_path = None | 257 | log_path = None |
| 237 | # If no logging path is specified, get the path from 'trace2.eventtarget'. | 258 | # If no logging path is specified, get the path from 'trace2.eventtarget'. |
| @@ -242,29 +263,66 @@ class EventLog(object): | |||
| 242 | if path is None: | 263 | if path is None: |
| 243 | return None | 264 | return None |
| 244 | 265 | ||
| 266 | path_is_socket = False | ||
| 267 | socket_type = None | ||
| 245 | if isinstance(path, str): | 268 | if isinstance(path, str): |
| 246 | # Get absolute path. | 269 | parts = path.split(':', 1) |
| 247 | path = os.path.abspath(os.path.expanduser(path)) | 270 | if parts[0] == 'af_unix' and len(parts) == 2: |
| 271 | path_is_socket = True | ||
| 272 | path = parts[1] | ||
| 273 | parts = path.split(':', 1) | ||
| 274 | if parts[0] == 'stream' and len(parts) == 2: | ||
| 275 | socket_type = socket.SOCK_STREAM | ||
| 276 | path = parts[1] | ||
| 277 | elif parts[0] == 'dgram' and len(parts) == 2: | ||
| 278 | socket_type = socket.SOCK_DGRAM | ||
| 279 | path = parts[1] | ||
| 280 | else: | ||
| 281 | # Get absolute path. | ||
| 282 | path = os.path.abspath(os.path.expanduser(path)) | ||
| 248 | else: | 283 | else: |
| 249 | raise TypeError('path: str required but got %s.' % type(path)) | 284 | raise TypeError('path: str required but got %s.' % type(path)) |
| 250 | 285 | ||
| 251 | # Git trace2 requires a directory to write log to. | 286 | # Git trace2 requires a directory to write log to. |
| 252 | 287 | ||
| 253 | # TODO(https://crbug.com/gerrit/13706): Support file (append) mode also. | 288 | # TODO(https://crbug.com/gerrit/13706): Support file (append) mode also. |
| 254 | if not os.path.isdir(path): | 289 | if not (path_is_socket or os.path.isdir(path)): |
| 290 | return None | ||
| 291 | |||
| 292 | if path_is_socket: | ||
| 293 | if socket_type == socket.SOCK_STREAM or socket_type is None: | ||
| 294 | try: | ||
| 295 | with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock: | ||
| 296 | sock.connect(path) | ||
| 297 | self._WriteLog(sock.sendall) | ||
| 298 | return f'af_unix:stream:{path}' | ||
| 299 | except OSError as err: | ||
| 300 | # If we tried to connect to a DGRAM socket using STREAM, ignore the | ||
| 301 | # attempt and continue to DGRAM below. Otherwise, issue a warning. | ||
| 302 | if err.errno != errno.EPROTOTYPE: | ||
| 303 | print(f'repo: warning: git trace2 logging failed: {err}', file=sys.stderr) | ||
| 304 | return None | ||
| 305 | if socket_type == socket.SOCK_DGRAM or socket_type is None: | ||
| 306 | try: | ||
| 307 | with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sock: | ||
| 308 | self._WriteLog(lambda bs: sock.sendto(bs, path)) | ||
| 309 | return f'af_unix:dgram:{path}' | ||
| 310 | except OSError as err: | ||
| 311 | print(f'repo: warning: git trace2 logging failed: {err}', file=sys.stderr) | ||
| 312 | return None | ||
| 313 | # Tried to open a socket but couldn't connect (SOCK_STREAM) or write | ||
| 314 | # (SOCK_DGRAM). | ||
| 315 | print('repo: warning: git trace2 logging failed: could not write to socket', file=sys.stderr) | ||
| 255 | return None | 316 | return None |
| 317 | |||
| 318 | # Path is an absolute path | ||
| 256 | # Use NamedTemporaryFile to generate a unique filename as required by git trace2. | 319 | # Use NamedTemporaryFile to generate a unique filename as required by git trace2. |
| 257 | try: | 320 | try: |
| 258 | with tempfile.NamedTemporaryFile(mode='x', prefix=self._sid, dir=path, | 321 | with tempfile.NamedTemporaryFile(mode='xb', prefix=self._sid, dir=path, |
| 259 | delete=False) as f: | 322 | delete=False) as f: |
| 260 | # TODO(https://crbug.com/gerrit/13706): Support writing events as they | 323 | # TODO(https://crbug.com/gerrit/13706): Support writing events as they |
| 261 | # occur. | 324 | # occur. |
| 262 | for e in self._log: | 325 | self._WriteLog(f.write) |
| 263 | # Dump in compact encoding mode. | ||
| 264 | # See 'Compact encoding' in Python docs: | ||
| 265 | # https://docs.python.org/3/library/json.html#module-json | ||
| 266 | json.dump(e, f, indent=None, separators=(',', ':')) | ||
| 267 | f.write('\n') | ||
| 268 | log_path = f.name | 326 | log_path = f.name |
| 269 | except FileExistsError as err: | 327 | except FileExistsError as err: |
| 270 | print('repo: warning: git trace2 logging failed: %r' % err, | 328 | print('repo: warning: git trace2 logging failed: %r' % err, |
