Support audit events rendered as JSON string
Allow to configure a different rendering for audit events
by specifying the renderer in gerrit.config.
Example:
[plugins "audit-sl4j"]
renderer = JSON
Change-Id: Iff734fc5429f15913df66779570d91dc51a73a32
diff --git a/BUILD b/BUILD
index 69730a6..2dec1e5 100644
--- a/BUILD
+++ b/BUILD
@@ -22,9 +22,22 @@
junit_tests(
name = "audit_sl4j_tests",
- srcs = glob(["src/test/java/**/*.java"]),
+ srcs = glob(["src/test/java/**/*Test.java"]),
visibility = ["//visibility:public"],
deps = PLUGIN_TEST_DEPS + PLUGIN_DEPS + [
":audit-sl4j__plugin",
+ ":audit_sl4j_util",
+ ],
+)
+
+java_library(
+ name = "audit_sl4j_util",
+ testonly = 1,
+ srcs = glob(
+ ["src/test/java/**/*.java"],
+ exclude = ["src/test/java/**/*Test.java"],
+ ),
+ deps = PLUGIN_TEST_DEPS + PLUGIN_DEPS + [
+ ":audit-sl4j__plugin",
],
)
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRecord.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRecord.java
new file mode 100644
index 0000000..a89be0a
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRecord.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.auditsl4j;
+
+import com.google.gerrit.audit.AuditEvent;
+
+public class AuditRecord {
+ public final String type;
+ public final AuditEvent event;
+
+ public AuditRecord(AuditEvent event) {
+ super();
+
+ String eventClass = event.getClass().getName();
+ this.type = eventClass.substring(eventClass.lastIndexOf('.') + 1);
+ this.event = event;
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRenderTypes.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRenderTypes.java
new file mode 100644
index 0000000..9d2e2f1
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRenderTypes.java
@@ -0,0 +1,20 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.auditsl4j;
+
+public enum AuditRenderTypes {
+ CSV,
+ JSON;
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRenderer.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRenderer.java
index f89046e..293c511 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRenderer.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRenderer.java
@@ -15,10 +15,11 @@
package com.googlesource.gerrit.plugins.auditsl4j;
import com.google.gerrit.audit.AuditEvent;
-import com.google.inject.ImplementedBy;
+import java.util.Optional;
-@ImplementedBy(AuditRendererToCsv.class)
public interface AuditRenderer {
String render(AuditEvent auditEvent);
+
+ Optional<String> headers();
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToCsv.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToCsv.java
index 4f2a6e3..041f846 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToCsv.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToCsv.java
@@ -20,6 +20,7 @@
import com.google.common.collect.Multimap;
import com.google.gerrit.audit.AuditEvent;
import java.util.Collection;
+import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
@@ -41,6 +42,12 @@
auditEvent.elapsed);
}
+ @Override
+ public Optional<String> headers() {
+ return Optional.of(
+ "EventId | EventTS | SessionId | User | Protocol data | Action | Parameters | Result | StartTS | Elapsed");
+ }
+
private Object getFormattedAuditList(Multimap<String, ?> params) {
if (params == null || params.size() == 0) {
return "[]";
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToJson.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToJson.java
new file mode 100644
index 0000000..7047adc
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToJson.java
@@ -0,0 +1,80 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.auditsl4j;
+
+import com.google.common.collect.ListMultimap;
+import com.google.gerrit.audit.AuditEvent;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.AccessPath;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.OutputFormat;
+import com.google.gson.ExclusionStrategy;
+import com.google.gson.FieldAttributes;
+import com.google.gson.Gson;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Optional;
+
+public class AuditRendererToJson implements AuditRenderer {
+ private final ExclusionStrategy INCLUDE_ONLY_WHITELISTED =
+ new ExclusionStrategy() {
+ private final HashSet<Class<?>> WHITELIST_CLASSES =
+ new HashSet<>(
+ Arrays.asList(
+ String.class,
+ Object.class,
+ CurrentUser.class,
+ Long.class,
+ Long.TYPE,
+ Integer.class,
+ Integer.TYPE,
+ AccessPath.class,
+ CurrentUser.PropertyKey.class,
+ Account.Id.class,
+ AuditRecord.class));
+ private final HashSet<String> BLACKLIST_FIELDS =
+ new HashSet<>(Arrays.asList("anonymousCowardName"));
+
+ @Override
+ public boolean shouldSkipField(FieldAttributes f) {
+ return BLACKLIST_FIELDS.contains(f.getName());
+ }
+
+ @Override
+ public boolean shouldSkipClass(Class<?> clazz) {
+ return !AuditEvent.class.isAssignableFrom(clazz)
+ && !CurrentUser.class.isAssignableFrom(clazz)
+ && !ListMultimap.class.isAssignableFrom(clazz)
+ && !AuditEvent.UUID.class.isAssignableFrom(clazz)
+ && !WHITELIST_CLASSES.contains(clazz);
+ }
+ };
+
+ private final Gson gson =
+ OutputFormat.JSON_COMPACT
+ .newGsonBuilder()
+ .setExclusionStrategies(INCLUDE_ONLY_WHITELISTED)
+ .create();
+
+ @Override
+ public String render(AuditEvent auditEvent) {
+ return gson.toJson(new AuditRecord(auditEvent));
+ }
+
+ @Override
+ public Optional<String> headers() {
+ return Optional.empty();
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToStringList.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToStringList.java
new file mode 100644
index 0000000..bb24056
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToStringList.java
@@ -0,0 +1,34 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.auditsl4j;
+
+import com.google.inject.Singleton;
+import java.util.ArrayList;
+import java.util.List;
+
+@Singleton
+public class AuditWriterToStringList implements AuditWriter {
+ public final List<String> strings = new ArrayList<>();
+
+ @Override
+ public void write(String msg) {
+ strings.add(msg);
+ }
+
+ @Override
+ public String toString() {
+ return strings.toString();
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAudit.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAudit.java
index 7328c60..5a0b590 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAudit.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAudit.java
@@ -31,12 +31,7 @@
this.auditWriter = auditWriter;
this.auditRenderer = auditRenderer;
- writeHeaders(auditWriter);
- }
-
- private void writeHeaders(AuditWriter auditWriter) {
- auditWriter.write(
- "EventId | EventTS | SessionId | User | Protocol data | Action | Parameters | Result | StartTS | Elapsed");
+ auditRenderer.headers().ifPresent(auditWriter::write);
}
@Override
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/Module.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/Module.java
index ba76f93..d70f4ac 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/Module.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/Module.java
@@ -15,12 +15,35 @@
package com.googlesource.gerrit.plugins.auditsl4j;
import com.google.gerrit.audit.AuditListener;
+import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
public class Module extends AbstractModule {
+ private final PluginConfig config;
+
+ @Inject
+ public Module(@PluginName String pluginName, PluginConfigFactory configFactory) {
+ config = configFactory.getFromGerritConfig(pluginName);
+ }
+
@Override
protected void configure() {
DynamicSet.bind(binder(), AuditListener.class).to(LoggerAudit.class);
+
+ AuditRenderTypes rendererType = config.getEnum("renderer", AuditRenderTypes.CSV);
+ switch (rendererType) {
+ case CSV:
+ bind(AuditRenderer.class).to(AuditRendererToCsv.class);
+ break;
+ case JSON:
+ bind(AuditRenderer.class).to(AuditRendererToJson.class);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported renderer '" + rendererType + "'");
+ }
}
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditTest.java b/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditToCsvTest.java
similarity index 71%
rename from src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditTest.java
rename to src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditToCsvTest.java
index 5470641..df60cab 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditToCsvTest.java
@@ -17,9 +17,14 @@
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.audit.AuditListener;
import com.google.gerrit.common.Version;
+import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.inject.AbstractModule;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.ArrayList;
@@ -27,43 +32,36 @@
import org.apache.http.client.fluent.Request;
import org.junit.Test;
+@Sandboxed
@TestPlugin(
name = "audit-sl4j",
- sysModule = "com.googlesource.gerrit.plugins.auditsl4j.LoggerAuditTest$TestModule")
-public class LoggerAuditTest extends LightweightPluginDaemonTest {
+ sysModule = "com.googlesource.gerrit.plugins.auditsl4j.LoggerAuditToCsvTest$TestModule")
+public class LoggerAuditToCsvTest extends LightweightPluginDaemonTest implements WaitForCondition {
@Inject @CanonicalWebUrl private String webUrl;
- public static class TestModule extends Module {
+ public static class TestModule extends AbstractModule {
@Override
protected void configure() {
bind(AuditWriter.class).to(AuditWriterToStringList.class);
- super.configure();
- }
- }
-
- @Singleton
- public static class AuditWriterToStringList implements AuditWriter {
- public final List<String> strings = new ArrayList<>();
-
- @Override
- public void write(String msg) {
- strings.add(msg);
+ bind(AuditRenderer.class).to(AuditRendererToCsv.class);
+ DynamicSet.bind(binder(), AuditListener.class).to(LoggerAudit.class);
}
}
@Test
- public void testHttpAudit() throws Exception {
+ public void testHttpCsvAudit() throws Exception {
AuditWriterToStringList auditStrings = getPluginInstance(AuditWriterToStringList.class);
Request.Get(webUrl + "config/server/version").execute().returnResponse();
- assertThat(auditStrings.strings).hasSize(2);
+ assertThat(waitFor(() -> auditStrings.strings.size() == 2)).isTrue();
assertThat(auditStrings.strings.get(1)).contains(Version.getVersion());
}
private <T> T getPluginInstance(Class<T> clazz) {
return plugin.getSysInjector().getInstance(clazz);
}
+
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditTest.java b/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditToJsonTest.java
similarity index 62%
copy from src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditTest.java
copy to src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditToJsonTest.java
index 5470641..e8a568f 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditToJsonTest.java
@@ -17,50 +17,49 @@
import static com.google.common.truth.Truth.assertThat;
import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.Sandboxed;
import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.audit.AuditListener;
import com.google.gerrit.common.Version;
+import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.inject.AbstractModule;
import com.google.inject.Inject;
-import com.google.inject.Singleton;
-import java.util.ArrayList;
-import java.util.List;
import org.apache.http.client.fluent.Request;
import org.junit.Test;
+@Sandboxed
@TestPlugin(
name = "audit-sl4j",
- sysModule = "com.googlesource.gerrit.plugins.auditsl4j.LoggerAuditTest$TestModule")
-public class LoggerAuditTest extends LightweightPluginDaemonTest {
+ sysModule = "com.googlesource.gerrit.plugins.auditsl4j.LoggerAuditToJsonTest$TestModule")
+public class LoggerAuditToJsonTest extends LightweightPluginDaemonTest implements WaitForCondition {
@Inject @CanonicalWebUrl private String webUrl;
- public static class TestModule extends Module {
+ public static class TestModule extends AbstractModule {
@Override
protected void configure() {
bind(AuditWriter.class).to(AuditWriterToStringList.class);
- super.configure();
- }
- }
-
- @Singleton
- public static class AuditWriterToStringList implements AuditWriter {
- public final List<String> strings = new ArrayList<>();
-
- @Override
- public void write(String msg) {
- strings.add(msg);
+ bind(AuditRenderer.class).to(AuditRendererToJson.class);
+ DynamicSet.bind(binder(), AuditListener.class).to(LoggerAudit.class);
}
}
@Test
- public void testHttpAudit() throws Exception {
+ public void testHttpJsonAudit() throws Exception {
AuditWriterToStringList auditStrings = getPluginInstance(AuditWriterToStringList.class);
Request.Get(webUrl + "config/server/version").execute().returnResponse();
- assertThat(auditStrings.strings).hasSize(2);
- assertThat(auditStrings.strings.get(1)).contains(Version.getVersion());
+ assertThat(waitFor(() -> auditStrings.strings.size() == 1)).isTrue();
+
+ String auditJsonString = auditStrings.strings.get(0);
+ assertThat(auditJsonString).contains(Version.getVersion());
+ JsonObject auditJson = new Gson().fromJson(auditJsonString, JsonObject.class);
+ assertThat(auditJson).isNotNull();
}
private <T> T getPluginInstance(Class<T> clazz) {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/WaitForCondition.java b/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/WaitForCondition.java
new file mode 100644
index 0000000..5548391
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/WaitForCondition.java
@@ -0,0 +1,49 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.googlesource.gerrit.plugins.auditsl4j;
+
+import com.google.common.base.Stopwatch;
+import java.time.Duration;
+import java.util.function.Supplier;
+
+public interface WaitForCondition {
+
+ public default Duration waitTimeout() {
+ return Duration.ofSeconds(5);
+ }
+
+ public default Duration waitInterval() {
+ return Duration.ofMillis(100);
+ }
+
+ public default boolean waitFor(Supplier<Boolean> condition) {
+ Stopwatch stopwatch = Stopwatch.createStarted();
+ try {
+ Duration maxWait = waitTimeout();
+ Duration sleep = waitInterval();
+ boolean conditionSucceeded = condition.get();
+ while (!conditionSucceeded && stopwatch.elapsed().compareTo(maxWait) < 0) {
+ try {
+ Thread.sleep(sleep.toMillis());
+ } catch (InterruptedException e) {
+ }
+ conditionSucceeded = condition.get();
+ }
+ return conditionSucceeded;
+ } finally {
+ stopwatch.stop();
+ }
+ }
+}