View Javadoc
1   package de.spiritscorp.datasync.io;
2   
3   /*-
4    * 		Data Sync
5    *
6    * 		Copyright ©   2022    The Spirit
7    * 		@email                        thespirit@spiritscorp.network
8    *
9    * 		This program is free software; you can redistribute it and/or modify
10   * 		it under the terms of the GNU General Public License as published by
11   * 		the Free Software Foundation; either version 3 of the License, or
12   * 		(at your option) any later version.
13   *
14   * 		This program is distributed in the hope that it will be useful,
15   * 		but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * 		MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17   * 		See the GNU General Public License for more details.
18   *
19   * 		You should have received a copy of the GNU General Public License
20   * 		along with this program. If not, see <http://www.gnu.org/licenses/>.
21   */
22  
23  import java.io.IOException;
24  import java.io.PrintStream;
25  import java.io.PrintWriter;
26  import java.io.StringWriter;
27  import java.nio.charset.StandardCharsets;
28  import java.nio.file.Files;
29  import java.nio.file.StandardOpenOption;
30  import java.time.LocalDateTime;
31  import java.time.ZoneId;
32  import java.time.format.DateTimeFormatter;
33  
34  import de.spiritscorp.datasync.Main;
35  
36  /**
37   * Utility class providing centralized logging capabilities for application diagnostics,
38   * errors, and exception tracking. All outputs are automatically enriched with a timestamp
39   * and the active application instance name.
40   */
41  public final class Debug {
42  
43  	/**
44  	 * The unique name of this application instance, used as a prefix in log outputs.
45  	 * <p>
46  	 * The value is dynamically retrieved from the system property {@code app.instance.name} at startup.
47  	 * If this property is not defined, it falls back to the default value "Standard Instance".
48  	 * </p>
49  	 */
50  	private static final String INSTANCE_NAME = System.getProperty( "app.instance.name", "Standard Instance" );
51  
52  	private Debug() {
53  	}
54  
55  	/**
56  	 * Writes a formatted debugging message to the standard output stream (stdout).
57  	 * The output is automatically prefixed with a current timestamp and the running instance name.
58  	 *
59  	 * @param format A format string as described in {@link java.util.Formatter} syntax
60  	 * @param args   Arguments referenced by the format specifiers in the format string. If there are more arguments than format specifiers,
61  	 *               the extra arguments are ignored. The number of arguments is variable and may be zero. The maximum number of arguments is limited
62  	 *               by the maximum dimension of a Java array as defined by The Java Virtual Machine Specification.
63  	 */
64  	public static void printDebug( final String format, final Object... args ) {
65  		print( System.out, format, args );
66  	}
67  
68  	/**
69  	 * Writes a formatted error message to the standard error stream (stderr).
70  	 * The output is automatically prefixed with a current timestamp and the running instance name.
71  	 *
72  	 * @param format A format string as described in {@link java.util.Formatter} syntax
73  	 * @param args   Arguments referenced by the format specifiers in the format string. If there are more arguments than format specifiers,
74  	 *               the extra arguments are ignored. The number of arguments is variable and may be zero. The maximum number of arguments is limited
75  	 *               by the maximum dimension of a Java array as defined by The Java Virtual Machine Specification.
76  	 */
77  	public static void printError( final String format, final Object... args ) {
78  		print( System.err, format, args );
79  	}
80  
81  	/**
82  	 * Logs or prints detailed diagnostic information about a caught exception.
83  	 * <p>
84  	 * This utility method formats and outputs the primary exception message,
85  	 * inspects and logs the root cause if present to prevent {@link NullPointerException},
86  	 * and extracts the complete formatted stack trace for deep debugging.
87  	 * <p>
88  	 * *
89  	 * <b>Example Output Structure:</b>
90  	 *
91  	 * <pre>
92  	 * Exception in Class: [com.example.MyService]:Message -> Connection failed
93  	 * ↳ Cause: java.net.ConnectException -> Message: Connection refused
94  	 * Full Info:
95  	 * java.lang.RuntimeException: Connection failed
96  	 * at com.example.MyService.start(MyService.java:24)
97  	 * ...
98  	 * <p>
99  	 *
100 	 * @param clazz     the {@link Class} context where the exception was caught, used for identification
101 	 * @param exception the {@link Exception} instance containing the error details and stack trace
102 	 */
103 	public static void printException( final Class<?> clazz, final Exception exception ) {
104 		printError( "Exception in Class: [%s]: Message -> %s", clazz.getName(), exception.getMessage() );
105 		if( exception.getCause() != null ) {
106 			printError( "  ↳ Cause: %s -> Message: %s", exception.getCause().getClass().getName(), exception.getCause().getMessage() );
107 		}
108 		final StringWriter writer = new StringWriter();
109 		exception.printStackTrace( new PrintWriter( writer ) );
110 		printError( "Full Info:%n%s", writer.toString() );
111 	}
112 
113 	/**
114 	 * A debug method to write a formatted string to this output stream using the specified format string and arguments.
115 	 *
116 	 * @param format A format string as described in {@link java.util.Formatter} syntax
117 	 * @param args   Arguments referenced by the format specifiers in the format string. If there are more arguments than format specifiers,
118 	 *               the extra arguments are ignored. The number of arguments is variable and may be zero. The maximum number of arguments is limited
119 	 *               by the maximum dimension of a Java array as defined by The Java Virtual Machine Specification.
120 	 */
121 	@SuppressWarnings( { "java:S3457", "java:S106" } )
122 	public static void printDebugTimeless( final String format, final Object... args ) {
123 		if( Main.isDebug() ) {
124 			System.out.printf( format + "%n", args ); // NOPMD
125 		}
126 	}
127 
128 	/**
129 	 * Redirects diagnostic logging output streams to a local file.
130 	 * Once invoked, messages processed by this utility will be appended to the designated
131 	 * log file infrastructure instead of solely printing to the console.
132 	 */
133 	@SuppressWarnings( {
134 			"PMD.SystemPrintln", "PMD.AvoidPrintStackTrace", // PMD
135 			"java:S106", "java:S4507" // SonarLint
136 	} )
137 	public static void setDebugToFile() {
138 		try {
139 			if( !Files.exists( PreferenceManager.getInstance().getDebugPath() ) ) Files.createDirectories( PreferenceManager.getInstance().getDebugPath().getParent() );
140 			System.setOut( new PrintStream(
141 					Files.newOutputStream(
142 							PreferenceManager.getInstance().getDebugPath(),
143 							StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND ),
144 					true, StandardCharsets.UTF_8 ) );
145 			System.setErr( new PrintStream(
146 					Files.newOutputStream(
147 							PreferenceManager.getInstance().getErrorPath(),
148 							StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND ),
149 					true, StandardCharsets.UTF_8 ) );
150 		}catch( final IOException e ) {
151 			System.err.println( e.getMessage() );
152 			e.printStackTrace();
153 		}
154 	}
155 
156 	private static void print( final PrintStream stream, final String format, final Object... args ) {
157 		if( Main.isDebug() ) {
158 			final String time = LocalDateTime.now( ZoneId.systemDefault() ).format( DateTimeFormatter.ofPattern( "dd-MM-yyyy HH:mm:ss.SSSSS" ) );
159 			final String message = String.format( format, args );
160 			stream.printf( "%s [ %s ] %s%n", time, INSTANCE_NAME, message );
161 		}
162 	}
163 }