ฉันมีคำสั่งการบันทึกจำนวนมากเพื่อตรวจแก้จุดบกพร่องตัวอย่างเช่น
Log.v(TAG, "Message here");
Log.w(TAG, " WARNING HERE");
ในขณะที่ปรับใช้แอปพลิเคชันนี้บนโทรศัพท์อุปกรณ์ฉันต้องการปิดการบันทึก verbose จากที่ฉันสามารถเปิด / ปิดการใช้งานการบันทึก
ฉันมีคำสั่งการบันทึกจำนวนมากเพื่อตรวจแก้จุดบกพร่องตัวอย่างเช่น
Log.v(TAG, "Message here");
Log.w(TAG, " WARNING HERE");
ในขณะที่ปรับใช้แอปพลิเคชันนี้บนโทรศัพท์อุปกรณ์ฉันต้องการปิดการบันทึก verbose จากที่ฉันสามารถเปิด / ปิดการใช้งานการบันทึก
คำตอบ:
วิธีทั่วไปคือการสร้าง loglevel แบบ int และกำหนดระดับการดีบักตาม loglevel
public static int LOGLEVEL = 2;
public static boolean ERROR = LOGLEVEL > 0;
public static boolean WARN = LOGLEVEL > 1;
...
public static boolean VERBOSE = LOGLEVEL > 4;
if (VERBOSE) Log.v(TAG, "Message here"); // Won't be shown
if (WARN) Log.w(TAG, "WARNING HERE"); // Still goes through
หลังจากนั้นคุณสามารถเปลี่ยน LOGLEVEL สำหรับระดับผลลัพธ์การดีบักทั้งหมด
เอกสาร Android กล่าวว่าต่อไปนี้เกี่ยวกับระดับการเข้าสู่ระบบ :
ไม่ควรรวบรวมข้อมูล verbose ลงในแอปพลิเคชันยกเว้นในระหว่างการพัฒนา บันทึกการดีบักจะถูกคอมไพล์ แต่ถูกตัดออกตอนรันไทม์ บันทึกข้อผิดพลาดคำเตือนและข้อมูลจะถูกเก็บไว้เสมอ
ดังนั้นคุณอาจต้องการที่จะต้องพิจารณาปอกบันทึกการบันทึกรายละเอียดงบออกอาจใช้ ProGuard ตามที่แนะนำในคำตอบอื่น
ตามเอกสารคุณสามารถกำหนดค่าการบันทึกบนอุปกรณ์การพัฒนาโดยใช้คุณสมบัติระบบ คุณสมบัติการเป็นชุดlog.tag.<YourTag>
และมันควรจะตั้งค่าใดค่าหนึ่งต่อไปนี้: VERBOSE
, DEBUG
, INFO
, WARN
, ERROR
, หรือ ASSERT
ข้อมูลเพิ่มเติมเกี่ยวกับสิ่งนี้มีอยู่ในเอกสารประกอบสำหรับวิธีการSUPPRESS
isLoggable()
คุณสามารถตั้งค่าคุณสมบัติชั่วคราวโดยใช้setprop
คำสั่ง ตัวอย่างเช่น:
C:\android>adb shell setprop log.tag.MyAppTag WARN
C:\android>adb shell getprop log.tag.MyAppTag
WARN
หรือคุณสามารถระบุได้ในไฟล์ '/data/local.prop' ดังนี้
log.tag.MyAppTag=WARN
รุ่นที่ใหม่กว่า Android ปรากฏว่าจำเป็นต้องให้ /data/local.prop อ่านได้เท่านั้น ไฟล์นี้จะถูกอ่านในเวลาบูตดังนั้นคุณจะต้องรีสตาร์ทหลังจากอัปเดต หาก/data/local.prop
เป็นโลกที่เขียนได้ก็อาจจะถูกละเว้น
สุดท้ายคุณก็สามารถตั้งค่าให้โปรแกรมโดยใช้วิธีการSystem.setProperty()
android.util.Config
ค่าคงที่ส่วนใหญ่ที่เลิกใช้แล้ว ค่าฮาร์ดโค้ดที่ระบุในเอกสาร API นั้นไม่มีประโยชน์เนื่องจากค่าเหล่านี้ (ตามที่คาดคะเน) จะแตกต่างกันไปตามบิลด์ ดังนั้นเส้นทาง ProGuard จึงเป็นทางออกที่ดีที่สุดสำหรับเรา
วิธีที่ง่ายที่สุดคือการรัน JAR ที่คอมไพล์ด้วยProGuardก่อนการปรับใช้ด้วยการกำหนดค่าเช่น:
-assumenosideeffects class android.util.Log {
public static int v(...);
}
นั่นจะ - นอกเหนือจากการเพิ่มประสิทธิภาพ ProGuard อื่น ๆ - ลบคำสั่งบันทึก verbose ใด ๆ โดยตรงจาก bytecode
ฉันใช้เส้นทางง่ายๆ - การสร้างคลาส wrapper ที่ใช้ประโยชน์จากรายการตัวแปรตัวแปร
public class Log{
public static int LEVEL = android.util.Log.WARN;
static public void d(String tag, String msgFormat, Object...args)
{
if (LEVEL<=android.util.Log.DEBUG)
{
android.util.Log.d(tag, String.format(msgFormat, args));
}
}
static public void d(String tag, Throwable t, String msgFormat, Object...args)
{
if (LEVEL<=android.util.Log.DEBUG)
{
android.util.Log.d(tag, String.format(msgFormat, args), t);
}
}
//...other level logging functions snipped
วิธีที่ดีกว่าคือการใช้ SLF4J API + บางส่วนของการใช้งาน
สำหรับแอปพลิเคชัน Android คุณสามารถใช้สิ่งต่อไปนี้:
logback-android
(เนื่องจากlogback
ไม่เข้ากัน) logback-android-1.0.10-1.jar
คือ 429 KB ซึ่งไม่เลวร้ายนักเมื่อพิจารณาคุณสมบัติที่มีให้ แต่นักพัฒนาส่วนใหญ่จะใช้ Proguard เพื่อปรับแต่งแอปพลิเคชันของตนให้ดีที่สุดอยู่ดี
คุณควรใช้
if (Log.isLoggable(TAG, Log.VERBOSE)) {
Log.v(TAG, "my log message");
}
การแยกการบันทึกด้วย proguard (ดูคำตอบจาก @Christopher) นั้นง่ายและรวดเร็ว แต่มันทำให้การติดตามสแต็กจากการผลิตไม่ตรงกันกับแหล่งที่มาหากมีการบันทึกการดีบักในไฟล์
นี่เป็นเทคนิคที่ใช้ระดับการบันทึกที่แตกต่างกันในการพัฒนากับการผลิตโดยสมมติว่า proguard ใช้ในการผลิตเท่านั้น มันรับรู้การผลิตโดยดูว่า proguard เปลี่ยนชื่อคลาสที่กำหนดหรือไม่ (ในตัวอย่างฉันใช้ "com.foo.Bar" - คุณจะแทนที่ด้วยชื่อคลาสที่ผ่านการรับรองซึ่งคุณรู้ว่าจะถูกเปลี่ยนชื่อโดย proguard)
เทคนิคนี้ใช้ประโยชน์จากการบันทึกทั่วไป
private void initLogging() {
Level level = Level.WARNING;
try {
// in production, the shrinker/obfuscator proguard will change the
// name of this class (and many others) so in development, this
// class WILL exist as named, and we will have debug level
Class.forName("com.foo.Bar");
level = Level.FINE;
} catch (Throwable t) {
// no problem, we are in production mode
}
Handler[] handlers = Logger.getLogger("").getHandlers();
for (Handler handler : handlers) {
Log.d("log init", "handler: " + handler.getClass().getName());
handler.setLevel(level);
}
}
Log4j หรือ slf4j ยังสามารถใช้เป็นเฟรมเวิร์กการบันทึกใน Android พร้อมกับ logcat ดูโครงการสนับสนุนandroid-logging-log4jหรือlog4j ใน android
มีการแทนที่แบบดรอปอินเล็กน้อยสำหรับคลาสบันทึกการทำงานของ android - https://github.com/zserge/log
โดยทั่วไปสิ่งที่คุณต้องทำคือการทดแทนการนำเข้าจากไปandroid.util.Log
trikita.log.Log
จากนั้นในApplication.onCreate()
initalizer ของคุณหรือในบางตรวจสอบคงที่BuilConfig.DEBUG
หรือธงอื่น ๆ และใช้Log.level(Log.D)
หรือLog.level(Log.E)
เปลี่ยนระดับการบันทึกน้อยที่สุด คุณสามารถใช้Log.useLog(false)
เพื่อปิดการบันทึกได้เลย
อาจจะเป็นคุณสามารถดูนี้เข้าสู่ระดับการขยาย: https://github.com/dbauduin/Android-Tools/tree/master/logs
ช่วยให้คุณสามารถควบคุมการบันทึกได้ดี ตัวอย่างเช่นคุณสามารถปิดการใช้งานบันทึกทั้งหมดหรือเพียงแค่บันทึกของแพคเกจหรือคลาสบางอย่าง
ยิ่งไปกว่านั้นมันเพิ่มฟังก์ชั่นที่มีประโยชน์บางอย่าง (เช่นคุณไม่จำเป็นต้องผ่านแท็กสำหรับแต่ละบันทึก)
ฉันสร้าง Utility / Wrapper ซึ่งแก้ปัญหานี้ + ปัญหาทั่วไปอื่น ๆ เกี่ยวกับการบันทึก
ยูทิลิตีการดีบักที่มีคุณสมบัติต่อไปนี้:
วิธีใช้?
ฉันได้พยายามที่จะทำให้เอกสารประกอบตัวเองน่าพอใจ
ข้อเสนอแนะเพื่อปรับปรุงยูทิลิตี้นี้ยินดีต้อนรับ
ใช้งาน / แบ่งปันได้ฟรี
นี่คือทางออกที่ซับซ้อนมากขึ้น คุณจะได้รับการติดตามแบบเต็มสแต็กและเมธอด toString () จะถูกเรียกใช้เฉพาะเมื่อจำเป็น (ประสิทธิภาพ) คุณลักษณะ BuildConfig.DEBUG จะเป็นเท็จในโหมดการผลิตดังนั้นบันทึกการติดตามและการดีบักทั้งหมดจะถูกลบออก คอมไพเลอร์ฮอตสปอตมีโอกาสที่จะลบการโทรออกเพราะปิดคุณสมบัติคงที่ขั้นสุดท้าย
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import android.util.Log;
public class Logger {
public enum Level {
error, warn, info, debug, trace
}
private static final String DEFAULT_TAG = "Project";
private static final Level CURRENT_LEVEL = BuildConfig.DEBUG ? Level.trace : Level.info;
private static boolean isEnabled(Level l) {
return CURRENT_LEVEL.compareTo(l) >= 0;
}
static {
Log.i(DEFAULT_TAG, "log level: " + CURRENT_LEVEL.name());
}
private String classname = DEFAULT_TAG;
public void setClassName(Class<?> c) {
classname = c.getSimpleName();
}
public String getClassname() {
return classname;
}
public boolean isError() {
return isEnabled(Level.error);
}
public boolean isWarn() {
return isEnabled(Level.warn);
}
public boolean isInfo() {
return isEnabled(Level.info);
}
public boolean isDebug() {
return isEnabled(Level.debug);
}
public boolean isTrace() {
return isEnabled(Level.trace);
}
public void error(Object... args) {
if (isError()) Log.e(buildTag(), build(args));
}
public void warn(Object... args) {
if (isWarn()) Log.w(buildTag(), build(args));
}
public void info(Object... args) {
if (isInfo()) Log.i(buildTag(), build(args));
}
public void debug(Object... args) {
if (isDebug()) Log.d(buildTag(), build(args));
}
public void trace(Object... args) {
if (isTrace()) Log.v(buildTag(), build(args));
}
public void error(String msg, Throwable t) {
if (isError()) error(buildTag(), msg, stackToString(t));
}
public void warn(String msg, Throwable t) {
if (isWarn()) warn(buildTag(), msg, stackToString(t));
}
public void info(String msg, Throwable t) {
if (isInfo()) info(buildTag(), msg, stackToString(t));
}
public void debug(String msg, Throwable t) {
if (isDebug()) debug(buildTag(), msg, stackToString(t));
}
public void trace(String msg, Throwable t) {
if (isTrace()) trace(buildTag(), msg, stackToString(t));
}
private String buildTag() {
String tag ;
if (BuildConfig.DEBUG) {
StringBuilder b = new StringBuilder(20);
b.append(getClassname());
StackTraceElement stackEntry = Thread.currentThread().getStackTrace()[4];
if (stackEntry != null) {
b.append('.');
b.append(stackEntry.getMethodName());
b.append(':');
b.append(stackEntry.getLineNumber());
}
tag = b.toString();
} else {
tag = DEFAULT_TAG;
}
}
private String build(Object... args) {
if (args == null) {
return "null";
} else {
StringBuilder b = new StringBuilder(args.length * 10);
for (Object arg : args) {
if (arg == null) {
b.append("null");
} else {
b.append(arg);
}
}
return b.toString();
}
}
private String stackToString(Throwable t) {
ByteArrayOutputStream baos = new ByteArrayOutputStream(500);
baos.toString();
t.printStackTrace(new PrintStream(baos));
return baos.toString();
}
}
ใช้แบบนี้:
Loggor log = new Logger();
Map foo = ...
List bar = ...
log.error("Foo:", foo, "bar:", bar);
// bad example (avoid something like this)
// log.error("Foo:" + " foo.toString() + "bar:" + bar);
ในสถานการณ์การบันทึกที่ง่ายมากซึ่งคุณเพียงแค่พยายามเขียนถึงคอนโซลในระหว่างการพัฒนาเพื่อการดีบั๊กมันอาจจะง่ายที่สุดในการค้นหาและแทนที่ก่อนที่การผลิตของคุณจะสร้างและแสดงความคิดเห็นเกี่ยวกับการเรียก Log หรือระบบทั้งหมด out.println
ตัวอย่างเช่นสมมติว่าคุณไม่ได้ใช้ "บันทึก" ที่ใดก็ตามนอกการเรียก Log.d หรือ Log.e ฯลฯ คุณสามารถทำการค้นหาและแทนที่ในโซลูชันทั้งหมดเพื่อแทนที่ "Log" ด้วย "// Log." เพื่อแสดงความคิดเห็นการบันทึกการโทรทั้งหมดของคุณหรือในกรณีของฉันฉันใช้ System.out.println ทุกที่ดังนั้นก่อนที่จะไปผลิตฉันจะทำการค้นหาแบบเต็มและแทนที่ "System.out.println" และแทนที่ด้วย "//System.out.println"
ฉันรู้ว่ามันไม่เหมาะและมันจะดีถ้าความสามารถในการค้นหาและคอมเม้นท์การโทรไปที่ Log และ System.out.println ถูกสร้างขึ้นใน Eclipse แต่จนกระทั่งเกิดขึ้นวิธีที่ง่ายที่สุดและเร็วที่สุดและดีที่สุดในการทำเช่นนี้คือ เพื่อแสดงความคิดเห็นโดยการค้นหาและแทนที่ หากคุณทำเช่นนี้คุณไม่ต้องกังวลเกี่ยวกับหมายเลขบรรทัดการติดตามสแต็กที่ไม่ตรงกันเนื่องจากคุณกำลังแก้ไขซอร์สโค้ดและคุณไม่ได้เพิ่มโอเวอร์เฮดใด ๆ โดยตรวจสอบการกำหนดค่าระดับบันทึก ฯลฯ
ในแอพของฉันฉันมีคลาสที่ล้อมคลาส Log ซึ่งมีบูลีน var แบบคงที่เรียกว่า "state" ตลอดรหัสของฉันฉันตรวจสอบค่าของตัวแปร "สถานะ" โดยใช้วิธีการคงที่ก่อนที่จะเขียนลงในบันทึก ฉันมีวิธีการแบบคงที่เพื่อตั้งค่าตัวแปร "สถานะ" ซึ่งรับรองว่าค่านั้นเป็นเรื่องปกติสำหรับทุกกรณีที่สร้างโดยแอป ซึ่งหมายความว่าฉันสามารถเปิดใช้งานหรือปิดใช้งานการบันทึกทั้งหมดสำหรับแอพในการโทรครั้งเดียว - แม้ว่าแอพจะทำงานอยู่ก็ตาม มีประโยชน์สำหรับการโทรติดต่อสนับสนุน ... หมายความว่าคุณต้องติดกับปืนเมื่อทำการดีบั๊กและไม่ต้องใช้คลาสบันทึกมาตรฐาน ...
นอกจากนี้ยังมีประโยชน์ (สะดวก) ที่ Java ตีความบูลีน var เป็นเท็จหากยังไม่ได้รับการกำหนดค่าซึ่งหมายความว่าจะสามารถทิ้งเป็นเท็จได้จนกว่าคุณจะต้องเปิดการบันทึก :-)
เราสามารถใช้คลาสLog
ในส่วนประกอบท้องถิ่นของเราและกำหนดวิธีการเป็น v / i / e / d ตามความต้องการของเราสามารถโทรติดต่อเพิ่มเติมได้
ตัวอย่างที่แสดงด้านล่าง
public class Log{
private static boolean TAG = false;
public static void d(String enable_tag, String message,Object...args){
if(TAG)
android.util.Log.d(enable_tag, message+args);
}
public static void e(String enable_tag, String message,Object...args){
if(TAG)
android.util.Log.e(enable_tag, message+args);
}
public static void v(String enable_tag, String message,Object...args){
if(TAG)
android.util.Log.v(enable_tag, message+args);
}
}
if we do not need any print(s), at-all make TAG as false for all else
remove the check for type of Log (say Log.d).
as
public static void i(String enable_tag, String message,Object...args){
// if(TAG)
android.util.Log.i(enable_tag, message+args);
}
ข้อความที่นี่มีไว้สำหรับstring
และและargs
เป็นค่าที่คุณต้องการพิมพ์
สำหรับฉันมันมักจะมีประโยชน์ที่จะสามารถตั้งค่าระดับการบันทึกที่แตกต่างกันสำหรับแต่ละแท็ก
ฉันใช้คลาส wrapper ง่าย ๆ นี้:
public class Log2 {
public enum LogLevels {
VERBOSE(android.util.Log.VERBOSE), DEBUG(android.util.Log.DEBUG), INFO(android.util.Log.INFO), WARN(
android.util.Log.WARN), ERROR(android.util.Log.ERROR);
int level;
private LogLevels(int logLevel) {
level = logLevel;
}
public int getLevel() {
return level;
}
};
static private HashMap<String, Integer> logLevels = new HashMap<String, Integer>();
public static void setLogLevel(String tag, LogLevels level) {
logLevels.put(tag, level.getLevel());
}
public static int v(String tag, String msg) {
return Log2.v(tag, msg, null);
}
public static int v(String tag, String msg, Throwable tr) {
if (logLevels.containsKey(tag)) {
if (logLevels.get(tag) > android.util.Log.VERBOSE) {
return -1;
}
}
return Log.v(tag, msg, tr);
}
public static int d(String tag, String msg) {
return Log2.d(tag, msg, null);
}
public static int d(String tag, String msg, Throwable tr) {
if (logLevels.containsKey(tag)) {
if (logLevels.get(tag) > android.util.Log.DEBUG) {
return -1;
}
}
return Log.d(tag, msg);
}
public static int i(String tag, String msg) {
return Log2.i(tag, msg, null);
}
public static int i(String tag, String msg, Throwable tr) {
if (logLevels.containsKey(tag)) {
if (logLevels.get(tag) > android.util.Log.INFO) {
return -1;
}
}
return Log.i(tag, msg);
}
public static int w(String tag, String msg) {
return Log2.w(tag, msg, null);
}
public static int w(String tag, String msg, Throwable tr) {
if (logLevels.containsKey(tag)) {
if (logLevels.get(tag) > android.util.Log.WARN) {
return -1;
}
}
return Log.w(tag, msg, tr);
}
public static int e(String tag, String msg) {
return Log2.e(tag, msg, null);
}
public static int e(String tag, String msg, Throwable tr) {
if (logLevels.containsKey(tag)) {
if (logLevels.get(tag) > android.util.Log.ERROR) {
return -1;
}
}
return Log.e(tag, msg, tr);
}
}
ตอนนี้เพียงแค่ตั้งค่าระดับบันทึกต่อ TAG ที่จุดเริ่มต้นของแต่ละชั้นเรียน:
Log2.setLogLevel(TAG, LogLevels.INFO);
อีกวิธีหนึ่งคือการใช้แพลตฟอร์มการบันทึกที่มีความสามารถในการเปิดและปิดการบันทึก สิ่งนี้สามารถให้ความยืดหยุ่นอย่างมากในบางครั้งแม้ในแอปที่ใช้งานจริงซึ่งควรเปิดบันทึกและปิดซึ่งขึ้นอยู่กับปัญหาที่คุณมีตัวอย่างเช่น: