Last active
January 6, 2020 07:31
-
-
Save joinAero/51117a3e9234cd47cdb5 to your computer and use it in GitHub Desktop.
Android - Log class with multiple channels.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cc.cubone.turbo.core.util; | |
import android.content.Context; | |
import android.os.Environment; | |
import java.io.File; | |
import java.io.FileWriter; | |
import java.io.IOException; | |
import java.io.PrintWriter; | |
import java.text.SimpleDateFormat; | |
import java.util.Date; | |
import java.util.HashMap; | |
/** | |
* Log class with multiple channels. | |
* | |
* <p>Example code to add file channel: | |
* <pre> | |
* Log.setLog(BuildConfig.DEBUG); | |
* if (Log.LOG_ON) { | |
* FileChannel fileChannel = new FileChannel(this); | |
* Log.i(TAG, "Log to file: " + fileChannel.getLogFile()); | |
* Log.addChannel(fileChannel); | |
* } | |
* </pre> | |
* | |
* <p>Requires {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} to | |
* write to external storage for file channel. | |
* | |
* @see android.Manifest.permission#WRITE_EXTERNAL_STORAGE | |
*/ | |
public final class Log { | |
public static final int NONE = -1; | |
public static final int VERBOSE = android.util.Log.VERBOSE; | |
public static final int DEBUG = android.util.Log.DEBUG; | |
public static final int INFO = android.util.Log.INFO; | |
public static final int WARN = android.util.Log.WARN; | |
public static final int ERROR = android.util.Log.ERROR; | |
public static final int ASSERT = android.util.Log.ASSERT; | |
/** | |
* Whether enable the log or not. | |
*/ | |
public static boolean LOG_ON = true; | |
private static final LogChannel mHeadChannel; | |
private static LogChannel mTailChannel; | |
static { | |
mHeadChannel = new ConsoleChannel(); | |
mTailChannel = mHeadChannel; | |
} | |
public static void setLog(boolean visible) { | |
LOG_ON = visible; | |
} | |
public static void addChannel(LogChannel channel) { | |
mTailChannel.setNext(channel); | |
mTailChannel = channel; | |
} | |
public static void removeChannel(LogChannel channel) { | |
LogChannel curr = mHeadChannel; | |
LogChannel next = curr.getNext(); | |
while (next != null) { | |
if (next == channel) { | |
next = next.getNext(); | |
curr.setNext(next); | |
} else { | |
curr = next; | |
next = next.getNext(); | |
} | |
} | |
mTailChannel = curr; | |
} | |
public static void clearChannel() { | |
mHeadChannel.setNext(null); | |
mTailChannel = mHeadChannel; | |
} | |
public static String getStackTraceString(Throwable tr) { | |
return android.util.Log.getStackTraceString(tr); | |
} | |
public static void println(int priority, String tag, String msg, Throwable tr) { | |
if (LOG_ON) { | |
mHeadChannel.println(priority, tag, msg, tr); | |
} | |
} | |
public static void println(int priority, String tag, String msg) { | |
println(priority, tag, msg, null); | |
} | |
/** | |
* Traces the class method which called this. | |
* @param priority Log level of the data being logged. Verbose, Error, etc. | |
*/ | |
public static void trace(int priority) { | |
if (!LOG_ON) { | |
return; | |
} | |
boolean sawLogger = false; | |
final String logClassName = Log.class.getName(); | |
for (StackTraceElement element : new Throwable().getStackTrace()) { | |
String current = element.getClassName(); | |
if (current.startsWith(logClassName)) { | |
sawLogger = true; | |
} else if (sawLogger) { | |
println(priority, element.getClassName(), element.getMethodName()); | |
break; | |
} | |
} | |
} | |
public static void v() { | |
trace(VERBOSE); | |
} | |
public static void d() { | |
trace(DEBUG); | |
} | |
public static void i() { | |
trace(INFO); | |
} | |
public static void w() { | |
trace(WARN); | |
} | |
public static void e() { | |
trace(ERROR); | |
} | |
public static void wtf() { | |
trace(ASSERT); | |
} | |
public static void v(String tag, String msg, Throwable tr) { | |
println(VERBOSE, tag, msg, tr); | |
} | |
public static void v(String tag, String msg) { | |
v(tag, msg, null); | |
} | |
public static void d(String tag, String msg, Throwable tr) { | |
println(DEBUG, tag, msg, tr); | |
} | |
public static void d(String tag, String msg) { | |
d(tag, msg, null); | |
} | |
public static void i(String tag, String msg, Throwable tr) { | |
println(INFO, tag, msg, tr); | |
} | |
public static void i(String tag, String msg) { | |
i(tag, msg, null); | |
} | |
public static void w(String tag, String msg, Throwable tr) { | |
println(WARN, tag, msg, tr); | |
} | |
public static void w(String tag, String msg) { | |
w(tag, msg, null); | |
} | |
public static void w(String tag, Throwable tr) { | |
w(tag, null, tr); | |
} | |
public static void e(String tag, String msg, Throwable tr) { | |
println(ERROR, tag, msg, tr); | |
} | |
public static void e(String tag, String msg) { | |
e(tag, msg, null); | |
} | |
public static void wtf(String tag, String msg, Throwable tr) { | |
println(ASSERT, tag, msg, tr); | |
} | |
public static void wtf(String tag, String msg) { | |
wtf(tag, msg, null); | |
} | |
public static void wtf(String tag, Throwable tr) { | |
wtf(tag, null, tr); | |
} | |
//------------------------------------------------------------------------------ | |
/** | |
* The log channel for being used to output. | |
*/ | |
public static abstract class LogChannel { | |
protected LogChannel mNext; | |
public LogChannel getNext() { | |
return mNext; | |
} | |
public void setNext(LogChannel next) { | |
mNext = next; | |
} | |
public void println(int priority, String tag, String msg, Throwable tr) { | |
String useMsg = msg; | |
if (useMsg == null) { | |
useMsg = ""; | |
} | |
if (tr != null) { | |
msg += '\n' + Log.getStackTraceString(tr); | |
} | |
println(priority, tag, useMsg); | |
// If this isn't the last node in the chain, move things along. | |
if (mNext != null) { | |
mNext.println(priority, tag, msg, tr); | |
} | |
} | |
public abstract void println(int priority, String tag, String msg); | |
} | |
/** | |
* The default log channel which wraps Android's native Log utility. | |
*/ | |
static final class ConsoleChannel extends LogChannel { | |
@Override | |
public void println(int priority, String tag, String msg) { | |
android.util.Log.println(priority, tag, msg); | |
} | |
} | |
/** | |
* The file log channel for outputting to a file. | |
* | |
* <p>Requires {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} to | |
* write to external storage. | |
*/ | |
public static final class FileChannel extends LogChannel { | |
static final String LOG_PREFIX = "log"; | |
static final SimpleDateFormat mDateFormat; | |
static final HashMap<Integer, Character> mLogToken; | |
static { | |
mDateFormat = new SimpleDateFormat("MM-dd HH:mm:ss.SSS"); | |
mLogToken = new HashMap<>(); | |
mLogToken.put(VERBOSE, 'V'); | |
mLogToken.put(DEBUG, 'D'); | |
mLogToken.put(INFO, 'I'); | |
mLogToken.put(WARN, 'W'); | |
mLogToken.put(ERROR, 'E'); | |
mLogToken.put(ASSERT, 'A'); | |
} | |
private File mLogFile; | |
private int mLogLevel = INFO; | |
private boolean mParentDirsCreated = false; | |
public FileChannel(Context context) { | |
// Default filepath for example: /storage/emulated/0/.cc.cubone.example/log | |
this(context, new File(Environment.getExternalStorageDirectory(), | |
'.' + context.getPackageName() + "/log")); | |
//this(context, new File(DirUtils.getExternalPackageDir(context), "log")); | |
} | |
public FileChannel(Context context, File logPath) { | |
setLogFile(new File(logPath, logName(context))); | |
} | |
public FileChannel(File logFile) { | |
setLogFile(logFile); | |
} | |
/** | |
* Sets the log file that you wanna save as. | |
*/ | |
public FileChannel setLogFile(File logFile) { | |
mLogFile = logFile; | |
mParentDirsCreated = false; | |
return this; | |
} | |
/** | |
* Sets the log path where you wanna save the log file. | |
*/ | |
public FileChannel setLogPath(File logPath) { | |
setLogFile(new File(logPath, mLogFile.getName())); | |
return this; | |
} | |
/** | |
* Sets the minimum log level that allows to write, INFO by default. | |
* @param priority Log level of the data being logged. Verbose, Error, etc. | |
*/ | |
public FileChannel setLogLevel(int priority) { | |
mLogLevel = priority; | |
return this; | |
} | |
public File getLogFile() { | |
return mLogFile; | |
} | |
public int getLogLevel() { | |
return mLogLevel; | |
} | |
private String logName(Context context) { | |
String suffix = ProcessUtils.procNameSuffix(context); | |
String name = LOG_PREFIX; | |
if (suffix != null) { | |
name = LOG_PREFIX + '_' + suffix; | |
} | |
String timeStamp = (new SimpleDateFormat("yyyyMMdd_HHmmssSSS")).format(new Date()); | |
return name + '_' + timeStamp; | |
} | |
private void createParentDirs(File file) throws IOException { | |
if (mParentDirsCreated) { | |
return; | |
} | |
final File parent = file.getParentFile(); | |
if (parent != null) { | |
parent.mkdirs(); | |
if (!parent.isDirectory()) { | |
throw new IOException("Unable to create parent directories of " + file); | |
} | |
} | |
mParentDirsCreated = true; | |
} | |
/** | |
* Log a message to the log file | |
*/ | |
private synchronized void println(String msg) { | |
try { | |
createParentDirs(mLogFile); | |
PrintWriter writer = new PrintWriter(new FileWriter(mLogFile, true)); | |
writer.println(msg); | |
writer.flush(); | |
writer.close(); | |
} catch (IOException e) { | |
e.printStackTrace(); | |
} | |
} | |
@Override | |
public void println(int priority, String tag, String msg) { | |
if (priority < mLogLevel) { | |
return; | |
} | |
Character token = mLogToken.get(priority); | |
if (token == null) { | |
token = '?'; | |
} | |
println(String.format("%s %s/%s: %s", mDateFormat.format(new Date()), token, tag, msg)); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cc.cubone.turbo.core.receiver; | |
import android.content.BroadcastReceiver; | |
import android.content.Context; | |
import android.content.Intent; | |
import java.io.File; | |
import cc.cubone.turbo.core.util.Log; | |
/** | |
* Log receiver to control the log state. | |
* | |
* <pre> | |
* <receiver android:name="cc.cubone.turbo.core.receiver.LogReceiver" | |
* android:exported="true"> | |
* <intent-filter> | |
* <action android:name="cc.cubone.action.LOG_ON" /> | |
* <action android:name="cc.cubone.action.LOG_OFF" /> | |
* <action android:name="cc.cubone.action.FILE_ON" /> | |
* <action android:name="cc.cubone.action.FILE_OFF" /> | |
* </intent-filter> | |
* </receiver> | |
* </pre> | |
*/ | |
public class LogReceiver extends BroadcastReceiver { | |
public static final String ACTION_LOG_ON = "cc.cubone.action.LOG_ON"; | |
public static final String ACTION_LOG_OFF = "cc.cubone.action.LOG_OFF"; | |
public static final String ACTION_FILE_ON = "cc.cubone.action.FILE_ON"; | |
public static final String ACTION_FILE_OFF = "cc.cubone.action.FILE_OFF"; | |
public static final String EXTRA_PATH = "cc.cubone.extra.PATH"; | |
public static final String EXTRA_LEVEL = "cc.cubone.extra.LEVEL"; | |
@Override | |
public void onReceive(Context context, Intent intent) { | |
if (intent == null) { return; } | |
final String action = intent.getAction(); | |
if (action == null) { return; } | |
android.util.Log.i("onReceive", "action: " + action); | |
if (action.equals(ACTION_LOG_ON)) { | |
Log.setLog(true); | |
} else if (action.equals(ACTION_LOG_OFF)) { | |
Log.setLog(false); | |
} else if (action.equals(ACTION_FILE_ON)) { | |
final Log.FileChannel fileChannel = new Log.FileChannel(context); | |
final String path = intent.getStringExtra(EXTRA_PATH); | |
if (path != null) { | |
fileChannel.setLogPath(new File(path)); | |
} | |
final int level = intent.getIntExtra(EXTRA_LEVEL, Log.INFO); | |
fileChannel.setLogLevel(level); | |
Log.addChannel(fileChannel); | |
} else if (action.equals(ACTION_FILE_OFF)) { | |
Log.clearChannel(); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package cc.cubone.turbo.core.util; | |
import android.app.ActivityManager; | |
import android.app.ActivityManager.RunningAppProcessInfo; | |
import android.content.Context; | |
import java.util.List; | |
/** | |
* Utility class to deal with android process. | |
*/ | |
public class ProcessUtils { | |
/** | |
* Gets all running services. | |
*/ | |
public static List<ActivityManager.RunningServiceInfo> getRunningServices(Context context) { | |
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); | |
return am.getRunningServices(Integer.MAX_VALUE); | |
} | |
/** | |
* Whether the service is running or not. | |
* | |
* @param context The context. | |
* @param service The service class. | |
*/ | |
public static boolean isServiceRunning(Context context, Class<?> service) { | |
List<ActivityManager.RunningServiceInfo> servList = getRunningServices(context); | |
final String servClsName = service.getName(); | |
for (ActivityManager.RunningServiceInfo servInfo : servList) { | |
if (servClsName.equals(servInfo.service.getClassName())) { | |
return true; | |
} | |
} | |
return false; | |
} | |
//------------------------------------------------------------------------------ | |
/** | |
* Gets all running processes. | |
*/ | |
public static List<RunningAppProcessInfo> getRunningProcesses(Context context) { | |
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); | |
return am.getRunningAppProcesses(); | |
} | |
/** | |
* Returns the identifier of this process. | |
* @see {@link android.os.Process#myPid()} | |
*/ | |
public static int pid() { | |
return android.os.Process.myPid(); | |
} | |
/** | |
* Returns the info of this process, or null if not found. | |
*/ | |
public static RunningAppProcessInfo procInfo(Context context) { | |
final int pid = pid(); | |
for (RunningAppProcessInfo info : getRunningProcesses(context)) { | |
if (info.pid == pid) { | |
return info; | |
} | |
} | |
return null; | |
} | |
/** | |
* Returns the name of this process, or null if not found. | |
*/ | |
public static String procName(Context context) { | |
final RunningAppProcessInfo info = procInfo(context); | |
return info == null ? null : info.processName; | |
} | |
/** | |
* Returns the suffix of process name, or null if it's the main process or not found. | |
*/ | |
public static String procNameSuffix(Context context) { | |
String pkgName = context.getPackageName(); | |
String procName = procName(context); | |
int pkgNameLen = pkgName.length(); | |
if (procName != null && procName.length() > pkgNameLen | |
&& procName.startsWith(pkgName) | |
&& ':' == procName.charAt(pkgNameLen)) { | |
return procName.substring(pkgNameLen + 1); | |
} | |
return null; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment