diff --git a/src/main/java/org/codelibs/fess/app/web/admin/maintenance/UpgradeForm.java b/src/main/java/org/codelibs/fess/app/web/admin/maintenance/ActionForm.java similarity index 97% rename from src/main/java/org/codelibs/fess/app/web/admin/maintenance/UpgradeForm.java rename to src/main/java/org/codelibs/fess/app/web/admin/maintenance/ActionForm.java index 638a3317f5bdfc8599c71feaa581cffc9dd9c2fb..f25a84332e74471f96e4ed0b4134c4e1a4dae759 100644 --- a/src/main/java/org/codelibs/fess/app/web/admin/maintenance/UpgradeForm.java +++ b/src/main/java/org/codelibs/fess/app/web/admin/maintenance/ActionForm.java @@ -20,7 +20,7 @@ import javax.validation.constraints.Size; import org.codelibs.fess.Constants; import org.codelibs.fess.util.ComponentUtil; -public class UpgradeForm { +public class ActionForm { @Size(max = 10) public String replaceAliases = Constants.ON; diff --git a/src/main/java/org/codelibs/fess/app/web/admin/maintenance/AdminMaintenanceAction.java b/src/main/java/org/codelibs/fess/app/web/admin/maintenance/AdminMaintenanceAction.java index 65cf9acc769a143aabdc4d5c3f722d2b4db851f1..aa01bc3c4d7db5c1ea64b712d452a43c9c1770e1 100644 --- a/src/main/java/org/codelibs/fess/app/web/admin/maintenance/AdminMaintenanceAction.java +++ b/src/main/java/org/codelibs/fess/app/web/admin/maintenance/AdminMaintenanceAction.java @@ -15,15 +15,31 @@ */ package org.codelibs.fess.app.web.admin.maintenance; +import java.io.IOException; +import java.net.InetAddress; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; +import java.util.Properties; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; import javax.annotation.Resource; +import org.codelibs.core.io.CopyUtil; +import org.codelibs.core.lang.StringUtil; +import org.codelibs.curl.CurlResponse; import org.codelibs.fess.app.web.base.FessAdminAction; import org.codelibs.fess.es.client.FessEsClient; +import org.codelibs.fess.mylasta.direction.FessConfig.SimpleImpl; +import org.codelibs.fess.util.ComponentUtil; import org.elasticsearch.action.ActionListener; import org.lastaflute.web.Execute; +import org.lastaflute.web.response.ActionResponse; import org.lastaflute.web.response.HtmlResponse; import org.lastaflute.web.ruts.process.ActionRuntime; import org.slf4j.Logger; @@ -36,6 +52,10 @@ public class AdminMaintenanceAction extends FessAdminAction { // private static final Logger logger = LoggerFactory.getLogger(AdminMaintenanceAction.class); + private static final String[] ES_CAT_NAMES = new String[] { "aliases", "allocation", "count", "fielddata", "health", "indices", + "master", "nodeattrs", "nodes", "pending_tasks", "plugins", "recovery", "repositories", "thread_pool", "shards", "segments", + "snapshots", "templates" }; + // =================================================================================== // Attribute // @@ -63,11 +83,11 @@ public class AdminMaintenanceAction extends FessAdminAction { } private HtmlResponse asIndexHtml() { - return asHtml(path_AdminMaintenance_AdminMaintenanceJsp).useForm(UpgradeForm.class); + return asHtml(path_AdminMaintenance_AdminMaintenanceJsp).useForm(ActionForm.class); } @Execute - public HtmlResponse reindexOnly(final UpgradeForm form) { + public HtmlResponse reindexOnly(final ActionForm form) { validate(form, messages -> {}, this::asIndexHtml); verifyToken(this::asIndexHtml); if (startReindex(isCheckboxEnabled(form.replaceAliases), form.numberOfShardsForDoc, form.autoExpandReplicasForDoc)) { @@ -77,7 +97,7 @@ public class AdminMaintenanceAction extends FessAdminAction { } @Execute - public HtmlResponse clearCrawlerIndex(final UpgradeForm form) { + public HtmlResponse clearCrawlerIndex(final ActionForm form) { validate(form, messages -> {}, this::asIndexHtml); verifyToken(this::asIndexHtml); fessEsClient @@ -94,7 +114,151 @@ public class AdminMaintenanceAction extends FessAdminAction { return redirect(getClass()); } - private boolean startReindex(final boolean replaceAliases, final String numberOfShards, final String autoExpandReplicas) { + @Execute + public ActionResponse downloadLogs(final ActionForm form) { + validate(form, messages -> {}, this::asIndexHtml); + verifyTokenKeep(this::asIndexHtml); + + final String diagnosticId = "log" + new SimpleDateFormat("yyyyMMddHHmm").format(ComponentUtil.getSystemHelper().getCurrentTime()); + return asStream(diagnosticId + ".zip").contentTypeOctetStream().stream(out -> { + try (ZipOutputStream zos = new ZipOutputStream(out.stream())) { + writeLogFiles(zos, diagnosticId); + writeSystemProperties(zos, diagnosticId); + writeFessBasicConfig(zos, diagnosticId); + writeFessConfig(zos, diagnosticId); + writeElasticsearchCat(zos, diagnosticId); + writeElasticsearchJson(zos, diagnosticId); + } + }); + } + + protected void writeElasticsearchJson(final ZipOutputStream zos, final String id) { + writeElastisearchJsonApi(zos, id, "cluster", "health"); + writeElastisearchJsonApi(zos, id, "cluster", "state"); + writeElastisearchJsonApi(zos, id, "cluster", "stats"); + writeElastisearchJsonApi(zos, id, "cluster", "pending_tasks"); + writeElastisearchJsonApi(zos, id, "nodes", "stats"); + writeElastisearchJsonApi(zos, id, "nodes", "_all"); + writeElastisearchJsonApi(zos, id, "nodes", "usage"); + writeElastisearchJsonApi(zos, id, "remote", "info"); + writeElastisearchJsonApi(zos, id, "tasks", ""); + writeElastisearchJsonApi(zos, id, "nodes", "hot_threads"); + } + + protected void writeElastisearchJsonApi(final ZipOutputStream zos, final String id, final String v1, final String v2) { + final ZipEntry entry = new ZipEntry(id + "/es_" + v1 + "_" + v2 + ".json"); + try { + zos.putNextEntry(entry); + try (CurlResponse response = ComponentUtil.getCurlHelper().get("/_" + v1 + "/" + v2).execute()) { + CopyUtil.copy(response.getContentAsStream(), zos); + } + } catch (final Exception e) { + logger.warn("Failed to access /_" + v1 + "/" + v2, e); + } + } + + protected void writeElasticsearchCat(final ZipOutputStream zos, final String id) { + Arrays.stream(ES_CAT_NAMES).forEach(name -> { + final ZipEntry entry = new ZipEntry(id + "/es_cat_" + name + ".txt"); + try { + zos.putNextEntry(entry); + try (CurlResponse response = ComponentUtil.getCurlHelper().get("/_cat/" + name).param("v", "").execute()) { + CopyUtil.copy(response.getContentAsStream(), zos); + } + } catch (final Exception e) { + logger.warn("Failed to access /_cat/" + name, e); + } + }); + } + + protected void writeFessConfig(final ZipOutputStream zos, final String id) { + if (fessConfig instanceof SimpleImpl) { + final Properties prop = new Properties(); + ((SimpleImpl) fessConfig).keySet().stream().forEach(k -> prop.setProperty(k, fessConfig.get(k))); + + final ZipEntry entry = new ZipEntry(id + "/fess_config.properties"); + try { + zos.putNextEntry(entry); + prop.store(zos, getHostInfo()); + } catch (final IOException e) { + logger.warn("Failed to access system.properties.", e); + } + } + } + + protected void writeFessBasicConfig(final ZipOutputStream zos, final String id) { + final ZipEntry entry = new ZipEntry(id + "/fess_basic_config.bulk"); + try { + zos.putNextEntry(entry); + try (CurlResponse response = + ComponentUtil.getCurlHelper().get("/.fess_basic_config/_data").param("format", "json") + .param("scroll", fessConfig.getIndexScrollSearchTimeout()).execute()) { + CopyUtil.copy(response.getContentAsStream(), zos); + } + } catch (final IOException e) { + logger.warn("Failed to access system.properties.", e); + } + } + + protected void writeSystemProperties(final ZipOutputStream zos, final String id) { + final ZipEntry entry = new ZipEntry(id + "/system.properties"); + try { + zos.putNextEntry(entry); + ComponentUtil.getSystemProperties().store(zos, getHostInfo()); + } catch (final IOException e) { + logger.warn("Failed to access system.properties.", e); + } + } + + protected void writeLogFiles(final ZipOutputStream zos, final String id) { + final String logFilePath = systemHelper.getLogFilePath(); + if (StringUtil.isNotBlank(logFilePath)) { + final Path logDirPath = Paths.get(logFilePath); + try (Stream stream = Files.list(logDirPath)) { + stream.filter(entry -> isLogFilename(entry.getFileName().toString())).forEach(filePath -> { + final ZipEntry entry = new ZipEntry(id + "/" + filePath.getFileName().toString()); + try { + zos.putNextEntry(entry); + final long len = Files.copy(filePath, zos); + if (logger.isDebugEnabled()) { + logger.debug(filePath.getFileName() + ": " + len); + } + } catch (final IOException e) { + logger.warn("Failed to access " + filePath, e); + } + }); + } catch (final Exception e) { + logger.warn("Failed to access log files.", e); + } + } + } + + protected String getHostInfo() { + final StringBuilder buf = new StringBuilder(); + try { + final InetAddress ia = InetAddress.getLocalHost(); + final String hostname = ia.getHostName(); + if (StringUtil.isNotBlank(hostname)) { + buf.append(hostname); + } + final String ip = ia.getHostAddress(); + if (StringUtil.isNotBlank(ip)) { + if (buf.length() > 0) { + buf.append(" : "); + } + buf.append(ip); + } + } catch (final Exception e) { + // ignore + } + return buf.toString(); + } + + protected boolean isLogFilename(final String name) { + return name.endsWith(".log") || name.endsWith(".log.gz"); + } + + protected boolean startReindex(final boolean replaceAliases, final String numberOfShards, final String autoExpandReplicas) { final String docIndex = "fess"; final String fromIndex = fessConfig.getIndexDocumentUpdateIndex(); final String toIndex = docIndex + "." + new SimpleDateFormat("yyyyMMddHHmm").format(new Date()); diff --git a/src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java b/src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java index 74cdf9bfe98c6c48279921fb0ef390c71abf833b..6945e09194b36f73674a8636b2b195a1e4df460f 100644 --- a/src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java +++ b/src/main/java/org/codelibs/fess/mylasta/action/FessLabels.java @@ -2829,6 +2829,12 @@ public class FessLabels extends UserMessages { /** The key of the message: Clear .crawler Indices */ public static final String LABELS_clear_crawler_index_button = "{labels.clear_crawler_index_button}"; + /** The key of the message: Diagnostic */ + public static final String LABELS_diagnostic_logs = "{labels.diagnostic_logs}"; + + /** The key of the message: Download Logs */ + public static final String LABELS_download_diagnostic_logs_button = "{labels.download_diagnostic_logs_button}"; + /** * Assert the property is not null. * @param property The value of the property. (NotNull) diff --git a/src/main/resources/fess_label.properties b/src/main/resources/fess_label.properties index 9ab250c11dbee8f5c64e051f6b1d847c1b3795ff..8dd73df5067180a0d125df8a5f1d468e4537b296 100644 --- a/src/main/resources/fess_label.properties +++ b/src/main/resources/fess_label.properties @@ -933,3 +933,5 @@ labels.number_of_shards_for_doc=The number of shards labels.auto_expand_replicas_for_doc=Auto expand replicas labels.clear_crawler_index=Crawler Indices labels.clear_crawler_index_button=Clear .crawler Indices +labels.diagnostic_logs=Diagnostic +labels.download_diagnostic_logs_button=Download Logs diff --git a/src/main/resources/fess_label_en.properties b/src/main/resources/fess_label_en.properties index a8a9d767a9112bba9737791309f166846e46e7a9..0c23f57041e7d60142aadef2c4c681ce5babce85 100644 --- a/src/main/resources/fess_label_en.properties +++ b/src/main/resources/fess_label_en.properties @@ -933,3 +933,5 @@ labels.number_of_shards_for_doc=The number of shards labels.auto_expand_replicas_for_doc=Auto expand replicas labels.clear_crawler_index=Crawler Indices labels.clear_crawler_index_button=Clear .crawler Indices +labels.diagnostic_logs=Diagnostic +labels.download_diagnostic_logs_button=Download Logs diff --git a/src/main/resources/fess_label_ja.properties b/src/main/resources/fess_label_ja.properties index 346d9f7bd95a5e6f81a90f4c671c544773b60416..a11f99c5f40f16d9d48c63417f4df26e8954e9ac 100644 --- a/src/main/resources/fess_label_ja.properties +++ b/src/main/resources/fess_label_ja.properties @@ -935,3 +935,5 @@ labels.number_of_shards_for_doc=シャード数 labels.auto_expand_replicas_for_doc=最大レプリカ数 labels.clear_crawler_index=Crawlerインデックス labels.clear_crawler_index_button=.crawlerインデックスの削除 +labels.diagnostic_logs=診断 +labels.download_diagnostic_logs_button=ログのダウンロード diff --git a/src/main/webapp/WEB-INF/view/admin/maintenance/admin_maintenance.jsp b/src/main/webapp/WEB-INF/view/admin/maintenance/admin_maintenance.jsp index 943027d7e589e40f2ce47e44fd920ea5b3374275..728d93bb09b7a6163d1f3fbfbfff7f1e79991bb2 100644 --- a/src/main/webapp/WEB-INF/view/admin/maintenance/admin_maintenance.jsp +++ b/src/main/webapp/WEB-INF/view/admin/maintenance/admin_maintenance.jsp @@ -101,6 +101,26 @@ +
+
+
+

+ +

+
+ + + +
+ +