use thread name formtting as simple formatter for default, remove png logging

This commit is contained in:
Jörg Prante 2024-07-06 22:36:57 +02:00
parent 81f3cc723d
commit b2ec110f85
10 changed files with 138 additions and 478 deletions

View file

@ -72,7 +72,7 @@ public final class LogContext implements AutoCloseable {
}
static {
final HashMap<String, Reference<Level, Void>> map = new HashMap<String, Reference<Level, Void>>();
final HashMap<String, Reference<Level, Void>> map = new HashMap<>();
addStrong(map, Level.OFF);
addStrong(map, Level.ALL);
addStrong(map, Level.SEVERE);

View file

@ -8,6 +8,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.ServiceConfigurationError;
import java.util.ServiceLoader;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BiFunction;
import java.util.function.Function;
@ -22,12 +23,17 @@ public final class LogManager extends java.util.logging.LogManager {
static {
try {
// Ensure the StandardOutputStreams are initialized early to capture the current System.out and System.err.
Class.forName(StandardOutputStreams.class.getName());
ClassLoader.getSystemClassLoader().loadClass(StandardOutputStreams.class.getName());
} catch (ClassNotFoundException ignore) {
// ignore
}
}
private static final AtomicBoolean CONFIGURED = new AtomicBoolean();
// Configuration
private final AtomicReference<LogContextConfigurator> configuratorRef = new AtomicReference<>();
/**
* Construct a new logmanager instance. Attempts to plug a known memory leak in {@link java.util.logging.Level} as
* well.
@ -35,25 +41,118 @@ public final class LogManager extends java.util.logging.LogManager {
public LogManager() {
}
// Configuration
private final AtomicReference<LogContextConfigurator> configuratorRef = new AtomicReference<>();
/**
* Configure the system log context initially.
*/
@Override
public void readConfiguration() {
if (CONFIGURED.compareAndSet(false, true)) {
doConfigure(null);
}
}
/**
* Configure the system log context initially withe given input stream.
* Configure the system log context initially with a given input stream.
*
* @param inputStream ignored
* @param inputStream the input stream
*/
@Override
public void readConfiguration(InputStream inputStream) {
if (CONFIGURED.compareAndSet(false, true)) {
doConfigure(inputStream);
}
}
/**
* Does nothing.
*
* @param mapper not used
*/
@Override
public void updateConfiguration(final Function<String, BiFunction<String, String, String>> mapper) throws IOException {
// no operation - the configuration API should be used
}
/**
* Does nothing.
*
* @param ins not used
* @param mapper not used
*/
@Override
public void updateConfiguration(final InputStream ins, final Function<String, BiFunction<String, String, String>> mapper)
throws IOException {
// no operation - the configuration API should be used
}
/**
* Configuration listeners are not currently supported.
*
* @param listener not used
* @return this log manager
*/
@Override
public java.util.logging.LogManager addConfigurationListener(final Runnable listener) {
// no operation
return this;
}
/**
* Configuration listeners are not currently supported.
*
* @param listener not used
*/
@Override
public void removeConfigurationListener(final Runnable listener) {
// no operation
}
/**
* Does nothing. Properties are not supported.
*
* @param name ignored
* @return {@code null}
*/
@Override
public String getProperty(String name) {
// no properties
return null;
}
/**
* Does nothing. This method only causes trouble.
*/
@Override
public void reset() {
// no operation!
}
@Override
public Enumeration<String> getLoggerNames() {
return LogContext.getLogContext().getLoggerNames();
}
/**
* Do nothing. Loggers are only added/acquired via {@link #getLogger(String)}.
*
* @param logger ignored
* @return {@code false}
*/
@Override
public boolean addLogger(java.util.logging.Logger logger) {
return false;
}
/**
* Get or create a logger with the given name.
*
* @param name the logger name
* @return the corresponding logger
*/
@Override
public Logger getLogger(String name) {
return LogContext.getLogContext().getLogger(name);
}
private void doConfigure(InputStream inputStream) {
final AtomicReference<LogContextConfigurator> configuratorRef = this.configuratorRef;
@ -100,87 +199,4 @@ public final class LogManager extends java.util.logging.LogManager {
}
configurator.configure(LogContext.getSystemLogContext(), inputStream);
}
/**
* Does nothing.
*
* @param mapper not used
*/
public void updateConfiguration(final Function<String, BiFunction<String, String, String>> mapper) throws IOException {
// no operation the configuration API should be used
}
/**
* Does nothing.
*
* @param ins not used
* @param mapper not used
*/
public void updateConfiguration(final InputStream ins, final Function<String, BiFunction<String, String, String>> mapper)
throws IOException {
// no operation the configuration API should be used
}
/**
* Configuration listeners are not currently supported.
*
* @param listener not used
* @return this log manager
*/
public java.util.logging.LogManager addConfigurationListener(final Runnable listener) {
// no operation
return this;
}
/**
* Configuration listeners are not currently supported.
*
* @param listener not used
*/
public void removeConfigurationListener(final Runnable listener) {
// no operation
}
/**
* Does nothing. Properties are not supported.
*
* @param name ignored
* @return {@code null}
*/
public String getProperty(String name) {
// no properties
return null;
}
/**
* Does nothing. This method only causes trouble.
*/
public void reset() {
// no operation!
}
@Override
public Enumeration<String> getLoggerNames() {
return LogContext.getLogContext().getLoggerNames();
}
/**
* Do nothing. Loggers are only added/acquired via {@link #getLogger(String)}.
*
* @param logger ignored
* @return {@code false}
*/
public boolean addLogger(java.util.logging.Logger logger) {
return false;
}
/**
* Get or create a logger with the given name.
*
* @param name the logger name
* @return the corresponding logger
*/
public Logger getLogger(String name) {
return LogContext.getLogContext().getLogger(name);
}
}

View file

@ -47,8 +47,8 @@ public class LoggerFinder extends System.LoggerFinder {
final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(name);
if (!logger.getClass().getName().equals("org.xbib.logging.Logger")) {
if (LOGGED.compareAndSet(false, true)) {
logger.log(Level.ERROR,
"The LogManager accessed before the \"java.util.logging.manager\" system property was set to \"org.xbib.loggingr.LogManager\". Results may be unexpected.");
logger.log(Level.WARN,
"The LogManager accessed before the \"java.util.logging.manager\" system property was set to \"org.xbib.logging.LogManager\". Results may be unexpected.");
}
}
return new SystemLogger(logger);

View file

@ -26,7 +26,6 @@ class ObjectBuilder<T> {
private final Map<String, String> properties;
private final Set<PropertyValue> definedProperties;
private final Set<String> postConstructMethods;
private String moduleName;
private ObjectBuilder(final ContextConfiguration contextConfiguration,
final Class<? extends T> baseClass, final String className) {
@ -113,7 +112,6 @@ class ObjectBuilder<T> {
* @return this builder
*/
ObjectBuilder<T> setModuleName(final String moduleName) {
this.moduleName = moduleName;
return this;
}
@ -133,7 +131,7 @@ class ObjectBuilder<T> {
final ClassLoader classLoader = getClass().getClassLoader();
final Class<? extends T> actualClass;
try {
actualClass = Class.forName(className, true, classLoader).asSubclass(baseClass);
actualClass = classLoader.loadClass(className).asSubclass(baseClass);
} catch (Exception e) {
throw new IllegalArgumentException(String.format("Failed to load class \"%s\"", className), e);
}

View file

@ -13,7 +13,7 @@ import java.util.logging.Logger;
import org.xbib.logging.Level;
import org.xbib.logging.LogContext;
import org.xbib.logging.LogContextConfigurator;
import org.xbib.logging.formatters.PatternFormatter;
import org.xbib.logging.formatters.SimpleFormatter;
import org.xbib.logging.handlers.ConsoleHandler;
import org.xbib.logging.util.StandardOutputStreams;
@ -60,11 +60,11 @@ public class PropertyLogContextConfigurator implements LogContextConfigurator {
final Iterator<LogContextConfigurator> serviceLoader =
ServiceLoader.load(LogContextConfigurator.class, PropertyLogContextConfigurator.class.getClassLoader()).iterator();
if (serviceLoader.hasNext()) {
serviceLoader.next().configure(context, null);
LogContextConfigurator logContextConfigurator = serviceLoader.next();
logContextConfigurator.configure(context, null);
} else {
// Configure a default console handler, pattern formatter and associated with the root logger
final ConsoleHandler handler =
new ConsoleHandler(new PatternFormatter("%d{yyyy-MM-dd'T'HH:mm:ssXXX} %-5p [%c] (%t) %s%e%n"));
// Configure a default console handler, thread formatter and associated with the root logger
final ConsoleHandler handler = new ConsoleHandler(new SimpleFormatter());
handler.setLevel(Level.INFO);
handler.setAutoFlush(true);
final Logger rootLogger = context.getLogger("");
@ -75,7 +75,7 @@ public class PropertyLogContextConfigurator implements LogContextConfigurator {
}
private static InputStream findConfiguration() {
final String propLoc = System.getProperty("logging.configuration");
final String propLoc = System.getProperty("org.xbib.logging.configuration");
if (propLoc != null) {
try {
return URI.create(propLoc).toURL().openStream();
@ -83,10 +83,9 @@ public class PropertyLogContextConfigurator implements LogContextConfigurator {
StandardOutputStreams.printError("Unable to read the logging configuration from '%s' (%s)%n", propLoc, e);
}
}
final ClassLoader cl = PropertyLogContextConfigurator.class.getClassLoader();
try {
return cl.getResourceAsStream("logging.properties");
} catch (Exception ignore) {
return PropertyLogContextConfigurator.class.getClassLoader().getResourceAsStream("logging.properties");
} catch (Exception e) {
return null;
}
}

View file

@ -10,16 +10,16 @@ import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import org.xbib.logging.util.StackTraceFormatter;
public class ThreadLoggingFormatter extends Formatter {
public class SimpleFormatter extends Formatter {
private static final String PROPERTY_KEY = "org.xbib.interlibrary.jul.ThreadLoggingFormatter.format";
private static final String PROPERTY_KEY = "org.xbib.logging.formatters.SimpleFormatter.format";
private static final String DEFAULT_FORMAT =
"%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS.%1$tL %4$-7s [%3$s] [%5$s] %6$s %7$s%n";
private final ThreadMXBean threadMXBean;
public ThreadLoggingFormatter() {
public SimpleFormatter() {
this.threadMXBean = ManagementFactory.getThreadMXBean();
}
@ -56,7 +56,7 @@ public class ThreadLoggingFormatter extends Formatter {
return LogManager.getLogManager().getProperty(name);
}
private static final String DEFAULT_FORMAT_STRING = getFormat(ThreadLoggingFormatter::getLoggingProperty);
private static final String DEFAULT_FORMAT_STRING = getFormat(SimpleFormatter::getLoggingProperty);
private static String getFormat(Function<String, String> defaultPropertyGetter) {
String format = System.getProperty(PROPERTY_KEY);

View file

@ -1,14 +1,9 @@
package org.xbib.logging.handlers;
import java.io.Console;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Objects;
import java.util.logging.ErrorManager;
import java.util.logging.Formatter;
import org.xbib.logging.errormanager.HandlerErrorManager;
@ -136,83 +131,6 @@ public class ConsoleHandler extends OutputStreamHandler {
private static final String ESC = Character.toString(27);
/**
* Write a PNG image to the console log, if it is supported.
* The image data stream must be closed by the caller.
*
* @param imageData the PNG image data stream to write (must not be {@code null})
* @param columns the number of text columns to occupy (0 for automatic)
* @param rows the number of text rows to occupy (0 for automatic)
* @return {@code true} if the image was written, or {@code false} if image support isn't found
* @throws IOException if the stream failed while writing the image
*/
public boolean writeImagePng(InputStream imageData, int columns, int rows) throws IOException {
Objects.requireNonNull(imageData, "imageData");
columns = Math.max(0, columns);
rows = Math.max(0, rows);
if (!isGraphicsSupportPassivelyDetected()) {
// no graphics
return false;
}
lock.lock();
try {
// clear out any pending stuff
final Writer writer = getWriter();
if (writer == null)
return false;
// start with the header
try (OutputStream os = Base64.getEncoder().wrap(new OutputStream() {
final byte[] buffer = new byte[2048];
int pos = 0;
public void write(final int b) throws IOException {
if (pos == buffer.length)
more();
buffer[pos++] = (byte) b;
}
public void write(final byte[] b, int off, int len) throws IOException {
while (len > 0) {
if (pos == buffer.length) {
more();
}
final int cnt = Math.min(len, buffer.length - pos);
System.arraycopy(b, off, buffer, pos, cnt);
pos += cnt;
off += cnt;
len -= cnt;
}
}
void more() throws IOException {
writer.write("m=1;");
writer.write(new String(buffer, 0, pos, StandardCharsets.US_ASCII));
writer.write(ESC + "\\");
// set up next segment
writer.write(ESC + "_G");
pos = 0;
}
public void close() throws IOException {
writer.write("m=0;");
writer.write(new String(buffer, 0, pos, StandardCharsets.US_ASCII));
writer.write(ESC + "\\\n");
writer.flush();
pos = 0;
}
})) {
// set the header
writer.write(String.format(ESC + "_Gf=100,a=T,c=%d,r=%d,", Integer.valueOf(columns), Integer.valueOf(rows)));
// write the data in encoded chunks
imageData.transferTo(os);
}
// OK
return true;
} finally {
lock.unlock();
}
}
/**
* Get the local error manager. This is an error manager that will publish errors to this console handler.
* The console handler itself should not use this error manager.
@ -264,24 +182,4 @@ public class ConsoleHandler extends OutputStreamHandler {
final String colorterm = System.getenv("COLORTERM");
return colorterm != null && (colorterm.contains("truecolor") || colorterm.contains("24bit"));
}
/**
* Determine whether the console can be passively detected to support graphical output.
* This call may be expensive, so the result should be captured for the lifetime of any formatter making use of
* this information.
*
* @return {@code true} if the console exists and supports graphical output; {@code false} otherwise or if
* graphical support cannot be passively detected
*/
public static boolean isGraphicsSupportPassivelyDetected() {
if (!hasConsole()) {
return false;
}
final String term = System.getenv("TERM");
final String termProgram = System.getenv("TERM_PROGRAM");
return term != null && (term.equalsIgnoreCase("kitty")
|| term.equalsIgnoreCase("xterm-kitty")
|| term.equalsIgnoreCase("wezterm")
|| term.equalsIgnoreCase("konsole")) || termProgram != null && termProgram.equalsIgnoreCase("wezterm");
}
}

View file

@ -1,25 +0,0 @@
package org.xbib.logging.handlers;
import java.util.logging.LogRecord;
/**
* Replacement for java.util.logging.ConsoleHandler.
*/
public class ThreadLoggingHandler extends ThreadLoggingStreamHandler {
public ThreadLoggingHandler() {
super();
this.setOutputStream(System.out);
}
@Override
public void publish(LogRecord record) {
super.publish(record);
this.flush();
}
@Override
public void close() {
this.flush();
}
}

View file

@ -1,220 +0,0 @@
package org.xbib.logging.handlers;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.io.Writer;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import org.xbib.logging.formatters.ThreadLoggingFormatter;
/**
* Replacement for java.util.logging.StreamHandler that is wired to the ThreadLoggingFormatter
* and does not use any fall-back formatter
*/
public class ThreadLoggingStreamHandler extends Handler {
private static final ThreadLoggingFormatter formatter = new ThreadLoggingFormatter();
private final ReentrantLock lock;
private OutputStream output;
private boolean doneHeader;
private volatile Writer writer;
public ThreadLoggingStreamHandler() {
super();
this.lock = this.initLocking();
setLevel(Level.ALL);
setFilter(null);
setFormatter(formatter);
try {
setEncoding("UTF-8");
} catch (UnsupportedEncodingException e) {
// can't happen
}
}
protected void setOutputStream(OutputStream out) {
if (this.tryUseLock()) {
try {
this.setOutputStream0(out);
} finally {
this.unlock();
}
} else {
synchronized (this) {
this.setOutputStream0(out);
}
}
}
private void setOutputStream0(OutputStream out) {
if (out == null) {
throw new NullPointerException();
} else {
this.flushAndClose();
this.output = out;
this.doneHeader = false;
String encoding = this.getEncoding();
if (encoding == null) {
this.writer = new OutputStreamWriter(this.output);
} else {
try {
this.writer = new OutputStreamWriter(this.output, encoding);
} catch (UnsupportedEncodingException e) {
throw new Error("Unexpected exception " + e);
}
}
}
}
@Override
public void setEncoding(String encoding) throws UnsupportedEncodingException {
if (this.tryUseLock()) {
try {
this.setEncoding0(encoding);
} finally {
this.unlock();
}
} else {
synchronized (this) {
this.setEncoding0(encoding);
}
}
}
private void setEncoding0(String encoding) throws UnsupportedEncodingException {
super.setEncoding(encoding);
if (this.output != null) {
this.flush();
if (encoding == null) {
this.writer = new OutputStreamWriter(this.output);
} else {
this.writer = new OutputStreamWriter(this.output, encoding);
}
}
}
@Override
public void publish(LogRecord record) {
if (this.tryUseLock()) {
try {
this.publish0(record);
} finally {
this.unlock();
}
} else {
synchronized (this) {
this.publish0(record);
}
}
}
private void publish0(LogRecord record) {
if (this.isLoggable(record)) {
String msg;
try {
msg = this.getFormatter().format(record);
} catch (Exception e) {
this.reportError(null, e, 5);
return;
}
try {
if (!this.doneHeader) {
this.writer.write(this.getFormatter().getHead(this));
this.doneHeader = true;
}
this.writer.write(msg);
} catch (Exception e) {
this.reportError(null, e, 1);
}
}
}
@Override
public boolean isLoggable(LogRecord record) {
return this.writer != null && record != null && super.isLoggable(record);
}
@Override
public void flush() {
if (this.tryUseLock()) {
try {
this.flush0();
} finally {
this.unlock();
}
} else {
synchronized (this) {
this.flush0();
}
}
}
private void flush0() {
if (this.writer != null) {
try {
this.writer.flush();
} catch (Exception e) {
this.reportError(null, e, 2);
}
}
}
private void flushAndClose() {
if (this.writer != null) {
try {
if (!this.doneHeader) {
this.writer.write(this.getFormatter().getHead(this));
this.doneHeader = true;
}
this.writer.write(this.getFormatter().getTail(this));
this.writer.flush();
this.writer.close();
} catch (Exception e) {
this.reportError(null, e, 3);
}
this.writer = null;
this.output = null;
}
}
@Override
public void close() {
if (this.tryUseLock()) {
try {
this.flushAndClose();
} finally {
this.unlock();
}
} else {
synchronized (this) {
this.flushAndClose();
}
}
}
private ReentrantLock initLocking() {
Class<?> clazz = this.getClass();
ClassLoader loader = clazz.getClassLoader();
return loader != null && loader != ClassLoader.getPlatformClassLoader() ? null : new ReentrantLock();
}
private boolean tryUseLock() {
if (this.lock == null) {
return false;
} else {
this.lock.lock();
return true;
}
}
private void unlock() {
this.lock.unlock();
}
}

View file

@ -7,6 +7,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.logging.Formatter;
import java.util.logging.Handler;
import java.util.logging.LogRecord;
@ -29,11 +30,11 @@ public final class HandlerTests {
@Test
public void testNullHandler() {
final ExtHandler handler = new ExtHandler() {
};
try (final ExtHandler handler = new ExtHandler() {}) {
handler.setLevel(Level.ALL);
handler.publish(new ExtLogRecord(Level.INFO, "Test message", null));
}
}
private void initHandler(ExtHandler handler) throws UnsupportedEncodingException {
handler.setFormatter(testFormatter);
@ -67,7 +68,7 @@ public final class HandlerTests {
final ByteArrayOutputStream stream = new ByteArrayOutputStream();
handler.setOutputStream(stream);
testPublish(handler);
assertEquals("Test message", new String(stream.toByteArray(), "utf-8"));
assertEquals("Test message", stream.toString(StandardCharsets.UTF_8));
}
@Test
@ -85,20 +86,13 @@ public final class HandlerTests {
handler.setFile(tempFile);
testPublish(handler);
handler.close();
final ByteArrayOutputStream os = new ByteArrayOutputStream();
try {
final FileInputStream is = new FileInputStream(tempFile);
try {
try (ByteArrayOutputStream os = new ByteArrayOutputStream();
FileInputStream is = new FileInputStream(tempFile)) {
int r;
while ((r = is.read()) != -1)
os.write(r);
assertEquals("Test message", new String(os.toByteArray(), "utf-8"));
assertEquals("Test message", os.toString(StandardCharsets.UTF_8));
tempFile.deleteOnExit();
} finally {
is.close();
}
} finally {
os.close();
}
} finally {
tempFile.delete();
@ -106,7 +100,7 @@ public final class HandlerTests {
}
@Test
public void testEnableDisableHandler() throws Throwable {
public void testEnableDisableHandler() {
final StringListHandler handler = new StringListHandler();
testPublish(handler);
assertEquals(1, handler.size());