diff --git a/core/src/main/java/hudson/logging/LogRecorder.java b/core/src/main/java/hudson/logging/LogRecorder.java index 918724bbb4..43b4cb4562 100644 --- a/core/src/main/java/hudson/logging/LogRecorder.java +++ b/core/src/main/java/hudson/logging/LogRecorder.java @@ -84,14 +84,38 @@ public class LogRecorder extends AbstractModelObject implements Saveable { public final CopyOnWriteList targets = new CopyOnWriteList(); - private transient /*almost final*/ RingBufferLogHandler handler = new RingBufferLogHandler() { + @Restricted(NoExternalUse.class) + Target[] orderedTargets() { + // will contain targets ordered by reverse name length (place specific targets at the beginning) + Target[] ts = targets.toArray(new Target[]{}); + + Arrays.sort(ts, new Comparator() { + public int compare(Target left, Target right) { + return right.getName().length() - left.getName().length(); + } + }); + + return ts; + } + + @Restricted(NoExternalUse.class) + transient /*almost final*/ RingBufferLogHandler handler = new RingBufferLogHandler() { @Override public void publish(LogRecord record) { - for (Target t : targets) { - if(t.includes(record)) { - super.publish(record); - return; + for (Target t : orderedTargets()) { + Boolean match = t.matches(record); + if (match == null) { + // domain does not match, so continue looking + continue; } + + if (match.booleanValue()) { + // most specific logger matches, so publish + super.publish(record); + } + // most specific logger does not match, so don't publish + // allows reducing log level for more specific loggers + return; } } }; @@ -123,6 +147,11 @@ public class LogRecorder extends AbstractModelObject implements Saveable { return Level.parse(String.valueOf(level)); } + public String getName() { + return name; + } + + @Deprecated public boolean includes(LogRecord r) { if(r.getLevel().intValue() < level) return false; // below the threshold @@ -136,6 +165,21 @@ public class LogRecorder extends AbstractModelObject implements Saveable { return rest.startsWith(".") || rest.length()==0; } + public Boolean matches(LogRecord r) { + boolean levelSufficient = r.getLevel().intValue() >= level; + if (name.length() == 0) { + return Boolean.valueOf(levelSufficient); // include if level matches + } + String logName = r.getLoggerName(); + if(logName==null || !logName.startsWith(name)) + return null; // not in the domain of this logger + String rest = logName.substring(name.length()); + if (rest.startsWith(".") || rest.length()==0) { + return Boolean.valueOf(levelSufficient); // include if level matches + } + return null; + } + public Logger getLogger() { if (logger == null) { logger = Logger.getLogger(name); diff --git a/core/src/test/java/hudson/logging/LogRecorderTest.java b/core/src/test/java/hudson/logging/LogRecorderTest.java index 5a9043e6d2..7d8e0a02f4 100644 --- a/core/src/test/java/hudson/logging/LogRecorderTest.java +++ b/core/src/test/java/hudson/logging/LogRecorderTest.java @@ -42,10 +42,72 @@ public class LogRecorderTest { assertTrue(includes("", "hudson.model.Hudson")); } - private static boolean includes(String target, String logger) { - LogRecord r = new LogRecord(Level.INFO, "whatever"); + @Test public void targetMatches() { + assertTrue(matches("hudson", "hudson")); + assertFalse(matches("hudson", "hudson", Level.FINE)); + assertNull(matches("hudson", "hudsone")); + assertNull(matches("hudson", "hudso")); + assertTrue(matches("hudson", "hudson.model.Hudson")); + assertFalse(matches("hudson", "hudson.model.Hudson", Level.FINE)); + assertNull(matches("hudson", "jenkins.model.Jenkins")); + assertTrue(matches("", "hudson.model.Hudson")); + assertFalse(matches("", "hudson.model.Hudson", Level.FINE)); + } + + @Test public void testSpecificExclusion() { + LogRecorder lr = new LogRecorder("foo"); + + LogRecorder.Target targetLevel0 = new LogRecorder.Target("", Level.FINE); + LogRecorder.Target targetLevel1 = new LogRecorder.Target("foo", Level.INFO); + LogRecorder.Target targetLevel2 = new LogRecorder.Target("foo.bar", Level.SEVERE); + + lr.targets.add(targetLevel1); + lr.targets.add(targetLevel2); + lr.targets.add(targetLevel0); + + assertEquals(lr.orderedTargets()[0], targetLevel2); + assertEquals(lr.orderedTargets()[1], targetLevel1); + assertEquals(lr.orderedTargets()[2], targetLevel0); + + LogRecord r1 = createLogRecord("baz", Level.INFO, "visible"); + LogRecord r2 = createLogRecord("foo", Level.FINE, "hidden"); + LogRecord r3 = createLogRecord("foo.bar", Level.INFO, "hidden"); + LogRecord r4 = createLogRecord("foo.bar.baz", Level.INFO, "hidden"); + LogRecord r5 = createLogRecord("foo.bar.baz", Level.SEVERE, "visible"); + LogRecord r6 = createLogRecord("foo", Level.INFO, "visible"); + lr.handler.publish(r1); + lr.handler.publish(r2); + lr.handler.publish(r3); + lr.handler.publish(r4); + lr.handler.publish(r5); + lr.handler.publish(r6); + + assertTrue(lr.handler.getView().contains(r1)); + assertFalse(lr.handler.getView().contains(r2)); + assertFalse(lr.handler.getView().contains(r3)); + assertFalse(lr.handler.getView().contains(r4)); + assertTrue(lr.handler.getView().contains(r5)); + assertTrue(lr.handler.getView().contains(r6)); + } + + private static LogRecord createLogRecord(String logger, Level level, String message) { + LogRecord r = new LogRecord(level, message); r.setLoggerName(logger); + return r; + } + + private static boolean includes(String target, String logger) { + LogRecord r = createLogRecord(logger, Level.INFO, "whatever"); return new LogRecorder.Target(target, Level.INFO).includes(r); } + private static Boolean matches(String target, String logger) { + return matches(target, logger, Level.INFO); + } + + private static Boolean matches(String target, String logger, Level loggerLevel) { + LogRecord r = createLogRecord(logger, loggerLevel, "whatever"); + return new LogRecorder.Target(target, Level.INFO).matches(r); + } + } diff --git a/war/src/main/webapp/help/LogRecorder/logger.html b/war/src/main/webapp/help/LogRecorder/logger.html index 5ac6374b47..bf290086d2 100644 --- a/war/src/main/webapp/help/LogRecorder/logger.html +++ b/war/src/main/webapp/help/LogRecorder/logger.html @@ -3,11 +3,11 @@ this log recorder will collect. For example, if you specify hudson.model.Hudson and info, any logs recorded for hudson.model.Hudson or its descendants with the log level - info or above will show up in this log recorder. + info or above will show up in this log recorder, unless a more specific + logger with higher log level is also specific.

- If you have multiple entries, a log entry that matches any of them - will be collected. + If you have multiple entries, a log entry will the most specific one will be collected.

Also see