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:
Genevieve Helsel 2020-10-28 15:46:50 -07:00 committed by Facebook GitHub Bot
parent afb35484f4
commit 8b42380d18
2 changed files with 111 additions and 0 deletions

View File

@ -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:

View File

@ -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")