001/*
002 * Copyright 2015 The AppAuth for Android Authors. All Rights Reserved.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
005 * in compliance with the License. You may obtain a copy of the License at
006 *
007 * http://www.apache.org/licenses/LICENSE-2.0
008 *
009 * Unless required by applicable law or agreed to in writing, software distributed under the
010 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
011 * express or implied. See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014
015package net.openid.appauth.internal;
016
017import static net.openid.appauth.Preconditions.checkNotNull;
018
019import android.util.Log;
020import androidx.annotation.NonNull;
021import androidx.annotation.Nullable;
022import androidx.annotation.VisibleForTesting;
023
024/**
025 * Convenience wrapper around {@link android.util.Log}, which evaluates the current log level of
026 * the logging tag once and uses this to determine whether logging should proceed. This minimizes
027 * the number of native calls made as part of logging.
028 */
029public final class Logger {
030
031    @VisibleForTesting
032    static final String LOG_TAG = "AppAuth";
033
034    @Nullable
035    private static Logger sInstance;
036
037    @NonNull
038    private final LogWrapper mLog;
039
040    private final int mLogLevel;
041
042    public static synchronized Logger getInstance() {
043        if (sInstance == null) {
044            sInstance = new Logger(AndroidLogWrapper.INSTANCE);
045        }
046        return sInstance;
047    }
048
049    @VisibleForTesting
050    public static synchronized void setInstance(Logger logger) {
051        sInstance = logger;
052    }
053
054    @VisibleForTesting
055    Logger(LogWrapper log) {
056        mLog = checkNotNull(log);
057        // determine the active logging level
058        int level = Log.ASSERT;
059        while (level >= Log.VERBOSE && mLog.isLoggable(LOG_TAG, level)) {
060            level--;
061        }
062
063        mLogLevel = level + 1;
064    }
065
066    public static void verbose(String message, Object... messageParams) {
067        getInstance().log(Log.VERBOSE, null, message, messageParams);
068    }
069
070    public static void verboseWithStack(Throwable tr, String message, Object... messageParams) {
071        getInstance().log(Log.VERBOSE, tr, message, messageParams);
072    }
073
074    public static void debug(String message, Object... messageParams) {
075        getInstance().log(Log.DEBUG, null, message, messageParams);
076    }
077
078    public static void debugWithStack(Throwable tr, String message, Object... messageParams) {
079        getInstance().log(Log.DEBUG, tr, message, messageParams);
080    }
081
082    public static void info(String message, Object... messageParams) {
083        getInstance().log(Log.INFO, null, message, messageParams);
084    }
085
086    public static void infoWithStack(Throwable tr, String message, Object... messageParams) {
087        getInstance().log(Log.INFO, tr, message, messageParams);
088    }
089
090    public static void warn(String message, Object... messageParams) {
091        getInstance().log(Log.WARN, null, message, messageParams);
092    }
093
094    public static void warnWithStack(Throwable tr, String message, Object... messageParams) {
095        getInstance().log(Log.WARN, tr, message, messageParams);
096    }
097
098    public static void error(String message, Object... messageParams) {
099        getInstance().log(Log.ERROR, null, message, messageParams);
100    }
101
102    public static void errorWithStack(Throwable tr, String message, Object... messageParams) {
103        getInstance().log(Log.ERROR, tr, message, messageParams);
104    }
105
106    public void log(int level, Throwable tr, String message, Object... messageParams) {
107        if (mLogLevel > level) {
108            return;
109        }
110        String formattedMessage;
111        if (messageParams == null || messageParams.length < 1) {
112            formattedMessage = message;
113        } else {
114            formattedMessage = String.format(message, messageParams);
115        }
116
117        if (tr != null) {
118            formattedMessage += "\n" + mLog.getStackTraceString(tr);
119        }
120
121        mLog.println(level, LOG_TAG, formattedMessage);
122    }
123
124    /**
125     * The core interface of {@link android.util.Log}, converted into instance methods so as to
126     * allow easier mock testing.
127     */
128    @VisibleForTesting
129    public interface LogWrapper {
130        void println(int level, String tag, String message);
131
132        boolean isLoggable(String tag, int level);
133
134        String getStackTraceString(Throwable tr);
135    }
136
137    /**
138     * Default {@link LogWrapper} implementation, using {@link android.util.Log} static methods.
139     */
140    private static final class AndroidLogWrapper implements LogWrapper {
141        private static final AndroidLogWrapper INSTANCE = new AndroidLogWrapper();
142
143        private AndroidLogWrapper() {}
144
145        public void println(int level, String tag, String message) {
146            Log.println(level, tag, message);
147        }
148
149        public boolean isLoggable(String tag, int level) {
150            return Log.isLoggable(tag, level);
151        }
152
153        public String getStackTraceString(Throwable tr) {
154            return Log.getStackTraceString(tr);
155        }
156    }
157}