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