mirror of
https://github.com/facebook/sapling.git
synced 2024-10-06 23:07:18 +03:00
introduce eden logs command
Summary: Introduces an `eden logs` command to read a large chunk (1M by default) file into a paste. This is also added to the the eden rage report to get more insight into systems in which we cannot log in to view the logs (like laptops). Reviewed By: kmancini Differential Revision: D24146812 fbshipit-source-id: 991f1595b974eb01f77e86559a8413b0b09a24a4
This commit is contained in:
parent
afb35484f4
commit
8b42380d18
@ -1742,6 +1742,53 @@ update your shell's working directory."""
|
||||
return 0
|
||||
|
||||
|
||||
@subcmd("logs", "Gather logs from eden")
|
||||
class LogsCmd(Subcmd):
|
||||
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
||||
parser.add_argument(
|
||||
"--stdout",
|
||||
action="store_true",
|
||||
help="Print the logs to stdout: ignore reporter.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--full", action="store_true", help="Gather the full logs from eden"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--size",
|
||||
type=int,
|
||||
default=1000000,
|
||||
help="The amount of the logs we should gather. If --full is passed in,"
|
||||
"we will ignore this value. Default to 1M",
|
||||
)
|
||||
|
||||
def run(self, args: argparse.Namespace) -> int:
|
||||
instance = get_eden_instance(args)
|
||||
eden_log_path = instance.get_log_path()
|
||||
if not eden_log_path.exists():
|
||||
print(f"No log file found at {eden_log_path}", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
# For ease of use, just use the same rage reporter
|
||||
rage_processor = instance.get_config_value("rage.reporter", default="")
|
||||
|
||||
proc: Optional[subprocess.Popen] = None
|
||||
if rage_processor and not args.stdout:
|
||||
proc = subprocess.Popen(shlex.split(rage_processor), stdin=subprocess.PIPE)
|
||||
sink = proc.stdin
|
||||
else:
|
||||
proc = None
|
||||
sink = sys.stdout.buffer
|
||||
|
||||
# pyre-fixme[6]: Expected `IO[bytes]` for 2nd param but got
|
||||
# `Optional[typing.IO[typing.Any]]`.
|
||||
rage_mod.print_log_file(eden_log_path, sink, args.full, args.size)
|
||||
if proc:
|
||||
# pyre-fixme[16]: `Optional` has no attribute `close`.
|
||||
sink.close()
|
||||
proc.wait()
|
||||
return 0
|
||||
|
||||
|
||||
@subcmd("rage", "Gather diagnostic information about eden")
|
||||
class RageCmd(Subcmd):
|
||||
def setup_parser(self, parser: argparse.ArgumentParser) -> None:
|
||||
|
@ -9,6 +9,8 @@ import getpass
|
||||
import io
|
||||
import os.path
|
||||
import platform
|
||||
import re
|
||||
import shlex
|
||||
import socket
|
||||
import subprocess
|
||||
import sys
|
||||
@ -53,6 +55,9 @@ def print_diagnostic_info(instance: EdenInstance, out: IO[bytes]) -> None:
|
||||
# TODO(zeyi): fix `eden doctor` on Windows
|
||||
print_eden_doctor_report(instance, out)
|
||||
|
||||
processor = instance.get_config_value("rage.reporter", default="")
|
||||
if processor:
|
||||
print_expanded_log_file(instance.get_log_path(), processor, out)
|
||||
print_tail_of_log_file(instance.get_log_path(), out)
|
||||
print_running_eden_process(out)
|
||||
|
||||
@ -124,6 +129,65 @@ def print_eden_doctor_report(instance: EdenInstance, out: IO[bytes]) -> None:
|
||||
out.write(traceback.format_exc().encode("utf-8") + b"\n")
|
||||
|
||||
|
||||
def read_chunk(logfile: IO[bytes]):
|
||||
CHUNK_SIZE = 20 * 1024
|
||||
while True:
|
||||
data = logfile.read(CHUNK_SIZE)
|
||||
if not data:
|
||||
break
|
||||
yield data
|
||||
|
||||
|
||||
def print_log_file(
|
||||
path: Path, out: IO[bytes], whole_file: bool, size: int = 1000000
|
||||
) -> None:
|
||||
try:
|
||||
with path.open("rb") as logfile:
|
||||
if not whole_file:
|
||||
LOG_AMOUNT = size
|
||||
size = logfile.seek(0, io.SEEK_END)
|
||||
logfile.seek(max(0, size - LOG_AMOUNT), io.SEEK_SET)
|
||||
for data in read_chunk(logfile):
|
||||
out.write(data)
|
||||
except Exception as e:
|
||||
out.write(b"Error reading the log file: %s\n" % str(e).encode())
|
||||
|
||||
|
||||
def print_expanded_log_file(path: Path, processor: str, out: IO[bytes]) -> None:
|
||||
try:
|
||||
proc = subprocess.Popen(
|
||||
shlex.split(processor), stdin=subprocess.PIPE, stdout=subprocess.PIPE
|
||||
)
|
||||
sink = proc.stdin
|
||||
output = proc.stdout
|
||||
|
||||
# pyre-fixme[6]: Expected `IO[bytes]` for 2nd param but got
|
||||
# `Optional[typing.IO[typing.Any]]`.
|
||||
print_log_file(path, sink, whole_file=False)
|
||||
|
||||
# pyre-fixme[16]: `Optional` has no attribute `close`.
|
||||
sink.close()
|
||||
|
||||
# pyre-fixme[16]: `Optional` has no attribute `read`.
|
||||
stdout = output.read().decode("utf-8")
|
||||
|
||||
output.close()
|
||||
proc.wait()
|
||||
|
||||
# Expected output to be in form "<str0>\n<str1>: <str2>\n"
|
||||
# and we want str1
|
||||
pattern = re.compile("^.*\\n[a-zA-Z0-9_.-]*: .*\\n$")
|
||||
match = pattern.match(stdout)
|
||||
|
||||
if not match:
|
||||
out.write(b"Verbose Eden logs: %s\n" % stdout.encode())
|
||||
else:
|
||||
paste, _ = stdout.split("\n")[1].split(": ")
|
||||
out.write(b"Verbose Eden logs: %s\n" % paste.encode())
|
||||
except Exception as e:
|
||||
out.write(b"Error generating expanded Eden logs: %s\n" % str(e).encode())
|
||||
|
||||
|
||||
def print_tail_of_log_file(path: Path, out: IO[bytes]) -> None:
|
||||
try:
|
||||
out.write(b"\nMost recent Eden logs:\n")
|
||||
|
Loading…
Reference in New Issue
Block a user