[GIT] Logback: the generic, reliable, fast and flexible logging framework. branch, master, updated. v0.9.18-57-gfc91f41

This is an automated email from the git hooks/post-receive script. It was generated because a ref change was pushed to the repository containing the project "Logback: the generic, reliable, fast and flexible logging framework.". The branch, master has been updated via fc91f4117cfaacbd6deac9fb319a6abe07b45115 (commit) from 63475d86b35850ed5ed1ea735bf2bf38521ebbaf (commit) Those revisions listed above that are new to this repository have not appeared on any other notification email; so we list those revisions in full, below. - Log ----------------------------------------------------------------- http://git.qos.ch/gitweb/?p=logback.git;a=commit;h=fc91f4117cfaacbd6deac9fb3... http://github.com/ceki/logback/commit/fc91f4117cfaacbd6deac9fb319a6abe07b451... commit fc91f4117cfaacbd6deac9fb319a6abe07b45115 Author: Ceki Gulcu <ceki@qos.ch> Date: Wed Mar 3 23:24:02 2010 +0100 - more tests and improvements to ResilientFileOutputStream diff --git a/logback-core/src/main/java/ch/qos/logback/core/FileAppender.java b/logback-core/src/main/java/ch/qos/logback/core/FileAppender.java index 46eae83..1dfbb39 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/FileAppender.java +++ b/logback-core/src/main/java/ch/qos/logback/core/FileAppender.java @@ -162,7 +162,7 @@ public class FileAppender<E> extends OutputStreamAppender<E> { ResilientFileOutputStream resilientFos = new ResilientFileOutputStream( file, append); - + resilientFos.setContext(context); setOutputStream(resilientFos); } } diff --git a/logback-core/src/main/java/ch/qos/logback/core/net/TelnetAppender.java b/logback-core/src/main/java/ch/qos/logback/core/net/TelnetAppender.java deleted file mode 100644 index a97e000..0000000 --- a/logback-core/src/main/java/ch/qos/logback/core/net/TelnetAppender.java +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Logback: the reliable, generic, fast and flexible logging framework. - * Copyright (C) 1999-2009, QOS.ch. All rights reserved. - * - * This program and the accompanying materials are dual-licensed under - * either the terms of the Eclipse Public License v1.0 as published by - * the Eclipse Foundation - * - * or (per the licensee's choosing) - * - * under the terms of the GNU Lesser General Public License version 2.1 - * as published by the Free Software Foundation. - */ -package ch.qos.logback.core.net; - -import ch.qos.logback.core.AppenderBase; - -public class TelnetAppender extends AppenderBase { - - int port; - - @Override - public void start() { - int errorCount = 0; - if (port == 0) { - errorCount++; - addError("No port was configured for appender" - + name - + " For more information, please visit http://logback.qos.ch/codes.html#socket_no_port"); - } - - //ServerSocket serverSocket = new ServerSocket(port); - -// connect(address, port); - - if (errorCount == 0) { - this.started = true; - } - } - - @Override - protected void append(Object eventObject) { - // TODO Auto-generated method stub - - } - - - -} diff --git a/logback-core/src/main/java/ch/qos/logback/core/recovery/ResilientFileOutputStream.java b/logback-core/src/main/java/ch/qos/logback/core/recovery/ResilientFileOutputStream.java index 59839c7..95ad0d2 100644 --- a/logback-core/src/main/java/ch/qos/logback/core/recovery/ResilientFileOutputStream.java +++ b/logback-core/src/main/java/ch/qos/logback/core/recovery/ResilientFileOutputStream.java @@ -20,12 +20,21 @@ import java.io.IOException; import java.io.OutputStream; import java.nio.channels.FileChannel; +import ch.qos.logback.core.Context; +import ch.qos.logback.core.status.ErrorStatus; +import ch.qos.logback.core.status.InfoStatus; +import ch.qos.logback.core.status.Status; +import ch.qos.logback.core.status.StatusManager; + public class ResilientFileOutputStream extends OutputStream { - RecoveryCoordinator recoveryCoordinator; - boolean bufferedIO; - int bufferSize; + final static int STATUS_COUNT_LIMIT = 2 * 4; + private int noContextWarning = 0; + private int statusCount = 0; + + Context context; + RecoveryCoordinator recoveryCoordinator; FileOutputStream fos; File file; boolean presumedClean = true; @@ -43,67 +52,128 @@ public class ResilientFileOutputStream extends OutputStream { return fos.getChannel(); } - public File getFile() { return file; } - public void write(byte b[], int off, int len) throws IOException { + boolean isPresumedInError() { // existence of recoveryCoordinator indicates failed state - if (recoveryCoordinator != null && !presumedClean) { + return (recoveryCoordinator != null && !presumedClean); + } + + public void write(byte b[], int off, int len) { + if (isPresumedInError()) { if (!recoveryCoordinator.isTooSoon()) { - performRecoveryAttempt(); + attemptRecovery(); } - // we return regardless of the success of the recovery attempt - return; + return; // return regardless of the success of the recovery attempt } try { fos.write(b, off, len); postSuccessfulWrite(); } catch (IOException e) { - presumedClean = false; - recoveryCoordinator = new RecoveryCoordinator(); + postIOFailure(e); } } - private void postSuccessfulWrite() { - recoveryCoordinator = null; - } - @Override - public void close() throws IOException { - if (fos != null) { - fos.close(); - } - } - - @Override - public void write(int b) throws IOException { - // existence of recoveryCoordinator indicates failed state - if (recoveryCoordinator != null) { + public void write(int b) { + if (isPresumedInError()) { if (!recoveryCoordinator.isTooSoon()) { - performRecoveryAttempt(); + attemptRecovery(); } - // we return regardless of the success of the recovery attempt - return; + return; // return regardless of the success of the recovery attempt } try { fos.write(b); postSuccessfulWrite(); } catch (IOException e) { + postIOFailure(e); + } + } + + private void postSuccessfulWrite() { + if (recoveryCoordinator != null) { + recoveryCoordinator = null; + statusCount = 0; + addStatus(new InfoStatus("Recovered from IO failure on file [" + file + + "]", this)); + } + } + + void postIOFailure(IOException e) { + addStatusIfCountNotOverLimit(new ErrorStatus( + "IO failure while writing to [" + file + "]", this, e)); + presumedClean = false; + if (recoveryCoordinator == null) { recoveryCoordinator = new RecoveryCoordinator(); } } - void performRecoveryAttempt() throws FileNotFoundException { + @Override + public void close() throws IOException { + if (fos != null) { + fos.close(); + } + } + + void attemptRecovery() { try { close(); } catch (IOException e) { } + addStatusIfCountNotOverLimit(new InfoStatus( + "Attempting to recover from IO failure on file [" + file + "]", this)); + // subsequent writes must always be in append mode - fos = new FileOutputStream(file, true); - presumedClean = true; + try { + fos = new FileOutputStream(file, true); + presumedClean = true; + } catch (IOException e) { + addStatusIfCountNotOverLimit(new ErrorStatus("Failed to open file [" + + file + "]", this, e)); + } + } + + void addStatusIfCountNotOverLimit(Status s) { + ++statusCount; + if (statusCount < STATUS_COUNT_LIMIT) { + addStatus(s); + } + + if (statusCount == STATUS_COUNT_LIMIT) { + addStatus(s); + addStatus(new InfoStatus("Will supress future messages regarding [" + + file + "]", this)); + } + } + + public void addStatus(Status status) { + if (context == null) { + if (noContextWarning++ == 0) { + System.out.println("LOGBACK: No context given for " + this); + } + return; + } + StatusManager sm = context.getStatusManager(); + if (sm != null) { + sm.add(status); + } + } + + public Context getContext() { + return context; + } + + public void setContext(Context context) { + this.context = context; + } + + @Override + public String toString() { + return "c.q.l.c.recovery.ResilientFileOutputStream@" + + System.identityHashCode(this); } } diff --git a/logback-core/src/test/java/ch/qos/logback/core/FileAppenderResilienceTest.java b/logback-core/src/test/java/ch/qos/logback/core/FileAppenderResilienceTest.java index e4216c2..5df81f3 100644 --- a/logback-core/src/test/java/ch/qos/logback/core/FileAppenderResilienceTest.java +++ b/logback-core/src/test/java/ch/qos/logback/core/FileAppenderResilienceTest.java @@ -1,165 +1,100 @@ package ch.qos.logback.core; -import static org.junit.Assert.assertTrue; - -import java.io.BufferedReader; import java.io.File; -import java.io.FileInputStream; -import java.io.FileReader; import java.io.IOException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.nio.channels.FileChannel; -import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; +import ch.qos.logback.core.contention.RunnableWithCounterAndDone; import ch.qos.logback.core.encoder.EchoEncoder; -import ch.qos.logback.core.testUtil.Env; +import ch.qos.logback.core.recovery.ResilientFileOutputStream; +import ch.qos.logback.core.status.OnConsoleStatusListener; import ch.qos.logback.core.testUtil.RandomUtil; -import ch.qos.logback.core.util.StatusPrinter; +import ch.qos.logback.core.util.CoreTestConstants; +import ch.qos.logback.core.util.ResilienceUtil; public class FileAppenderResilienceTest { - static String MOUNT_POINT = "/mnt/loop/"; - - static String LONG_STR = " xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; - - static String PATH_LOOPFS_SCRIPT = "/home/ceki/java/logback/logback-core/src/test/loopfs.sh"; - - - enum LoopFSCommand { - setup, shake, teardown; - } - + FileAppender<Object> fa = new FileAppender<Object>(); Context context = new ContextBase(); int diff = RandomUtil.getPositiveInt(); - String outputDirStr = MOUNT_POINT + "resilience-" + diff + "/"; + String outputDirStr = CoreTestConstants.OUTPUT_DIR_PREFIX + "resilience-" + + diff + "/"; + + //String outputDirStr = "\\\\192.168.1.3\\lbtest\\" + "resilience-"+ diff + "/";; String logfileStr = outputDirStr + "output.log"; - - FileAppender<Object> fa = new FileAppender<Object>(); - static boolean isConformingHost() { - return Env.isLocalHostNameInList(new String[] {"gimmel"}); - } - @Before - public void setUp() throws IOException, InterruptedException { - if(!isConformingHost()) { - return; - } - Process p = runLoopFSScript(LoopFSCommand.setup); - p.waitFor(); - - dump("/tmp/loopfs.log"); + public void setUp() throws InterruptedException { - fa.setContext(context); + context.getStatusManager().add(new OnConsoleStatusListener()); + File outputDir = new File(outputDirStr); outputDir.mkdirs(); - System.out.println("FileAppenderResilienceTest output dir [" + outputDirStr - + "]"); + fa.setContext(context); fa.setName("FILE"); fa.setEncoder(new EchoEncoder<Object>()); fa.setFile(logfileStr); fa.start(); - } - void dump(String file) throws IOException { - FileInputStream fis = null; - try { - fis = new FileInputStream(file); - int r; - while ((r = fis.read()) != -1) { - char c = (char) r; - System.out.print(c); - } - } finally { - if (fis != null) { - fis.close(); - } - } } - - @After - public void tearDown() throws IOException, InterruptedException { - if(!isConformingHost()) { - return; + @Test + @Ignore + public void manual() throws InterruptedException, IOException { + Runner runner = new Runner(fa); + Thread t = new Thread(runner); + t.start(); + + while (true) { + Thread.sleep(110); } - StatusPrinter.print(context); - fa.stop(); - Process p = runLoopFSScript(LoopFSCommand.teardown); - p.waitFor(); - System.out.println("Tearing down"); } - static int TOTAL_DURATION = 5000; - static int NUM_STEPS = 500; - static int DELAY = TOTAL_DURATION / NUM_STEPS; @Test - public void go() throws IOException, InterruptedException { - if(!isConformingHost()) { - return; - } - Process p = runLoopFSScript(LoopFSCommand.shake); - for (int i = 0; i < NUM_STEPS; i++) { - fa.append(String.valueOf(i) + LONG_STR); - Thread.sleep(DELAY); + public void smoke() throws InterruptedException, IOException { + Runner runner = new Runner(fa); + Thread t = new Thread(runner); + t.start(); + + for (int i = 0; i < 10; i++) { + Thread.sleep(100); + ResilientFileOutputStream resilientFOS = (ResilientFileOutputStream) fa + .getOutputStream(); + FileChannel fileChannel = resilientFOS.getChannel(); + fileChannel.close(); } - p.waitFor(); - verify(logfileStr); - System.out.println("Done go"); + runner.setDone(true); + t.join(); + + ResilienceUtil + .verify(logfileStr, "^hello (\\d{1,5})$", runner.getCounter()); } +} - // the loopfs script is tightly coupled with the host machine - // it needs to be Unix, with sudo privileges granted to the script - Process runLoopFSScript(LoopFSCommand cmd) throws IOException, - InterruptedException { - // causing a NullPointerException is better than locking the whole - // machine which the next operation can and will do. - if(!isConformingHost()) { - return null; - } - ProcessBuilder pb = new ProcessBuilder(); - pb.command("/usr/bin/sudo", PATH_LOOPFS_SCRIPT, cmd.toString()); - Process process = pb.start(); - return process; +class Runner extends RunnableWithCounterAndDone { + FileAppender<Object> fa; + + Runner(FileAppender<Object> fa) { + this.fa = fa; } - void verify(String logfile) throws NumberFormatException, IOException { - FileReader fr = new FileReader(logfile); - BufferedReader br = new BufferedReader(fr); - String regExp = "^(\\d{1,3}) x*$"; - Pattern p = Pattern.compile(regExp); - String line; - - int totalLines = 0; - int oldNum = -1; - int gaps = 0; - while ((line = br.readLine()) != null) { - Matcher m = p.matcher(line); - if (m.matches()) { - totalLines++; - String g = m.group(1); - int num = Integer.parseInt(g); - if(num != oldNum+1) { - gaps++; + public void run() { + while (!isDone()) { + counter++; + fa.doAppend("hello " + counter); + if (counter % 1024 == 0) { + try { + Thread.sleep(10); + } catch (InterruptedException e) { } - oldNum = num; } } - fr.close(); - br.close(); - - // at least 40% of the logs should have been written - int lowerLimit = (int) (NUM_STEPS*0.4); - assertTrue("totalLines="+totalLines+" less than "+lowerLimit, totalLines > lowerLimit); - - // we want some gaps which indicate recuperation - assertTrue("gaps="+gaps+" less than 3", gaps > 3); - } -} +} \ No newline at end of file diff --git a/logback-core/src/test/java/ch/qos/logback/core/FileAppenderResilienceTest.java b/logback-core/src/test/java/ch/qos/logback/core/FileAppenderResilience_AS_ROOT_Test.java similarity index 73% copy from logback-core/src/test/java/ch/qos/logback/core/FileAppenderResilienceTest.java copy to logback-core/src/test/java/ch/qos/logback/core/FileAppenderResilience_AS_ROOT_Test.java index e4216c2..0ed804b 100644 --- a/logback-core/src/test/java/ch/qos/logback/core/FileAppenderResilienceTest.java +++ b/logback-core/src/test/java/ch/qos/logback/core/FileAppenderResilience_AS_ROOT_Test.java @@ -1,14 +1,21 @@ +/** + * Logback: the reliable, generic, fast and flexible logging framework. + * Copyright (C) 1999-2010, QOS.ch. All rights reserved. + * + * This program and the accompanying materials are dual-licensed under either + * the terms of the Eclipse Public License v1.0 as published by the Eclipse + * Foundation + * + * or (per the licensee's choosing) + * + * under the terms of the GNU Lesser General Public License version 2.1 as + * published by the Free Software Foundation. + */ package ch.qos.logback.core; -import static org.junit.Assert.assertTrue; - -import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; -import java.io.FileReader; import java.io.IOException; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.junit.After; import org.junit.Before; @@ -17,9 +24,10 @@ import org.junit.Test; import ch.qos.logback.core.encoder.EchoEncoder; import ch.qos.logback.core.testUtil.Env; import ch.qos.logback.core.testUtil.RandomUtil; +import ch.qos.logback.core.util.ResilienceUtil; import ch.qos.logback.core.util.StatusPrinter; -public class FileAppenderResilienceTest { +public class FileAppenderResilience_AS_ROOT_Test { static String MOUNT_POINT = "/mnt/loop/"; @@ -109,7 +117,7 @@ public class FileAppenderResilienceTest { Thread.sleep(DELAY); } p.waitFor(); - verify(logfileStr); + ResilienceUtil.verify(logfileStr, "^(\\d{1,3}) x*$", NUM_STEPS); System.out.println("Done go"); } @@ -127,39 +135,4 @@ public class FileAppenderResilienceTest { Process process = pb.start(); return process; } - - void verify(String logfile) throws NumberFormatException, IOException { - FileReader fr = new FileReader(logfile); - BufferedReader br = new BufferedReader(fr); - String regExp = "^(\\d{1,3}) x*$"; - Pattern p = Pattern.compile(regExp); - String line; - - int totalLines = 0; - int oldNum = -1; - int gaps = 0; - while ((line = br.readLine()) != null) { - Matcher m = p.matcher(line); - if (m.matches()) { - totalLines++; - String g = m.group(1); - int num = Integer.parseInt(g); - if(num != oldNum+1) { - gaps++; - } - oldNum = num; - } - } - fr.close(); - br.close(); - - // at least 40% of the logs should have been written - int lowerLimit = (int) (NUM_STEPS*0.4); - assertTrue("totalLines="+totalLines+" less than "+lowerLimit, totalLines > lowerLimit); - - // we want some gaps which indicate recuperation - assertTrue("gaps="+gaps+" less than 3", gaps > 3); - - } - } diff --git a/logback-core/src/test/java/ch/qos/logback/core/WriterAppenderTest.java b/logback-core/src/test/java/ch/qos/logback/core/OutputStreamAppenderTest.java similarity index 98% rename from logback-core/src/test/java/ch/qos/logback/core/WriterAppenderTest.java rename to logback-core/src/test/java/ch/qos/logback/core/OutputStreamAppenderTest.java index e5988a7..0d475c8 100644 --- a/logback-core/src/test/java/ch/qos/logback/core/WriterAppenderTest.java +++ b/logback-core/src/test/java/ch/qos/logback/core/OutputStreamAppenderTest.java @@ -25,7 +25,7 @@ import org.junit.Test; import ch.qos.logback.core.html.LayoutWrappingEncoder; import ch.qos.logback.core.pattern.parser.SamplePatternLayout; -public class WriterAppenderTest { +public class OutputStreamAppenderTest { Context context = new ContextBase(); diff --git a/logback-core/src/test/java/ch/qos/logback/core/PackageTest.java b/logback-core/src/test/java/ch/qos/logback/core/PackageTest.java index 190f370..e6e17a5 100644 --- a/logback-core/src/test/java/ch/qos/logback/core/PackageTest.java +++ b/logback-core/src/test/java/ch/qos/logback/core/PackageTest.java @@ -18,6 +18,6 @@ import org.junit.runners.Suite; import org.junit.runners.Suite.SuiteClasses; @RunWith(Suite.class) -@SuiteClasses( { ContextBaseTest.class, WriterAppenderTest.class }) +@SuiteClasses( { ContextBaseTest.class, OutputStreamAppenderTest.class, FileAppenderResilienceTest.class, FileAppenderResilience_AS_ROOT_Test.class }) public class PackageTest { } diff --git a/logback-core/src/test/java/ch/qos/logback/core/util/ResilienceUtil.java b/logback-core/src/test/java/ch/qos/logback/core/util/ResilienceUtil.java new file mode 100644 index 0000000..2233fbc --- /dev/null +++ b/logback-core/src/test/java/ch/qos/logback/core/util/ResilienceUtil.java @@ -0,0 +1,46 @@ +package ch.qos.logback.core.util; + +import static org.junit.Assert.assertTrue; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class ResilienceUtil { + + + static public void verify(String logfile, String regexp, long totalSteps) throws NumberFormatException, IOException { + FileReader fr = new FileReader(logfile); + BufferedReader br = new BufferedReader(fr); + Pattern p = Pattern.compile(regexp); + String line; + + int totalLines = 0; + int oldNum = -1; + int gaps = 0; + while ((line = br.readLine()) != null) { + Matcher m = p.matcher(line); + if (m.matches()) { + totalLines++; + String g = m.group(1); + int num = Integer.parseInt(g); + if(num != oldNum+1) { + gaps++; + } + oldNum = num; + } + } + fr.close(); + br.close(); + + // at least 40% of the logs should have been written + int lowerLimit = (int) (totalSteps*0.4); + assertTrue("totalLines="+totalLines+" less than "+lowerLimit, totalLines > lowerLimit); + + // we want some gaps which indicate recuperation + assertTrue("gaps="+gaps+" less than 3", gaps > 3); + + } +} diff --git a/logback-site/src/site/pages/news.html b/logback-site/src/site/pages/news.html index 964c4dd..2b2c803 100644 --- a/logback-site/src/site/pages/news.html +++ b/logback-site/src/site/pages/news.html @@ -58,6 +58,16 @@ </div> + <p><code>FileAppender</code> and derived classes can now + gracefully deal with IO failreus and recover quickly after the + original cause of the IO failure is corrected. For example, if the + log file is located on a <a + href="http://en.wikipedia.org/wiki/Network-attached_storage">network-attached + storage (NAS)</a>, and the connection to the NAS server is lost, + <code>FileAppender</code> and derived classes will recover quickly + after the connection to the server is re-established. + </p> + <p>Added <a href="manual/appenders.html#OnMarkerEvaluator">OnMarkerEvaluator</a> which evaluates to true in the presence of user-specified ----------------------------------------------------------------------- Summary of changes: .../java/ch/qos/logback/core/FileAppender.java | 2 +- .../ch/qos/logback/core/net/TelnetAppender.java | 49 ------ .../core/recovery/ResilientFileOutputStream.java | 132 +++++++++++---- .../logback/core/FileAppenderResilienceTest.java | 177 ++++++------------- ...va => FileAppenderResilience_AS_ROOT_Test.java} | 59 ++----- ...nderTest.java => OutputStreamAppenderTest.java} | 2 +- .../test/java/ch/qos/logback/core/PackageTest.java | 2 +- .../ch/qos/logback/core/util/ResilienceUtil.java | 46 +++++ logback-site/src/site/pages/news.html | 10 + 9 files changed, 232 insertions(+), 247 deletions(-) delete mode 100644 logback-core/src/main/java/ch/qos/logback/core/net/TelnetAppender.java copy logback-core/src/test/java/ch/qos/logback/core/{FileAppenderResilienceTest.java => FileAppenderResilience_AS_ROOT_Test.java} (73%) rename logback-core/src/test/java/ch/qos/logback/core/{WriterAppenderTest.java => OutputStreamAppenderTest.java} (98%) create mode 100644 logback-core/src/test/java/ch/qos/logback/core/util/ResilienceUtil.java hooks/post-receive -- Logback: the generic, reliable, fast and flexible logging framework.
participants (1)
-
git-noreply@pixie.qos.ch