Jelajahi Sumber

fix #1764 download logs on maintenance page

Shinsuke Sugaya 7 tahun lalu
induk
melakukan
50ba9b0b1b

+ 1 - 1
src/main/java/org/codelibs/fess/app/web/admin/maintenance/UpgradeForm.java → 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;
 

+ 168 - 4
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<Path> 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());

+ 6 - 0
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)

+ 2 - 0
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

+ 2 - 0
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

+ 2 - 0
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=ログのダウンロード

+ 20 - 0
src/main/webapp/WEB-INF/view/admin/maintenance/admin_maintenance.jsp

@@ -101,6 +101,26 @@
 							</div>
 							<!-- /.box -->
 						</div>
+						<div class="col-md-12">
+							<div class="box box-primary">
+								<div class="box-header with-border">
+									<h3 class="box-title">
+										<la:message key="labels.diagnostic_logs" />
+									</h3>
+								</div>
+								<!-- /.box-header -->
+								<div class="box-footer">
+									<button type="submit" class="btn btn-primary"
+										name="downloadLogs"
+										value="<la:message key="labels.download_diagnostic_logs_button"/>">
+										<i class="fa fa-arrow-circle-right"></i>
+										<la:message key="labels.download_diagnostic_logs_button" />
+									</button>
+								</div>
+								<!-- /.box-footer -->
+							</div>
+							<!-- /.box -->
+						</div>
 					</la:form>
 				</div>
 			</section>