Index: log4j-api/src/main/java/org/apache/logging/log4j/FluentLogger.java =================================================================== --- log4j-api/src/main/java/org/apache/logging/log4j/FluentLogger.java (revision 0) +++ log4j-api/src/main/java/org/apache/logging/log4j/FluentLogger.java (working copy) @@ -0,0 +1,193 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j; + +/** + * This interface serves as an extension of the {@link Logger} interface that supports a fluent logging style. To log + * fluently, you first call one of the seven methods that return a {@link MessageBuilder}. You can then call any of + * the methods on the builder to set the message, arguments, marker, and/or exception. Once you are finished with + * building the message, calling {@link MessageBuilder#log()} commits the message to the logger. You cannot call any + * methods on the builder after this (doing so will result in an {@link IllegalStateException}).
+ *
+ * Here are some sample uses of fluent logging:
+ * + * logger.info().message("Something interesting happened.").marker(myMarker).log(); + * logger.error().message("An error occurred with argument {}.").argument(arg).exception(e).log(); + * logger.trace().message("Entered HelloTest.main({}, {}, {})").arguments(arg1, arg2, arg3).log(); + * logger.trace().message("Entered GoodbyeTest.main({}, {})").argument(arg1).argument(arg2).log(); + * + *
+ * Implementations should eagerly determine eligibility of a logging message so that CPU cycles are not needlessly spent + * updating the message builder for no reason. + * + * @see MessageBuilder + */ +public interface FluentLogger extends Logger { + /** + * Returns a fluent message builder for trace messages, or a no-op message builder if trace messages are disabled + * for this logger. + * + * @return a fluent message builder. + * @see MessageBuilder + * @see org.apache.logging.log4j.spi.NullFluentMessageBuilder + */ + MessageBuilder trace(); + + /** + * Returns a fluent message builder for debug messages, or a no-op message builder if debug messages are disabled + * for this logger. + * + * @return a fluent message builder. + * @see MessageBuilder + * @see org.apache.logging.log4j.spi.NullFluentMessageBuilder + */ + MessageBuilder debug(); + + /** + * Returns a fluent message builder for info messages, or a no-op message builder if info messages are disabled + * for this logger. + * + * @return a fluent message builder. + * @see MessageBuilder + * @see org.apache.logging.log4j.spi.NullFluentMessageBuilder + */ + MessageBuilder info(); + + /** + * Returns a fluent message builder for warn messages, or a no-op message builder if warn messages are disabled + * for this logger. + * + * @return a fluent message builder. + * @see MessageBuilder + * @see org.apache.logging.log4j.spi.NullFluentMessageBuilder + */ + MessageBuilder warn(); + + /** + * Returns a fluent message builder for error messages, or a no-op message builder if error messages are disabled + * for this logger. + * + * @return a fluent message builder. + * @see MessageBuilder + * @see org.apache.logging.log4j.spi.NullFluentMessageBuilder + */ + MessageBuilder error(); + + /** + * Returns a fluent message builder for fatal messages, or a no-op message builder if fatal messages are disabled + * for this logger. + * + * @return a fluent message builder. + * @see MessageBuilder + * @see org.apache.logging.log4j.spi.NullFluentMessageBuilder + */ + MessageBuilder fatal(); + + /** + * Returns a fluent message builder for {@code level} messages, or a no-op message builder if {@code level} messages + * are disabled for this logger. + * + * @return a fluent message builder. + * @see MessageBuilder + * @see org.apache.logging.log4j.spi.NullFluentMessageBuilder + */ + MessageBuilder log(Level level); + + interface MessageBuilder { + /** + * Sets the message to the specified message. Overrides any previous values set by this or the other + * {@code message} methods. + * + * @param message The message + * @return this builder. + * @throws IllegalStateException if called after {@link #log}. + */ + MessageBuilder message(String message); + + /** + * Sets the message to the specified message. Overrides any previous values set by this or the other + * {@code message} methods. + * + * @param message The message + * @return this builder. + * @throws IllegalStateException if called after {@link #log}. + */ + MessageBuilder message(Object message); + + /** + * Sets the message template to the specified message with the given arguments. Overrides any previous values + * set by this or the other {@code message} methods. Instead of using this method, you could use one of the + * other methods and {@link #argument} or {@link #arguments}. + * + * @param template The message template + * @param arguments The arguments for the message + * @return this builder. + * @throws IllegalStateException if called after {@link #log}. + */ + MessageBuilder message(String template, Object... arguments); + + /** + * Sets the marker for this message. Overrides the marker set on any previous calls. + * + * @param marker The marker + * @return this builder. + * @throws IllegalStateException if called after {@link #log}. + */ + MessageBuilder marker(Marker marker); + + /** + * Sets the exception for this message. Overrides the exception set on any previous calls. + * + * @param exception The exception + * @return this builder. + * @throws IllegalStateException if called after {@link #log}. + */ + MessageBuilder exception(Throwable exception); + + /** + * Adds an argument to this message. Should not be used in conjunction with {@link #arguments}, + * {@link #message(String, Object...)}, or {@link #message(Object)}. Throws an exception if called after + * {@link #message(Object)}. + * + * @param argumentToAdd The argument + * @return this builder. + * @throws IllegalStateException if called after {@link #log} or {@link #message(Object)}. + */ + MessageBuilder argument(Object argumentToAdd); + + /** + * Sets the arguments to this message. Overrides any arguments previously added by {@link #argument} or + * {@link #message(String, Object...)}, or {@link #message(Object)}. Throws an exception if called after + * {@link #message(Object)}. + * + * @param arguments The arguments + * @return this builder. + * @throws IllegalStateException if called after {@link #log} or {@link #message(Object)}. + */ + MessageBuilder arguments(Object... arguments); + + /** + * Commits the logging event to the underlying logger. Once this method is called, you cannot call any other + * methods on this builder. Additionally, implementations may pool and reuse builders, resulting in unexpected + * behavior if you hold on to an instance of this builder. Once you have called this method, you should discard + * the builder instance and never use it again. + * + * @throws IllegalStateException if called more than once. + */ + void log(); + } +} Index: log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java =================================================================== --- log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java (revision 1561928) +++ log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLogger.java (working copy) @@ -16,6 +16,7 @@ */ package org.apache.logging.log4j.spi; +import org.apache.logging.log4j.FluentLogger; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; @@ -27,11 +28,13 @@ import org.apache.logging.log4j.status.StatusLogger; import java.io.Serializable; +import java.util.Queue; +import java.util.concurrent.ConcurrentLinkedQueue; /** * Base implementation of a Logger. It is highly recommended that any Logger implementation extend this class. */ -public abstract class AbstractLogger implements Logger, Serializable { +public abstract class AbstractLogger implements FluentLogger, Serializable { private static final long serialVersionUID = 2L; @@ -76,6 +79,11 @@ private static final String CATCHING = "catching"; + private static final NullFluentMessageBuilder NULL_FLUENT_MESSAGE_BUILDER = new NullFluentMessageBuilder(); + + private static final Queue MESSAGE_BUILDERS = + new ConcurrentLinkedQueue(); + /** * Checks that the message factory a logger was created with is the same as the given messageFactory. If they are * different log a warning to the {@linkplain StatusLogger}. A null MessageFactory translates to the default @@ -367,6 +375,11 @@ } } + @Override + public MessageBuilder debug() { + return this.getMessageBuilderForLevel(Level.DEBUG); + } + /** * Logs entry to a method. */ @@ -606,6 +619,11 @@ } } + @Override + public MessageBuilder error() { + return this.getMessageBuilderForLevel(Level.ERROR); + } + /** * Logs exit from a method. */ @@ -829,6 +847,11 @@ } } + @Override + public MessageBuilder fatal() { + return this.getMessageBuilderForLevel(Level.FATAL); + } + /** * Gets the message factory. * @@ -1061,6 +1084,11 @@ } } + @Override + public MessageBuilder info() { + return this.getMessageBuilderForLevel(Level.INFO); + } + /** * Checks whether this Logger is enabled for the DEBUG Level. * @@ -1466,6 +1494,43 @@ } /** + * Gets a fluent message builder for the given level. Returns the constant no-op message builder if logging is + * disabled for this level. Otherwise, borrows a builder from the pool (or creates a new one if the pool is empty), + * prepares it, and returns it. + * + * @param level The level to return a builder for + * @return the builder. + */ + private MessageBuilder getMessageBuilderForLevel(Level level) { + if (!isEnabled(level)) { // if logging disabled for this level/logger, return the null builder + return NULL_FLUENT_MESSAGE_BUILDER; + } + PoolableFluentMessageBuilder builder = MESSAGE_BUILDERS.poll(); // get the oldest builder out of the pool + if (builder == null) { + builder = new PoolableFluentMessageBuilder(); // if no builders are available in the pool, create a new one + } + return builder.prepareForReturn(this, level); + } + + /** + * Writes the message created by the message builder, then returns the builder to the pool. This method is only + * called from {@link PoolableFluentMessageBuilder}. + * + * @param builder The builder to return + * @param marker The event marker + * @param level The event level + * @param data The event message + * @param t The event exception + */ + void commitFluentMessage(PoolableFluentMessageBuilder builder, Marker marker, Level level, Message data, + Throwable t) { + if (isEnabled(level, marker, data, t)) { // double-check, marker or message may have disabled logging + log(marker, FQCN, level, data, t); + } + MESSAGE_BUILDERS.add(builder); // add the builder back to the pool as the youngest builder + } + + /** * Logs a message with location information. * * @param marker The Marker @@ -1770,6 +1835,11 @@ } } + @Override + public MessageBuilder trace() { + return this.getMessageBuilderForLevel(Level.TRACE); + } + /** * Logs a message with the specific Marker at the WARN level. * @@ -1964,4 +2034,8 @@ } } + @Override + public MessageBuilder warn() { + return this.getMessageBuilderForLevel(Level.WARN); + } } Index: log4j-api/src/main/java/org/apache/logging/log4j/spi/NullFluentMessageBuilder.java =================================================================== --- log4j-api/src/main/java/org/apache/logging/log4j/spi/NullFluentMessageBuilder.java (revision 0) +++ log4j-api/src/main/java/org/apache/logging/log4j/spi/NullFluentMessageBuilder.java (working copy) @@ -0,0 +1,52 @@ +package org.apache.logging.log4j.spi; + +import org.apache.logging.log4j.FluentLogger; +import org.apache.logging.log4j.Marker; + +/** + * A no-op {@link FluentLogger.MessageBuilder MessageBuilder} that performs no operations if logging is disabled for + * the chosen {@link org.apache.logging.log4j.Level}. + * + * @see FluentLogger + */ +public class NullFluentMessageBuilder implements FluentLogger.MessageBuilder { + @Override + public FluentLogger.MessageBuilder message(String message) { + return this; + } + + @Override + public FluentLogger.MessageBuilder message(Object message) { + return this; + } + + @Override + public FluentLogger.MessageBuilder message(String template, Object... arguments) { + return this; + } + + @Override + public FluentLogger.MessageBuilder marker(Marker marker) { + return this; + } + + @Override + public FluentLogger.MessageBuilder exception(Throwable exception) { + return this; + } + + @Override + public FluentLogger.MessageBuilder argument(Object argumentToAdd) { + return this; + } + + @Override + public FluentLogger.MessageBuilder arguments(Object... arguments) { + return this; + } + + @Override + public void log() { + + } +} Index: log4j-api/src/main/java/org/apache/logging/log4j/spi/PoolableFluentMessageBuilder.java =================================================================== --- log4j-api/src/main/java/org/apache/logging/log4j/spi/PoolableFluentMessageBuilder.java (revision 0) +++ log4j-api/src/main/java/org/apache/logging/log4j/spi/PoolableFluentMessageBuilder.java (working copy) @@ -0,0 +1,128 @@ +package org.apache.logging.log4j.spi; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.logging.log4j.FluentLogger; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.message.Message; + +final class PoolableFluentMessageBuilder implements FluentLogger.MessageBuilder { + private AbstractLogger logger; + private Level level; + + private Object messageObject; + private String messageString; + + private Object[] argumentArray; + private final List argumentList = new ArrayList(); + + private Marker marker; + + private Throwable exception; + + private boolean closed = false; + + @Override + public FluentLogger.MessageBuilder message(String message) { + this.assertOpen(); + this.messageObject = null; + this.messageString = message; + return this; + } + + @Override + public FluentLogger.MessageBuilder message(Object message) { + this.assertOpen(); + this.messageString = null; + this.messageObject = message; + return this; + } + + @Override + public FluentLogger.MessageBuilder message(String template, Object... arguments) { + this.assertOpen(); + this.messageObject = null; + this.messageString = template; + this.argumentArray = arguments; + return this; + } + + @Override + public FluentLogger.MessageBuilder marker(Marker marker) { + this.assertOpen(); + this.marker = marker; + return this; + } + + @Override + public FluentLogger.MessageBuilder exception(Throwable exception) { + this.assertOpen(); + this.exception = exception; + return this; + } + + @Override + public FluentLogger.MessageBuilder argument(Object argumentToAdd) { + this.assertOpen(); + if (this.messageObject != null) { + throw new IllegalStateException("Added argument to MessageBuilder after calling message(Object)."); + } + this.argumentList.add(argumentToAdd); + return this; + } + + @Override + public FluentLogger.MessageBuilder arguments(Object... arguments) { + this.assertOpen(); + if (this.messageObject != null) { + throw new IllegalStateException("Set arguments on MessageBuilder after calling message(Object)."); + } + this.argumentArray = arguments; + return this; + } + + @Override + public void log() { + this.assertOpen(); + this.closed = true; + + Message message; + if (this.messageObject != null) { + message = this.logger.getMessageFactory().newMessage(this.messageObject); + } else { + if (this.messageString == null) { + this.messageString = ""; + } + if (this.argumentArray != null) { + message = this.logger.getMessageFactory().newMessage(this.messageString, this.argumentArray); + } else if (this.argumentList.size() > 0) { + message = this.logger.getMessageFactory().newMessage(this.messageString, this.argumentList.toArray()); + } else { + message = this.logger.getMessageFactory().newMessage(this.messageString); + } + } + + this.logger.commitFluentMessage(this, this.marker, this.level, message, this.exception); + } + + PoolableFluentMessageBuilder prepareForReturn(AbstractLogger logger, Level level) { + this.logger = logger; + this.level = level; + this.messageObject = null; + this.messageString = null; + this.argumentArray = null; + this.argumentList.clear(); + this.marker = null; + this.exception = null; + this.closed = false; + return this; + } + + private void assertOpen() { + if (this.closed) { + throw new IllegalStateException("Call to MessageBuilder method after builder discarded."); + } + } +}