Merge branch 'stable-2.15'
* stable-2.15:
Allow storing audits into a separate file
Extract audit configuration into a separate class
Rename 'renderer' to 'format'
Move CSV-specific classes into CSV-renderer
Support audit events rendered as JSON string
Extract Guice Module into a new file
Extract the object o string rendering engine
Extract AuditWriter and make the plugin testable
Fix audit object class to RpcAuditEvent
Change-Id: I6105cc65622721bf688340b4ac706577732b7ff9
diff --git a/BUILD b/BUILD
index f75e83a..2dec1e5 100644
--- a/BUILD
+++ b/BUILD
@@ -12,9 +12,32 @@
manifest_entries = [
"Gerrit-PluginName: audit-sl4j",
"Gerrit-ReloadMode: reload",
+ "Gerrit-Module: com.googlesource.gerrit.plugins.auditsl4j.Module",
"Implementation-Title: Gerrit Audit provider for SLF4J",
"Implementation-URL: https://gerrit.googlesource.com/plugins/audit-sl4j/",
],
resources = glob(["src/main/resources/**/*"]),
deps = [ ],
)
+
+junit_tests(
+ name = "audit_sl4j_tests",
+ 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/AuditConfig.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditConfig.java
new file mode 100644
index 0000000..291c1f3
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditConfig.java
@@ -0,0 +1,38 @@
+// 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.extensions.annotations.PluginName;
+import com.google.gerrit.server.config.PluginConfig;
+import com.google.gerrit.server.config.PluginConfigFactory;
+import com.google.inject.Inject;
+import java.util.Optional;
+
+public class AuditConfig {
+ private final PluginConfig config;
+
+ @Inject
+ public AuditConfig(@PluginName String pluginName, PluginConfigFactory configFactory) {
+ config = configFactory.getFromGerritConfig(pluginName);
+ }
+
+ public AuditFormatTypes getFormat() {
+ return config.getEnum("format", AuditFormatTypes.CSV);
+ }
+
+ public Optional<String> getLogName() {
+ return Optional.ofNullable(config.getString("logName"));
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditEventFormat.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditFormatRenderer.java
similarity index 73%
rename from src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditEventFormat.java
rename to src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditFormatRenderer.java
index 8c172b6..0d7482e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditEventFormat.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditFormatRenderer.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// 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.
@@ -16,12 +16,11 @@
import com.google.gerrit.server.audit.AuditEvent;
import com.google.gerrit.server.audit.SshAuditEvent;
+import java.util.Optional;
-public class AuditEventFormat implements AuditFormatter<SshAuditEvent> {
- public static final Class<?> CLASS = AuditEvent.class;
+public interface AuditFormatRenderer {
- @Override
- public String format(SshAuditEvent result) {
- return "";
- }
+ String render(AuditEvent auditEvent);
+
+ Optional<String> headers();
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditFormatter.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditFormatTypes.java
similarity index 83%
rename from src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditFormatter.java
rename to src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditFormatTypes.java
index 67c2589..518cc84 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditFormatter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditFormatTypes.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// 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.
@@ -14,7 +14,7 @@
package com.googlesource.gerrit.plugins.auditsl4j;
-public interface AuditFormatter<T> {
-
- String format(T result);
+public enum AuditFormatTypes {
+ CSV,
+ JSON;
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditEventFormat.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRecord.java
similarity index 65%
copy from src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditEventFormat.java
copy to src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRecord.java
index 8c172b6..d33bd12 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditEventFormat.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRecord.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// 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.
@@ -15,13 +15,16 @@
package com.googlesource.gerrit.plugins.auditsl4j;
import com.google.gerrit.server.audit.AuditEvent;
-import com.google.gerrit.server.audit.SshAuditEvent;
-public class AuditEventFormat implements AuditFormatter<SshAuditEvent> {
- public static final Class<?> CLASS = AuditEvent.class;
+public class AuditRecord {
+ public final String type;
+ public final AuditEvent event;
- @Override
- public String format(SshAuditEvent result) {
- return "";
+ 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/AuditRendererToCsv.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToCsv.java
new file mode 100644
index 0000000..50d4b1e
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditRendererToCsv.java
@@ -0,0 +1,160 @@
+// 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.Multimap;
+import com.google.gerrit.server.audit.AuditEvent;
+import com.google.gerrit.server.audit.ExtendedHttpAuditEvent;
+import com.google.gerrit.server.audit.HttpAuditEvent;
+import com.google.gerrit.server.audit.RpcAuditEvent;
+import com.google.gerrit.server.audit.SshAuditEvent;
+import java.text.SimpleDateFormat;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+
+public class AuditRendererToCsv implements AuditFormatRenderer {
+
+ private static final SimpleDateFormat dateFmt = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss.SSSS");
+
+ @SuppressWarnings("serial")
+ private static final Map<Class<?>, CsvFieldFormatter<?>> FIELD_CSV_FORMATTERS =
+ Collections.unmodifiableMap(
+ new HashMap<Class<?>, CsvFieldFormatter<? extends Object>>() {
+ {
+ put(HttpAuditEvent.class, new HttpAuditEventFormat());
+ put(ExtendedHttpAuditEvent.class, new HttpAuditEventFormat());
+ put(RpcAuditEvent.class, new RpcAuditEventFormat());
+ put(SshAuditEvent.class, new SshAuditEventFormat());
+ put(AuditEvent.class, new AuditEventFormat());
+ }
+ });
+
+ interface CsvFieldFormatter<T> {
+ String formatToCsv(T result);
+ }
+
+ static class RpcAuditEventFormat implements CsvFieldFormatter<RpcAuditEvent> {
+ @Override
+ public String formatToCsv(RpcAuditEvent result) {
+ return "RPC-" + result.httpMethod + ", Status:" + result.httpStatus;
+ }
+ }
+
+ static class HttpAuditEventFormat implements CsvFieldFormatter<HttpAuditEvent> {
+
+ @Override
+ public String formatToCsv(HttpAuditEvent result) {
+ return "HTTP-" + result.httpMethod + ", Status:" + result.httpStatus;
+ }
+ }
+
+ static class SshAuditEventFormat implements CsvFieldFormatter<SshAuditEvent> {
+ @Override
+ public String formatToCsv(SshAuditEvent result) {
+ return "SSH";
+ }
+ }
+
+ static class AuditEventFormat implements CsvFieldFormatter<SshAuditEvent> {
+
+ @Override
+ public String formatToCsv(SshAuditEvent result) {
+ return "";
+ }
+ }
+
+ @Override
+ public String render(AuditEvent auditEvent) {
+ return String.format(
+ "%1$s | %2$s | %3$s | %4$s | %5$s | %6$s | %7$s | %8$s | %9$s | %10$s",
+ auditEvent.uuid.uuid(),
+ getFormattedTS(auditEvent.when),
+ auditEvent.sessionId,
+ getFieldAsCsv(auditEvent.who),
+ getFieldAsCsv(auditEvent),
+ auditEvent.what,
+ getFormattedAuditList(auditEvent.params),
+ getFieldAsCsv(auditEvent.result),
+ getFormattedTS(auditEvent.timeAtStart),
+ 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 "[]";
+ }
+
+ StringBuilder formattedOut = new StringBuilder("[");
+
+ Set<String> paramNames = new TreeSet<>(params.keySet());
+
+ int numParams = 0;
+ for (String paramName : paramNames) {
+ if (numParams++ > 0) {
+ formattedOut.append(",");
+ }
+ formattedOut.append(paramName);
+ formattedOut.append("=");
+ formattedOut.append(getFormattedAudit(params.get(paramName)));
+ }
+
+ formattedOut.append(']');
+
+ return formattedOut.toString();
+ }
+
+ private Object getFormattedAudit(Collection<? extends Object> values) {
+ StringBuilder out = new StringBuilder();
+ int numValues = 0;
+ for (Object object : values) {
+ if (numValues > 0) {
+ out.append(",");
+ }
+ out.append(getFieldAsCsv(object));
+ numValues++;
+ }
+
+ if (numValues > 1) {
+ return "[" + out.toString() + "]";
+ }
+ return out.toString();
+ }
+
+ public static <T> String getFieldAsCsv(T result) {
+ if (result == null) return "";
+
+ @SuppressWarnings("unchecked")
+ CsvFieldFormatter<T> fmt = (CsvFieldFormatter<T>) FIELD_CSV_FORMATTERS.get(result.getClass());
+ if (fmt == null) return result.toString();
+
+ return fmt.formatToCsv(result);
+ }
+
+ public static synchronized String getFormattedTS(long when) {
+ return dateFmt.format(new Date(when));
+ }
+}
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..ba02a53
--- /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.reviewdb.client.Account;
+import com.google.gerrit.server.AccessPath;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.OutputFormat;
+import com.google.gerrit.server.audit.AuditEvent;
+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 AuditFormatRenderer {
+ 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/AuditFormatter.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriter.java
similarity index 71%
copy from src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditFormatter.java
copy to src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriter.java
index 67c2589..1da9b2a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditFormatter.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriter.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// 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.
@@ -14,7 +14,11 @@
package com.googlesource.gerrit.plugins.auditsl4j;
-public interface AuditFormatter<T> {
+import com.google.gerrit.server.audit.HttpAuditEvent;
+import com.google.inject.ImplementedBy;
- String format(T result);
+@ImplementedBy(AuditWriterToLogger.class)
+public interface AuditWriter {
+
+ void write(String msg);
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToAsyncAppender.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToAsyncAppender.java
new file mode 100644
index 0000000..194af59
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToAsyncAppender.java
@@ -0,0 +1,55 @@
+// 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.server.util.SystemLog;
+import com.google.gerrit.server.util.time.TimeUtil;
+import com.google.inject.Singleton;
+import org.apache.log4j.AsyncAppender;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.log4j.PatternLayout;
+import org.apache.log4j.spi.LoggingEvent;
+
+@Singleton
+public class AuditWriterToAsyncAppender implements AuditWriter {
+ private final Logger log = Logger.getLogger(LoggerAudit.AUDIT_LOGGER_NAME);
+ private final AsyncAppender appender;
+
+ public AuditWriterToAsyncAppender(AuditConfig config, SystemLog systemLog) {
+ String logName = config.getLogName().get();
+ appender = systemLog.createAsyncAppender(logName, new PatternLayout());
+ }
+
+ @Override
+ public void write(String auditBody) {
+ appender.append(newLoggingEvent(auditBody));
+ }
+
+ private LoggingEvent newLoggingEvent(String auditBody) {
+ return new LoggingEvent( //
+ LoggerAudit.AUDIT_LOGGER_NAME,
+ log, // logger
+ TimeUtil.nowMs(), // when
+ Level.INFO, // level
+ auditBody, // message text
+ "HTTPD", // thread name
+ null, // exception information
+ null, // current NDC string
+ null, // caller location
+ null // MDC properties
+ );
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditEventFormat.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToLogger.java
similarity index 67%
copy from src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditEventFormat.java
copy to src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToLogger.java
index 8c172b6..98f64e2 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditEventFormat.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToLogger.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// 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.
@@ -14,14 +14,15 @@
package com.googlesource.gerrit.plugins.auditsl4j;
-import com.google.gerrit.server.audit.AuditEvent;
import com.google.gerrit.server.audit.SshAuditEvent;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
-public class AuditEventFormat implements AuditFormatter<SshAuditEvent> {
- public static final Class<?> CLASS = AuditEvent.class;
+public class AuditWriterToLogger implements AuditWriter {
+ private static final Logger log = LoggerFactory.getLogger(LoggerAudit.AUDIT_LOGGER_NAME);
@Override
- public String format(SshAuditEvent result) {
- return "";
+ public void write(String auditBody) {
+ log.info(auditBody);
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/RpcAuditEventFormat.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToStringList.java
similarity index 64%
rename from src/main/java/com/googlesource/gerrit/plugins/auditsl4j/RpcAuditEventFormat.java
rename to src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToStringList.java
index a910cf4..e612792 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/RpcAuditEventFormat.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/AuditWriterToStringList.java
@@ -1,4 +1,4 @@
-// Copyright (C) 2012 The Android Open Source Project
+// 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.
@@ -16,12 +16,21 @@
import com.google.gerrit.server.audit.HttpAuditEvent;
import com.google.gerrit.server.audit.RpcAuditEvent;
+import com.google.inject.Singleton;
+import java.util.ArrayList;
+import java.util.List;
-public class RpcAuditEventFormat implements AuditFormatter<HttpAuditEvent> {
- protected static final Class<?> CLASS = RpcAuditEvent.class;
+@Singleton
+public class AuditWriterToStringList implements AuditWriter {
+ public final List<String> strings = new ArrayList<>();
@Override
- public String format(HttpAuditEvent result) {
- return "RPC-" + result.httpMethod + ", Status:" + result.httpStatus;
+ 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/HttpAuditEventFormat.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/HttpAuditEventFormat.java
deleted file mode 100644
index 43b884a..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/HttpAuditEventFormat.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2012 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.server.audit.HttpAuditEvent;
-
-public class HttpAuditEventFormat implements AuditFormatter<HttpAuditEvent> {
- protected static final Class<?> CLASS = HttpAuditEvent.class;
-
- @Override
- public String format(HttpAuditEvent result) {
- return "HTTP-" + result.httpMethod + ", Status:" + result.httpStatus;
- }
-}
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 7ce623b..97cee43 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAudit.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAudit.java
@@ -14,117 +14,29 @@
package com.googlesource.gerrit.plugins.auditsl4j;
-import com.google.common.collect.Multimap;
-import com.google.gerrit.extensions.annotations.Listen;
import com.google.gerrit.server.audit.AuditEvent;
import com.google.gerrit.server.audit.AuditListener;
+import com.google.inject.Inject;
import com.google.inject.Singleton;
-import java.text.SimpleDateFormat;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-import java.util.TreeSet;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-@Listen
@Singleton
public class LoggerAudit implements AuditListener {
- private static final Logger log = LoggerFactory.getLogger(LoggerAudit.class);
- private final SimpleDateFormat dateFmt = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss.SSSS");
+ private final AuditWriter auditWriter;
+ private final AuditFormatRenderer auditRenderer;
- @SuppressWarnings("serial")
- private static final Map<Class<?>, AuditFormatter<?>> AUDIT_FORMATTERS =
- Collections.unmodifiableMap(
- new HashMap<Class<?>, AuditFormatter<? extends Object>>() {
- {
- put(HttpAuditEventFormat.CLASS, new HttpAuditEventFormat());
- put(RpcAuditEventFormat.CLASS, new RpcAuditEventFormat());
- put(SshAuditEventFormat.CLASS, new SshAuditEventFormat());
- put(AuditEventFormat.CLASS, new AuditEventFormat());
- }
- });
+ public static final String AUDIT_LOGGER_NAME = LoggerAudit.class.getName();
- static {
- log.info(
- "EventId | EventTS | SessionId | User | Protocol data | Action | Parameters | Result | StartTS | Elapsed");
+ @Inject
+ LoggerAudit(AuditWriter auditWriter, AuditFormatRenderer auditRenderer) {
+ this.auditWriter = auditWriter;
+ this.auditRenderer = auditRenderer;
+
+ auditRenderer.headers().ifPresent(auditWriter::write);
}
@Override
- public void onAuditableAction(AuditEvent action) {
- log.info(getFormattedAudit(action));
- }
-
- private String getFormattedAudit(AuditEvent action) {
- return String.format(
- "%1$s | %2$s | %3$s | %4$s | %5$s | %6$s | %7$s | %8$s | %9$s | %10$s",
- action.uuid.uuid(),
- getFormattedTS(action.when),
- action.sessionId,
- getFormattedAuditSingle(action.who),
- getFormattedAuditSingle(action),
- action.what,
- getFormattedAuditList(action.params),
- getFormattedAuditSingle(action.result),
- getFormattedTS(action.timeAtStart),
- action.elapsed);
- }
-
- private Object getFormattedAuditList(Multimap<String, ?> params) {
- if (params == null || params.size() == 0) {
- return "[]";
- }
-
- StringBuilder formattedOut = new StringBuilder("[");
-
- Set<String> paramNames = new TreeSet<>(params.keySet());
-
- int numParams = 0;
- for (String paramName : paramNames) {
- if (numParams++ > 0) {
- formattedOut.append(",");
- }
- formattedOut.append(paramName);
- formattedOut.append("=");
- formattedOut.append(getFormattedAudit(params.get(paramName)));
- }
-
- formattedOut.append(']');
-
- return formattedOut.toString();
- }
-
- private Object getFormattedAudit(Collection<? extends Object> values) {
- StringBuilder out = new StringBuilder();
- int numValues = 0;
- for (Object object : values) {
- if (numValues > 0) {
- out.append(",");
- }
- out.append(getFormattedAuditSingle(object));
- numValues++;
- }
-
- if (numValues > 1) {
- return "[" + out.toString() + "]";
- }
- return out.toString();
- }
-
- private <T> String getFormattedAuditSingle(T result) {
- if (result == null) return "";
-
- @SuppressWarnings("unchecked")
- AuditFormatter<T> fmt = (AuditFormatter<T>) AUDIT_FORMATTERS.get(result.getClass());
- if (fmt == null) return result.toString();
-
- return fmt.format(result);
- }
-
- private synchronized String getFormattedTS(long when) {
- return dateFmt.format(new Date(when));
+ public void onAuditableAction(AuditEvent auditEvent) {
+ String auditString = auditRenderer.render(auditEvent);
+ auditWriter.write(auditString);
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/Module.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/Module.java
new file mode 100644
index 0000000..fc11a00
--- /dev/null
+++ b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/Module.java
@@ -0,0 +1,50 @@
+// 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.extensions.registration.DynamicSet;
+import com.google.gerrit.server.audit.AuditListener;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+
+public class Module extends AbstractModule {
+ private final AuditConfig config;
+
+ @Inject
+ public Module(AuditConfig config) {
+ this.config = config;
+ }
+
+ @Override
+ protected void configure() {
+ DynamicSet.bind(binder(), AuditListener.class).to(LoggerAudit.class);
+
+ AuditFormatTypes rendererType = config.getFormat();
+ switch (rendererType) {
+ case CSV:
+ bind(AuditFormatRenderer.class).to(AuditRendererToCsv.class);
+ break;
+ case JSON:
+ bind(AuditFormatRenderer.class).to(AuditRendererToJson.class);
+ break;
+ default:
+ throw new IllegalArgumentException("Unsupported renderer '" + rendererType + "'");
+ }
+
+ if (config.getLogName().isPresent()) {
+ bind(AuditWriter.class).to(AuditWriterToAsyncAppender.class);
+ }
+ }
+}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/SshAuditEventFormat.java b/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/SshAuditEventFormat.java
deleted file mode 100644
index 1ded66b..0000000
--- a/src/main/java/com/googlesource/gerrit/plugins/auditsl4j/SshAuditEventFormat.java
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright (C) 2012 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.server.audit.SshAuditEvent;
-
-public class SshAuditEventFormat implements AuditFormatter<SshAuditEvent> {
- protected static final Class<?> CLASS = SshAuditEvent.class;
-
- @Override
- public String format(SshAuditEvent result) {
- return "SSH";
- }
-}
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
new file mode 100644
index 0000000..4ad33c2
--- /dev/null
+++ b/src/main/resources/Documentation/about.md
@@ -0,0 +1,4 @@
+This plugin can push the audit events coming from Gerrit into
+a SLF4J appender named com.googlesource.gerrit.plugins.auditsl4j.LoggerAudit.
+
+The audit event object can be saved either in CSV or JSON format.
\ No newline at end of file
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
new file mode 100644
index 0000000..a18a878
--- /dev/null
+++ b/src/main/resources/Documentation/config.md
@@ -0,0 +1,16 @@
+Audit Configuration
+===================
+
+File `gerrit.config`
+--------------------
+
+The audit configuration can be defined in the main gerrit.config
+in a specific section dedicated to the audit-sl4j plugin.
+
+gerrit.audit-sl4j.format
+: Output format of the audit record. Can be set to either JSON
+ or CSV. By default, CSV.
+
+gerrit.audit-sl4j.logName
+: Write audit to a separate log name under Gerrit logs directory.
+ By default, audit records are put into the error_log.
\ No newline at end of file
diff --git a/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditToCsvTest.java b/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditToCsvTest.java
new file mode 100644
index 0000000..de89e0d
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditToCsvTest.java
@@ -0,0 +1,62 @@
+// 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 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.common.Version;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.audit.AuditListener;
+import com.google.gerrit.server.config.CanonicalWebUrl;
+import com.google.inject.AbstractModule;
+import com.google.inject.Inject;
+import org.apache.http.client.fluent.Request;
+import org.junit.Test;
+
+@Sandboxed
+@TestPlugin(
+ name = "audit-sl4j",
+ 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 AbstractModule {
+
+ @Override
+ protected void configure() {
+ bind(AuditWriter.class).to(AuditWriterToStringList.class);
+ bind(AuditFormatRenderer.class).to(AuditRendererToCsv.class);
+ DynamicSet.bind(binder(), AuditListener.class).to(LoggerAudit.class);
+ }
+ }
+
+ @Test
+ public void testHttpCsvAudit() throws Exception {
+ AuditWriterToStringList auditStrings = getPluginInstance(AuditWriterToStringList.class);
+
+ Request.Get(webUrl + "config/server/version").execute().returnResponse();
+
+ 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/LoggerAuditToJsonTest.java b/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditToJsonTest.java
new file mode 100644
index 0000000..9d0e48c
--- /dev/null
+++ b/src/test/java/com/googlesource/gerrit/plugins/auditsl4j/LoggerAuditToJsonTest.java
@@ -0,0 +1,68 @@
+// 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 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.common.Version;
+import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.server.audit.AuditListener;
+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 org.apache.http.client.fluent.Request;
+import org.junit.Test;
+
+@Sandboxed
+@TestPlugin(
+ name = "audit-sl4j",
+ 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 AbstractModule {
+
+ @Override
+ protected void configure() {
+ bind(AuditWriter.class).to(AuditWriterToStringList.class);
+ bind(AuditFormatRenderer.class).to(AuditRendererToJson.class);
+ DynamicSet.bind(binder(), AuditListener.class).to(LoggerAudit.class);
+ }
+ }
+
+ @Test
+ public void testHttpJsonAudit() throws Exception {
+ AuditWriterToStringList auditStrings = getPluginInstance(AuditWriterToStringList.class);
+
+ Request.Get(webUrl + "config/server/version").execute().returnResponse();
+
+ 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) {
+ return plugin.getSysInjector().getInstance(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();
+ }
+ }
+}