I solved the problem myself, not sure if my solution is the best, but anyway. I introduced these two methods:
/** Redirects all writing to System.out stream to the logger (logging messages at INFO level). */
public static void redirectSystemOut() {
System.setOut(new PrintStream(new LineReadingOutputStream(Logger::info), true));
}
/** Redirects all writing to System.err stream to the logger (logging messages at ERROR level). */
public static void redirectSystemErr() {
System.setErr(new PrintStream(new LineReadingOutputStream(Logger::error), true));
}
As far as LineReadingOutputStream class goes, I found it here: Java OutputStream reading lines of strings
One final element of this solution is creation of custom console writer, since the default one outputs everything to System.out / System.err, and that would create an infinite loop. The trick is to wrap the two streams and send strings to them. This is the code of my class that I call "IsolatedConsoleWriter" and has to be registered via META-INF (as described in the tinylog docs):
package com.betalord.sgx.core;
import org.tinylog.Level;
import org.tinylog.core.ConfigurationParser;
import org.tinylog.core.LogEntry;
import org.tinylog.core.LogEntryValue;
import org.tinylog.provider.InternalLogger;
import org.tinylog.writers.AbstractFormatPatternWriter;
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
/**
* Console writer based on org.tinylog.writers.ConsoleWriter class, with one difference: this one
* doesn't write logs to System.out/err but rather to our own streams, that output to the same system
* streams but wrapped. This enables us to redirect System.out/err to the logger (if we were to use
* normal console writer, we would create an infinite loop upon writing to System.out(err).
*
* Comes as a solution to this problem:
* <a href="https://stackoverflow.com/questions/75776644/how-to-redirect-system-out-and-system-err-to-tinylog-logger">https://stackoverflow.com/questions/75776644/how-to-redirect-system-out-and-system-err-to-tinylog-logger</a>
*
* @author Betalord
*/
public class IsolatedConsoleWriter extends AbstractFormatPatternWriter {
private final Level errorLevel;
private final PrintStream outStream, errStream;
public IsolatedConsoleWriter() {
this(Collections.<String, String>emptyMap());
}
public IsolatedConsoleWriter(final Map<String, String> properties) {
super(properties);
// Set the default level for stderr logging
Level levelStream = Level.WARN;
// Check stream property
String stream = getStringValue("stream");
if (stream != null) {
// Check whether we have the err@LEVEL syntax
String[] streams = stream.split("@", 2);
if (streams.length == 2) {
levelStream = ConfigurationParser.parse(streams[1], levelStream);
if (!streams[0].equals("err")) {
InternalLogger.log(Level.ERROR, "Stream with level must be \"err\", \"" + streams[0] + "\" is an invalid name");
}
stream = null;
}
}
if (stream == null) {
errorLevel = levelStream;
} else if ("err".equalsIgnoreCase(stream)) {
errorLevel = Level.TRACE;
} else if ("out".equalsIgnoreCase(stream)) {
errorLevel = Level.OFF;
} else {
InternalLogger.log(Level.ERROR, "Stream must be \"out\" or \"err\", \"" + stream + "\" is an invalid stream name");
errorLevel = levelStream;
}
outStream = new PrintStream(new FileOutputStream(FileDescriptor.out), true);
errStream = new PrintStream(new FileOutputStream(FileDescriptor.err), true);
}
@Override
public Collection<LogEntryValue> getRequiredLogEntryValues() {
Collection<LogEntryValue> logEntryValues = super.getRequiredLogEntryValues();
logEntryValues.add(LogEntryValue.LEVEL);
return logEntryValues;
}
@Override
public void write(final LogEntry logEntry) {
if (logEntry.getLevel().ordinal() < errorLevel.ordinal()) {
outStream.print(render(logEntry));
} else {
errStream.print(render(logEntry));
}
}
@Override
public void flush() {
outStream.flush();
errStream.flush();
}
@Override
public void close() {
outStream.close();
errStream.close();
}
}
So this writer is exact copy of ConsoleWriter, it add only two fields: errStream and outStream.
So, putting all of these elements together, I managed to achieve what I wanted - all System.out.println() and similar calls are rerouted to my logger, which formats all the data according to the defined rules (I actually use several writers - isolated console, as shown here, then the rolling file one, the "normal" file one, and also logcat, when I run my app under android).
If anyone comes up with a better solution, please let me know.
However I believe that the functionality that achieves what I want should be part of the tinylog library itself (rather than using custom hacks like this one).