def test_file_logging() -> None: native = Native() native.init_rust_logging( level=LogLevel.INFO.level, # Tests assume a log level of INFO log_show_rust_3rdparty=False, use_color=False, show_target=False, log_levels_by_target={}, message_regex_filters=(), ) logger = logging.getLogger("my_file_logger") with temporary_dir() as tmpdir: setup_logging_to_file(LogLevel.INFO, log_dir=tmpdir) log_file = Path(tmpdir, "pants.log") cat = "🐈" logger.warning("this is a warning") logger.info("this is some info") logger.debug("this is some debug info") logger.info(f"unicode: {cat}") loglines = log_file.read_text().splitlines() print(loglines) assert len(loglines) == 3 assert "[WARN] this is a warning" in loglines[0] assert "[INFO] this is some info" in loglines[1] assert f"[INFO] unicode: {cat}" in loglines[2]
def _pantsd_logging(self) -> Iterator[None]: """A context manager that runs with pantsd logging. Asserts that stdio (represented by file handles 0, 1, 2) is closed to ensure that we can safely reuse those fd numbers. """ # Ensure that stdio is closed so that we can safely reuse those file descriptors. for fd in (0, 1, 2): try: os.fdopen(fd) raise AssertionError(f"pantsd logging cannot initialize while stdio is open: {fd}") except OSError: pass # Redirect stdio to /dev/null for the rest of the run to reserve those file descriptors. with stdio_as(stdin_fd=-1, stdout_fd=-1, stderr_fd=-1): # Reinitialize logging for the daemon context. global_options = self._bootstrap_options.for_global_scope() setup_logging(global_options, stderr_logging=False) log_dir = os.path.join(self._work_dir, self.name) setup_logging_to_file(global_options.level, log_dir=log_dir, log_filename=self.LOG_NAME) self._logger.debug("Logging reinitialized in pantsd context") yield
def _pantsd_logging(self) -> Iterator[IO[str]]: """A context manager that runs with pantsd logging. Asserts that stdio (represented by file handles 0, 1, 2) is closed to ensure that we can safely reuse those fd numbers. """ # Ensure that stdio is closed so that we can safely reuse those file descriptors. for fd in (0, 1, 2): try: os.fdopen(fd) raise AssertionError( f"pantsd logging cannot initialize while stdio is open: {fd}" ) except OSError: pass # Redirect stdio to /dev/null for the rest of the run, to reserve those file descriptors # for further forks. with stdio_as(stdin_fd=-1, stdout_fd=-1, stderr_fd=-1): # Reinitialize logging for the daemon context. init_rust_logger(self._log_level, self._log_show_rust_3rdparty) # We can't statically prove it, but we won't execute `launch()` (which # calls `run_sync` which calls `_pantsd_logging`) unless PantsDaemon # is launched with full_init=True. If PantsdDaemon is launched with # full_init=True, we can guarantee self._native and self._bootstrap_options # are non-None. native = cast(Native, self._native) bootstrap_options = cast(OptionValueContainer, self._bootstrap_options) level = self._log_level ignores = bootstrap_options.for_global_scope( ).ignore_pants_warnings clear_previous_loggers() setup_logging_to_stderr(level, warnings_filter_regexes=ignores) log_dir = os.path.join(self._work_dir, self.name) log_handler = setup_logging_to_file( level, log_dir=log_dir, log_filename=self.LOG_NAME, warnings_filter_regexes=ignores) native.override_thread_logging_destination_to_just_pantsd() # Do a python-level redirect of stdout/stderr, which will not disturb `0,1,2`. # TODO: Consider giving these pipes/actual fds, in order to make them "deep" replacements # for `1,2`, and allow them to be used via `stdio_as`. sys.stdout = _LoggerStream(logging.getLogger(), logging.INFO, log_handler) # type: ignore[assignment] sys.stderr = _LoggerStream(logging.getLogger(), logging.WARN, log_handler) # type: ignore[assignment] self._logger.debug("logging initialized") yield log_handler.stream
def logger( self, log_level: LogLevel ) -> Iterator[Tuple[Logger, NativeHandler, Path]]: native = self.scheduler._scheduler._native # TODO(gregorys) - if this line isn't here this test fails with no stdout. Figure out why. print(f"Native: {native}") logger = logging.getLogger("my_file_logger") with temporary_dir() as tmpdir: handler = setup_logging_to_file(log_level, log_dir=tmpdir) log_file = Path(tmpdir, "pants.log") yield logger, handler, log_file
def _pantsd_logging(self) -> Iterator[IO[str]]: """A context manager that runs with pantsd logging. Asserts that stdio (represented by file handles 0, 1, 2) is closed to ensure that we can safely reuse those fd numbers. """ # Ensure that stdio is closed so that we can safely reuse those file descriptors. for fd in (0, 1, 2): try: os.fdopen(fd) raise AssertionError( f"pantsd logging cannot initialize while stdio is open: {fd}" ) except OSError: pass # Redirect stdio to /dev/null for the rest of the run, to reserve those file descriptors # for further forks. with stdio_as(stdin_fd=-1, stdout_fd=-1, stderr_fd=-1): # Reinitialize logging for the daemon context. use_color = self._bootstrap_options.for_global_scope().colors init_rust_logger(self._log_level, self._log_show_rust_3rdparty, use_color=use_color) level = self._log_level ignores = self._bootstrap_options.for_global_scope( ).ignore_pants_warnings clear_logging_handlers() log_dir = os.path.join(self._work_dir, self.name) log_handler = setup_logging_to_file( level, log_dir=log_dir, log_filename=self.LOG_NAME, warnings_filter_regexes=ignores) self._native.override_thread_logging_destination_to_just_pantsd() # Do a python-level redirect of stdout/stderr, which will not disturb `0,1,2`. # TODO: Consider giving these pipes/actual fds, in order to make them "deep" replacements # for `1,2`, and allow them to be used via `stdio_as`. sys.stdout = _LoggerStream(logging.getLogger(), logging.INFO, log_handler) # type: ignore[assignment] sys.stderr = _LoggerStream(logging.getLogger(), logging.WARN, log_handler) # type: ignore[assignment] self._logger.debug("logging initialized") yield log_handler.stream
def create( cls, env: Mapping[str, str], options_bootstrapper: OptionsBootstrapper, specs: Optional[Specs] = None, daemon_graph_session: Optional[LegacyGraphSession] = None, ) -> "LocalPantsRunner": """Creates a new LocalPantsRunner instance by parsing options. :param env: The environment (e.g. os.environ) for this run. :param options_bootstrapper: The OptionsBootstrapper instance to reuse. :param specs: The specs for this run, i.e. either the address or filesystem specs. :param daemon_graph_session: The graph helper for this session. """ build_root = get_buildroot() global_options = options_bootstrapper.bootstrap_options.for_global_scope( ) # This works as expected due to the encapsulated_logger in DaemonPantsRunner and # we don't have to gate logging setup anymore. level = LogLevel.ERROR if getattr(global_options, "quiet", False) else global_options.level ignores = global_options.ignore_pants_warnings clear_previous_loggers() setup_logging_to_stderr(level, warnings_filter_regexes=ignores) log_dir = global_options.logdir if log_dir: setup_logging_to_file(level, log_dir=log_dir, warnings_filter_regexes=ignores) options, build_config = LocalPantsRunner.parse_options( options_bootstrapper) # Option values are usually computed lazily on demand, # but command line options are eagerly computed for validation. for scope in options.scope_to_flags.keys(): options.for_scope(scope) # Verify configs. if global_options.verify_config: options.verify_configs(options_bootstrapper.config) union_membership = UnionMembership(build_config.union_rules()) # If we're running with the daemon, we'll be handed a session from the # resident graph helper - otherwise initialize a new one here. graph_session = (daemon_graph_session if daemon_graph_session else cls._init_graph_session( options_bootstrapper, build_config, options)) if specs is None: global_options = options.for_global_scope() specs = SpecsCalculator.create( options=options, build_root=build_root, session=graph_session.scheduler_session, exclude_patterns=tuple(global_options.exclude_target_regexp), tags=tuple(global_options.tag), ) profile_path = env.get("PANTS_PROFILE") return cls( build_root=build_root, options=options, options_bootstrapper=options_bootstrapper, build_config=build_config, specs=specs, graph_session=graph_session, union_membership=union_membership, is_daemon=daemon_graph_session is not None, profile_path=profile_path, )