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 }