Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions doc/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ public void read(String xml) throws XMLStreamException {
case "label":
label = attr.getValue();
break;
default:
break;
}
}
break;
Expand Down
7 changes: 7 additions & 0 deletions splunk-devops/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,13 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.kohsuke.stapler</groupId>
<artifactId>stapler</artifactId>
<version>1.259</version>
<scope>compile</scope>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -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<CustomLoggerItem> {
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();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this depend on LogRecorderManager, which adds a https://github.com/jenkinsci/jenkins/blob/75b4b31efd3f3340805497b6b23ff4b0cb32d2f3/core/src/main/java/hudson/util/RingBufferLogHandler.java automatically, that means if you want to sent to splunk verbose level log, it has to be logged to RingBufferLogHandler too, unnecessary performance burden.


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<CustomLoggerItem> getDescriptor() {
return Jenkins.getInstance().getDescriptor(getClass());
}


@Extension
public static final class DescriptorImpl extends Descriptor<CustomLoggerItem> {
@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;
}
}
}
Original file line number Diff line number Diff line change
@@ -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<CustomLoggerItem> 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<CustomLoggerItem> customLoggers) {
if (customLoggers == null) {
this.customLoggers = Collections.emptyList();
} else {
this.customLoggers = customLoggers;
}
this.save();
}

public List<CustomLoggerItem> getCustomLoggers() {
return customLoggers;
}

@DataBoundSetter
public void setCustomLoggers(List<CustomLoggerItem> 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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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()) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Existing logic https://github.com/jenkinsci/splunk-devops-plugin/blob/master/splunk-devops/src/main/java/com/splunk/splunkjenkins/LoggingInitStep.java#L40
Logger.getLogger(rootLoggerName).addHandler(JdkSplunkLogHandler.LogHolder.LOG_HANDLER); registers to root logger, that means every log record will loop the config items. Assume you have 10 targets configured, and 1000 log records per second, that means 10000 additional checks even if the logger is not in the config list

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 {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<table width="100%">
<f:entry title="${%CustomLoggerName}" field="customLoggerName">
<f:select />
</f:entry>
<f:entry>
<div align="right">
<f:repeatableDeleteButton />
</div>
</f:entry>
</table>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:section title="Splunk Custom Loggers Configuration">
<f:entry title="Custom Log Recorders to Send to Splunk">
<f:repeatable field="customLoggers" minimum="0" default="">
<st:include page="config.jelly" class="${descriptor.clazz}" />
</f:repeatable>
</f:entry>
</f:section>
</j:jelly>
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,8 @@ public void publish() throws Exception {
verifySplunkSearchResult(message + " level=INFO", 1);
}

@Test
public void publishCustomLogger() throws Exception {

}
}