Skip to content

Commit

Permalink
[FIXED JENKINS-18274] LogRecorder.getSlaveLogRecords
Browse files Browse the repository at this point in the history
(cherry picked from commit d10fbc5)
  • Loading branch information
jglick authored and olivergondza committed Sep 25, 2013
1 parent e94e687 commit 76163e2
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 2 deletions.
105 changes: 104 additions & 1 deletion core/src/main/java/hudson/logging/LogRecorder.java
Expand Up @@ -25,12 +25,20 @@

import com.thoughtworks.xstream.XStream;
import hudson.BulkChange;
import hudson.Extension;
import hudson.FilePath;
import hudson.Util;
import hudson.XmlFile;
import hudson.model.AbstractModelObject;
import hudson.model.Computer;
import jenkins.model.Jenkins;
import hudson.model.Saveable;
import hudson.model.TaskListener;
import hudson.model.listeners.SaveableListener;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.VirtualChannel;
import hudson.slaves.ComputerListener;
import hudson.util.CopyOnWriteList;
import hudson.util.RingBufferLogHandler;
import hudson.util.XStream2;
Expand All @@ -43,11 +51,20 @@
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.text.Collator;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

/**
* Records a selected set of logs so that the system administrator
Expand Down Expand Up @@ -133,6 +150,54 @@ public void enable() {
Logger l = getLogger();
if(!l.isLoggable(getLevel()))
l.setLevel(getLevel());
new SetLevel(name, getLevel()).broadcast();
}

public void disable() {
getLogger().setLevel(null);
new SetLevel(name, null).broadcast();
}

}

private static final class SetLevel implements Callable<Void,Error> {
/** known loggers (kept per slave), to avoid GC */
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private static final Set<Logger> loggers = new HashSet<Logger>();
private final String name;
private final Level level;
SetLevel(String name, Level level) {
this.name = name;
this.level = level;
}
@Override public Void call() throws Error {
Logger logger = Logger.getLogger(name);
loggers.add(logger);
logger.setLevel(level);
return null;
}
void broadcast() {
for (Computer c : Jenkins.getInstance().getComputers()) {
if (c.getName().length() > 0) { // i.e. not master
VirtualChannel ch = c.getChannel();
if (ch != null) {
try {
ch.call(this);
} catch (Exception x) {
Logger.getLogger(LogRecorder.class.getName()).log(Level.WARNING, "could not set up logging on " + c, x);
}
}
}
}
}
}

@Extension @Restricted(NoExternalUse.class) public static final class ComputerLogInitializer extends ComputerListener {
@Override public void preOnline(Computer c, Channel channel, FilePath root, TaskListener listener) throws IOException, InterruptedException {
for (LogRecorder recorder : Jenkins.getInstance().getLog().logRecorders.values()) {
for (Target t : recorder.targets) {
channel.call(new SetLevel(t.name, t.getLevel()));
}
}
}
}

Expand Down Expand Up @@ -216,7 +281,7 @@ public synchronized void doDoDelete(StaplerResponse rsp) throws IOException, Ser
// Disable logging for all our targets,
// then reenable all other loggers in case any also log the same targets
for (Target t : targets)
t.getLogger().setLevel(null);
t.disable();
for (LogRecorder log : getParent().logRecorders.values())
for (Target t : log.targets)
t.enable();
Expand Down Expand Up @@ -244,6 +309,44 @@ public List<LogRecord> getLogRecords() {
return handler.getView();
}

/**
* Gets a view of log records per slave matching this recorder.
* @return a map (sorted by display name) from computer to (nonempty) list of log records
* @since 1.519
*/
public Map<Computer,List<LogRecord>> getSlaveLogRecords() {
Map<Computer,List<LogRecord>> result = new TreeMap<Computer,List<LogRecord>>(new Comparator<Computer>() {
final Collator COLL = Collator.getInstance();
public int compare(Computer c1, Computer c2) {
return COLL.compare(c1.getDisplayName(), c2.getDisplayName());
}
});
for (Computer c : Jenkins.getInstance().getComputers()) {
if (c.getName().length() == 0) {
continue; // master
}
List<LogRecord> recs = new ArrayList<LogRecord>();
try {
for (LogRecord rec : c.getLogRecords()) {
for (Target t : targets) {
if (t.includes(rec)) {
recs.add(rec);
break;
}
}
}
} catch (IOException x) {
continue;
} catch (InterruptedException x) {
continue;
}
if (!recs.isEmpty()) {
result.put(c, recs);
}
}
return result;
}

/**
* Thread-safe reusable {@link XStream}.
*/
Expand Down
Expand Up @@ -34,6 +34,12 @@ THE SOFTWARE.
<j:forEach var="log" items="${it.logRecords}">
<pre>${h.printLogRecord(log)}</pre>
</j:forEach>
<j:forEach var="entry" items="${it.slaveLogRecords.entrySet()}">
<h2><a href="${rootURL}/${entry.key.url}" class="model-link">${entry.key.displayName}</a></h2>
<j:forEach var="log" items="${entry.value}">
<pre>${h.printLogRecord(log)}</pre>
</j:forEach>
</j:forEach>

<st:include it="${it.parent}" page="feeds.jelly" />
</l:main-panel>
Expand Down
9 changes: 9 additions & 0 deletions core/src/main/resources/hudson/slaves/SlaveComputer/log.jelly
Expand Up @@ -33,6 +33,15 @@ THE SOFTWARE.
<img src="${imagesURL}/spinner.gif" alt=""/>
</div>
<t:progressiveText href="logText/progressiveHtml" idref="out" spinner="spinner" />
<!-- TODO dubious value: INFO+ shown in logText anyway; FINE- configured in /log/*/ maybe better viewed there
<j:set var="logRecords" value="${it.logRecords}"/>
<j:if test="${!logRecords.isEmpty()}">
<h1>${%Log Records}</h1>
<j:forEach var="log" items="${logRecords}">
<pre>${h.printLogRecord(log)}</pre>
</j:forEach>
</j:if>
-->
</l:hasPermission>
</l:main-panel>
</l:layout>
Expand Down
79 changes: 78 additions & 1 deletion test/src/test/java/hudson/logging/LogRecorderManagerTest.java
Expand Up @@ -26,12 +26,18 @@
import org.jvnet.hudson.test.Url;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import hudson.model.Computer;
import hudson.remoting.Callable;
import hudson.remoting.VirtualChannel;
import java.util.List;

import java.util.logging.Logger;
import java.util.logging.Level;
import static org.junit.Assert.assertEquals;
import java.util.logging.LogRecord;
import static org.junit.Assert.*;
import org.junit.Rule;
import org.junit.Test;
import org.jvnet.hudson.test.Bug;
import org.jvnet.hudson.test.JenkinsRule;

/**
Expand All @@ -56,4 +62,75 @@ public class LogRecorderManagerTest {

assertEquals(logger.getLevel(), Level.FINEST);
}

@Bug(18274)
@Test public void loggingOnSlaves() throws Exception {
// TODO could also go through WebClient to assert that the config UI works
LogRecorderManager mgr = j.jenkins.getLog();
LogRecorder r1 = new LogRecorder("r1");
mgr.logRecorders.put("r1", r1);
LogRecorder.Target t = new LogRecorder.Target("ns1", Level.FINE);
r1.targets.add(t);
r1.save();
t.enable();
Computer c = j.createOnlineSlave().toComputer();
assertNotNull(c);
t = new LogRecorder.Target("ns2", Level.FINER);
r1.targets.add(t);
r1.save();
t.enable();
LogRecorder r2 = new LogRecorder("r2");
mgr.logRecorders.put("r2", r2);
t = new LogRecorder.Target("ns3", Level.FINE);
r2.targets.add(t);
r2.save();
t.enable();
VirtualChannel ch = c.getChannel();
assertNotNull(ch);
assertTrue(ch.call(new Log(Level.FINE, "ns1", "msg #1")));
assertTrue(ch.call(new Log(Level.FINER, "ns2", "msg #2")));
assertTrue(ch.call(new Log(Level.FINE, "ns3", "msg #3")));
assertFalse(ch.call(new Log(Level.FINER, "ns3", "not displayed")));
assertTrue(ch.call(new Log(Level.INFO, "ns4", "msg #4")));
assertFalse(ch.call(new Log(Level.FINE, "ns4", "not displayed")));
List<LogRecord> recs = c.getLogRecords();
assertEquals(show(recs), 4, recs.size());
recs = r1.getSlaveLogRecords().get(c);
assertNotNull(recs);
assertEquals(show(recs), 2, recs.size());
recs = r2.getSlaveLogRecords().get(c);
assertNotNull(recs);
assertEquals(show(recs), 1, recs.size());
String text = j.createWebClient().goTo("log/r1/").asText();
assertTrue(text, text.contains(c.getDisplayName()));
assertTrue(text, text.contains("msg #1"));
assertTrue(text, text.contains("msg #2"));
assertFalse(text, text.contains("msg #3"));
assertFalse(text, text.contains("msg #4"));
}

private static final class Log implements Callable<Boolean,Error> {
private final Level level;
private final String logger;
private final String message;
Log(Level level, String logger, String message) {
this.level = level;
this.logger = logger;
this.message = message;
}
@Override public Boolean call() throws Error {
Logger log = Logger.getLogger(logger);
log.log(level, message);
return log.isLoggable(level);
}
}

private static String show(List<LogRecord> recs) {
StringBuilder b = new StringBuilder();
for (LogRecord rec : recs) {
b.append('\n').append(rec.getLoggerName()).append(':').append(rec.getLevel()).append(':').append(rec.getMessage());
}
return b.toString();
}

}

0 comments on commit 76163e2

Please sign in to comment.