Przeglądaj źródła

fix #2736 use ecs log format

Shinsuke Sugaya 2 lat temu
rodzic
commit
697d47e483

+ 101 - 80
src/main/java/org/codelibs/fess/helper/ActivityHelper.java

@@ -19,12 +19,14 @@ import static org.codelibs.core.stream.StreamUtil.stream;
 
 import java.time.ZonedDateTime;
 import java.time.format.DateTimeFormatter;
+import java.util.LinkedHashMap;
 import java.util.Locale;
 import java.util.Map;
 import java.util.stream.Collectors;
 
 import javax.annotation.PostConstruct;
 
+import org.apache.commons.text.StringEscapeUtils;
 import org.apache.logging.log4j.LogManager;
 import org.apache.logging.log4j.Logger;
 import org.codelibs.core.lang.StringUtil;
@@ -46,113 +48,120 @@ public class ActivityHelper {
 
     protected String permissionSeparator = "|";
 
+    protected boolean useEcsFormat = false;
+
+    protected String ecsVersion = "1.2.0";
+
+    protected String ecsServiceName = "fess";
+
+    protected String ecsEventDataset = "app";
+
     @PostConstruct
     public void init() {
         logger = LogManager.getLogger(loggerName);
+        final String logFormat = ComponentUtil.getFessConfig().getAppAuditLogFormat();
+        if (StringUtil.isBlank(logFormat)) {
+            useEcsFormat = "docker".equals(System.getenv("FESS_APP_TYPE"));
+        } else if ("ecs".equals(logFormat)) {
+            useEcsFormat = true;
+        }
     }
 
     public void login(final OptionalThing<FessUserBean> user) {
-        final StringBuilder buf = new StringBuilder(100);
-        buf.append("action:");
-        buf.append(Action.LOGIN);
-        buf.append('\t');
-        buf.append("user:");
-        buf.append(user.map(FessUserBean::getUserId).orElse("-"));
-        buf.append('\t');
-        buf.append("permissions:");
-        buf.append(user.map(u -> stream(u.getPermissions()).get(stream -> stream.collect(Collectors.joining(permissionSeparator))))
-                .filter(StringUtil::isNotBlank).orElse("-"));
-        log(buf);
+        final Map<String, String> valueMap = new LinkedHashMap<>();
+        valueMap.put("action", Action.LOGIN.name());
+        valueMap.put("user", user.map(FessUserBean::getUserId).orElse("-"));
+        valueMap.put("permissions",
+                user.map(u -> stream(u.getPermissions()).get(stream -> stream.collect(Collectors.joining(permissionSeparator))))
+                        .filter(StringUtil::isNotBlank).orElse("-"));
+        log(valueMap);
     }
 
     public void loginFailure(final OptionalThing<LoginCredential> credential) {
-        final StringBuilder buf = new StringBuilder(100);
-        buf.append("action:");
-        buf.append(Action.LOGIN_FAILURE);
-        credential.map(c -> {
-            final StringBuilder buffer = new StringBuilder(100);
-            buffer.append('\t');
-            buffer.append("class:");
-            buffer.append(c.getClass().getSimpleName());
-            if (c instanceof FessCredential) {
-                buffer.append('\t');
-                buffer.append("user:");
-                buffer.append(((FessCredential) c).getUserId());
+        final Map<String, String> valueMap = new LinkedHashMap<>();
+        valueMap.put("action", Action.LOGIN_FAILURE.name());
+        credential.ifPresent(c -> {
+            valueMap.put("class", c.getClass().getSimpleName());
+            if (c instanceof FessCredential fessCredential) {
+                valueMap.put("user", fessCredential.getUserId());
             }
-            return buffer.toString();
-        }).ifPresent(buf::append);
-        log(buf);
+        });
+        log(valueMap);
     }
 
     public void logout(final OptionalThing<FessUserBean> user) {
-        final StringBuilder buf = new StringBuilder(100);
-        buf.append("action:");
-        buf.append(Action.LOGOUT);
-        buf.append('\t');
-        buf.append("user:");
-        buf.append(user.map(FessUserBean::getUserId).orElse("-"));
-        buf.append('\t');
-        buf.append("permissions:");
-        buf.append(user.map(u -> stream(u.getPermissions()).get(stream -> stream.collect(Collectors.joining(permissionSeparator))))
-                .filter(StringUtil::isNotBlank).orElse("-"));
-        log(buf);
+        final Map<String, String> valueMap = new LinkedHashMap<>();
+        valueMap.put("action", Action.LOGOUT.name());
+        valueMap.put("user", user.map(FessUserBean::getUserId).orElse("-"));
+        valueMap.put("permissions",
+                user.map(u -> stream(u.getPermissions()).get(stream -> stream.collect(Collectors.joining(permissionSeparator))))
+                        .filter(StringUtil::isNotBlank).orElse("-"));
+        log(valueMap);
     }
 
     public void access(final OptionalThing<FessUserBean> user, final String path, final String execute) {
-        final StringBuilder buf = new StringBuilder(100);
-        buf.append("action:");
-        buf.append(Action.ACCESS);
-        buf.append('\t');
-        buf.append("user:");
-        buf.append(user.map(FessUserBean::getUserId).orElse("-"));
-        buf.append('\t');
-        buf.append("path:");
-        buf.append(path);
-        buf.append('\t');
-        buf.append("execute:");
-        buf.append(execute);
-        log(buf);
+        final Map<String, String> valueMap = new LinkedHashMap<>();
+        valueMap.put("action", Action.ACCESS.name());
+        valueMap.put("user", user.map(FessUserBean::getUserId).orElse("-"));
+        valueMap.put("path", path);
+        valueMap.put("execute", execute);
+        log(valueMap);
     }
 
     public void permissionChanged(final OptionalThing<FessUserBean> user) {
-        final StringBuilder buf = new StringBuilder(100);
-        buf.append("action:");
-        buf.append(Action.UPDATE_PERMISSION);
-        buf.append('\t');
-        buf.append("user:");
-        buf.append(user.map(FessUserBean::getUserId).orElse("-"));
-        buf.append('\t');
-        buf.append("permissions:");
-        buf.append(user.map(u -> stream(u.getPermissions()).get(stream -> stream.collect(Collectors.joining(permissionSeparator))))
-                .filter(StringUtil::isNotBlank).orElse("-"));
-        log(buf);
+        final Map<String, String> valueMap = new LinkedHashMap<>();
+        valueMap.put("action", Action.UPDATE_PERMISSION.name());
+        valueMap.put("user", user.map(FessUserBean::getUserId).orElse("-"));
+        valueMap.put("permissions",
+                user.map(u -> stream(u.getPermissions()).get(stream -> stream.collect(Collectors.joining(permissionSeparator))))
+                        .filter(StringUtil::isNotBlank).orElse("-"));
+        log(valueMap);
     }
 
     public void print(final String action, final OptionalThing<FessUserBean> user, final Map<String, String> params) {
-        final StringBuilder buf = new StringBuilder(100);
-        buf.append("action:");
-        buf.append(action.replace('\t', '_').toUpperCase(Locale.ENGLISH));
-        buf.append('\t');
-        buf.append("user:");
-        buf.append(user.map(FessUserBean::getUserId).orElse("-"));
-        params.entrySet().stream().map(e -> e.getKey() + ":" + e.getValue()).map(s -> s.replace('\t', '_')).sorted().forEach(s -> {
-            buf.append('\t');
-            buf.append(s);
+        final Map<String, String> valueMap = new LinkedHashMap<>();
+        valueMap.put("action", action.replace('\t', '_').toUpperCase(Locale.ENGLISH));
+        valueMap.put("user", user.map(FessUserBean::getUserId).orElse("-"));
+        params.entrySet().stream().sorted((e1, e2) -> e1.getKey().compareTo(e2.getKey())).forEach(e -> {
+            valueMap.put(e.getKey(), e.getValue().replace('\t', '_'));
         });
-        log(buf);
+        log(valueMap);
+    }
+
+    protected void log(final Map<String, String> valueMap) {
+        valueMap.put("ip", getClientIp());
+        valueMap.put("time", DateTimeFormatter.ISO_INSTANT.format(ZonedDateTime.now()));
+        if (useEcsFormat) {
+            printByEcs(valueMap);
+        } else {
+            printByLtsv(valueMap);
+        }
+    }
+
+    protected void printByLtsv(final Map<String, String> valueMap) {
+        printLog(valueMap.entrySet().stream().map(e -> e.getKey() + ":" + e.getValue()).collect(Collectors.joining("\t")));
+    }
+
+    protected void printByEcs(final Map<String, String> valueMap) {
+        final StringBuilder buf = new StringBuilder(100);
+        buf.append("{\"@timestamp\":\"").append(valueMap.remove("time")).append('"');
+        buf.append(",\"log.level\":\"INFO\"");
+        buf.append(",\"ecs.version\":\"").append(ecsVersion).append('"');
+        buf.append(",\"service.name\":\"").append(ecsServiceName).append('"');
+        buf.append(",\"event.dataset\":\"").append(ecsEventDataset).append('"');
+        buf.append(",\"process.thread.name\":\"").append(StringEscapeUtils.escapeJson(Thread.currentThread().getName())).append('"');
+        buf.append(",\"log.logger\":\"").append(StringEscapeUtils.escapeJson(this.getClass().getName())).append('"');
+        valueMap.entrySet().stream().forEach(e -> buf.append(",\"labels.").append(e.getKey()).append("\":\"")
+                .append(StringEscapeUtils.escapeJson(e.getValue())).append('"'));
+        buf.append('}');
+        printLog(buf.toString());
     }
 
-    protected void log(final StringBuilder buf) {
-        buf.append('\t');
-        buf.append("ip:");
-        buf.append(getClientIp());
-        buf.append('\t');
-        buf.append("time:");
-        buf.append(DateTimeFormatter.ISO_INSTANT.format(ZonedDateTime.now()));
-        logger.info(buf.toString());
+    protected void printLog(final String message) {
+        logger.info(message);
     }
 
-    protected static String getClientIp() {
+    protected String getClientIp() {
         return LaRequestUtil.getOptionalRequest().map(req -> ComponentUtil.getViewHelper().getClientIp(req)).orElse("-");
     }
 
@@ -167,4 +176,16 @@ public class ActivityHelper {
     public void setPermissionSeparator(final String permissionSeparator) {
         this.permissionSeparator = permissionSeparator;
     }
+
+    public void setEcsVersion(String ecsVersion) {
+        this.ecsVersion = ecsVersion;
+    }
+
+    public void setEcsServiceName(String ecsServiceName) {
+        this.ecsServiceName = ecsServiceName;
+    }
+
+    public void setEcsEventDataset(String ecsEventDataset) {
+        this.ecsEventDataset = ecsEventDataset;
+    }
 }

+ 27 - 0
src/main/java/org/codelibs/fess/mylasta/direction/FessConfig.java

@@ -58,6 +58,9 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
     /** The key of the configuration. e.g.  */
     String APP_EXTENSION_NAMES = "app.extension.names";
 
+    /** The key of the configuration. e.g.  */
+    String APP_AUDIT_LOG_FORMAT = "app.audit.log.format";
+
     /** The key of the configuration. e.g. -Djava.awt.headless=true<br>
      * -Dfile.encoding=UTF-8<br>
      * -Djna.nosys=true<br>
@@ -1943,6 +1946,21 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
      */
     Integer getAppExtensionNamesAsInteger();
 
+    /**
+     * Get the value for the key 'app.audit.log.format'. <br>
+     * The value is, e.g.  <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     */
+    String getAppAuditLogFormat();
+
+    /**
+     * Get the value for the key 'app.audit.log.format' as {@link Integer}. <br>
+     * The value is, e.g.  <br>
+     * @return The value of found property. (NotNull: if not found, exception but basically no way)
+     * @throws NumberFormatException When the property is not integer.
+     */
+    Integer getAppAuditLogFormatAsInteger();
+
     /**
      * Get the value for the key 'jvm.crawler.options'. <br>
      * The value is, e.g. -Djava.awt.headless=true<br>
@@ -7694,6 +7712,14 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             return getAsInteger(FessConfig.APP_EXTENSION_NAMES);
         }
 
+        public String getAppAuditLogFormat() {
+            return get(FessConfig.APP_AUDIT_LOG_FORMAT);
+        }
+
+        public Integer getAppAuditLogFormatAsInteger() {
+            return getAsInteger(FessConfig.APP_AUDIT_LOG_FORMAT);
+        }
+
         public String getJvmCrawlerOptions() {
             return get(FessConfig.JVM_CRAWLER_OPTIONS);
         }
@@ -10693,6 +10719,7 @@ public interface FessConfig extends FessEnv, org.codelibs.fess.mylasta.direction
             defaultMap.put(FessConfig.APP_DIGEST_ALGORISM, "sha256");
             defaultMap.put(FessConfig.APP_ENCRYPT_PROPERTY_PATTERN, ".*password|.*key|.*token|.*secret");
             defaultMap.put(FessConfig.APP_EXTENSION_NAMES, "");
+            defaultMap.put(FessConfig.APP_AUDIT_LOG_FORMAT, "");
             defaultMap.put(FessConfig.JVM_CRAWLER_OPTIONS,
                     "-Djava.awt.headless=true\n-Dfile.encoding=UTF-8\n-Djna.nosys=true\n-Djdk.io.permissionsUseCanonicalPath=true\n-Dhttp.maxConnections=20\n-Djava.util.logging.manager=org.apache.logging.log4j.jul.LogManager\n-server\n-Xms128m\n-Xmx512m\n-XX:MaxMetaspaceSize=128m\n-XX:CompressedClassSpaceSize=32m\n-XX:-UseGCOverheadLimit\n-XX:+UseTLAB\n-XX:+DisableExplicitGC\n-XX:-HeapDumpOnOutOfMemoryError\n-XX:-OmitStackTraceInFastThrow\n-XX:+UnlockExperimentalVMOptions\n-XX:+UseG1GC\n-XX:InitiatingHeapOccupancyPercent=45\n-XX:G1HeapRegionSize=1m\n-XX:MaxGCPauseMillis=60000\n-XX:G1NewSizePercent=5\n-XX:G1MaxNewSizePercent=5\n-Djcifs.smb.client.responseTimeout=30000\n-Djcifs.smb.client.soTimeout=35000\n-Djcifs.smb.client.connTimeout=60000\n-Djcifs.smb.client.sessionTimeout=60000\n-Djcifs.smb1.smb.client.connTimeout=60000\n-Djcifs.smb1.smb.client.soTimeout=35000\n-Djcifs.smb1.smb.client.responseTimeout=30000\n-Dio.netty.noUnsafe=true\n-Dio.netty.noKeySetOptimization=true\n-Dio.netty.recycler.maxCapacityPerThread=0\n-Dlog4j.shutdownHookEnabled=false\n-Dlog4j2.formatMsgNoLookups=true\n-Dlog4j2.disable.jmx=true\n-Dlog4j.skipJansi=true\n-Dsun.java2d.cmm=sun.java2d.cmm.kcms.KcmsServiceProvider\n-Dorg.apache.pdfbox.rendering.UsePureJavaCMYKConversion=true\n");
             defaultMap.put(FessConfig.JVM_SUGGEST_OPTIONS,

+ 2 - 0
src/main/resources/fess_config.properties

@@ -24,6 +24,8 @@ app.encrypt.property.pattern=.*password|.*key|.*token|.*secret
 
 app.extension.names=
 
+app.audit.log.format=
+
 # JVM options
 jvm.crawler.options=\
 -Djava.awt.headless=true\n\

+ 128 - 3
src/test/java/org/codelibs/fess/helper/ActivityHelperTest.java

@@ -15,11 +15,13 @@
  */
 package org.codelibs.fess.helper;
 
+import java.util.Map;
+
+import org.codelibs.core.lang.StringUtil;
 import org.codelibs.fess.entity.FessUser;
 import org.codelibs.fess.mylasta.action.FessUserBean;
 import org.codelibs.fess.unit.UnitFessTestCase;
 import org.dbflute.optional.OptionalThing;
-import org.opensearch.common.collect.Map;
 
 public class ActivityHelperTest extends UnitFessTestCase {
 
@@ -32,13 +34,33 @@ public class ActivityHelperTest extends UnitFessTestCase {
         super.setUp();
         activityHelper = new ActivityHelper() {
             @Override
-            protected void log(final StringBuilder buf) {
-                localLogMsg.set(buf.toString());
+            protected void printByLtsv(final Map<String, String> valueMap) {
+                valueMap.remove("time");
+                valueMap.remove("ip");
+                super.printByLtsv(valueMap);
+            }
+
+            @Override
+            protected void printByEcs(final Map<String, String> valueMap) {
+                valueMap.put("time", "2022-01-01T00:00:00.000Z");
+                valueMap.remove("ip");
+                super.printByEcs(valueMap);
+            }
+
+            @Override
+            protected void printLog(final String message) {
+                localLogMsg.set(message);
+            }
+
+            @Override
+            protected String getClientIp() {
+                return StringUtil.EMPTY;
             }
         };
     }
 
     public void test_login() {
+        activityHelper.useEcsFormat = false;
         activityHelper.login(OptionalThing.empty());
         assertEquals("action:LOGIN\tuser:-\tpermissions:-", localLogMsg.get());
 
@@ -50,11 +72,13 @@ public class ActivityHelperTest extends UnitFessTestCase {
     }
 
     public void test_loginFailure() {
+        activityHelper.useEcsFormat = false;
         activityHelper.loginFailure(OptionalThing.empty());
         assertEquals("action:LOGIN_FAILURE", localLogMsg.get());
     }
 
     public void test_logout() {
+        activityHelper.useEcsFormat = false;
         activityHelper.logout(OptionalThing.empty());
         assertEquals("action:LOGOUT\tuser:-\tpermissions:-", localLogMsg.get());
 
@@ -66,6 +90,7 @@ public class ActivityHelperTest extends UnitFessTestCase {
     }
 
     public void test_access() {
+        activityHelper.useEcsFormat = false;
         activityHelper.access(OptionalThing.empty(), "/", "aaa");
         assertEquals("action:ACCESS\tuser:-\tpath:/\texecute:aaa", localLogMsg.get());
 
@@ -77,6 +102,7 @@ public class ActivityHelperTest extends UnitFessTestCase {
     }
 
     public void test_permissionChanged() {
+        activityHelper.useEcsFormat = false;
         activityHelper.permissionChanged(OptionalThing.empty());
         assertEquals("action:UPDATE_PERMISSION\tuser:-\tpermissions:-", localLogMsg.get());
 
@@ -88,6 +114,7 @@ public class ActivityHelperTest extends UnitFessTestCase {
     }
 
     public void test_print() {
+        activityHelper.useEcsFormat = false;
         activityHelper.print("aaa", OptionalThing.empty(), Map.of());
         assertEquals("action:AAA\tuser:-", localLogMsg.get());
 
@@ -98,6 +125,104 @@ public class ActivityHelperTest extends UnitFessTestCase {
         assertEquals("action:CCC\tuser:testuser\t111:222\t333:444", localLogMsg.get());
     }
 
+    public void test_login_ecs() {
+        activityHelper.useEcsFormat = true;
+        activityHelper.login(OptionalThing.empty());
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"LOGIN\",\"labels.user\":\"-\",\"labels.permissions\":\"-\"}",
+                localLogMsg.get());
+
+        activityHelper.login(createUser("testuser", new String[0]));
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"LOGIN\",\"labels.user\":\"testuser\",\"labels.permissions\":\"-\"}",
+                localLogMsg.get());
+
+        activityHelper.login(createUser("testuser", new String[] { "111", "222" }));
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"LOGIN\",\"labels.user\":\"testuser\",\"labels.permissions\":\"111|222\"}",
+                localLogMsg.get());
+    }
+
+    public void test_loginFailure_ecs() {
+        activityHelper.useEcsFormat = true;
+        activityHelper.loginFailure(OptionalThing.empty());
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"LOGIN_FAILURE\"}",
+                localLogMsg.get());
+    }
+
+    public void test_logout_ecs() {
+        activityHelper.useEcsFormat = true;
+        activityHelper.logout(OptionalThing.empty());
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"LOGOUT\",\"labels.user\":\"-\",\"labels.permissions\":\"-\"}",
+                localLogMsg.get());
+
+        activityHelper.logout(createUser("testuser", new String[0]));
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"LOGOUT\",\"labels.user\":\"testuser\",\"labels.permissions\":\"-\"}",
+                localLogMsg.get());
+
+        activityHelper.logout(createUser("testuser", new String[] { "111", "222" }));
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"LOGOUT\",\"labels.user\":\"testuser\",\"labels.permissions\":\"111|222\"}",
+                localLogMsg.get());
+    }
+
+    public void test_access_ecs() {
+        activityHelper.useEcsFormat = true;
+        activityHelper.access(OptionalThing.empty(), "/", "aaa");
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"ACCESS\",\"labels.user\":\"-\",\"labels.path\":\"\\/\",\"labels.execute\":\"aaa\"}",
+                localLogMsg.get());
+
+        activityHelper.access(createUser("testuser", new String[0]), "/aaa", "bbb");
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"ACCESS\",\"labels.user\":\"testuser\",\"labels.path\":\"\\/aaa\",\"labels.execute\":\"bbb\"}",
+                localLogMsg.get());
+
+        activityHelper.access(createUser("testuser", new String[] { "111", "222" }), "/aaa/bbb", "ccc");
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"ACCESS\",\"labels.user\":\"testuser\",\"labels.path\":\"\\/aaa\\/bbb\",\"labels.execute\":\"ccc\"}",
+                localLogMsg.get());
+    }
+
+    public void test_permissionChanged_ecs() {
+        activityHelper.useEcsFormat = true;
+        activityHelper.permissionChanged(OptionalThing.empty());
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"UPDATE_PERMISSION\",\"labels.user\":\"-\",\"labels.permissions\":\"-\"}",
+                localLogMsg.get());
+
+        activityHelper.permissionChanged(createUser("testuser", new String[0]));
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"UPDATE_PERMISSION\",\"labels.user\":\"testuser\",\"labels.permissions\":\"-\"}",
+                localLogMsg.get());
+
+        activityHelper.permissionChanged(createUser("testuser", new String[] { "111", "222" }));
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"UPDATE_PERMISSION\",\"labels.user\":\"testuser\",\"labels.permissions\":\"111|222\"}",
+                localLogMsg.get());
+    }
+
+    public void test_print_ecs() {
+        activityHelper.useEcsFormat = true;
+        activityHelper.print("aaa", OptionalThing.empty(), Map.of());
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"AAA\",\"labels.user\":\"-\"}",
+                localLogMsg.get());
+
+        activityHelper.print("aaa bbb", createUser("testuser", new String[0]), Map.of("111", "222"));
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"AAA BBB\",\"labels.user\":\"testuser\",\"labels.111\":\"222\"}",
+                localLogMsg.get());
+
+        activityHelper.print("ccc", createUser("testuser", new String[] { "111", "222" }), Map.of("111", "222", "333", "444"));
+        assertEquals(
+                "{\"@timestamp\":\"2022-01-01T00:00:00.000Z\",\"log.level\":\"INFO\",\"ecs.version\":\"1.2.0\",\"service.name\":\"fess\",\"event.dataset\":\"app\",\"process.thread.name\":\"main\",\"log.logger\":\"org.codelibs.fess.helper.ActivityHelperTest$1\",\"labels.action\":\"CCC\",\"labels.user\":\"testuser\",\"labels.111\":\"222\",\"labels.333\":\"444\"}",
+                localLogMsg.get());
+    }
+
     OptionalThing<FessUserBean> createUser(String name, String[] permissions) {
         return OptionalThing.of(new FessUserBean(new TestUser(name, permissions)));
     }