-
Notifications
You must be signed in to change notification settings - Fork 45
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
#87 Grouping stack traces into single log statements for sub-processes #90
base: master
Are you sure you want to change the base?
Changes from 33 commits
3bd48e8
a52948b
828ee4f
534bb5c
fcb0007
80df3b6
2975b06
1d48789
fed0c7d
256d4f3
6e07c94
a3bfd27
12c800b
0516292
cc2ab93
e2a9c1b
0c00490
ed7a354
5c92779
c2873ae
9f257d7
1ea04f2
bbc143b
9ad8ac3
74be2fa
8a10cf9
2a1b9cc
4a4a465
85cfd53
4dc1ca1
6b8e6f4
6deffec
c8c1efa
df9445a
63c19a2
8e4a50f
d14e4da
ac89ad5
c796d56
399c3ed
c4d4ac1
0b0ebe6
308aec4
61d46ab
699ad4d
25f2849
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -389,6 +389,38 @@ private static void close(final Closeable res) { | |
* Stream monitor. | ||
*/ | ||
private static final class Monitor implements Callable<Void> { | ||
|
||
/** | ||
* Maximum number of log lines for a stack trace. Set to 1000 | ||
* to avoid storing too many lines in memory before flushing. | ||
*/ | ||
private static final int MAX_STACK_LENGTH = 1000; | ||
/** | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark can you also separate these constants with empty lines? |
||
* Prefix "at ". Needed to check if line is part of stack trace. | ||
*/ | ||
private static final String PREFIX_AT = "at "; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark this looks like http://www.yegor256.com/2015/09/01/redundant-variables-are-evil.html, can you inline all redundant variables? |
||
/** | ||
* Prefix "Caused by". Needed to check if line is part of stack trace. | ||
*/ | ||
private static final String PREFIX_CB = "Caused by"; | ||
/** | ||
* Prefix "... ". Needed to check if line is part of stack trace. | ||
*/ | ||
private static final String PREFIX_DOTS = "... "; | ||
/** | ||
* Format string for log statements. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark can you give more information here why we need it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mkordas we need those three There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark right, so can you add this info to Javadoc |
||
*/ | ||
private static final String LOG_FORMAT = ">> %s"; | ||
/** | ||
* Empty String. | ||
*/ | ||
private static final String EMPTY_STRING = ""; | ||
/** | ||
* Newline string. | ||
*/ | ||
private static final String NEW_LINE = System.getProperty( | ||
"line.separator" | ||
); | ||
/** | ||
* Stream to read. | ||
*/ | ||
|
@@ -420,38 +452,20 @@ private static final class Monitor implements Callable<Void> { | |
this.output = out; | ||
this.level = lvl; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark I didn't mean to remove this logic. I just meant that when you had throws IOException, ClosedByInterruptException the second exception was unncecessary, as |
||
@Override | ||
public Void call() throws Exception { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark this method has become massive, it needs to be split across many private ones |
||
final BufferedReader reader = new BufferedReader( | ||
Channels.newReader( | ||
Channels.newChannel(this.input), | ||
VerboseProcess.UTF_8 | ||
Channels.newChannel(this.input), VerboseProcess.UTF_8 | ||
) | ||
); | ||
try { | ||
final BufferedWriter writer = new BufferedWriter( | ||
new OutputStreamWriter(this.output, VerboseProcess.UTF_8) | ||
); | ||
try { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark for so complex logic added in this PR I'd expect at least dozen of unit tests, but I don't see any 😞 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. have you read this: http://www.yegor256.com/2017/03/24/tdd-that-works.html? What about writing unit tests only when bugs will appear? |
||
while (true) { | ||
if (Thread.interrupted()) { | ||
Logger.debug( | ||
VerboseProcess.class, | ||
"explicitly interrupting read from buffer" | ||
); | ||
break; | ||
} | ||
final String line = reader.readLine(); | ||
if (line == null) { | ||
break; | ||
} | ||
Logger.log( | ||
this.level, VerboseProcess.class, | ||
">> %s", line | ||
); | ||
writer.write(line); | ||
writer.newLine(); | ||
} | ||
this.logFromReader(reader, writer); | ||
} catch (final ClosedByInterruptException ex) { | ||
Thread.interrupted(); | ||
Logger.debug( | ||
|
@@ -473,6 +487,106 @@ public Void call() throws Exception { | |
} | ||
return null; | ||
} | ||
|
||
/** | ||
* Logs supplied StringBuilder to supplied Logger and Writer. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark this sounds a bit strange, we are not supplying any |
||
* @param reader Reader to use | ||
* @param writer Writer to use | ||
* @throws IOException writer could throw this | ||
*/ | ||
private void logFromReader(final BufferedReader reader, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark Javadoc is missing here |
||
final BufferedWriter writer) throws IOException { | ||
final StringBuilder builder = new StringBuilder(); | ||
String previous = EMPTY_STRING; | ||
int count = 0; | ||
while (true) { | ||
if (Thread.interrupted()) { | ||
Logger.debug( | ||
VerboseProcess.class, | ||
"explicitly interrupting read from buffer" | ||
); | ||
break; | ||
} | ||
if (!previous.isEmpty()) { | ||
appendLine(previous, builder); | ||
previous = EMPTY_STRING; | ||
} | ||
final String line = reader.readLine(); | ||
if (line == null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark as I mentioned before, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mkordas this is from the existing code, and I can't control how readLine() works There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark you are right - sorry, I didn't notice |
||
logAndClear(writer, this.level, builder); | ||
break; | ||
} | ||
if (shouldAppend(line) | ||
&& count < MAX_STACK_LENGTH) { | ||
appendLine(line, builder); | ||
++count; | ||
} else { | ||
if (builder.length() > 0) { | ||
logAndClear(writer, this.level, builder); | ||
} | ||
count = 1; | ||
previous = line; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Appends a line and a newline character to the builder. | ||
* @param line String to append | ||
* @param builder StringBuilder with log statement | ||
*/ | ||
private static void appendLine(final String line, | ||
final StringBuilder builder) { | ||
builder.append(line); | ||
builder.append(NEW_LINE); | ||
} | ||
|
||
/** | ||
* Logs StringBuilder to supplied Logger and Writer then clears out | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark again, we do not supply |
||
* the builder. | ||
* @param writer Writer to use | ||
* @param level Level to log at | ||
* @param builder StringBuilder with log statement | ||
* @throws IOException writer could throw this | ||
*/ | ||
private static void logAndClear(final BufferedWriter writer, | ||
final Level level, final StringBuilder builder) | ||
throws IOException { | ||
if (builder.length() > 0) { | ||
final String text = builder.toString(); | ||
Logger.log(level, VerboseProcess.class, LOG_FORMAT, text); | ||
writer.write(text); | ||
} | ||
builder.setLength(0); | ||
} | ||
|
||
/** | ||
* Checks if line is part of a stack trace and should be appended. | ||
* @param input String to check | ||
* @return Result, true or false | ||
*/ | ||
private static boolean shouldAppend(final String input) { | ||
final String stripped = stripStart(input); | ||
return stripped.startsWith(PREFIX_AT) | ||
|| stripped.startsWith(PREFIX_CB) | ||
|| stripped.startsWith(PREFIX_DOTS); | ||
} | ||
|
||
/** | ||
* Strips whitespace at beginning of String. | ||
* @param input String to strip | ||
* @return Stripped string | ||
*/ | ||
private static String stripStart(final String input) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark by the way, can we do just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark this method should be named like a noun, please read http://www.yegor256.com/images/books/elegant-objects/seven-pages.pdf There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mkordas removed this in favor of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark I don't see your changes, can you please push them? |
||
final int length = input.length(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark can we inline this variable? |
||
int start = 0; | ||
while (start != length | ||
&& Character.isWhitespace(input.charAt(start))) { | ||
++start; | ||
} | ||
return input.substring(start); | ||
} | ||
|
||
} | ||
|
||
/** | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
/** | ||
* Copyright (c) 2012-2015, jcabi.com | ||
* All rights reserved. | ||
* | ||
* Redistribution and use in source and binary forms, with or without | ||
* modification, are permitted provided that the following conditions | ||
* are met: 1) Redistributions of source code must retain the above | ||
* copyright notice, this list of conditions and the following | ||
* disclaimer. 2) Redistributions in binary form must reproduce the above | ||
* copyright notice, this list of conditions and the following | ||
* disclaimer in the documentation and/or other materials provided | ||
* with the distribution. 3) Neither the name of the jcabi.com nor | ||
* the names of its contributors may be used to endorse or promote | ||
* products derived from this software without specific prior written | ||
* permission. | ||
* | ||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT | ||
* NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | ||
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL | ||
* THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | ||
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | ||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | ||
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | ||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | ||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | ||
* OF THE POSSIBILITY OF SUCH DAMAGE. | ||
*/ | ||
package com.jcabi.log; | ||
|
||
/** | ||
* Example application to help test {@link VerboseProcess}. | ||
* @author Dean Clark ([email protected]) | ||
* @version $Id$ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark we also need |
||
*/ | ||
@SuppressWarnings("PMD.DefaultPackage") | ||
public final class VerboseProcessExample { | ||
|
||
/** | ||
* System output line 1. | ||
*/ | ||
static final String SYSOUT_1 = "sysout line 1"; | ||
|
||
/** | ||
* System output line 2. | ||
*/ | ||
static final String SYSOUT_2 = "sysout line 2"; | ||
|
||
/** | ||
* Error output line 1. | ||
*/ | ||
static final String SYSERR_1 = "syserr line 1"; | ||
|
||
/** | ||
* Error output line 2. | ||
*/ | ||
static final String SYSERR_2 = "syserr line 2"; | ||
|
||
/** | ||
* Exception to be thrown and caught. | ||
*/ | ||
static final String CAUGHT_ERR_MSG = "throw/catch me"; | ||
|
||
/** | ||
* Exception to be thrown. | ||
*/ | ||
static final String THROWN_ERR_MSG = "just throw me"; | ||
|
||
/** | ||
* Private constructor. Intentionally empty. | ||
*/ | ||
private VerboseProcessExample() { } | ||
|
||
/** | ||
* Instantiates instance of this class and calls primary method. | ||
* @param args Any args passed to main method | ||
*/ | ||
public static void main(final String... args) { | ||
final VerboseProcessExample instance = new VerboseProcessExample(); | ||
instance.doWork(); | ||
} | ||
|
||
/** | ||
* Will log to standard output and error and then intentionally fail with a | ||
* stack trace. | ||
*/ | ||
@SuppressWarnings("PMD.SystemPrintln") | ||
private void doWork() { | ||
System.out.println(SYSOUT_1); | ||
System.err.println(SYSERR_1); | ||
System.out.println(SYSOUT_2); | ||
System.err.println(SYSERR_2); | ||
catchAndThrow(); | ||
} | ||
|
||
/** | ||
* Call a method which will throw an exception. Then catch and re-throw it. | ||
*/ | ||
private static void catchAndThrow() { | ||
try { | ||
countdownAndThrow(2); | ||
} catch (final IllegalStateException ex) { | ||
throw new IllegalStateException(THROWN_ERR_MSG, ex); | ||
} | ||
} | ||
|
||
/** | ||
* Recursively loops and then throws an exception. | ||
* @param loops Times to loop | ||
*/ | ||
private static void countdownAndThrow(final int loops) { | ||
if (loops == 0) { | ||
throw new IllegalStateException(CAUGHT_ERR_MSG); | ||
} | ||
countdownAndThrow(loops - 1); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark why not just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @mkordas There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dean-e-clark right, thanks for explaining |
||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@dean-e-clark fields should be separated by empty line