多线程 JBehave 日志记录混乱
Multi-threaded JBehave logging mess
虽然很清楚 how to configure 多线程 jBehave 运行,
我不太清楚如何处理日志记录混乱。
这里有哪些选项?
将应用程序的输出重定向到标准输出(JBehave 的输出已经存在)。注意事项follow=true
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.follow=true
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %-64.64C %4L | %m%n
log4j.rootLogger=error, stdout
log4j.logger.com.company.app.interesting.module=debug
...
每个线程文件输出
@SuppressWarnings("resource")
public class ThreadFileOutput extends PrintStream {
private static ThreadLocal<FileOutputStream> threadOutput = new ThreadLocal<>();
private static PrintStream stdout = System.out;
private static PrintStream stderr = System.err;
static {
System.setOut(new ThreadFileOutput(stdout));
System.setErr(new ThreadFileOutput(stderr));
}
public ThreadFileOutput(OutputStream out) {
super(out);
}
public static void startThreadOutputRedirect(FileOutputStream stream) {
threadOutput.set(stream);
}
public static void stopThreadOutputRedirect() {
FileOutputStream stream = threadOutput.get();
if (stream != null) {
threadOutput.set(null);
try {
stream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public static void forceOut(String line) {
stdout.println(line);
}
public static void forceErr(String line) {
stderr.println(line);
}
@Override
public void write(byte[] b) throws IOException {
FileOutputStream stream = threadOutput.get();
if (stream != null) {
try {
stream.write(b);
} catch (IOException e) {
threadOutput.set(null);
throw new RuntimeException(e);
}
} else {
super.write(b);
}
}
@Override
public void write(int b) {
FileOutputStream stream = threadOutput.get();
if (stream != null) {
try {
stream.write(b);
} catch (IOException e) {
threadOutput.set(null);
throw new RuntimeException(e);
}
} else {
super.write(b);
}
}
@Override
public void write(byte[] buf, int off, int len) {
FileOutputStream stream = threadOutput.get();
if (stream != null) {
try {
stream.write(buf, off, len);
} catch (IOException e) {
threadOutput.set(null);
throw new RuntimeException(e);
}
} else {
super.write(buf, off, len);
}
}
@Override
public void flush() {
FileOutputStream stream = threadOutput.get();
if (stream != null) {
try {
stream.flush();
} catch (IOException e) {
threadOutput.set(null);
throw new RuntimeException(e);
}
} else {
super.flush();
}
}
}
测试前开始将线程输出重定向到文件,测试后停止
startThreadOutputRedirect(new FileOutputStream(new File(workDirRelative(story.getPath()))));
stopThreadOutputRedirect();
在
/**
* JBehave to TC integration.
*/
public class TeamCityReporter extends NullStoryReporter {
private static final LookupTranslator ESCAPE_TABLE = new LookupTranslator(new String[][] {
{ "'", "|'" },
{ "\n", "|n" },
{ "\r", "|r" },
{ "\u", "|0x" },
{ "|", "||" },
{ "[", "|[" },
{ "]", "|]" }
});
private ThreadLocal<Story> story = new ThreadLocal<>();
private ThreadLocal<String> scenario = new ThreadLocal<>();
@Override
@SuppressWarnings("resource")
public void beforeStory(Story story, boolean givenStory) {
this.story.set(story);
this.scenario.set(null);
try {
startThreadOutputRedirect(new FileOutputStream(new File(workDirRelative(story.getPath()))));
} catch (FileNotFoundException e) {
throw new RuntimeException(e);
}
forceOut(format("##teamcity[testSuiteStarted name='%s']", escape(story.getPath())));
out.println(story.getPath());
super.beforeStory(story, givenStory);
}
@Override
public void afterStory(boolean givenStory) {
forceOut(format("##teamcity[testSuiteFinished name='%s']", escape(story.get().getPath())));
stopThreadOutputRedirect();
super.afterStory(givenStory);
}
@Override
public void beforeScenario(String scenario) {
this.scenario.set(scenario);
forceOut(format("##teamcity[testStarted name='%s']", escape(scenario)));
out.println(scenario);
super.beforeScenario(scenario);
}
@Override
public void afterScenario() {
forceOut(format("##teamcity[testFinished name='%s']", escape(scenario.get())));
this.scenario.set(null);
super.afterScenario();
}
@Override
public void beforeStep(String step) {
out.println(format("\n%s\n", step));
super.beforeStep(step);
}
@Override
public void storyNotAllowed(Story story, String filter) {
forceOut(format("##teamcity[message text='story not allowed %s' status='WARNING']", escape(story.getName())));
out.println(format("\n(Not allowed) %s\n", story.getPath()));
super.storyNotAllowed(story, filter);
}
@Override
public void failed(String step, Throwable cause) {
forceOut(format("##teamcity[testFailed name='%s' message='%s' details='%s']", new String[] { escape(scenario.get()), escape(getRootCauseMessage(cause)), escape(getStackTrace(cause)) }));
out.println(format("\n(Failed) %s\n", step));
cause.printStackTrace();
super.failed(step, cause);
}
@Override
public void pending(String step) {
forceOut(format("##teamcity[testFailed name='%s' message='Step in PENDING state: %s']", escape(scenario.get()), escape(step)));
out.println(format("\n(Pending) %s\n", step));
super.pending(step);
}
@Override
public void notPerformed(String step) {
out.println(format("\n(Not performed) %s\n", step));
super.notPerformed(step);
}
private static String escape(String string) {
return ESCAPE_TABLE.translate(string);
}
}
开启并行 JBehave 执行
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
...
})
public class Stories extends JUnitStories {
@Before
public void setUp() throws Exception {
configuredEmbedder()
// turn on parallel test execution
.useExecutorService(newFixedThreadPool(30, new ThreadFactoryBuilder()
.setDaemon(true)
.build()));
configuredEmbedder()
.embedderControls()
...
// don't use it this way not to produce multiThreading = true and delayed StoryReporter callbacks
// and you will see your application logging 'for each jbehave step'
// .useThreads(30);
}
@Override
public Configuration configuration() {
return new MostUsefulConfiguration()
...
.useStoryReporterBuilder(new StoryReporterBuilder()
...
.withFormats(HTML)
.withReporters(teamCityReporter));
}
}
因此,每个并行测试都会有一个日志文件,其中包含测试输出和应用程序输出(只有测试运行程序线程正在执行的代码)。
奖励 - TeamCityReporter(JBehave 到 TC 集成)将实时成功计算 运行 个并行测试,并在 TC GUI 上报告任何测试失败。将测试输出目录配置为 TC 工件路径以访问每个测试输出。
虽然很清楚 how to configure 多线程 jBehave 运行,
我不太清楚如何处理日志记录混乱。
这里有哪些选项?
将应用程序的输出重定向到标准输出(JBehave 的输出已经存在)。注意事项
follow=true
log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.follow=true log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} | %-5.5p | %-16.16t | %-32.32c{1} | %-64.64C %4L | %m%n log4j.rootLogger=error, stdout log4j.logger.com.company.app.interesting.module=debug ...
每个线程文件输出
@SuppressWarnings("resource") public class ThreadFileOutput extends PrintStream { private static ThreadLocal<FileOutputStream> threadOutput = new ThreadLocal<>(); private static PrintStream stdout = System.out; private static PrintStream stderr = System.err; static { System.setOut(new ThreadFileOutput(stdout)); System.setErr(new ThreadFileOutput(stderr)); } public ThreadFileOutput(OutputStream out) { super(out); } public static void startThreadOutputRedirect(FileOutputStream stream) { threadOutput.set(stream); } public static void stopThreadOutputRedirect() { FileOutputStream stream = threadOutput.get(); if (stream != null) { threadOutput.set(null); try { stream.close(); } catch (IOException e) { throw new RuntimeException(e); } } } public static void forceOut(String line) { stdout.println(line); } public static void forceErr(String line) { stderr.println(line); } @Override public void write(byte[] b) throws IOException { FileOutputStream stream = threadOutput.get(); if (stream != null) { try { stream.write(b); } catch (IOException e) { threadOutput.set(null); throw new RuntimeException(e); } } else { super.write(b); } } @Override public void write(int b) { FileOutputStream stream = threadOutput.get(); if (stream != null) { try { stream.write(b); } catch (IOException e) { threadOutput.set(null); throw new RuntimeException(e); } } else { super.write(b); } } @Override public void write(byte[] buf, int off, int len) { FileOutputStream stream = threadOutput.get(); if (stream != null) { try { stream.write(buf, off, len); } catch (IOException e) { threadOutput.set(null); throw new RuntimeException(e); } } else { super.write(buf, off, len); } } @Override public void flush() { FileOutputStream stream = threadOutput.get(); if (stream != null) { try { stream.flush(); } catch (IOException e) { threadOutput.set(null); throw new RuntimeException(e); } } else { super.flush(); } } }
测试前开始将线程输出重定向到文件,测试后停止
startThreadOutputRedirect(new FileOutputStream(new File(workDirRelative(story.getPath())))); stopThreadOutputRedirect();
在
/** * JBehave to TC integration. */ public class TeamCityReporter extends NullStoryReporter { private static final LookupTranslator ESCAPE_TABLE = new LookupTranslator(new String[][] { { "'", "|'" }, { "\n", "|n" }, { "\r", "|r" }, { "\u", "|0x" }, { "|", "||" }, { "[", "|[" }, { "]", "|]" } }); private ThreadLocal<Story> story = new ThreadLocal<>(); private ThreadLocal<String> scenario = new ThreadLocal<>(); @Override @SuppressWarnings("resource") public void beforeStory(Story story, boolean givenStory) { this.story.set(story); this.scenario.set(null); try { startThreadOutputRedirect(new FileOutputStream(new File(workDirRelative(story.getPath())))); } catch (FileNotFoundException e) { throw new RuntimeException(e); } forceOut(format("##teamcity[testSuiteStarted name='%s']", escape(story.getPath()))); out.println(story.getPath()); super.beforeStory(story, givenStory); } @Override public void afterStory(boolean givenStory) { forceOut(format("##teamcity[testSuiteFinished name='%s']", escape(story.get().getPath()))); stopThreadOutputRedirect(); super.afterStory(givenStory); } @Override public void beforeScenario(String scenario) { this.scenario.set(scenario); forceOut(format("##teamcity[testStarted name='%s']", escape(scenario))); out.println(scenario); super.beforeScenario(scenario); } @Override public void afterScenario() { forceOut(format("##teamcity[testFinished name='%s']", escape(scenario.get()))); this.scenario.set(null); super.afterScenario(); } @Override public void beforeStep(String step) { out.println(format("\n%s\n", step)); super.beforeStep(step); } @Override public void storyNotAllowed(Story story, String filter) { forceOut(format("##teamcity[message text='story not allowed %s' status='WARNING']", escape(story.getName()))); out.println(format("\n(Not allowed) %s\n", story.getPath())); super.storyNotAllowed(story, filter); } @Override public void failed(String step, Throwable cause) { forceOut(format("##teamcity[testFailed name='%s' message='%s' details='%s']", new String[] { escape(scenario.get()), escape(getRootCauseMessage(cause)), escape(getStackTrace(cause)) })); out.println(format("\n(Failed) %s\n", step)); cause.printStackTrace(); super.failed(step, cause); } @Override public void pending(String step) { forceOut(format("##teamcity[testFailed name='%s' message='Step in PENDING state: %s']", escape(scenario.get()), escape(step))); out.println(format("\n(Pending) %s\n", step)); super.pending(step); } @Override public void notPerformed(String step) { out.println(format("\n(Not performed) %s\n", step)); super.notPerformed(step); } private static String escape(String string) { return ESCAPE_TABLE.translate(string); } }
开启并行 JBehave 执行
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { ... }) public class Stories extends JUnitStories { @Before public void setUp() throws Exception { configuredEmbedder() // turn on parallel test execution .useExecutorService(newFixedThreadPool(30, new ThreadFactoryBuilder() .setDaemon(true) .build())); configuredEmbedder() .embedderControls() ... // don't use it this way not to produce multiThreading = true and delayed StoryReporter callbacks // and you will see your application logging 'for each jbehave step' // .useThreads(30); } @Override public Configuration configuration() { return new MostUsefulConfiguration() ... .useStoryReporterBuilder(new StoryReporterBuilder() ... .withFormats(HTML) .withReporters(teamCityReporter)); } }
因此,每个并行测试都会有一个日志文件,其中包含测试输出和应用程序输出(只有测试运行程序线程正在执行的代码)。
奖励 - TeamCityReporter(JBehave 到 TC 集成)将实时成功计算 运行 个并行测试,并在 TC GUI 上报告任何测试失败。将测试输出目录配置为 TC 工件路径以访问每个测试输出。