View Javadoc
1   /**
2    * Logback: the reliable, generic, fast and flexible logging framework.
3    * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
4    *
5    * This program and the accompanying materials are dual-licensed under
6    * either the terms of the Eclipse Public License v1.0 as published by
7    * the Eclipse Foundation
8    *
9    *   or (per the licensee's choosing)
10   *
11   * under the terms of the GNU Lesser General Public License version 2.1
12   * as published by the Free Software Foundation.
13   */
14  package ch.qos.logback.core;
15  
16  import static org.junit.Assert.assertEquals;
17  import static org.junit.Assert.assertTrue;
18  
19  import java.io.File;
20  import java.io.IOException;
21  import java.nio.channels.FileChannel;
22  
23  import org.junit.Before;
24  import org.junit.Ignore;
25  import org.junit.Test;
26  
27  import ch.qos.logback.core.contention.RunnableWithCounterAndDone;
28  import ch.qos.logback.core.encoder.EchoEncoder;
29  import ch.qos.logback.core.recovery.RecoveryCoordinator;
30  import ch.qos.logback.core.recovery.RecoveryListener;
31  import ch.qos.logback.core.recovery.ResilientFileOutputStream;
32  import ch.qos.logback.core.status.OnConsoleStatusListener;
33  import ch.qos.logback.core.testUtil.CoreTestConstants;
34  import ch.qos.logback.core.testUtil.RandomUtil;
35  import ch.qos.logback.core.util.ResilienceUtil;
36  
37  public class FileAppenderResilienceTest implements RecoveryListener {
38  
39      FileAppender<Object> fa = new FileAppender<Object>();
40      
41      ResilientFileOutputStream resilientFOS;
42      
43      Context context = new ContextBase();
44      int diff = RandomUtil.getPositiveInt();
45      String outputDirStr = CoreTestConstants.OUTPUT_DIR_PREFIX + "resilience-" + diff + "/";
46  
47      // String outputDirStr = "\\\\192.168.1.3\\lbtest\\" + "resilience-"+ diff +
48      // "/";;
49      String logfileStr = outputDirStr + "output.log";
50  
51      boolean failedState = false;
52  
53      int recoveryCounter = 0;
54      int failureCounter = 0;
55      
56      
57      @Before
58      public void setUp() throws InterruptedException {
59  
60          context.getStatusManager().add(new OnConsoleStatusListener());
61  
62          File outputDir = new File(outputDirStr);
63          outputDir.mkdirs();
64  
65          fa.setContext(context);
66          fa.setName("FILE");
67          fa.setEncoder(new EchoEncoder<Object>());
68          fa.setFile(logfileStr);
69          fa.start();
70          resilientFOS = (ResilientFileOutputStream) fa.getOutputStream();
71          resilientFOS.addRecoveryListener(this);
72          
73      }
74  
75      @Test
76      @Ignore
77      public void manual() throws InterruptedException, IOException {
78          Runner runner = new Runner(fa);
79          Thread t = new Thread(runner);
80          t.start();
81  
82          while (true) {
83              Thread.sleep(110);
84          }
85      }
86  
87      @Test
88      public void smoke() throws InterruptedException, IOException {
89          Runner runner = new Runner(fa);
90          Thread t = new Thread(runner);
91          t.start();
92  
93          double delayCoefficient = 2.0;
94          for (int i = 0; i < 5; i++) {
95              Thread.sleep((int) (RecoveryCoordinator.BACKOFF_COEFFICIENT_MIN * delayCoefficient));
96              closeLogFileOnPurpose();
97          }
98          runner.setDone(true);
99          t.join();
100 
101         double bestCaseSuccessRatio = 1 / delayCoefficient;
102         // expect to loose at most 35% of the events
103         double lossinessFactor = 0.35;
104         double resilianceFactor = (1 - lossinessFactor);
105 
106         assertTrue("at least one recovery should have occured", recoveryCounter > 0);
107         assertTrue("at least one failure should have occured", failureCounter > 0);
108         
109         System.out.println("recoveryCounter=" + recoveryCounter);
110         System.out.println("failureCounter=" + failureCounter);
111         
112        
113                 
114         String errmsg0 = "failureCounter="+failureCounter+" must be greater or equal to recoveryCounter="+recoveryCounter;
115         assertTrue(errmsg0, failureCounter >= recoveryCounter);
116         
117         String errmsg1 = "Difference between failureCounter="+failureCounter+" and recoveryCounter="+recoveryCounter+" should not exceeed 1";
118         assertTrue(errmsg1, failureCounter - recoveryCounter <= 1);
119         
120         
121         
122         int actuallyWritten = ResilienceUtil.countLines(logfileStr, "^hello (\\d{1,5})$");
123         long exptectedWrites = runner.getCounter()-recoveryCounter;
124         assertEquals(exptectedWrites, actuallyWritten);
125     }
126 
127     private void closeLogFileOnPurpose() throws IOException {
128         ResilientFileOutputStream resilientFOS = (ResilientFileOutputStream) fa.getOutputStream();
129         FileChannel fileChannel = resilientFOS.getChannel();
130         fileChannel.close();
131     }
132 
133     @Override
134     public void newFailure(IOException e) {
135         failedState = true;
136         failureCounter++;
137         
138     }
139 
140     @Override
141     public void recoveryOccured() {
142         failedState = false;
143         recoveryCounter++;
144     }
145     
146     class Runner extends RunnableWithCounterAndDone {
147         FileAppender<Object> fa;
148 
149         Runner(FileAppender<Object> fa) {
150             this.fa = fa;
151         }
152 
153         public void run() {
154             while (!isDone()) {
155                 fa.doAppend("hello " + counter);
156                 if(!FileAppenderResilienceTest.this.failedState) { 
157                     counter++;
158                 }
159                 if (counter % 128 == 0) {
160                     try {
161                         Thread.sleep(10);
162                     } catch (InterruptedException e) {
163                     }
164                 }
165             }
166         }
167 
168     }
169 }
170