001/** 002 * Logback: the reliable, generic, fast and flexible logging framework. 003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved. 004 * 005 * This program and the accompanying materials are dual-licensed under 006 * either the terms of the Eclipse Public License v1.0 as published by 007 * the Eclipse Foundation 008 * 009 * or (per the licensee's choosing) 010 * 011 * under the terms of the GNU Lesser General Public License version 2.1 012 * as published by the Free Software Foundation. 013 */ 014package ch.qos.logback.core.rolling; 015 016import static ch.qos.logback.core.testUtil.CoreTestConstants.FAILURE_EXIT_CODE; 017import static ch.qos.logback.core.testUtil.CoreTestConstants.SUCCESSFUL_EXIT_CODE; 018import static org.junit.Assert.assertEquals; 019import static org.junit.Assert.fail; 020 021import java.io.File; 022import java.io.FileOutputStream; 023import java.io.FilenameFilter; 024import java.io.IOException; 025import java.io.OutputStream; 026 027import ch.qos.logback.core.testUtil.CoreTestConstants; 028import ch.qos.logback.core.testUtil.EnvUtilForTests; 029 030import org.junit.After; 031import org.junit.Before; 032import org.junit.Test; 033 034import ch.qos.logback.core.Context; 035import ch.qos.logback.core.ContextBase; 036import ch.qos.logback.core.contention.MultiThreadedHarness; 037import ch.qos.logback.core.contention.RunnableWithCounterAndDone; 038import ch.qos.logback.core.encoder.EchoEncoder; 039import ch.qos.logback.core.encoder.Encoder; 040import ch.qos.logback.core.testUtil.RandomUtil; 041import ch.qos.logback.core.testUtil.StatusChecker; 042import ch.qos.logback.core.util.FileSize; 043import ch.qos.logback.core.util.StatusPrinter; 044 045public class MultiThreadedRollingTest { 046 047 final static int NUM_THREADS = 10; 048 final static int TOTAL_DURATION = 600; 049 RunnableWithCounterAndDone[] runnableArray; 050 051 Encoder<Object> encoder; 052 Context context = new ContextBase(); 053 054 static String VERIFY_SH = "verify.sh"; 055 056 int diff = RandomUtil.getPositiveInt(); 057 String outputDirStr = CoreTestConstants.OUTPUT_DIR_PREFIX + "multi-" + diff + "/"; 058 059 RollingFileAppender<Object> rfa = new RollingFileAppender<Object>(); 060 061 String pathToBash = EnvUtilForTests.getPathToBash(); 062 OutputStream scriptOS; 063 064 @Before 065 public void setUp() throws Exception { 066 encoder = new EchoEncoder<Object>(); 067 File outputDir = new File(outputDirStr); 068 outputDir.mkdirs(); 069 070 System.out.println("Output dir [" + outputDirStr + "]"); 071 072 scriptOS = openScript(); 073 074 rfa.setName("rolling"); 075 rfa.setEncoder(encoder); 076 rfa.setContext(context); 077 rfa.setFile(outputDirStr + "output.log"); 078 079 } 080 081 void close(OutputStream os) { 082 if (os != null) { 083 try { 084 os.close(); 085 } catch (IOException e) { 086 } 087 } 088 } 089 090 @After 091 public void tearDown() throws Exception { 092 rfa.stop(); 093 } 094 095 public void setUpTimeBasedTriggeringPolicy(RollingFileAppender<Object> rfa) { 096 String datePattern = "yyyy-MM-dd'T'HH_mm_ss_SSS"; 097 TimeBasedRollingPolicy<Object> tbrp = new TimeBasedRollingPolicy<>(); 098 tbrp.setFileNamePattern(outputDirStr + "test-%d{" + datePattern + "}"); 099 tbrp.setContext(context); 100 tbrp.setParent(rfa); 101 tbrp.start(); 102 103 rfa.setRollingPolicy(tbrp); 104 rfa.start(); 105 } 106 107 public void setUpSizeBasedTriggeringPolicy(RollingFileAppender<Object> rfa) { 108 SizeBasedTriggeringPolicy<Object> zbtp = new SizeBasedTriggeringPolicy<>(); 109 zbtp.setContext(context); 110 zbtp.setMaxFileSize(FileSize.valueOf("100KB")); 111 112 zbtp.start(); 113 rfa.setTriggeringPolicy(zbtp); 114 115 FixedWindowRollingPolicy fwrp = new FixedWindowRollingPolicy(); 116 fwrp.setContext(context); 117 fwrp.setFileNamePattern(outputDirStr + "test-%i.log"); 118 fwrp.setMaxIndex(20); 119 fwrp.setMinIndex(0); 120 fwrp.setParent(rfa); 121 fwrp.start(); 122 rfa.setRollingPolicy(fwrp); 123 rfa.start(); 124 } 125 126 RunnableWithCounterAndDone[] buildRunnableArray(boolean withDelay) { 127 RunnableWithCounterAndDone[] runnableArray = new RunnableWithCounterAndDone[NUM_THREADS]; 128 for (int i = 0; i < NUM_THREADS; i++) { 129 runnableArray[i] = new RFARunnable(i, rfa, withDelay); 130 } 131 return runnableArray; 132 } 133 134 OutputStream openScript() throws IOException { 135 return new FileOutputStream(outputDirStr + VERIFY_SH); 136 } 137 138 @Test 139 public void multiThreadedTimedBased() throws InterruptedException, IOException { 140 setUpTimeBasedTriggeringPolicy(rfa); 141 executeHarness(TOTAL_DURATION, false); 142 printScriptForTimeBased(); 143 verify(); 144 } 145 146 int testFileCount() { 147 File outputDir = new File(outputDirStr); 148 FilenameFilter filter = new FilenameFilter() { 149 public boolean accept(File dir, String name) { 150 if (name.matches("test-\\d{1,2}.log")) { 151 return true; 152 } 153 return false; 154 } 155 }; 156 File[] files = outputDir.listFiles(filter); 157 return files.length; 158 } 159 160 void verify() throws IOException, InterruptedException { 161 close(scriptOS); 162 // no point in this test if we don't have bash 163 if (pathToBash == null) { 164 return; 165 } 166 ProcessBuilder pb = new ProcessBuilder(); 167 pb.command(pathToBash, VERIFY_SH); 168 pb.directory(new File(outputDirStr)); 169 Process process = pb.start(); 170 process.waitFor(); 171 int exitCode = process.exitValue(); 172 173 assertEquals(SUCCESSFUL_EXIT_CODE, exitCode); 174 System.out.println("External script based verification returned with exit code " + exitCode); 175 } 176 177 @Test 178 public void multiThreadedSizeBased() throws InterruptedException, IOException { 179 setUpSizeBasedTriggeringPolicy(rfa); 180 // on a fast machine with a fast hard disk, if the tests runs for too 181 // long the MAX_WINDOW_SIZE is reached, resulting in data loss which 182 // we cannot test for. 183 executeHarness(TOTAL_DURATION, true); 184 int numFiles = testFileCount(); 185 printScriptForSizeBased(numFiles); 186 verify(); 187 } 188 189 private void printScriptHeader(String type) throws IOException { 190 out("# ===================================================="); 191 out("# A script to check the exactness of the output "); 192 out("# produced by " + type + " test"); 193 out("# ===================================================="); 194 out("# "); 195 } 196 197 private void printCommonScriptCore() throws IOException { 198 out(""); 199 out("for t in $(seq 0 1 " + (NUM_THREADS - 1) + ")"); 200 out("do"); 201 out(" echo \"Testing results of thread $t\""); 202 out(" grep \"$t \" aggregated | cut -d ' ' -f 2 > ${t}-sample"); 203 out(" for j in $(seq 1 1 ${end[$t]}); do echo $j; done > ${t}-witness"); 204 out(" diff -q -w ${t}-sample ${t}-witness;"); 205 out(" res=$?"); 206 out(" if [ $res != \"0\" ]; then"); 207 out(" echo \"FAILED for $t\""); 208 out(" exit " + FAILURE_EXIT_CODE); 209 out(" fi"); 210 out("done"); 211 out(""); 212 out("exit " + SUCCESSFUL_EXIT_CODE); 213 } 214 215 private void printScriptForTimeBased() throws IOException { 216 printScriptHeader("TimeBased"); 217 for (int i = 0; i < NUM_THREADS; i++) { 218 out("end[" + i + "]=" + this.runnableArray[i].getCounter()); 219 } 220 out(""); 221 out("rm aggregated"); 222 out("cat test* output.log >> aggregated"); 223 printCommonScriptCore(); 224 225 } 226 227 private void printScriptForSizeBased(int numfiles) throws IOException { 228 printScriptHeader("SizeBased"); 229 230 for (int i = 0; i < NUM_THREADS; i++) { 231 out("end[" + i + "]=" + this.runnableArray[i].getCounter()); 232 } 233 out(""); 234 out("rm aggregated"); 235 out("for i in $(seq " + (numfiles - 1) + " -1 0); do cat test-$i.log >> aggregated; done"); 236 out("cat output.log >> aggregated"); 237 out(""); 238 printCommonScriptCore(); 239 } 240 241 private void out(String msg) throws IOException { 242 scriptOS.write(msg.getBytes()); 243 scriptOS.write("\n".getBytes()); 244 } 245 246 private void executeHarness(int duration, boolean withDelay) throws InterruptedException { 247 MultiThreadedHarness multiThreadedHarness = new MultiThreadedHarness(duration); 248 this.runnableArray = buildRunnableArray(withDelay); 249 multiThreadedHarness.execute(runnableArray); 250 251 StatusChecker checker = new StatusChecker(context.getStatusManager()); 252 if (!checker.isErrorFree(0)) { 253 StatusPrinter.print(context); 254 fail("errors reported"); 255 } 256 } 257 258 long diff(long start) { 259 return System.currentTimeMillis() - start; 260 } 261 262 static class RFARunnable extends RunnableWithCounterAndDone { 263 RollingFileAppender<Object> rfa; 264 int id; 265 boolean withInducedDelay; 266 267 RFARunnable(int id, RollingFileAppender<Object> rfa, boolean withInducedDelay) { 268 this.id = id; 269 this.rfa = rfa; 270 this.withInducedDelay = withInducedDelay; 271 } 272 273 public void run() { 274 while (!isDone()) { 275 counter++; 276 rfa.doAppend(id + " " + counter); 277 if ((counter % 64 == 0) && withInducedDelay) { 278 try { 279 Thread.sleep(10); 280 } catch (InterruptedException e) { 281 } 282 } 283 } 284 } 285 } 286}