本文介绍了多线程JBehave日志记录混乱的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

虽然很清楚如何配置多线程jBehave运行,
我还不清楚如何处理伐木混乱.

While it is qute clear how to configure multi-threaded jBehave run,
it is not qute clear for me how to deal with logging mess.

这里有什么选择?

推荐答案

  1. 重新将应用程序的输出输出到标准输出(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();
            }
        }
    }
    

  • 开始将线程输出重定向到测试之前的文件,并在测试之后停止

  • Start redirecting thread output to the file befor the test and stop it after the test

    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执行

  • Turn on parallel JBehave execution

    @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(与TC集成的JBehave)将成功地实时计算正在运行的并行测试,并在TC GUI上报告任何测试失败.将测试输出目录配置为TC工件路径,以访问每个测试输出.

    As a result, there will be a log file for each parallel test having both test output and application output (only the code being executed by test runner thread).
    Bonus - TeamCityReporter (JBehave to TC integration) will successfully count running parallel tests in real time and report any test failures on TC GUI. Configure test output directory as TC artifact path to access each test output.

    这篇关于多线程JBehave日志记录混乱的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

    06-01 09:41