/* * Copyright 2016 JetBrains s.r.o. * * 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. */ @file:Suppress("unused", "NOTHING_TO_INLINE", "MatchingDeclarationName", "NAME_SHADOWING") @file:JvmName("Logging") package org.jetbrains.anko import android.util.Log /** * Interface for the Anko logger. * Normally you should pass the logger tag to the [Log] methods, such as [Log.d] or [Log.e]. * This can be inconvenient because you should store the tag somewhere or hardcode it, * which is considered to be a bad practice. * * Instead of hardcoding tags, Anko provides an [AnkoLogger] interface. You can just add the interface to * any of your classes, and use any of the provided extension functions, such as * [AnkoLogger.debug] or [AnkoLogger.error]. * * The tag is the simple class name by default, but you can change it to anything you want just * by overriding the [loggerTag] property. */ interface AnkoLogger { /** * The logger tag used in extension functions for the [AnkoLogger]. * Note that the tag length should not be more than 23 symbols. */ val loggerTag: String get() = getTag(javaClass) } fun AnkoLogger(clazz: Class<*>): AnkoLogger = object : AnkoLogger { override val loggerTag = getTag(clazz) } fun AnkoLogger(tag: String): AnkoLogger = object : AnkoLogger { init { assert(tag.length <= 23) { "The maximum tag length is 23, got $tag" } } override val loggerTag = tag } inline fun AnkoLogger(): AnkoLogger = AnkoLogger(T::class.java) /** * Send a log message with the [Log.VERBOSE] severity. * Note that the log message will not be written if the current log level is above [Log.VERBOSE]. * The default log level is [Log.INFO]. * * @param message the message text to log. `null` value will be represent as "null", for any other value * the [Any.toString] will be invoked. * @param thr an exception to log (optional). * * @see [Log.v]. */ fun AnkoLogger.verbose(message: Any?, thr: Throwable? = null) { log(this, message, thr, Log.VERBOSE, { tag, msg -> Log.v(tag, msg) }, { tag, msg, thr -> Log.v(tag, msg, thr) }) } /** * Send a log message with the [Log.DEBUG] severity. * Note that the log message will not be written if the current log level is above [Log.DEBUG]. * The default log level is [Log.INFO]. * * @param message the message text to log. `null` value will be represent as "null", for any other value * the [Any.toString] will be invoked. * @param thr an exception to log (optional). * * @see [Log.d]. */ fun AnkoLogger.debug(message: Any?, thr: Throwable? = null) { log(this, message, thr, Log.DEBUG, { tag, msg -> Log.d(tag, msg) }, { tag, msg, thr -> Log.d(tag, msg, thr) }) } /** * Send a log message with the [Log.INFO] severity. * Note that the log message will not be written if the current log level is above [Log.INFO] * (it is the default level). * * @param message the message text to log. `null` value will be represent as "null", for any other value * the [Any.toString] will be invoked. * @param thr an exception to log (optional). * * @see [Log.i]. */ fun AnkoLogger.info(message: Any?, thr: Throwable? = null) { log(this, message, thr, Log.INFO, { tag, msg -> Log.i(tag, msg) }, { tag, msg, thr -> Log.i(tag, msg, thr) }) } /** * Send a log message with the [Log.WARN] severity. * Note that the log message will not be written if the current log level is above [Log.WARN]. * The default log level is [Log.INFO]. * * @param message the message text to log. `null` value will be represent as "null", for any other value * the [Any.toString] will be invoked. * @param thr an exception to log (optional). * * @see [Log.w]. */ fun AnkoLogger.warn(message: Any?, thr: Throwable? = null) { log(this, message, thr, Log.WARN, { tag, msg -> Log.w(tag, msg) }, { tag, msg, thr -> Log.w(tag, msg, thr) }) } /** * Send a log message with the [Log.ERROR] severity. * Note that the log message will not be written if the current log level is above [Log.ERROR]. * The default log level is [Log.INFO]. * * @param message the message text to log. `null` value will be represent as "null", for any other value * the [Any.toString] will be invoked. * @param thr an exception to log (optional). * * @see [Log.e]. */ fun AnkoLogger.error(message: Any?, thr: Throwable? = null) { log(this, message, thr, Log.ERROR, { tag, msg -> Log.e(tag, msg) }, { tag, msg, thr -> Log.e(tag, msg, thr) }) } /** * Send a log message with the "What a Terrible Failure" severity. * Report an exception that should never happen. * * @param message the message text to log. `null` value will be represent as "null", for any other value * the [Any.toString] will be invoked. * @param thr an exception to log (optional). * * @see [Log.wtf]. */ fun AnkoLogger.wtf(message: Any?, thr: Throwable? = null) { if (thr != null) { Log.wtf(loggerTag, message?.toString() ?: "null", thr) } else { Log.wtf(loggerTag, message?.toString() ?: "null") } } /** * Send a log message with the [Log.VERBOSE] severity. * Note that the log message will not be written if the current log level is above [Log.VERBOSE]. * The default log level is [Log.INFO]. * * @param message the function that returns message text to log. * `null` value will be represent as "null", for any other value the [Any.toString] will be invoked. * * @see [Log.v]. */ inline fun AnkoLogger.verbose(message: () -> Any?) { val tag = loggerTag if (Log.isLoggable(tag, Log.VERBOSE)) { Log.v(tag, message()?.toString() ?: "null") } } /** * Send a log message with the [Log.DEBUG] severity. * Note that the log message will not be written if the current log level is above [Log.DEBUG]. * The default log level is [Log.INFO]. * * @param message the function that returns message text to log. * `null` value will be represent as "null", for any other value the [Any.toString] will be invoked. * * @see [Log.d]. */ inline fun AnkoLogger.debug(message: () -> Any?) { val tag = loggerTag if (Log.isLoggable(tag, Log.DEBUG)) { Log.d(tag, message()?.toString() ?: "null") } } /** * Send a log message with the [Log.INFO] severity. * Note that the log message will not be written if the current log level is above [Log.INFO]. * The default log level is [Log.INFO]. * * @param message the function that returns message text to log. * `null` value will be represent as "null", for any other value the [Any.toString] will be invoked. * * @see [Log.i]. */ inline fun AnkoLogger.info(message: () -> Any?) { val tag = loggerTag if (Log.isLoggable(tag, Log.INFO)) { Log.i(tag, message()?.toString() ?: "null") } } /** * Send a log message with the [Log.WARN] severity. * Note that the log message will not be written if the current log level is above [Log.WARN]. * The default log level is [Log.INFO]. * * @param message the function that returns message text to log. * `null` value will be represent as "null", for any other value the [Any.toString] will be invoked. * * @see [Log.w]. */ inline fun AnkoLogger.warn(message: () -> Any?) { val tag = loggerTag if (Log.isLoggable(tag, Log.WARN)) { Log.w(tag, message()?.toString() ?: "null") } } /** * Send a log message with the [Log.ERROR] severity. * Note that the log message will not be written if the current log level is above [Log.ERROR]. * The default log level is [Log.INFO]. * * @param message the function that returns message text to log. * `null` value will be represent as "null", for any other value the [Any.toString] will be invoked. * * @see [Log.e]. */ inline fun AnkoLogger.error(message: () -> Any?) { val tag = loggerTag if (Log.isLoggable(tag, Log.ERROR)) { Log.e(tag, message()?.toString() ?: "null") } } /** * Return the stack trace [String] of a throwable. */ inline fun Throwable.getStackTraceString(): String = Log.getStackTraceString(this) private inline fun log( logger: AnkoLogger, message: Any?, thr: Throwable?, level: Int, f: (String, String) -> Unit, fThrowable: (String, String, Throwable) -> Unit, ) { val tag = logger.loggerTag if (Log.isLoggable(tag, level)) { if (thr != null) { fThrowable(tag, message?.toString() ?: "null", thr) } else { f(tag, message?.toString() ?: "null") } } } private fun getTag(clazz: Class<*>): String { val tag = clazz.simpleName return if (tag.length <= 23) { tag } else { tag.substring(0, 23) } }