Giter Site home page Giter Site logo

Maven Central

elf4j - Easy Logging Facade for Java

API and SPI of a no-fluff Java logging facade

  • ... because sometimes the wheel should be reinvented.

User stories

  1. As a Java application developer, I want to use a log service API, so that I can choose or switch to use any compliant log service provider, at application deployment time without code change or re-compile.
  2. As a log service/engine/framework provider, I want to implement a Service Provider Interface (SPI), so that the log service client can opt to use my service implementation, at application deployment time without code change or re-compile.

Prerequisite

Java 8 or better, although individual logging service providers may have higher JDK version prerequisite.

What it is...

A logging Service Interface and Access API

public interface Logger {
    static Logger instance() {
        return LogServiceProviderLocator.INSTANCE.logServiceProvider().logger();
    }

    default Logger atTrace() {
        return this.atLevel(Level.TRACE);
    }

    default Logger atDebug() {
        return this.atLevel(Level.DEBUG);
    }

    default Logger atInfo() {
        return this.atLevel(Level.INFO);
    }

    default Logger atWarn() {
        return this.atLevel(Level.WARN);
    }

    default Logger atError() {
        return this.atLevel(Level.ERROR);
    }

    Logger atLevel(Level level);

    Level getLevel();

    boolean isEnabled();

    void log(Object message);

    default void log(Supplier<?> message) {
        log((Object) message);
    }

    void log(String message, Object... arguments);

    default void log(String message, Supplier<?>... arguments) {
        log(message, (Object[]) arguments);
    }

    void log(Throwable throwable);

    void log(Throwable throwable, Object message);

    default void log(Throwable throwable, Supplier<?> message) {
        log(throwable, (Object) message);
    }

    void log(Throwable throwable, String message, Object... arguments);

    default void log(Throwable throwable, String message, Supplier<?>... arguments) {
        log(throwable, message, (Object[]) arguments);
    }
}

A logging Service Provider Interface (SPI)

public interface LogServiceProvider {
    Logger logger();
}

Conventions, Defaults, and Implementation Notes (a.k.a. "the spec")

Thread safety

Any Logger instance should be thread-safe.

Severity Level

If a Logger instance is obtained via the Logger.instance() static factory method, then the default severity level of such instance is decided by the service provider implementation. If a Logger instance is obtained via one of the Logger.at<Level> instance factory methods, then its severity level should be as requested.

Placeholder token

The empty curly braces token {} should be the placeholder for message arguments. This is by convention, and does not syntactically appear in the API or SPI. Both the API user and the Service Provider must honor such convention.

Lazy arguments

Lazy arguments are those whose runtime type is java.util.function.Supplier. Compared to other types of arguments, lazy ones have to be treated specially in that the Supplier function must be applied first before the result is used as the substitution to the argument placeholder. This special handling of lazy arguments is by convention, and not syntactically enforced by the API or SPI. It allows for the API user to mix up lazy and eager arguments within the same logging method call.

Get it

Maven Central

Install as a compile-scope dependency in Maven or other build tools alike.

Use it - for a client of the logging service API

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

    @Nested
    class plainText {
        @Test
        void declarationsAndLevels() {
            logger.log(
                    "Logger instance is thread-safe so it can be declared and used as a local, instance, or static variable");
            logger.log("Default severity level is decided by the logging provider implementation");
            Logger trace = logger.atTrace();
            trace.log("Explicit severity level is specified by user i.e. TRACE");
            Logger.instance().atTrace().log("Same explicit level TRACE");
            logger.atDebug().log("Severity level is DEBUG");
            logger.atInfo().log("Severity level is INFO");
            trace.atWarn().log("Severity level is WARN, not TRACE");
            logger.atError().log("Severity level is ERROR");
            Logger.instance()
                    .atDebug()
                    .atError()
                    .atTrace()
                    .atWarn()
                    .atInfo()
                    .log("Not a practical example but the severity level is INFO");
        }
    }

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

        @Test
        void lazyAndEagerArgumentsCanBeMixed() {
            info.log("Message can have any number of arguments of {} type", Object.class.getTypeName());
            info.log(
                    "Lazy arguments, of {} type, whose values may be {} can be mixed with eager arguments of non-Supplier types",
                    Supplier.class.getTypeName(),
                    (Supplier) () -> "expensive to compute");
            info.atWarn()
                    .log("The Supplier downcast is mandatory per lambda syntax because arguments are declared as generic Object rather than functional interface");
        }
    }

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

        @Test
        void noDowncastNeededWhenAllMessageOrArgumentsAreSuppliers() {
            logger.log(
                    () ->
                            "No downcast needed when message or arguments are all of Supplier type, rather than mixed with Object types");
            logger.log("Message can have any number of {} type arguments", Supplier.class::getTypeName);
            logger.log(
                    "Lazy arguments of {} type can be used to supply values that may be {}",
                    Supplier.class::getTypeName,
                    () -> "expensive to compute");
            Exception ex = new Exception("test ex for Suppliers");
            logger.log(ex, () -> "Exception log message can be a Supplier");
            logger.log(ex, "So can the {}'s {}", () -> "message", () -> "arguments");
        }
    }

    @Nested
    class throwable {
        @Test
        void asTheFirstArgument() {
            Exception exception = new Exception("Exception message");
            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 {} log message and following arguments work the same way {}.",
                            "optional",
                            (Supplier) () -> "as usual");
        }
    }
}

Note that elf4j is a logging service facade and specification, rather than the implementation. As such,

No-op by default

  • Nothing will be logging out (no-op) unless a properly configured elf4j service provider JAR is discovered at the application start time. The elf4j facade itself only ships with a default no-op logging provider.

Only one in-effect logging provider

  • The elf4j API user can select or change into using any elf4j service provider at deploy time, without application code change or re-compile.

  • The recommended setup is to ensure that only one desired logging provider with its associated JAR(s) be present in the classpath; or, no provider JAR when no-op is desired. In this case, nothing further is needed for elf4j to work.

  • If multiple eligible providers are present in classpath, somehow, then the system property elf4j.service.provider.fqcn has to be used to select the desired provider. No-op applies if the specified provider is absent.

    java -Delf4j.service.provider.fqcn="elf4j.log4j.Log4jLoggerFactory" MyApplication
    

    With the default no-op logging provider, this system property can also be used to turn OFF all logging services discovered by the elf4j facade:

    java -Delf4j.service.provider.fqcn="elf4j.util.NoopLogServiceProvider" MyApplication
    
  • It is considered a setup error to have multiple providers in the classpath without a selection. The elf4j facade falls back to no-op on any setup errors.

Use it - for a service provider implementing the logging service SPI

To enable an independent logging framework/engine via the elf4j spec, the service provider should follow instructions of Java Service Provider Framework. Namely, the implementation should include

  • the provider class implementing the LogServiceProvider SPI, the service class implementing the Logger API, and their associated classes as needed
  • the provider-configuration file, named elf4j.spi.LogServiceProvider in the resource directory META-INF/services, whose content is the Fully Qualified Name of the SPI provider class implementing the LogServiceProvider SPI interface

Available logging service providers of elf4j

Visitor Count

Easy Logging Facade for Java (ELF4J)'s Projects

elf4j icon elf4j

A no-fluff Java logging facade

elf4j-engine icon elf4j-engine

A stand-alone asynchronous Java log engine, implementing the ELF4J (Easy Logging Facade for Java) API

elf4j-jul icon elf4j-jul

An adapter to use java.util.logging as service provider and runtime logging engine for the ELF4J (Easy Logging Facade for Java) API

elf4j-log4j icon elf4j-log4j

An adapter to use LOG4J as service provider and runtime log engine for the ELF4J (Easy Logging Facade for Java) API

elf4j-logback icon elf4j-logback

An adapter to use LOGBACK as service provider and runtime log engine for the ELF4J (Easy Logging Facade for Java) API

elf4j-provider icon elf4j-provider

A native logging service provider implementation of ELF4J (Easy Logging Facade for Java), and an independent logging solution for any Java application

elf4j-slf4j icon elf4j-slf4j

An adapter to use SLF4J as service provider and runtime log engine for the ELF4J (Easy Logging Facade for Java) API

elf4j-tinylog icon elf4j-tinylog

An adapter to use tinylog as service provider and runtime log engine for the ELF4J (Easy Logging Facade for Java) API

jpl-elf4j icon jpl-elf4j

An adapter to use elf4j-engine as runtime logging service provider of Java 9 System.Logger API

slf4j-elf4j icon slf4j-elf4j

An adapter to use elf4j-engine as runtime logging service provider for SLF4J API

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.