com.codepunk.codepunklib.util.log.FormattingLogger.java Source code

Java tutorial

Introduction

Here is the source code for com.codepunk.codepunklib.util.log.FormattingLogger.java

Source

/*
 * Copyright 2016 Codepunk, LLC
 *
 * Licensed 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 com.codepunk.codepunklib.util.log;

import android.support.annotation.NonNull;
import android.text.TextUtils;

import org.apache.commons.lang3.ArrayUtils;

import java.util.Locale;

/**
 * <p>
 * A special {@link Logger} that automatically formats log messages with pertinent information
 * before sending.
 * </p>
 *
 * <p>
 * The {@link Placeholder} class is used to specify tag and message formats that will be used when
 * formatting log messages. Each placeholder takes the place of a piece of information (i.e. class
 * name, method name, line number, etc.) that are only known at runtime.
 * </p>
 *
 * <p>
 * To use FormattingLogger:
 * </p>
 *
 * <p>
 * (Optional) Set the tag format string and arguments. For example, to have a tag that always
 * displays the fully-qualified class name where the log message originated, use the following:
 * </p>
 * <pre>
 * FormattingLogger logger = new FormattingLogger();
 * logger.setTagFormat("%s", Placeholder.CLASS_NAME);
 * </pre>
 *
 * <p>
 * If you want the tag to display a friendly message along with the name of the (simple) class
 * name and method in which the log message orginated, use the following:
 * </p>
 * <pre>
 * logger.setTagFormat("Hi! %s.%s", Placeholder.SIMPLE_CLASS_NAME, Placeholder.METHOD_NAME);
 * </pre>
 *
 * <p>
 * If you want the tag to display the filename in which the log message originated along with a
 * custom message specified at the time the message is logged, use the following:
 * </p>
 * <pre>
 * logger.setTagFormat("%s: %s", Placeholder.FILE_NAME, Placeholder.SUPPLIED);
 * </pre>
 *
 * <p>
 * Then you can specify a custom tag string that will take the placed of the <code>SUPPLIED</code>
 * placeholder:
 * </p>
 * <pre>
 * logger.d("Custom Tag", "My message.");
 * </pre>
 *
 * <p>
 * (Optional) Set the message format string and arguments. The same concepts used in setting the
 * tag format string and arguments apply here as well.
 * </p>
 *
 * <p>
 * When sending log messages, if you have set up a tag that expects no
 * <code>Placeholder.SUPPLIED</code> strings, then you can leave out the tag argument altogether:
 * </p>
 * <pre>
 * logger.d("My message.");
 * </pre>
 *
 * <p>
 * You can also pass a {@link Throwable}:
 * </p>
 * <pre>
 * logger.e("Something went wrong.", new IllegalArgumentException());
 * </pre>
 *
 * <p>
 * Note that you <b>must</b> include <code>Placeholder.SUPPLIED</code> in your tag or message
 * formats as described above in order for any tag or message string you pass along at runtime
 * to appear in the formatted log message.
 * </p>
 *
 * <p>
 * For example, if you call the following:
 * </p>
 * <pre>
 * logger.setTagFormat("%s", Placeholder.SIMPLE_CLASS_NAME);
 * logger.setMsgFormat("%s", Placeholder.FILE_NAME);
 * logger.d("My Tag", "My message.");
 * </pre>
 *
 * <p>
 * Then neither "My Tag" nor "My message" will appear in the formatted log message. You can
 * address this by including the <code>Placeholder.SUPPLIED</code> somewhere in your formats:
 * </p>
 * <pre>
 * logger.setTagFormat("%s: %s", Placeholder.SIMPLE_CLASS_NAME, Placeholder.SUPPLIED);
 * logger.setMsgFormat("%s: %s", Placeholder.FILE_NAME, Placeholder.SUPPLIED);
 * logger.d("My Tag", "My message.");
 * </pre>
 */
@SuppressWarnings({ "unused", "WeakerAccess" })
public class FormattingLogger extends LoggerWrapper {

    /**
     * Placeholders that will be used to generate formatted logging messages.
     */
    public enum Placeholder {
        /**
         * An enum constant that serves as a placeholder for the full class name in a log message.
         */
        CLASS_NAME,

        /**
         * An enum constant that serves as a placeholder for the file name in a log message.
         */
        FILE_NAME,

        /**
         * An enum constant that serves as a placeholder for the hash code in a log message.
         */
        HASH_CODE,

        /**
         * An enum constant that serves as a placeholder for the line number in a log message.
         */
        LINE_NUMBER,

        /**
         * An enum constant that serves as a placeholder for the method name in a log message.
         */
        METHOD_NAME,

        /**
         * An enum constant that serves as a placeholder for the package name in a log message.
         */
        PACKAGE,

        /**
         * An enum constant that serves as a placeholder for the simple class name in a log message.
         */
        SIMPLE_CLASS_NAME,

        /**
         * <p>
         * An enum constant that serves as a placeholder for the message that is passed along to the
         * log message.
         * </p>
         *
         * <p>
         * In the following example, any placeholders of <code>Placeholder.SUPPLIED</code> specified
         * in {@link FormattingLogger#setTagFormat(String, Object[])} or
         * {@link FormattingLogger#setMsgFormat(String, Object[])} will be replaced with the string
         * "This is the supplied message".
         * </p>
         * <pre>
         * FormattingLogger logger = ...;
         * logger.d("This is the supplied message");
         * </pre>
         */
        SUPPLIED
    }

    /**
     * The default log tag format.
     */
    private static final String DEFAULT_TAG_FORMAT = "%s";

    /**
     * The default log tag arguments.
     */
    private static final Object[] DEFAULT_TAG_ARGS = new Object[] { Placeholder.SUPPLIED };

    /**
     * The default log message format.
     */
    private static final String DEFAULT_MSG_FORMAT = "%s(%s:%d) %s";

    /**
     * The default log message arguments.
     */
    private static final Object[] DEFAULT_MSG_ARGS = new Object[] { Placeholder.METHOD_NAME, Placeholder.FILE_NAME,
            Placeholder.LINE_NUMBER, Placeholder.SUPPLIED };

    /**
     * Empty string for calling log methods without specifying a tag value.
     */
    private static final String EMPTY_STRING = "";

    /**
     * Used to truncate tag strings that are too long.
     */
    private static final String ELLIPSIS = "";

    /**
     * Character that indicates an anonymous class in full class names.
     */
    private static final String ANONYMOUS_CLASS_INDICATOR = "$";

    /**
     * Anonymous class indicator in regex form.
     */
    private static final String ANONYMOUS_CLASS_INDICATOR_REGEX = "\\$";

    /**
     * Default result for class names that cannot be determined at runtime.
     */
    private static final String UNKNOWN_CLASS_NAME = "[Unknown]";

    /**
     * The maximum tag length.
     */
    private static final int MAX_TAG_LENGTH = 23;

    /**
     * The string format that will be used to format log tags.
     */
    private String mTagFormat = DEFAULT_TAG_FORMAT;

    /**
     * The arguments (including {@link Placeholder}s) that will be used to format
     * <code>mTagFormat</code> when generating log messages.
     */
    private Object[] mTagArgs = DEFAULT_TAG_ARGS;

    /**
     * The string format that will be used to format log messages.
     */
    private String mMsgFormat = DEFAULT_MSG_FORMAT;

    /**
     * The arguments (including {@link Placeholder}s) that will be used to format
     * <code>mMsgFormat</code> when generating log messages.
     */
    private Object[] mMsgArgs = DEFAULT_MSG_ARGS;

    /**
     * Constructor that takes a base {@link Logger} to which log statements will be sent after
     * formatting.
     * @param baseLogger The base {@link Logger} to which formatted log statements will be sent.
     */
    public FormattingLogger(Logger baseLogger) {
        super(baseLogger);
    }

    /**
     * Returns the {@link Class} that corresponds to the given {@link StackTraceElement}.
     * @param element A {@link StackTraceElement}.
     * @return The Class object for the class with the name specified by <code>element</code>.
     * @throws ClassNotFoundException If the class cannot be located.
     */
    private static Class getClass(@NonNull StackTraceElement element) throws ClassNotFoundException {
        String className = element.getClassName();
        if (className.contains(ANONYMOUS_CLASS_INDICATOR)) {
            className = className.split(ANONYMOUS_CLASS_INDICATOR_REGEX)[0];
        }
        return Class.forName(className);
    }

    /**
     * Returns the first {@link StackTraceElement} in the stack trace found in <code>tr</code>
     * that was called from outside of this class.
     * @param tr The {@link Throwable} containing the stack trace.
     * @param index The number of levels in the stack trace that result from methods in this class.
     * @return The first significant {@link StackTraceElement}; that is, the first element
     * representing a call made outside of this class.
     */
    private static StackTraceElement getSignificantStackTraceElement(Throwable tr, int index) {
        StackTraceElement element;
        try {
            if (tr == null) {
                element = (new Throwable()).getStackTrace()[index + 1];
            } else if (tr.getCause() == null) {
                element = tr.getStackTrace()[0];
            } else {
                element = tr.getCause().getStackTrace()[0];
            }
        } catch (Exception e) {
            element = null;
        }
        return element;
    }

    /**
     * Returns the concrete information from the given {@link StackTraceElement} that is represented
     * by the given Placeholder.
     * @param element The {@link StackTraceElement}.
     * @param placeholder The {@link Placeholder} to replace.
     * @return The information represented by the Placeholder.
     */
    private static Object getFromElement(StackTraceElement element, Placeholder placeholder) {
        try {
            switch (placeholder) {
            case FILE_NAME:
                return element.getFileName();
            case HASH_CODE:
                return element.hashCode();
            case LINE_NUMBER:
                return element.getLineNumber();
            case METHOD_NAME:
                return element.getMethodName();
            case PACKAGE: {
                return getClass(element).getPackage().getName();
            }
            case CLASS_NAME:
            case SIMPLE_CLASS_NAME:
            default: {
                String name;
                Class cls = getClass(element);
                do {
                    if (cls == null) {
                        name = UNKNOWN_CLASS_NAME;
                    } else if (placeholder == Placeholder.CLASS_NAME) {
                        name = cls.getName();
                    } else {
                        name = cls.getSimpleName();
                    }
                } while (TextUtils.isEmpty(name));
                return name;
            }
            }
        } catch (ClassNotFoundException e) {
            return UNKNOWN_CLASS_NAME;
        }
    }

    /**
     * Formats a format string with the supplied arguments, replacing {@link Placeholder}s with
     * concrete information found in <code>element</code> as well as <code>supplied</code>.
     * @param format The format string.
     * @param args Arguments referenced by the format specifiers in the format string. Can be
     *             any object type as well as {@link Placeholder}, which will result in the arg
     *             being replaced with concrete information before being placed in the format string.
     * @param element A {@link StackTraceElement}.
     * @param supplied The text that was originally supplied to this log call.
     * @param maxLength An optional maximum string length. This is needed because Logcat log
     *                  statements have a maximum tag length of 23.
     * @return A formatted string.
     * @see String#format(Locale, String, Object...)
     */
    private static String format(String format, Object[] args, StackTraceElement element, String supplied,
            int maxLength) {
        String formattedString;
        if (ArrayUtils.isEmpty(args) || element == null) {
            formattedString = format;
        } else {
            Object[] resolvedArgs = new Object[args.length];
            for (int i = 0; i < args.length; i++) {
                if (!(args[i] instanceof Placeholder)) {
                    resolvedArgs[i] = args[i];
                } else if (args[i] == Placeholder.SUPPLIED) {
                    resolvedArgs[i] = supplied;
                } else {
                    resolvedArgs[i] = getFromElement(element, (Placeholder) args[i]);
                }
            }
            formattedString = String.format(Locale.US, format, resolvedArgs);
        }
        if (formattedString != null && formattedString.length() > maxLength) {
            formattedString = formattedString.substring(0, maxLength - 1).concat(ELLIPSIS);
        }
        return formattedString;
    }

    /**
     * Formats a format string with the supplied arguments, replacing {@link Placeholder}s with
     * concrete information found in <code>element</code> as well as <code>supplied</code>.
     * @param format The format string.
     * @param args Arguments referenced by the format specifiers in the format string. Can be
     *             any object type as well as {@link Placeholder}, which will result in the arg
     *             being replaced with concrete information before being placed in the format string.
     * @param element A {@link StackTraceElement}.
     * @param supplied The text that was originally supplied to this log call.
     * @return A formatted string.
     * @see String#format(Locale, String, Object...)
     */
    private static String format(String format, Object[] args, StackTraceElement element, String supplied) {
        return format(format, args, element, supplied, Integer.MAX_VALUE);
    }

    /**
     * Sets the tag format and arguments for formatted log statements.
     * @param format The format string to use for log tags.
     * @param args The format arguments to use for log tags.
     */
    public void setTagFormat(String format, Object... args) {
        mTagFormat = format;
        mTagArgs = args;
    }

    /**
     * Sets the message format and arguments for formatted log statements.
     * @param format The format string to use for log messages.
     * @param args The format arguments to use for log messages.
     */
    public void setMsgFormat(String format, Object... args) {
        mMsgFormat = format;
        mMsgArgs = args;
    }

    @Override
    public int d(String tag, String msg) {
        return d(tag, msg, null, 1);
    }

    @Override
    public int d(String tag, String msg, Throwable tr) {
        return d(tag, msg, tr, 1);
    }

    @Override
    public int e(String tag, String msg) {
        return e(tag, msg, null, 1);
    }

    @Override
    public int e(String tag, String msg, Throwable tr) {
        return e(tag, msg, tr, 1);
    }

    @Override
    public int i(String tag, String msg) {
        return i(tag, msg, null, 1);
    }

    @Override
    public int i(String tag, String msg, Throwable tr) {
        return i(tag, msg, tr, 1);
    }

    @Override
    public int v(String tag, String msg) {
        return v(tag, msg, null, 1);
    }

    @Override
    public int v(String tag, String msg, Throwable tr) {
        return v(tag, msg, tr, 1);
    }

    @Override
    public int w(String tag, String msg) {
        return w(tag, msg, null, 1);
    }

    @Override
    public int w(String tag, String msg, Throwable tr) {
        return w(tag, msg, tr, 1);
    }

    /**
     * Send formatted a {@link android.util.Log#DEBUG} log message.
     * @param tag Used to identify the source of a log message. It usually identifies the class or
     *            activity where the log call occurs.
     * @param msg The message you would like logged.
     * @param tr An exception to log.
     * @param index The number of calls in the stack trace (before this call) that originated within
     *              this class.
     * @return The number of bytes written.
     */
    private int d(String tag, String msg, Throwable tr, int index) {
        StackTraceElement element = getSignificantStackTraceElement(tr, index + 1);
        return super.d(format(mTagFormat, mTagArgs, element, tag, MAX_TAG_LENGTH),
                format(mMsgFormat, mMsgArgs, element, msg), tr);
    }

    /**
     * Send formatted a {@link android.util.Log#ERROR} log message.
     * @param tag Used to identify the source of a log message. It usually identifies the class or
     *            activity where the log call occurs.
     * @param msg The message you would like logged.
     * @param tr An exception to log.
     * @param index The number of calls in the stack trace (before this call) that originated within
     *              this class.
     * @return The number of bytes written.
     */
    private int e(String tag, String msg, Throwable tr, int index) {
        StackTraceElement element = getSignificantStackTraceElement(tr, index + 1);
        return super.e(format(mTagFormat, mTagArgs, element, tag, MAX_TAG_LENGTH),
                format(mMsgFormat, mMsgArgs, element, msg), tr);
    }

    /**
     * Send formatted a {@link android.util.Log#INFO} log message.
     * @param tag Used to identify the source of a log message. It usually identifies the class or
     *            activity where the log call occurs.
     * @param msg The message you would like logged.
     * @param tr An exception to log.
     * @param index The number of calls in the stack trace (before this call) that originated within
     *              this class.
     * @return The number of bytes written.
     */
    private int i(String tag, String msg, Throwable tr, int index) {
        StackTraceElement element = getSignificantStackTraceElement(tr, index + 1);
        return super.i(format(mTagFormat, mTagArgs, element, tag, MAX_TAG_LENGTH),
                format(mMsgFormat, mMsgArgs, element, msg), tr);
    }

    /**
     * Send formatted a {@link android.util.Log#VERBOSE} log message.
     * @param tag Used to identify the source of a log message. It usually identifies the class or
     *            activity where the log call occurs.
     * @param msg The message you would like logged.
     * @param tr An exception to log.
     * @param index The number of calls in the stack trace (before this call) that originated within
     *              this class.
     * @return The number of bytes written.
     */
    private int v(String tag, String msg, Throwable tr, int index) {
        StackTraceElement element = getSignificantStackTraceElement(tr, index + 1);
        return super.v(format(mTagFormat, mTagArgs, element, tag, MAX_TAG_LENGTH),
                format(mMsgFormat, mMsgArgs, element, msg), tr);
    }

    /**
     * Send formatted a {@link android.util.Log#WARN} log message.
     * @param tag Used to identify the source of a log message. It usually identifies the class or
     *            activity where the log call occurs.
     * @param msg The message you would like logged.
     * @param tr An exception to log.
     * @param index The number of calls in the stack trace (before this call) that originated within
     *              this class.
     * @return The number of bytes written.
     */
    private int w(String tag, String msg, Throwable tr, int index) {
        StackTraceElement element = getSignificantStackTraceElement(tr, index + 1);
        return super.w(format(mTagFormat, mTagArgs, element, tag, MAX_TAG_LENGTH),
                format(mMsgFormat, mMsgArgs, element, msg), tr);
    }

    /**
     * Send a formatted {@link android.util.Log#DEBUG} log message.
     * @param msg The message you would like logged.
     * @return The number of bytes written.
     */
    public int d(String msg) {
        return d(EMPTY_STRING, msg, null, 1);
    }

    /**
     * Send formatted a {@link android.util.Log#DEBUG} log message.
     * @param msg The message you would like logged.
     * @param tr An exception to log.
     * @return The number of bytes written.
     */
    public int d(String msg, Throwable tr) {
        return d(EMPTY_STRING, msg, tr, 1);
    }

    /**
     * Send a formatted {@link android.util.Log#ERROR} log message.
     * @param msg The message you would like logged.
     * @return The number of bytes written.
     */
    public int e(String msg) {
        return e(EMPTY_STRING, msg, null, 1);
    }

    /**
     * Send formatted a {@link android.util.Log#ERROR} log message.
     * @param msg The message you would like logged.
     * @param tr An exception to log.
     * @return The number of bytes written.
     */
    public int e(String msg, Throwable tr) {
        return e(EMPTY_STRING, msg, tr, 1);
    }

    /**
     * Send a formatted {@link android.util.Log#INFO} log message.
     * @param msg The message you would like logged.
     * @return The number of bytes written.
     */
    public int i(String msg) {
        return i(EMPTY_STRING, msg, null, 1);
    }

    /**
     * Send formatted a {@link android.util.Log#INFO} log message.
     * @param msg The message you would like logged.
     * @param tr An exception to log.
     * @return The number of bytes written.
     */
    public int i(String msg, Throwable tr) {
        return i(EMPTY_STRING, msg, tr, 1);
    }

    /**
     * Send a formatted {@link android.util.Log#VERBOSE} log message.
     * @param msg The message you would like logged.
     * @return The number of bytes written.
     */
    public int v(String msg) {
        return v(EMPTY_STRING, msg, null, 1);
    }

    /**
     * Send formatted a {@link android.util.Log#VERBOSE} log message.
     * @param msg The message you would like logged.
     * @param tr An exception to log.
     * @return The number of bytes written.
     */
    public int v(String msg, Throwable tr) {
        return v(EMPTY_STRING, msg, tr, 1);
    }

    /**
     * Send a formatted {@link android.util.Log#WARN} log message.
     * @param msg The message you would like logged.
     * @return The number of bytes written.
     */
    public int w(String msg) {
        return w(EMPTY_STRING, msg, null, 1);
    }

    /**
     * Send formatted a {@link android.util.Log#WARN} log message.
     * @param msg The message you would like logged.
     * @param tr An exception to log.
     * @return The number of bytes written.
     */
    public int w(String msg, Throwable tr) {
        return w(EMPTY_STRING, msg, tr, 1);
    }
}