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.classic.corpus;
15  
16  import java.util.List;
17  import java.util.Random;
18  
19  import ch.qos.logback.classic.Level;
20  import ch.qos.logback.classic.LoggerContext;
21  import ch.qos.logback.classic.spi.ClassPackagingData;
22  import ch.qos.logback.classic.spi.LoggerContextVO;
23  import ch.qos.logback.classic.spi.PubLoggerContextVO;
24  import ch.qos.logback.classic.spi.StackTraceElementProxy;
25  import ch.qos.logback.classic.spi.ThrowableProxy;
26  import ch.qos.logback.classic.spi.ThrowableProxyVO;
27  
28  /**
29   * Models the corpus.
30   * 
31   * <p>
32   * This contains the probability distributions of levels, logger names,
33   * messages, message arguments.
34   * 
35   * @author Ceki G&uuml;lc&uuml;
36   * 
37   */
38  public class CorpusModel {
39  
40      // N(u,s) denotes a random variable normally distributed with mean u and
41      // variance sqrt(s), where sqrt() is the square root function. For an
42      // explanation of normal distribution please see
43      // http://en.wikipedia.org/wiki/Normal_distribution
44  
45      // It is assumed that the number of parts in a logger name is a random
46      // variable normally distributed with mean AVERAGE_LOGGER_NAME_PARTS and
47      // standard deviation STD_DEV_FOR_LOGGER_NAME_PARTS
48      static final int AVERAGE_LOGGER_NAME_PARTS = 6;
49      static final int STD_DEV_FOR_LOGGER_NAME_PARTS = 3;
50  
51      // It is assumed that there are LOGGER_POOL_SIZE logger names
52      // in our model corpus.
53      static final int LOGGER_POOL_SIZE = 1000;
54  
55      // It is assumed that there are LOG_STATEMENT_POOL_SIZE log statements
56      // in our model corpus.
57      static final int LOG_STATEMENT_POOL_SIZE = LOGGER_POOL_SIZE * 8;
58  
59      // level distribution is determined by the following table
60      // It corresponds to TRACE 3%, DEBUG 30%, INFO 30%, WARN 5%,
61      // ERROR 5%. See also getRandomLevel() method.
62      static final double[] LEVEL_DISTRIBUTION = new double[] { .3, .3, .9, .95 };
63  
64      // It is assumed that the number of words in the message (contained in a log
65      // statement) is a random variable normally distributed with mean
66      // AVERAGE_MESSAGE_WORDS and standard deviation STD_DEV_FOR_MESSAGE_WORDS
67      static final int AVERAGE_MESSAGE_WORDS = 8;
68      static final int STD_DEV_FOR_MESSAGE_WORDS = 4;
69  
70      // messages will have no arguments 80% of the time, one argument in 8%, two
71      // arguments in 7% and three arguments in 5% of cases
72      static final double[] ARGUMENT_DISTRIBUTION = new double[] { .80, .88, 0.95 };
73  
74      static final double THROWABLE_PROPABILITY_FOR_WARNING = .1;
75      static final double THROWABLE_PROPABILITY_FOR_ERRORS = .3;
76      // .5 of throwables are nested once
77      static final double NESTING_PROBABILITY = .5;
78  
79      // For each logging event the timer is incremented by a certain value. it is
80      // assumed that this value is a random variable normally distributed with mean
81      // AVERAGE_MILLIS_INCREMENT and standard deviation
82      // STD_DEV_FOR_MILLIS_INCREMENT
83      static final int AVERAGE_MILLIS_INCREMENT = 10;
84      static final int STD_DEV_FOR_MILLIS_INCREMENT = 5;
85  
86      // assume that there are THREAD_POOL_SIZE threads in the corpus
87      static final int THREAD_POOL_SIZE = 10;
88  
89      final Random random;
90      final List<String> worldList;
91      String[] threadNamePool;
92      LogStatement[] logStatementPool;
93      String[] loggerNamePool;
94  
95      // 2009-03-06 13:08 GMT
96      long lastTimeStamp = 1236344888578L;
97  
98      public CorpusModel(long seed, List<String> worldList) {
99          random = new Random(seed);
100         this.worldList = worldList;
101         buildThreadNamePool();
102         buildLoggerNamePool();
103         buildLogStatementPool();
104     }
105 
106     private void buildThreadNamePool() {
107         threadNamePool = new String[THREAD_POOL_SIZE];
108         for (int i = 0; i < THREAD_POOL_SIZE; i++) {
109             threadNamePool[i] = "CorpusMakerThread-" + i;
110         }
111     }
112 
113     private void buildLoggerNamePool() {
114         loggerNamePool = new String[LOGGER_POOL_SIZE];
115         for (int i = 0; i < LOGGER_POOL_SIZE; i++) {
116             loggerNamePool[i] = makeRandomLoggerName();
117         }
118     }
119 
120     private void buildLogStatementPool() {
121         logStatementPool = new LogStatement[LOG_STATEMENT_POOL_SIZE];
122         for (int i = 0; i < LOG_STATEMENT_POOL_SIZE; i++) {
123             logStatementPool[i] = makeRandomLogStatement(loggerNamePool);
124         }
125     }
126 
127     private int[] getRandomAnchorPositions(int wordCount, int numAnchors) {
128         // note that the same position may appear multiple times in
129         // positionsIndex, but without serious consequences
130         int[] positionsIndex = new int[numAnchors];
131         for (int i = 0; i < numAnchors; i++) {
132             positionsIndex[i] = random.nextInt(wordCount);
133         }
134         return positionsIndex;
135     }
136 
137     private String[] getRandomWords(int n) {
138         String[] wordArray = new String[n];
139         for (int i = 0; i < n; i++) {
140             wordArray[i] = getRandomWord();
141         }
142         return wordArray;
143     }
144 
145     public long getRandomLong() {
146         return random.nextLong();
147     }
148 
149     public String getRandomThreadNameFromPool() {
150         int index = random.nextInt(THREAD_POOL_SIZE);
151         return threadNamePool[index];
152     }
153 
154     public LogStatement getRandomLogStatementFromPool() {
155         int index = random.nextInt(logStatementPool.length);
156         return logStatementPool[index];
157     }
158 
159     private String getRandomLoggerNameFromPool(String[] loggerNamePool) {
160         int index = random.nextInt(loggerNamePool.length);
161         return loggerNamePool[index];
162     }
163 
164     public long getRandomTimeStamp() {
165         // subtract 1 so that 0 is allowed
166         lastTimeStamp += RandomUtil.gaussianAsPositiveInt(random, AVERAGE_MILLIS_INCREMENT,
167                 STD_DEV_FOR_MILLIS_INCREMENT) - 1;
168         return lastTimeStamp;
169     }
170 
171     PubLoggerContextVO getRandomlyNamedLoggerContextVO() {
172         LoggerContext lc = new LoggerContext();
173         lc.setName(getRandomJavaIdentifier());
174         return new PubLoggerContextVO(lc);
175     }
176 
177     String getRandomWord() {
178         int size = worldList.size();
179         int randomIndex = random.nextInt(size);
180         return worldList.get(randomIndex);
181     }
182 
183     String extractLastPart(String loggerName) {
184         int i = loggerName.lastIndexOf('.');
185         if (i == -1) {
186             return loggerName;
187         } else {
188             return loggerName.substring(i + 1);
189         }
190     }
191 
192     public StackTraceElement[] getRandomCallerData(int depth, String loggerName) {
193         StackTraceElement[] cda = new StackTraceElement[depth];
194         StackTraceElement cd = new StackTraceElement(loggerName, getRandomJavaIdentifier(), extractLastPart(loggerName),
195                 0);
196         cda[0] = cd;
197         for (int i = 1; i < depth; i++) {
198             String ln = getRandomLoggerNameFromPool(loggerNamePool);
199             cda[i] = new StackTraceElement(ln, getRandomJavaIdentifier(), extractLastPart(ln), i * 10);
200         }
201         return cda;
202     }
203 
204     public Object[] getRandomArgumentArray(int numOfArguments) {
205         if (numOfArguments == 0) {
206             return null;
207         }
208         Object[] argumentArray = new Object[numOfArguments];
209         for (int i = 0; i < numOfArguments; i++) {
210             argumentArray[i] = Long.valueOf(random.nextLong());
211         }
212         return argumentArray;
213     }
214 
215     private MessageArgumentTuple makeRandomMessageArgumentTuple() {
216         int numOfArguments = getNumberOfMessageArguments();
217 
218         int wordCount = RandomUtil.gaussianAsPositiveInt(random, AVERAGE_MESSAGE_WORDS, STD_DEV_FOR_MESSAGE_WORDS);
219         String[] wordArray = getRandomWords(wordCount);
220 
221         int[] anchorPositions = getRandomAnchorPositions(wordCount, numOfArguments);
222 
223         for (int anchorIndex : anchorPositions) {
224             wordArray[anchorIndex] = "{}";
225         }
226 
227         StringBuilder sb = new StringBuilder();
228         for (int i = 1; i < wordCount; i++) {
229             sb.append(wordArray[i]).append(' ');
230         }
231         sb.append(getRandomWord());
232         return new MessageArgumentTuple(sb.toString(), numOfArguments);
233     }
234 
235     private LogStatement makeRandomLogStatement(String[] loggerNamePool) {
236         MessageArgumentTuple mat = makeRandomMessageArgumentTuple();
237         String loggerName = getRandomLoggerNameFromPool(loggerNamePool);
238         Level randomLevel = getRandomLevel();
239         Throwable t = getRandomThrowable(randomLevel);
240         ThrowableProxyVO throwableProxy = null;
241         if (t != null) {
242             throwableProxy = ThrowableProxyVO.build(new ThrowableProxy(t));
243             pupulateWithPackagingData(throwableProxy.getStackTraceElementProxyArray());
244         }
245         return new LogStatement(loggerName, randomLevel, mat, throwableProxy);
246     }
247 
248     private Throwable getRandomThrowable(Level level) {
249         double rn = random.nextDouble();
250         if ((level == Level.WARN && rn < THROWABLE_PROPABILITY_FOR_WARNING)
251                 || (level == Level.ERROR && rn < THROWABLE_PROPABILITY_FOR_ERRORS)) {
252             return ExceptionBuilder.build(random, NESTING_PROBABILITY);
253         } else {
254             return null;
255         }
256     }
257 
258     private void pupulateWithPackagingData(StackTraceElementProxy[] stepArray) {
259         int i = 0;
260         for (StackTraceElementProxy step : stepArray) {
261             String identifier = "na";
262             String version = "na";
263             if (i++ % 2 == 0) {
264                 identifier = getRandomJavaIdentifier();
265                 version = getRandomJavaIdentifier();
266             }
267             ClassPackagingData cpd = new ClassPackagingData(identifier, version);
268             step.setClassPackagingData(cpd);
269         }
270     }
271 
272     private int getNumberOfMessageArguments() {
273         double rn = random.nextDouble();
274         if (rn < ARGUMENT_DISTRIBUTION[0]) {
275             return 0;
276         }
277         if (rn < ARGUMENT_DISTRIBUTION[1]) {
278             return 1;
279         }
280         if (rn < ARGUMENT_DISTRIBUTION[2]) {
281             return 2;
282         }
283         return 3;
284     }
285 
286     String getRandomJavaIdentifier() {
287         String w = getRandomWord();
288         w = w.replaceAll("\\p{Punct}", "");
289         return w;
290     }
291 
292     private String makeRandomLoggerName() {
293         int parts = RandomUtil.gaussianAsPositiveInt(random, AVERAGE_LOGGER_NAME_PARTS, STD_DEV_FOR_LOGGER_NAME_PARTS);
294         StringBuilder sb = new StringBuilder();
295         for (int i = 1; i < parts; i++) {
296             sb.append(getRandomJavaIdentifier()).append('.');
297         }
298         sb.append(getRandomJavaIdentifier());
299         return sb.toString();
300     }
301 
302     private Level getRandomLevel() {
303         double rn = random.nextDouble();
304         if (rn < LEVEL_DISTRIBUTION[0]) {
305             return Level.TRACE;
306         }
307         if (rn < LEVEL_DISTRIBUTION[1]) {
308             return Level.DEBUG;
309         }
310 
311         if (rn < LEVEL_DISTRIBUTION[2]) {
312             return Level.INFO;
313         }
314 
315         if (rn < LEVEL_DISTRIBUTION[3]) {
316             return Level.WARN;
317         }
318 
319         return Level.ERROR;
320     }
321 }