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