diff --git a/nvflare/lighter/constants.py b/nvflare/lighter/constants.py index 2ed5d4b63d..59c29881a0 100644 --- a/nvflare/lighter/constants.py +++ b/nvflare/lighter/constants.py @@ -43,6 +43,7 @@ class PropKey: OVERSEER_END_POINT = "overseer_end_point" ADMIN_PORT = "admin_port" FED_LEARN_PORT = "fed_learn_port" + ALLOW_ERROR_SENDING = "allow_error_sending" CONN_SECURITY = "connection_security" CUSTOM_CA_CERT = "custom_ca_cert" diff --git a/nvflare/lighter/ctx.py b/nvflare/lighter/ctx.py index f55cdfd303..2d0a2ce603 100644 --- a/nvflare/lighter/ctx.py +++ b/nvflare/lighter/ctx.py @@ -103,8 +103,33 @@ def yaml_load_template_section(self, section_key: str): def json_load_template_section(self, section_key: str): return json.loads(self.get_template_section(section_key)) - def build_from_template(self, dest_dir: str, temp_section: str, file_name, replacement=None, mode="t", exe=False): + def build_from_template( + self, + dest_dir: str, + temp_section: str, + file_name, + replacement=None, + mode="t", + exe=False, + content_modify_cb=None, + **cb_kwargs, + ): + """Build a file from a template section and writes it to the specified location. + + Args: + dest_dir: destination directory + temp_section: template section key + file_name: file name + replacement: replacement dict + mode: file mode + exe: executable + content_modify_cb: content modification callback, can be included to take the section content as the first argument and return the modified content + cb_kwargs: additional keyword arguments for the callback + + """ section = self.get_template_section(temp_section) if replacement: section = utils.sh_replace(section, replacement) + if content_modify_cb: + section = content_modify_cb(section, **cb_kwargs) utils.write(os.path.join(dest_dir, file_name), section, mode, exe=exe) diff --git a/nvflare/lighter/impl/static_file.py b/nvflare/lighter/impl/static_file.py index 76c678e600..e8ffcaaf90 100644 --- a/nvflare/lighter/impl/static_file.py +++ b/nvflare/lighter/impl/static_file.py @@ -254,7 +254,11 @@ def _build_client(self, client, ctx): ctx.build_from_template(dest_dir, TemplateSectionKey.LOG_CONFIG, ProvFileName.LOG_CONFIG_DEFAULT) ctx.build_from_template( - dest_dir, TemplateSectionKey.LOCAL_CLIENT_RESOURCES, ProvFileName.RESOURCES_JSON_DEFAULT + dest_dir, + TemplateSectionKey.LOCAL_CLIENT_RESOURCES, + ProvFileName.RESOURCES_JSON_DEFAULT, + content_modify_cb=self._modify_error_sender, + client=client, ) ctx.build_from_template( @@ -269,6 +273,20 @@ def _build_client(self, client, ctx): dest_dir = ctx.get_ws_dir(client) ctx.build_from_template(dest_dir, TemplateSectionKey.CLIENT_README, ProvFileName.README_TXT) + def _modify_error_sender(self, section: dict, client: Participant): + if not isinstance(section, dict): + return section + allow = client.get_prop_fb(PropKey.ALLOW_ERROR_SENDING, False) + if not allow: + components = section.get("components") + assert isinstance(components, list) + for c in components: + if c["id"] == "error_log_sender": + components.remove(c) + break + + return section + @staticmethod def _check_host_name(host_name: str, server: Participant) -> str: if host_name == server.get_default_host(): diff --git a/nvflare/lighter/templates/master_template.yml b/nvflare/lighter/templates/master_template.yml index 68961077d2..d9dbd58521 100644 --- a/nvflare/lighter/templates/master_template.yml +++ b/nvflare/lighter/templates/master_template.yml @@ -97,6 +97,11 @@ local_client_resources: | "id": "process_launcher", "path": "nvflare.app_common.job_launcher.client_process_launcher.ClientProcessJobLauncher", "args": {} + }, + { + "id": "error_log_sender", + "path": "nvflare.app_common.logging.log_sender.ErrorLogSender", + "args": {} } ] } @@ -226,6 +231,11 @@ local_server_resources: | "id": "process_launcher", "path": "nvflare.app_common.job_launcher.server_process_launcher.ServerProcessJobLauncher", "args": {} + }, + { + "id": "log_receiver", + "path": "nvflare.app_common.logging.log_receiver.LogReceiver", + "args": {} } ] }