diff --git a/doc/CHANGELOG.md b/doc/CHANGELOG.md index f7cad216..5bad98aa 100644 --- a/doc/CHANGELOG.md +++ b/doc/CHANGELOG.md @@ -1,3 +1,5 @@ +### 1.10.1 (July 11, 2022) +- Add CustomLoggersConfig global configuration that allows for specificying custom loggers to always send to Splunk regardless of log level, thanks to [Pierson Yieh](https://github.com/pyieh) ### 1.10.0 (July 4, 2022) - SECURITY-2128 plugin logs unmasked credentials, thanks to [Pierson Yieh](https://github.com/pyieh) - JENKINS-68775 pipeline build step is considered a stage in splunk plugin, thanks to [Guilherme Mota](https://github.com/guilhermemotadock) diff --git a/splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/console/ConsoleNoteHandler.java b/splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/console/ConsoleNoteHandler.java index bf5363a6..255cc1a2 100644 --- a/splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/console/ConsoleNoteHandler.java +++ b/splunk-devops-extend/src/main/java/com/splunk/splunkjenkins/console/ConsoleNoteHandler.java @@ -83,6 +83,8 @@ public void read(String xml) throws XMLStreamException { case "label": label = attr.getValue(); break; + default: + break; } } break; diff --git a/splunk-devops/pom.xml b/splunk-devops/pom.xml index 0dea4248..d60fb240 100644 --- a/splunk-devops/pom.xml +++ b/splunk-devops/pom.xml @@ -223,6 +223,13 @@ + + org.kohsuke.stapler + stapler + 1.259 + compile + + diff --git a/splunk-devops/src/main/java/com/splunk/splunkjenkins/CustomLoggerItem.java b/splunk-devops/src/main/java/com/splunk/splunkjenkins/CustomLoggerItem.java new file mode 100644 index 00000000..7face5b1 --- /dev/null +++ b/splunk-devops/src/main/java/com/splunk/splunkjenkins/CustomLoggerItem.java @@ -0,0 +1,80 @@ +package com.splunk.splunkjenkins; + +import hudson.Extension; +import hudson.logging.LogRecorder; +import hudson.logging.LogRecorderManager; +import hudson.model.Describable; +import hudson.model.Descriptor; +import hudson.util.FormValidation; +import hudson.util.ListBoxModel; +import jenkins.model.Jenkins; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.QueryParameter; + +public class CustomLoggerItem implements Describable { + private static final java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(CustomLoggerItem.class.getName()); + + private static transient final LogRecorderManager logRecorderManager = Jenkins.getInstance().getLog(); + + String customLoggerName; + LogRecorder logRecorder; + + @DataBoundConstructor + public CustomLoggerItem(String customLoggerName) { + this.customLoggerName = customLoggerName; + logRecorder = logRecorderManager.getLogRecorder(customLoggerName); + if (logRecorder == null) { + LOG.warning("CustomLoggerItem created for non-existent custom logger with name: " + customLoggerName); + } + } + + public String getCustomLoggerName() { + return customLoggerName; + } + + public void setCustomLoggerName(String customLoggerName) { + this.customLoggerName = customLoggerName; + } + + public LogRecorder getLogRecorder() { + return logRecorder; + } + + public void setLogRecorder(LogRecorder logRecorder) { + this.logRecorder = logRecorder; + } + + public String toString() { + return "CustomLoggerName: " + customLoggerName; + } + + public Descriptor getDescriptor() { + return Jenkins.getInstance().getDescriptor(getClass()); + } + + + @Extension + public static final class DescriptorImpl extends Descriptor { + @Override + public String getDisplayName() { + return "CustomLoggerItem"; + } + + public FormValidation doCheckName(@QueryParameter String value) { + if (value == null || value.isEmpty()) { + return FormValidation.error("Name cannot be empty"); + } else { + return FormValidation.ok(); + } + } + + public static ListBoxModel doFillCustomLoggerNameItems() { + ListBoxModel items = new ListBoxModel(); + + for (LogRecorder r : logRecorderManager.getRecorders()) { + items.add(r.getName(), r.getDisplayName()); + } + return items; + } + } +} diff --git a/splunk-devops/src/main/java/com/splunk/splunkjenkins/CustomLoggersConfig.java b/splunk-devops/src/main/java/com/splunk/splunkjenkins/CustomLoggersConfig.java new file mode 100644 index 00000000..76b36ba2 --- /dev/null +++ b/splunk-devops/src/main/java/com/splunk/splunkjenkins/CustomLoggersConfig.java @@ -0,0 +1,66 @@ +package com.splunk.splunkjenkins; + +import hudson.Extension; +import jenkins.model.GlobalConfiguration; +import jenkins.model.Jenkins; +import net.sf.json.JSONArray; +import net.sf.json.JSONObject; +import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.DataBoundSetter; +import org.kohsuke.stapler.StaplerRequest; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +@Extension +public class CustomLoggersConfig extends GlobalConfiguration { + private static final java.util.logging.Logger LOG = java.util.logging.Logger.getLogger(CustomLoggersConfig.class.getName()); + + List customLoggers; + + public static CustomLoggersConfig get() { + return (CustomLoggersConfig) Jenkins.getInstance().getDescriptor(CustomLoggersConfig.class); + } + + public CustomLoggersConfig() { + super.load(); + if (customLoggers == null) { + this.customLoggers = new ArrayList<>(); + } + } + + @DataBoundConstructor + public CustomLoggersConfig(List customLoggers) { + if (customLoggers == null) { + this.customLoggers = Collections.emptyList(); + } else { + this.customLoggers = customLoggers; + } + this.save(); + } + + public List getCustomLoggers() { + return customLoggers; + } + + @DataBoundSetter + public void setCustomLoggers(List customLoggers) { + this.customLoggers = customLoggers; + } + + public void addCustomLogger(CustomLoggerItem customLoggerItem) { + this.customLoggers.add(customLoggerItem); + this.save(); + } + + @Override + public boolean configure(StaplerRequest req, JSONObject json) throws FormException { + if (!json.has("customLoggers")) { + json.put("customLoggers", new JSONArray()); + } + req.bindJSON(this, json); + save(); + return true; + } +} diff --git a/splunk-devops/src/main/java/com/splunk/splunkjenkins/JdkSplunkLogHandler.java b/splunk-devops/src/main/java/com/splunk/splunkjenkins/JdkSplunkLogHandler.java index 51964d46..d722febc 100644 --- a/splunk-devops/src/main/java/com/splunk/splunkjenkins/JdkSplunkLogHandler.java +++ b/splunk-devops/src/main/java/com/splunk/splunkjenkins/JdkSplunkLogHandler.java @@ -4,17 +4,25 @@ import com.splunk.splunkjenkins.model.EventType; import com.splunk.splunkjenkins.utils.LogConsumer; import com.splunk.splunkjenkins.utils.SplunkLogService; +import hudson.logging.LogRecorder; import hudson.model.Computer; import jenkins.model.Jenkins; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; -import java.util.logging.*; +import java.util.logging.Filter; import java.util.logging.Formatter; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; import static com.splunk.splunkjenkins.Constants.JDK_FINE_LOG_BATCH; @@ -77,6 +85,28 @@ public void flush() { SplunkLogService.getInstance().sendBatch(copyList, EventType.LOG); } + @Override + public boolean isLoggable(LogRecord record) { + if (!record.getLoggerName().contains(JdkSplunkLogHandler.class.getName()) + && !record.getLoggerName().contains(CustomLoggersConfig.class.getName())) { // ignores self references + CustomLoggersConfig clConfig = CustomLoggersConfig.get(); + if (clConfig != null) { + for (CustomLoggerItem clItem : clConfig.getCustomLoggers()) { + LogRecorder logRecorder = clItem.getLogRecorder(); + if (logRecorder != null && logRecorder.getLoggers() != null) { + for (LogRecorder.Target target : logRecorder.getLoggers()) { + if (Boolean.TRUE.equals(target.matches(record))) { + return true; + } + } + } + } + } + } + return super.isLoggable(record); + } + + @Override public void close() throws SecurityException { diff --git a/splunk-devops/src/main/resources/com/splunk/splunkjenkins/CustomLoggerItem/config.jelly b/splunk-devops/src/main/resources/com/splunk/splunkjenkins/CustomLoggerItem/config.jelly new file mode 100644 index 00000000..4bbde769 --- /dev/null +++ b/splunk-devops/src/main/resources/com/splunk/splunkjenkins/CustomLoggerItem/config.jelly @@ -0,0 +1,14 @@ + + + + + + + +
+ +
+
+
+
diff --git a/splunk-devops/src/main/resources/com/splunk/splunkjenkins/CustomLoggersConfig/config.jelly b/splunk-devops/src/main/resources/com/splunk/splunkjenkins/CustomLoggersConfig/config.jelly new file mode 100644 index 00000000..467000fb --- /dev/null +++ b/splunk-devops/src/main/resources/com/splunk/splunkjenkins/CustomLoggersConfig/config.jelly @@ -0,0 +1,11 @@ + + + + + + + + + + diff --git a/splunk-devops/src/test/java/com/splunk/splunkjenkins/JdkSplunkLogHandlerTest.java b/splunk-devops/src/test/java/com/splunk/splunkjenkins/JdkSplunkLogHandlerTest.java index 03a0f319..79e86368 100644 --- a/splunk-devops/src/test/java/com/splunk/splunkjenkins/JdkSplunkLogHandlerTest.java +++ b/splunk-devops/src/test/java/com/splunk/splunkjenkins/JdkSplunkLogHandlerTest.java @@ -36,4 +36,8 @@ public void publish() throws Exception { verifySplunkSearchResult(message + " level=INFO", 1); } + @Test + public void publishCustomLogger() throws Exception { + + } } \ No newline at end of file