diff --git a/changelog.html b/changelog.html
index e1cf485e1e00c3451aceb8a7a90760b7c363be74..2dc45f12fb05abfff073c281d8cfdece1dd8cbf7 100644
--- a/changelog.html
+++ b/changelog.html
@@ -79,6 +79,8 @@ Upcoming changes
(pull request #119)
Plugins can now override where jobs are executed.
+
+ Rotate the slave log files instead of deleting them.
Added a mechanism to control the XML parser behaviour
(pull request #67)
diff --git a/core/src/main/java/hudson/slaves/SlaveComputer.java b/core/src/main/java/hudson/slaves/SlaveComputer.java
index 0354d4534915fc602bf12cc6eafc05a07c7f4946..efb26660bb73ce6217f935ef4754e341742ce160 100644
--- a/core/src/main/java/hudson/slaves/SlaveComputer.java
+++ b/core/src/main/java/hudson/slaves/SlaveComputer.java
@@ -24,6 +24,7 @@
package hudson.slaves;
import hudson.model.*;
+import hudson.util.io.ReopenableRotatingFileOutputStream;
import jenkins.model.Jenkins.MasterComputer;
import hudson.remoting.Channel;
import hudson.remoting.VirtualChannel;
@@ -115,7 +116,7 @@ public class SlaveComputer extends Computer {
public SlaveComputer(Slave slave) {
super(slave);
- this.log = new ReopenableFileOutputStream(getLogFile());
+ this.log = new ReopenableRotatingFileOutputStream(getLogFile(),10);
this.taskListener = new StreamTaskListener(log);
}
diff --git a/core/src/main/java/hudson/util/io/ReopenableFileOutputStream.java b/core/src/main/java/hudson/util/io/ReopenableFileOutputStream.java
index 27d53bcfa691278ef614e6d69ba0db84bcd01e2c..9a68052653d6011851e9789e186c29ab4a2a501f 100644
--- a/core/src/main/java/hudson/util/io/ReopenableFileOutputStream.java
+++ b/core/src/main/java/hudson/util/io/ReopenableFileOutputStream.java
@@ -41,7 +41,7 @@ import java.io.OutputStream;
* @author Kohsuke Kawaguchi
*/
public class ReopenableFileOutputStream extends OutputStream {
- private final File out;
+ protected final File out;
private OutputStream current;
private boolean appendOnNextOpen = false;
diff --git a/core/src/main/java/hudson/util/io/ReopenableRotatingFileOutputStream.java b/core/src/main/java/hudson/util/io/ReopenableRotatingFileOutputStream.java
new file mode 100644
index 0000000000000000000000000000000000000000..3fecde3ea1bbb1c9795428d1d1db176c60648b09
--- /dev/null
+++ b/core/src/main/java/hudson/util/io/ReopenableRotatingFileOutputStream.java
@@ -0,0 +1,70 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2011, CloudBees, Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+package hudson.util.io;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * {@link ReopenableFileOutputStream} that does log rotation upon rewind.
+ *
+ * @author Kohsuke Kawaguchi
+ * @since 1.416
+ */
+public class ReopenableRotatingFileOutputStream extends ReopenableFileOutputStream {
+ /**
+ * Number of log files to keep.
+ */
+ private final int size;
+
+ public ReopenableRotatingFileOutputStream(File out, int size) {
+ super(out);
+ this.size = size;
+ }
+
+ protected File getNumberedFileName(int n) {
+ if (n==0) return out;
+ return new File(out.getPath()+"."+n);
+ }
+
+ @Override
+ public void rewind() throws IOException {
+ super.rewind();
+ for (int i=size-1;i>=0;i--) {
+ File fi = getNumberedFileName(i);
+ if (fi.exists()) {
+ fi.renameTo(getNumberedFileName(i+1));
+ }
+ }
+ }
+
+ /**
+ * Deletes all the log files, including rotated files.
+ */
+ public void deleteAll() {
+ for (int i=0; i<=size; i++) {
+ getNumberedFileName(i).delete();
+ }
+ }
+}
diff --git a/core/src/test/java/hudson/util/io/ReopenableRotatingFileOutputStreamTest.java b/core/src/test/java/hudson/util/io/ReopenableRotatingFileOutputStreamTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..3cbebc60acc9a170ea95521b64ccd498d3b12dab
--- /dev/null
+++ b/core/src/test/java/hudson/util/io/ReopenableRotatingFileOutputStreamTest.java
@@ -0,0 +1,32 @@
+package hudson.util.io;
+
+import hudson.FilePath;
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+/**
+ * @author Kohsuke Kawaguchi
+ */
+public class ReopenableRotatingFileOutputStreamTest extends TestCase {
+ public void testRotation() throws IOException {
+ File base = File.createTempFile("test", "log");
+ ReopenableRotatingFileOutputStream os = new ReopenableRotatingFileOutputStream(base,3);
+ PrintWriter w = new PrintWriter(os,true);
+ for (int i=0; i<=4; i++) {
+ w.println("Content"+i);
+ os.rewind();
+ }
+ w.println("Content5");
+
+ assertEquals("Content5", new FilePath(base).readToString().trim());
+ assertEquals("Content4", new FilePath(new File(base.getPath() + ".1")).readToString().trim());
+ assertEquals("Content3", new FilePath(new File(base.getPath() + ".2")).readToString().trim());
+ assertEquals("Content2", new FilePath(new File(base.getPath() + ".3")).readToString().trim());
+ assertFalse(new File(base.getPath() + ".4").exists());
+
+ os.deleteAll();
+ }
+}