001/* 002 * Copyright © 2025 CUI-OpenSource-Software (info@cuioss.de) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package de.cuioss.benchmarking.common.util; 017 018import org.apache.commons.io.output.TeeOutputStream; 019 020import java.io.FileOutputStream; 021import java.io.IOException; 022import java.io.PrintStream; 023import java.nio.file.Files; 024import java.nio.file.Path; 025import java.time.LocalDateTime; 026import java.time.format.DateTimeFormatter; 027import java.util.logging.*; 028 029/** 030 * Centralized logging configuration for JMH benchmarks. 031 * Sets up logging to write to both console and a timestamped file in benchmark-results. 032 * 033 * <p>This class provides: 034 * <ul> 035 * <li>Dual output to console and file via TeeOutputStream</li> 036 * <li>Capture of all System.out and System.err output</li> 037 * <li>Timestamped log files in benchmark results directory</li> 038 * <li>Configurable java.util.logging levels</li> 039 * </ul> 040 * 041 */ 042@SuppressWarnings("java:S106") // System.out and System.err usage is intentional for logging setup 043public final class BenchmarkLoggingSetup { 044 045 private static final DateTimeFormatter TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss"); 046 private static final String ROOT_LOGGER_NAME = ""; 047 private static final String DE_PACKAGE = "de"; 048 private static final String DE_CUIOSS_PACKAGE = "de.cuioss"; 049 private static final String DE_CUIOSS_BENCHMARKING_PACKAGE = "de.cuioss.benchmarking"; 050 private static final String JMH_PACKAGE = "org.openjdk.jmh"; 051 052 // Keep references to original streams 053 private static final PrintStream ORIGINAL_OUT = System.out; 054 private static final PrintStream ORIGINAL_ERR = System.err; 055 056 // Keep reference to file output stream for proper cleanup 057 private static FileOutputStream currentFileOut = null; 058 059 private BenchmarkLoggingSetup() { 060 // Utility class 061 } 062 063 /** 064 * Configures logging to write to both console and a timestamped file in benchmark-results. 065 * Also redirects System.out and System.err to capture all console output. 066 * 067 * @param benchmarkResultsDir the directory where benchmark results are stored 068 */ 069 // cui-rewrite:disable CuiLoggerStandardsRecipe 070 // System.err is appropriate here as logging infrastructure is not yet set up 071 public static void configureLogging(String benchmarkResultsDir) { 072 // Prepare paths and directories 073 Path resultsPath = Path.of(benchmarkResultsDir); 074 try { 075 Files.createDirectories(resultsPath); 076 } catch (IOException e) { 077 System.err.println("Failed to create benchmark results directory: " + e.getMessage()); 078 System.err.println("Continuing with console-only logging"); 079 return; 080 } 081 082 String timestamp = LocalDateTime.now().format(TIMESTAMP_FORMAT); 083 String logFileName = "benchmark-run_%s.log".formatted(timestamp); 084 Path logFile = resultsPath.resolve(logFileName); 085 086 try { 087 // Close any previous file output stream 088 closeCurrentFileOut(); 089 090 // Create new file output stream - kept open for the duration of the benchmark 091 currentFileOut = new FileOutputStream(logFile.toFile(), true); 092 093 TeeOutputStream teeOut = new TeeOutputStream(ORIGINAL_OUT, currentFileOut); 094 PrintStream newOut = new PrintStream(teeOut, true); // auto-flush enabled 095 096 TeeOutputStream teeErr = new TeeOutputStream(ORIGINAL_ERR, currentFileOut); 097 PrintStream newErr = new PrintStream(teeErr, true); // auto-flush enabled 098 099 // Redirect System.out and System.err 100 System.setOut(newOut); 101 System.setErr(newErr); 102 103 // Configure java.util.logging 104 configureJavaUtilLogging(resultsPath, timestamp); 105 106 // Log configuration success 107 System.out.println("Benchmark logging configured - writing to: " + logFile); 108 System.out.println("All console output (System.out/err and JMH) will be captured to both console and file"); 109 110 } catch (IOException e) { 111 System.err.println("Failed to configure file logging: " + e.getMessage()); 112 System.err.println("Continuing with console-only logging"); 113 closeCurrentFileOut(); 114 } 115 } 116 117 private static void closeCurrentFileOut() { 118 if (currentFileOut != null) { 119 try { 120 currentFileOut.close(); 121 } catch (IOException e) { 122 // Ignore close errors 123 } finally { 124 currentFileOut = null; 125 } 126 } 127 } 128 129 130 private static void configureJavaUtilLogging(Path resultsPath, String timestamp) throws IOException { 131 Logger rootLogger = Logger.getLogger(ROOT_LOGGER_NAME); 132 133 Handler[] handlers = rootLogger.getHandlers(); 134 for (Handler handler : handlers) { 135 rootLogger.removeHandler(handler); 136 } 137 138 ConsoleHandler consoleHandler = new ConsoleHandler(); 139 consoleHandler.setLevel(Level.INFO); 140 consoleHandler.setFormatter(new SimpleFormatter()); 141 rootLogger.addHandler(consoleHandler); 142 143 String logFileName = "benchmark-jul_%s.log".formatted(timestamp); 144 Path julLogFile = resultsPath.resolve(logFileName); 145 FileHandler fileHandler = new FileHandler(julLogFile.toString(), true); 146 fileHandler.setLevel(Level.ALL); 147 fileHandler.setFormatter(new SimpleFormatter()); 148 rootLogger.addHandler(fileHandler); 149 150 rootLogger.setLevel(Level.INFO); 151 152 // Configure de.cuioss packages 153 configurePackageLogging(DE_PACKAGE, Level.INFO); 154 configurePackageLogging(DE_CUIOSS_PACKAGE, Level.INFO); 155 configurePackageLogging(DE_CUIOSS_BENCHMARKING_PACKAGE, Level.INFO); 156 157 // Disable verbose JMH internal logging 158 configurePackageLogging(JMH_PACKAGE, Level.WARNING); 159 } 160 161 private static void configurePackageLogging(String packageName, Level level) { 162 Logger.getLogger(packageName).setLevel(level); 163 } 164 165}