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