Repository: orhanobut/logger
Branch: master
Commit: c64a72c2ce3d
Files: 48
Total size: 95.9 KB
Directory structure:
gitextract_lpzk4f8q/
├── .github/
│ └── ISSUE_TEMPLATE.md
├── .gitignore
├── .idea/
│ ├── codeStyles/
│ │ ├── Project.xml
│ │ └── codeStyleConfig.xml
│ ├── encodings.xml
│ └── vcs.xml
├── .travis.yml
├── LICENSE
├── README.md
├── build.gradle
├── checkstyle.xml
├── gradle/
│ ├── maven_push.gradle
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── logger/
│ ├── build.gradle
│ ├── gradle.properties
│ └── src/
│ ├── main/
│ │ ├── AndroidManifest.xml
│ │ └── java/
│ │ └── com/
│ │ └── orhanobut/
│ │ └── logger/
│ │ ├── AndroidLogAdapter.java
│ │ ├── CsvFormatStrategy.java
│ │ ├── DiskLogAdapter.java
│ │ ├── DiskLogStrategy.java
│ │ ├── FormatStrategy.java
│ │ ├── LogAdapter.java
│ │ ├── LogStrategy.java
│ │ ├── LogcatLogStrategy.java
│ │ ├── Logger.java
│ │ ├── LoggerPrinter.java
│ │ ├── PrettyFormatStrategy.java
│ │ ├── Printer.java
│ │ └── Utils.java
│ └── test/
│ └── java/
│ └── com.orhanobut.logger/
│ ├── AndroidLogAdapterTest.kt
│ ├── CsvFormatStrategyTest.kt
│ ├── DiskLogAdapterTest.kt
│ ├── DiskLogStrategyTest.kt
│ ├── LogAssert.kt
│ ├── LogcatLogStrategyTest.kt
│ ├── LoggerPrinterTest.kt
│ ├── LoggerTest.kt
│ ├── PrettyFormatStrategyTest.kt
│ └── UtilsTest.kt
├── sample/
│ ├── build.gradle
│ └── src/
│ └── main/
│ ├── AndroidManifest.xml
│ ├── java/
│ │ └── com/
│ │ └── orhanobut/
│ │ └── sample/
│ │ └── MainActivity.java
│ └── res/
│ └── layout/
│ └── activity_main.xml
└── settings.gradle
================================================
FILE CONTENTS
================================================
================================================
FILE: .github/ISSUE_TEMPLATE.md
================================================
Please try to fill all questions below before submitting an issue.
- Android studio version:
- Android gradle plugin version:
- Logger version:
- Emulator/phone information:
- If possible, please add how did you initialize Logger?
- Is it flaky or does it happen all the time?
================================================
FILE: .gitignore
================================================
local.properties
# Generated files
build/
# Mac store
.DS_Store
# Gradle files
.gradle/
/captures
# Log Files
*.log
# IDEA/Android Studio
*.iml
*.ipr
*.iws
**/.idea/shelf
**/.idea/workspace.xml
**/.idea/tasks.xml
**/.idea/datasources.xml
**/.idea/dataSources.ids
**/.idea/gradle.xml
**/.idea/misc.xml
**/.idea/modules.xml
**/.idea/libraries
**/.idea/dictionaries
**/.idea/runConfigurations.xml
.idea/caches
# layout inspector view captures
captures
sample/libs/
================================================
FILE: .idea/codeStyles/Project.xml
================================================
[](https://travis-ci.org/orhanobut/logger)
### Logger
Simple, pretty and powerful logger for android
### Setup
Download
```groovy
implementation 'com.orhanobut:logger:2.2.0'
```
Initialize
```java
Logger.addLogAdapter(new AndroidLogAdapter());
```
And use
```java
Logger.d("hello");
```
### Output
### Options
```java
Logger.d("debug");
Logger.e("error");
Logger.w("warning");
Logger.v("verbose");
Logger.i("information");
Logger.wtf("What a Terrible Failure");
```
String format arguments are supported
```java
Logger.d("hello %s", "world");
```
Collections are supported (only available for debug logs)
```java
Logger.d(MAP);
Logger.d(SET);
Logger.d(LIST);
Logger.d(ARRAY);
```
Json and Xml support (output will be in debug level)
```java
Logger.json(JSON_CONTENT);
Logger.xml(XML_CONTENT);
```
### Advanced
```java
FormatStrategy formatStrategy = PrettyFormatStrategy.newBuilder()
.showThreadInfo(false) // (Optional) Whether to show thread info or not. Default true
.methodCount(0) // (Optional) How many method line to show. Default 2
.methodOffset(7) // (Optional) Hides internal method calls up to offset. Default 5
.logStrategy(customLog) // (Optional) Changes the log strategy to print out. Default LogCat
.tag("My custom tag") // (Optional) Global tag for every log. Default PRETTY_LOGGER
.build();
Logger.addLogAdapter(new AndroidLogAdapter(formatStrategy));
```
### Loggable
Log adapter checks whether the log should be printed or not by checking this function.
If you want to disable/hide logs for output, override `isLoggable` method.
`true` will print the log message, `false` will ignore it.
```java
Logger.addLogAdapter(new AndroidLogAdapter() {
@Override public boolean isLoggable(int priority, String tag) {
return BuildConfig.DEBUG;
}
});
```
### Save logs to the file
//TODO: More information will be added later
```java
Logger.addLogAdapter(new DiskLogAdapter());
```
Add custom tag to Csv format strategy
```java
FormatStrategy formatStrategy = CsvFormatStrategy.newBuilder()
.tag("custom")
.build();
Logger.addLogAdapter(new DiskLogAdapter(formatStrategy));
```
### How it works
### More
- Use filter for a better result. PRETTY_LOGGER or your custom tag
- Make sure that wrap option is disabled
- You can also simplify output by changing settings.
- Timber Integration
```java
// Set methodOffset to 5 in order to hide internal method calls
Timber.plant(new Timber.DebugTree() {
@Override protected void log(int priority, String tag, String message, Throwable t) {
Logger.log(priority, tag, message, t);
}
});
```
### License
Copyright 2018 Orhan Obut 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: build.gradle ================================================ buildscript { ext.kotlinVersion = '1.2.51' repositories { google() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:3.2.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion" } } allprojects { repositories { google() jcenter() } } subprojects { project -> group = GROUP version = VERSION_NAME apply plugin: 'checkstyle' task checkstyle(type: Checkstyle) { configFile rootProject.file('checkstyle.xml') source 'src/main/java' ignoreFailures false showViolations true include '**/*.java' classpath = files() } afterEvaluate { if (project.tasks.findByName('check')) { check.dependsOn('checkstyle') } } } task clean(type: Delete) { delete rootProject.buildDir } ext { minSdkVersion = 8 targetSdkVersion = 28 compileSdkVersion = 28 buildToolsVersion = '28.0.2' } ext.deps = [ junit : 'junit:junit:4.12', truth : 'com.google.truth:truth:0.28', robolectric : 'org.robolectric:robolectric:3.3', mockito : "org.mockito:mockito-core:2.8.9", json : "org.json:json:20160810", supportAnnotations: 'androidx.annotation:annotation:1.0.0', kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlinVersion" ] ================================================ FILE: checkstyle.xml ================================================
* ┌────────────────────────── * │ Method stack history * ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ * │ Log message * └────────────────────────── **/ public class AndroidLogAdapter implements LogAdapter { @NonNull private final FormatStrategy formatStrategy; public AndroidLogAdapter() { this.formatStrategy = PrettyFormatStrategy.newBuilder().build(); } public AndroidLogAdapter(@NonNull FormatStrategy formatStrategy) { this.formatStrategy = checkNotNull(formatStrategy); } @Override public boolean isLoggable(int priority, @Nullable String tag) { return true; } @Override public void log(int priority, @Nullable String tag, @NonNull String message) { formatStrategy.log(priority, tag, message); } } ================================================ FILE: logger/src/main/java/com/orhanobut/logger/CsvFormatStrategy.java ================================================ package com.orhanobut.logger; import android.os.Environment; import android.os.Handler; import android.os.HandlerThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; import static com.orhanobut.logger.Utils.checkNotNull; /** * CSV formatted file logging for Android. * Writes to CSV the following data: * epoch timestamp, ISO8601 timestamp (human-readable), log level, tag, log message. */ public class CsvFormatStrategy implements FormatStrategy { private static final String NEW_LINE = System.getProperty("line.separator"); private static final String NEW_LINE_REPLACEMENT = "
* ┌──────────────────────────────────────────── * │ LOGGER * ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ * │ Standard logging mechanism * ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ * │ But more pretty, simple and powerful * └──────────────────────────────────────────── ** *
* Logger.addLogAdapter(new AndroidLogAdapter());
*
*
* And use the appropriate static Logger methods.
*
*
* Logger.d("debug");
* Logger.e("error");
* Logger.w("warning");
* Logger.v("verbose");
* Logger.i("information");
* Logger.wtf("What a Terrible Failure");
*
*
*
* Logger.d("hello %s", "world");
*
*
*
* Logger.d(MAP);
* Logger.d(SET);
* Logger.d(LIST);
* Logger.d(ARRAY);
*
*
*
* Logger.json(JSON_CONTENT);
* Logger.xml(XML_CONTENT);
*
*
* * ┌────────────────────────── * │ Method stack history * ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ * │ Thread information * ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ * │ Log message * └────────────────────────── ** *
* FormatStrategy formatStrategy = PrettyFormatStrategy.newBuilder()
* .showThreadInfo(false) // (Optional) Whether to show thread info or not. Default true
* .methodCount(0) // (Optional) How many method line to show. Default 2
* .methodOffset(7) // (Optional) Hides internal method calls up to offset. Default 5
* .logStrategy(customLog) // (Optional) Changes the log strategy to print out. Default LogCat
* .tag("My custom tag") // (Optional) Global tag for every log. Default PRETTY_LOGGER
* .build();
*
*/
public class PrettyFormatStrategy implements FormatStrategy {
/**
* Android's max limit for a log entry is ~4076 bytes,
* so 4000 bytes is used as chunk size since default charset
* is UTF-8
*/
private static final int CHUNK_SIZE = 4000;
/**
* The minimum stack trace index, starts at this class after two native calls.
*/
private static final int MIN_STACK_OFFSET = 5;
/**
* Drawing toolbox
*/
private static final char TOP_LEFT_CORNER = '┌';
private static final char BOTTOM_LEFT_CORNER = '└';
private static final char MIDDLE_CORNER = '├';
private static final char HORIZONTAL_LINE = '│';
private static final String DOUBLE_DIVIDER = "────────────────────────────────────────────────────────";
private static final String SINGLE_DIVIDER = "┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄";
private static final String TOP_BORDER = TOP_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER;
private static final String BOTTOM_BORDER = BOTTOM_LEFT_CORNER + DOUBLE_DIVIDER + DOUBLE_DIVIDER;
private static final String MIDDLE_BORDER = MIDDLE_CORNER + SINGLE_DIVIDER + SINGLE_DIVIDER;
private final int methodCount;
private final int methodOffset;
private final boolean showThreadInfo;
@NonNull private final LogStrategy logStrategy;
@Nullable private final String tag;
private PrettyFormatStrategy(@NonNull Builder builder) {
checkNotNull(builder);
methodCount = builder.methodCount;
methodOffset = builder.methodOffset;
showThreadInfo = builder.showThreadInfo;
logStrategy = builder.logStrategy;
tag = builder.tag;
}
@NonNull public static Builder newBuilder() {
return new Builder();
}
@Override public void log(int priority, @Nullable String onceOnlyTag, @NonNull String message) {
checkNotNull(message);
String tag = formatTag(onceOnlyTag);
logTopBorder(priority, tag);
logHeaderContent(priority, tag, methodCount);
//get bytes of message with system's default charset (which is UTF-8 for Android)
byte[] bytes = message.getBytes();
int length = bytes.length;
if (length <= CHUNK_SIZE) {
if (methodCount > 0) {
logDivider(priority, tag);
}
logContent(priority, tag, message);
logBottomBorder(priority, tag);
return;
}
if (methodCount > 0) {
logDivider(priority, tag);
}
for (int i = 0; i < length; i += CHUNK_SIZE) {
int count = Math.min(length - i, CHUNK_SIZE);
//create a new String with system's default charset (which is UTF-8 for Android)
logContent(priority, tag, new String(bytes, i, count));
}
logBottomBorder(priority, tag);
}
private void logTopBorder(int logType, @Nullable String tag) {
logChunk(logType, tag, TOP_BORDER);
}
@SuppressWarnings("StringBufferReplaceableByString")
private void logHeaderContent(int logType, @Nullable String tag, int methodCount) {
StackTraceElement[] trace = Thread.currentThread().getStackTrace();
if (showThreadInfo) {
logChunk(logType, tag, HORIZONTAL_LINE + " Thread: " + Thread.currentThread().getName());
logDivider(logType, tag);
}
String level = "";
int stackOffset = getStackOffset(trace) + methodOffset;
//corresponding method count with the current stack may exceeds the stack trace. Trims the count
if (methodCount + stackOffset > trace.length) {
methodCount = trace.length - stackOffset - 1;
}
for (int i = methodCount; i > 0; i--) {
int stackIndex = i + stackOffset;
if (stackIndex >= trace.length) {
continue;
}
StringBuilder builder = new StringBuilder();
builder.append(HORIZONTAL_LINE)
.append(' ')
.append(level)
.append(getSimpleClassName(trace[stackIndex].getClassName()))
.append(".")
.append(trace[stackIndex].getMethodName())
.append(" ")
.append(" (")
.append(trace[stackIndex].getFileName())
.append(":")
.append(trace[stackIndex].getLineNumber())
.append(")");
level += " ";
logChunk(logType, tag, builder.toString());
}
}
private void logBottomBorder(int logType, @Nullable String tag) {
logChunk(logType, tag, BOTTOM_BORDER);
}
private void logDivider(int logType, @Nullable String tag) {
logChunk(logType, tag, MIDDLE_BORDER);
}
private void logContent(int logType, @Nullable String tag, @NonNull String chunk) {
checkNotNull(chunk);
String[] lines = chunk.split(System.getProperty("line.separator"));
for (String line : lines) {
logChunk(logType, tag, HORIZONTAL_LINE + " " + line);
}
}
private void logChunk(int priority, @Nullable String tag, @NonNull String chunk) {
checkNotNull(chunk);
logStrategy.log(priority, tag, chunk);
}
private String getSimpleClassName(@NonNull String name) {
checkNotNull(name);
int lastIndex = name.lastIndexOf(".");
return name.substring(lastIndex + 1);
}
/**
* Determines the starting index of the stack trace, after method calls made by this class.
*
* @param trace the stack trace
* @return the stack offset
*/
private int getStackOffset(@NonNull StackTraceElement[] trace) {
checkNotNull(trace);
for (int i = MIN_STACK_OFFSET; i < trace.length; i++) {
StackTraceElement e = trace[i];
String name = e.getClassName();
if (!name.equals(LoggerPrinter.class.getName()) && !name.equals(Logger.class.getName())) {
return --i;
}
}
return -1;
}
@Nullable private String formatTag(@Nullable String tag) {
if (!Utils.isEmpty(tag) && !Utils.equals(this.tag, tag)) {
return this.tag + "-" + tag;
}
return this.tag;
}
public static class Builder {
int methodCount = 2;
int methodOffset = 0;
boolean showThreadInfo = true;
@Nullable LogStrategy logStrategy;
@Nullable String tag = "PRETTY_LOGGER";
private Builder() {
}
@NonNull public Builder methodCount(int val) {
methodCount = val;
return this;
}
@NonNull public Builder methodOffset(int val) {
methodOffset = val;
return this;
}
@NonNull public Builder showThreadInfo(boolean val) {
showThreadInfo = val;
return this;
}
@NonNull public Builder logStrategy(@Nullable LogStrategy val) {
logStrategy = val;
return this;
}
@NonNull public Builder tag(@Nullable String tag) {
this.tag = tag;
return this;
}
@NonNull public PrettyFormatStrategy build() {
if (logStrategy == null) {
logStrategy = new LogcatLogStrategy();
}
return new PrettyFormatStrategy(this);
}
}
}
================================================
FILE: logger/src/main/java/com/orhanobut/logger/Printer.java
================================================
package com.orhanobut.logger;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
/**
* A proxy interface to enable additional operations.
* Contains all possible Log message usages.
*/
public interface Printer {
void addAdapter(@NonNull LogAdapter adapter);
Printer t(@Nullable String tag);
void d(@NonNull String message, @Nullable Object... args);
void d(@Nullable Object object);
void e(@NonNull String message, @Nullable Object... args);
void e(@Nullable Throwable throwable, @NonNull String message, @Nullable Object... args);
void w(@NonNull String message, @Nullable Object... args);
void i(@NonNull String message, @Nullable Object... args);
void v(@NonNull String message, @Nullable Object... args);
void wtf(@NonNull String message, @Nullable Object... args);
/**
* Formats the given json content and print it
*/
void json(@Nullable String json);
/**
* Formats the given xml content and print it
*/
void xml(@Nullable String xml);
void log(int priority, @Nullable String tag, @Nullable String message, @Nullable Throwable throwable);
void clearLogAdapters();
}
================================================
FILE: logger/src/main/java/com/orhanobut/logger/Utils.java
================================================
package com.orhanobut.logger;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.UnknownHostException;
import java.util.Arrays;
import static com.orhanobut.logger.Logger.ASSERT;
import static com.orhanobut.logger.Logger.DEBUG;
import static com.orhanobut.logger.Logger.ERROR;
import static com.orhanobut.logger.Logger.INFO;
import static com.orhanobut.logger.Logger.VERBOSE;
import static com.orhanobut.logger.Logger.WARN;
/**
* Provides convenient methods to some common operations
*/
final class Utils {
private Utils() {
// Hidden constructor.
}
/**
* Returns true if the string is null or 0-length.
*
* @param str the string to be examined
* @return true if str is null or zero length
*/
static boolean isEmpty(CharSequence str) {
return str == null || str.length() == 0;
}
/**
* Returns true if a and b are equal, including if they are both null.
* Note: In platform versions 1.1 and earlier, this method only worked well if * both the arguments were instances of String.
* * @param a first CharSequence to check * @param b second CharSequence to check * @return true if a and b are equal *
* NOTE: Logic slightly change due to strict policy on CI -
* "Inner assignments should be avoided"
*/
static boolean equals(CharSequence a, CharSequence b) {
if (a == b) return true;
if (a != null && b != null) {
int length = a.length();
if (length == b.length()) {
if (a instanceof String && b instanceof String) {
return a.equals(b);
} else {
for (int i = 0; i < length; i++) {
if (a.charAt(i) != b.charAt(i)) return false;
}
return true;
}
}
}
return false;
}
/**
* Copied from "android.util.Log.getStackTraceString()" in order to avoid usage of Android stack
* in unit tests.
*
* @return Stack trace in form of String
*/
static String getStackTraceString(Throwable tr) {
if (tr == null) {
return "";
}
// This is to reduce the amount of log spew that apps do in the non-error
// condition of the network being unavailable.
Throwable t = tr;
while (t != null) {
if (t instanceof UnknownHostException) {
return "";
}
t = t.getCause();
}
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
tr.printStackTrace(pw);
pw.flush();
return sw.toString();
}
static String logLevel(int value) {
switch (value) {
case VERBOSE:
return "VERBOSE";
case DEBUG:
return "DEBUG";
case INFO:
return "INFO";
case WARN:
return "WARN";
case ERROR:
return "ERROR";
case ASSERT:
return "ASSERT";
default:
return "UNKNOWN";
}
}
public static String toString(Object object) {
if (object == null) {
return "null";
}
if (!object.getClass().isArray()) {
return object.toString();
}
if (object instanceof boolean[]) {
return Arrays.toString((boolean[]) object);
}
if (object instanceof byte[]) {
return Arrays.toString((byte[]) object);
}
if (object instanceof char[]) {
return Arrays.toString((char[]) object);
}
if (object instanceof short[]) {
return Arrays.toString((short[]) object);
}
if (object instanceof int[]) {
return Arrays.toString((int[]) object);
}
if (object instanceof long[]) {
return Arrays.toString((long[]) object);
}
if (object instanceof float[]) {
return Arrays.toString((float[]) object);
}
if (object instanceof double[]) {
return Arrays.toString((double[]) object);
}
if (object instanceof Object[]) {
return Arrays.deepToString((Object[]) object);
}
return "Couldn't find a correct type for the object";
}
@NonNull static