Skip to content

Instantly share code, notes, and snippets.

@joinAero
Last active January 6, 2020 07:31
Show Gist options
  • Save joinAero/51117a3e9234cd47cdb5 to your computer and use it in GitHub Desktop.
Save joinAero/51117a3e9234cd47cdb5 to your computer and use it in GitHub Desktop.
Android - Log class with multiple channels.
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));
}
}
}
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();
}
}
}
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