Skip to content

Commit

Permalink
type of out stream - stdout/err/auto can be configured per each writer (
Browse files Browse the repository at this point in the history
  • Loading branch information
elf4j committed Mar 17, 2023
1 parent 1ba737b commit 0336b62
Show file tree
Hide file tree
Showing 16 changed files with 91 additions and 50 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
The Java library portion of [elf4j-impl](https://github.com/elf4j/elf4j-impl), where this library will be packaged via
the Java [Service Provider Framework](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) mechanism
to make a native logging service provider implementation of the [ELF4J](https://github.com/elf4j/) (Easy Logging Facade
for Java) SPI.
for Java) SPI. See [elf4j-impl](https://github.com/elf4j/elf4j-impl) for description.

See [elf4j-impl](https://github.com/elf4j/elf4j-impl) for description.
Although directly implementing the [ELF4J](https://github.com/elf4j/elf4j) API, this POJO library is designed for ease
of adaptation to serve as the engine of any logging API.
38 changes: 24 additions & 14 deletions src/main/java/elf4j/impl/core/NativeLogger.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,30 +35,40 @@
import javax.annotation.concurrent.ThreadSafe;

/**
*
* Instances of this class are thread-safe, and can be used as class, instance, or local variables. It is recommended,
* however, to use class variables for instances that are returned by the static factory method
* {@link Logger#instance()}, as those instances are more expensive to create. Other instances returned by the
* fluent-style factory methods, such as {@link Logger#atError()}, are inexpensive to create and can be used (and
* discarded) in-line or as convenient.
*/
@ThreadSafe
@Value
public class NativeLogger implements Logger {
/**
* Taken from the same name of this logger's "owner class" - the logging service client class that created this
* logger instance via the service access API. The owner class is usually also the same as the "caller class" - the
* logging service client class that calls the logging service API methods of this instance. In rare and
* unrecommended scenarios, the owner class can be different from the caller class i.e. the owner class could pass a
* reference of this logger instance out to a different/caller class. Once set, though, the name of the logger will
* not change even when the owner class is different from the caller class.
* Name of this logger's "owner class" - the logging service client class that first requested for this logger
* instance via the {@link Logger#instance()} service access method. The owner class is usually the same as the
* "caller class" - the client class that calls the service interface methods, such as {@link Logger#log(Object)}.
* <p></p>
* In rare and not-recommended scenarios, the owner class can be different from the caller class: e.g. the owner
* class could pass a reference of this logger instance out to a different/caller class. Once set, though, the value
* of this field will never change even when the owner class is different from the caller.
* <p></p>
* In general, this native ELF4J implementation assumes owner and caller class to be the same.
*/
@NonNull String name;
@NonNull String ownerClassName;
@NonNull Level level;
@EqualsAndHashCode.Exclude @NonNull LogService logService;

/**
* @param name logger name, same as that of the owner class that created this logger instance
* @param level severity level of this logger instance
* @param logService service delegate to do the logging
* Constructor only meant to be used by {@link NativeLoggerFactory} and this class itself
*
* @param ownerClassName name of the owner class that requested this instance via the {@link Logger#instance()}
* method
* @param level severity level of this logger instance
* @param logService service delegate to do the logging
*/
public NativeLogger(@NonNull String name, @NonNull Level level, @NonNull LogService logService) {
this.name = name;
public NativeLogger(@NonNull String ownerClassName, @NonNull Level level, @NonNull LogService logService) {
this.ownerClassName = ownerClassName;
this.level = level;
this.logService = logService;
}
Expand Down Expand Up @@ -128,7 +138,7 @@ public void log(Throwable t, String message, Object... args) {
* @return logger instance of the same name, with the specified level
*/
public NativeLogger atLevel(Level level) {
return this.level == level ? this : new NativeLogger(this.name, level, logService);
return this.level == level ? this : new NativeLogger(this.ownerClassName, level, logService);
}

private void service(Throwable exception, Object message, Object[] args) {
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/elf4j/impl/core/NativeLoggerFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,11 @@ public NativeLoggerFactory(@NonNull Class<?> loggingServiceAccessInterface) {
this.logService = logService;
}

/**
* A bit heavy as it uses stack trace to locate the client class (owner class) requesting the Logger instance.
*
* @return new instance of {@link NativeLogger}
*/
@Override
public NativeLogger logger() {
return new NativeLogger(StackTraceUtils.callerOf(this.loggingServiceAccessInterface).getClassName(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import elf4j.Level;
import elf4j.impl.core.NativeLogger;
import elf4j.impl.core.util.InternalLogger;
import elf4j.impl.core.writer.LogWriter;
import lombok.NonNull;

Expand Down Expand Up @@ -92,7 +93,7 @@ private boolean loadLoggerConfigurationCache(NativeLogger nativeLogger) {
private void setRepositories(@NonNull Properties properties) {
this.noop = Boolean.parseBoolean(properties.getProperty("noop"));
if (this.noop) {
System.err.println("ELF4J status: No-op per configuration");
InternalLogger.log(Level.WARN, "No-op per configuration");
}
this.levelRepository = new LevelRepository(properties);
this.writerRepository = new WriterRepository(properties);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ public LevelRepository(Properties properties) {
* @return configured min output level for the specified logger
*/
public Level getLoggerMinimumLevel(NativeLogger nativeLogger) {
String callerClassName = nativeLogger.getName();
String callerClassName = nativeLogger.getOwnerClassName();
int rootPackageLength = callerClassName.indexOf('.');
if (rootPackageLength == -1) {
rootPackageLength = callerClassName.length();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@

package elf4j.impl.core.configuration;

import elf4j.Level;
import elf4j.impl.core.util.InternalLogger;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
Expand Down Expand Up @@ -53,7 +56,7 @@ public Properties load() {
if (customPropertiesLocation == null) {
propertiesInputStream = fromDefaultPropertiesLocation();
if (propertiesInputStream == null) {
System.err.println("ELF4J status: No configuration file located");
InternalLogger.log(Level.WARN, "No configuration file located");
properties.setProperty("noop", "true");
return properties;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@

package elf4j.impl.core.configuration;

import elf4j.Level;
import elf4j.impl.core.util.InternalLogger;
import elf4j.impl.core.writer.GroupWriter;
import elf4j.impl.core.writer.LogWriter;
import elf4j.impl.core.writer.StandardStreamsWriter;
Expand All @@ -43,7 +45,17 @@ public class WriterRepository {
*/
public WriterRepository(Properties properties) {
GroupWriter groupWriter = GroupWriter.from(properties);
this.logServiceWriter = groupWriter.isEmpty() ? DEFAULT_WRITER : groupWriter;
if (groupWriter.isEmpty()) {
InternalLogger.log(Level.WARN,
String.format("No writer found in configuration %s, using default writer %s",
properties,
DEFAULT_WRITER));
this.logServiceWriter = DEFAULT_WRITER;
return;
}
InternalLogger.log(Level.INFO,
String.format("Service writer %s found in configuration %s", groupWriter, properties));
this.logServiceWriter = groupWriter;
}

LogWriter getLogServiceWriter() {
Expand Down
10 changes: 5 additions & 5 deletions src/main/java/elf4j/impl/core/service/LogEntry.java
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ private static Object supply(Object o) {
* @return the name of the application client class calling the logging method of this logger instance
*/
public String getCallerClassName() {
return callerFrame == null ? nativeLogger.getName() : callerFrame.getClassName();
return callerFrame == null ? nativeLogger.getOwnerClassName() : callerFrame.getClassName();
}

/**
Expand All @@ -95,10 +95,10 @@ public String getResolvedMessage() {
@Value
@Builder
public static class StackTraceFrame {
String className;
String methodName;
@NonNull String className;
@NonNull String methodName;
int lineNumber;
String fileName;
@NonNull String fileName;
}

/**
Expand All @@ -107,7 +107,7 @@ public static class StackTraceFrame {
@Value
@Builder
public static class ThreadInformation {
String name;
@NonNull String name;
long id;
}
}
12 changes: 12 additions & 0 deletions src/main/java/elf4j/impl/core/util/InternalLogger.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package elf4j.impl.core.util;

import elf4j.Level;

public class InternalLogger {
private InternalLogger() {
}

public static void log(Level level, String message) {
System.err.println("ELF4J status " + level + ": " + message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import elf4j.impl.core.writer.pattern.GroupPattern;
import elf4j.impl.core.writer.pattern.LogPattern;

import javax.annotation.Nullable;
import java.io.BufferedOutputStream;
import java.io.PrintStream;
import java.util.Map;
Expand Down Expand Up @@ -62,12 +61,12 @@ public static StandardStreamsWriter defaultWriter() {

/**
* @param configuration properties map to make a console writer
* @param outStreamType out stream type, either stdout or stderr
* @return console writer per the specified configuration
*/
public static StandardStreamsWriter from(Map<String, String> configuration, @Nullable String outStreamType) {
public static StandardStreamsWriter from(Map<String, String> configuration) {
String level = configuration.get("level");
String pattern = configuration.get("pattern");
String outStreamType = configuration.get("stream");
return new StandardStreamsWriter(level == null ? DEFAULT_MINIMUM_LEVEL : Level.valueOf(level.toUpperCase()),
GroupPattern.from(pattern == null ? DEFAULT_PATTERN : pattern),
outStreamType == null ? DEFAULT_OUT_STREAM : OutStreamType.valueOf(outStreamType.trim().toUpperCase()));
Expand Down
11 changes: 5 additions & 6 deletions src/main/java/elf4j/impl/core/writer/WriterType.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,17 +36,16 @@
*
*/
public enum WriterType {

/**
*
* Type of writer that only output to standard streams
*/
STANDARD_STREAMS {
STANDARD {
@Override
Set<LogWriter> parseWriters(Properties properties) {
String writerOutStreamType = properties.getProperty("stream.type");
return PropertiesUtils.getPropertiesGroupOfType("stream", properties)
return PropertiesUtils.getPropertiesGroupOfType(STANDARD.name().toLowerCase(), properties)
.stream()
.map(streamWriterConfiguration -> StandardStreamsWriter.from(streamWriterConfiguration,
writerOutStreamType))
.map(StandardStreamsWriter::from)
.collect(Collectors.toSet());
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/elf4j/impl/core/IntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ void hey() {
Exception issue = new Exception("Test ex message");
logger.atWarn().log(issue, "Testing issue '{}' in {}", issue, this.getClass());

assertEquals(this.getClass().getName(), ((NativeLogger) logger).getName());
assertEquals(this.getClass().getName(), ((NativeLogger) logger).getOwnerClassName());
}
}
}
2 changes: 1 addition & 1 deletion src/test/java/elf4j/impl/core/NativeLoggerFactoryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ void name() {

NativeLogger logger = nativeLoggerFactory.logger();

assertSame(this.getClass().getName(), logger.getName());
assertSame(this.getClass().getName(), logger.getOwnerClassName());
}

@Test
Expand Down
2 changes: 1 addition & 1 deletion src/test/java/elf4j/impl/core/NativeLoggerTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ void instanceForDifferentLevel() {
NativeLogger warn = info.atWarn();

assertNotSame(warn, info);
assertEquals(info.getName(), warn.getName());
assertEquals(info.getOwnerClassName(), warn.getOwnerClassName());
assertEquals(WARN, warn.getLevel());
}

Expand Down
15 changes: 7 additions & 8 deletions src/test/java/elf4j/impl/core/SampleUsageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@
import java.util.function.Supplier;

class SampleUsageTest {
static Logger logger = Logger.instance();

@Nested
class plainText {
Logger logger = Logger.instance();

@Test
void declarationsAndLevels() {
logger.log(
Expand All @@ -60,7 +60,7 @@ void declarationsAndLevels() {

@Nested
class textWithArguments {
Logger info = Logger.instance().atInfo();
Logger info = logger.atInfo();

@Test
void lazyAndEagerArgumentsCanBeMixed() {
Expand All @@ -76,15 +76,14 @@ void lazyAndEagerArgumentsCanBeMixed() {

@Nested
class throwable {
Logger logger = Logger.instance();

@Test
void asTheFirstArgument() {
Exception exception = new Exception("Exception message");
logger.atWarn().log(exception);
logger.atError()
logger.atError().log(exception);
logger.atError().log(exception, "Optional log message");
logger.atInfo()
.log(exception,
"Exception is always the first argument to a logging method. The {} message and arguments that follow work the same way {}.",
"Exception is always the first argument to a logging method. The {} log message and following arguments work the same way {}.",
"optional",
(Supplier) () -> "as usual");
}
Expand Down
10 changes: 5 additions & 5 deletions src/test/resources/elf4j-test.properties
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@
### Any level is optional, default to TRACE if omitted
### This override the default global level
level=info
### Global output stream type, default to stdout; cannot differ per individual writers
stream.type=stderr
### These override level of all caller classes included the specified package
#level@elf4j.impl=error
level@org.springframework=warn
### Any writer is optional, default to a single stream writer if no writer configured
writer1=stream
writer1=standard
### Writer stream can be stdout/stderr/auto, default to stdout; auto means to use stdout if severity level is lower than WARN, otherwise stderr
#writer1.stream=auto
### This is the default output pattern, can be omitted
writer1.pattern={timestamp} {level} [{thread}] {class} - {message}
### This would customize the format patterns of the specified writer
#writer1.pattern={timestamp:yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ} {level:5} [{thread:name|id}] {class:simple|full|compressed} - {message}
### Multiple writers are supported, each with its own configurations
writer2=stream
writer2=standard
#writer2.level=trace
### Default json pattern does not include thread and caller details, and uses minified one-line format for the JSON string
#writer2.pattern={json}
Expand All @@ -47,5 +47,5 @@ writer2.pattern={json:caller-thread,caller-detail,pretty}
### This would print JSON string in pretty format
#writer2.pattern={json:caller-thread,caller-detail,pretty}
### This would force the writer to use stderr instead of stdout
writer3=stream
writer3=standard
writer3.pattern={json:pretty}

0 comments on commit 0336b62

Please sign in to comment.