Ghi nhật ký Android vào tệp văn bản


128

Tôi đang cố gắng ghi nhật ký vào tệp Log.txt tùy chỉnh trên tệp Android bằng mã này của Mine nhưng sau đó phương thức này tạo tệp nhưng không chứa gì. Về cơ bản tôi muốn đọc các nội dung trước đó của tệp và sau đó nối dữ liệu của tôi với nội dung hiện có.

Quy tắc như sau:

public static void write(String str) 
    {
        InputStream fileInputStream = null;
        FileOutputStream fileOutpurStream = null;
        try
        { 
            fileInputStream = new FileInputStream(file);
            fileOutpurStream = new FileOutputStream(file);
            if(file.exists())
            {
                int ch = 0;
                int current = 0;
                StringBuffer buffer = new StringBuffer();
                while((ch = fileInputStream.read()) != -1)
                {
                    buffer.append((char) ch);
                    current++;
                }
                byte data[]=new byte[(int)file.length()];
                fileInputStream.read(data);   
                fileOutpurStream.write(data);
                fileOutpurStream.write(str.getBytes(),0,str.getBytes().length);
                fileOutpurStream.flush();
            } 
            else
            {   
                file.createNewFile();
                fileOutpurStream.write(str.getBytes(),0,str.getBytes().length);
                fileOutpurStream.flush();
            }
        }
        catch(Exception e)
        {
            e.printStackTrace();
        }
        finally
        {
            try
            {
                fileInputStream.close();
                fileOutpurStream.flush();
                fileOutpurStream.close();
                fileOutpurStream = null;
                fileInputStream = null;
            }
            catch (IOException e)
            {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

Câu trả lời:


247

Hy vọng điều này có thể giúp ...

public void appendLog(String text)
{       
   File logFile = new File("sdcard/log.file");
   if (!logFile.exists())
   {
      try
      {
         logFile.createNewFile();
      } 
      catch (IOException e)
      {
         // TODO Auto-generated catch block
         e.printStackTrace();
      }
   }
   try
   {
      //BufferedWriter for performance, true to set append to file flag
      BufferedWriter buf = new BufferedWriter(new FileWriter(logFile, true)); 
      buf.append(text);
      buf.newLine();
      buf.close();
   }
   catch (IOException e)
   {
      // TODO Auto-generated catch block
      e.printStackTrace();
   }
}

82
Đừng quên thêm quyền cho write_external_st Storage trong Manifest!
virusss8

5
Tôi đã cần thêm buf.flush (); beforebuf.c Đóng (); nhưng chức năng tjis là chính xác những gì tôi cần. cảm ơn
Csabi

1
Tôi nên gọi hàm này từ đâu để nó ghi tất cả tệp đăng nhập của ứng dụng ???
uniruddh

10
Tôi sẽ khuyên bạn nên cấu trúc lại mã để buf.close()nằm trong một finallykhối.
William Giá

2
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />tất nhiên, ý của @ virusss8 là gì.
Bengt

29

Đối với những người mới đăng nhập Java nói chung và ghi nhật ký Android

  1. Log4j là triển khai ghi nhật ký java chung và hiện là một dự án của nền tảng phần mềm Apache. Nó không phải là Android cụ thể và do đó có một số không tương thích với Android.
  2. SL4J không phải là một triển khai đăng nhập, nó là một lớp trừu tượng. Nó giúp tránh các tình huống như, mỗi thư viện của bên thứ 3 phụ thuộc vào một dự án, cố gắng sử dụng triển khai ghi nhật ký của chính nó như Log4j. Nguồn .

Một số tùy chọn để đăng nhập vào txt trong Android bên dưới

  1. Sử dụng logcat -fnhư trong câu trả lời này để đăng nhập vào tập tin. Lưu ý rằng từ Android 4.2, quyền READ_LOGS không có bất kỳ tác động nào và mọi Ứng dụng (trừ khi điện thoại đã được root) chỉ có thể đọc nhật ký của chính nó. Nhược điểm ở đây là bộ đệm logcat có hình tròn và có giới hạn kích thước. Bạn có thể không nhận được nhật ký trước đó.
  2. Sử dụng microlog4android (được viết cho các thiết bị di động như Android) như trong câu trả lời trước đó . Có thể có một cách, nhưng tôi không thể tìm ra cách sử dụng microlog4Android để đăng nhập vào bộ nhớ trong của ứng dụng. Tùy chọn duy nhất cho đường dẫn nhật ký là bộ nhớ ngoài như sdcard và vì vậy tôi không thể sử dụng nó.
  3. Sử dụng Log4j với đăng nhập android-log4j . Android-log-log4j làm gì? Nó làm cho Log4j dễ sử dụng hơn trong Android bằng cách cung cấp hai chức năng.

    • tùy chọn gửi nhật ký đến logcat ngoài tệp nhật ký
    • một cách đơn giản để đặt các tùy chọn cấu hình Log4j như đường dẫn tệp, kích thước tệp tối đa, số lần sao lưu, v.v. bằng cách cung cấp lớp LogConfigurator.

    Ví dụ đơn giản dưới đây. Lưu ý rằng loggerđối tượng trong ví dụ dưới đây là một đối tượng Log4j được trả về và không phải là một lớp android-log-log4j. Vì vậy, android-log-log4j chỉ được sử dụng để cấu hình Log4j.

  4. Chưa thử LogBack . LogBack được phát triển bởi cùng một người đã đưa ra các thư viện Log4J 1.x và SL4J. Không liên quan đến Log4j 2.x mặc dù.

Các bước để sử dụng Log4j trong Android.

  1. Thêm cả log4j-1.2.x.jarandroid-log-log4j-1.0.3.jar vào thư mục libs.

  2. Chỉ thêm quyền nếu sử dụng bộ nhớ ngoài
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

  3. Viết Log4jlớp người trợ giúp

    package com.example.logger;
    
    import android.os.Environment;
    import de.mindpipe.android.logging.log4j.LogConfigurator;
    
    public class Log4jHelper {
        private final static LogConfigurator mLogConfigrator = new LogConfigurator();
    
        static {
            configureLog4j();
        }
    
        private static void configureLog4j() {
            String fileName = Environment.getExternalStorageDirectory() + "/" + "log4j.log";
            String filePattern = "%d - [%c] - %p : %m%n";
            int maxBackupSize = 10;
            long maxFileSize = 1024 * 1024;
    
            configure( fileName, filePattern, maxBackupSize, maxFileSize );
        }
    
        private static void configure( String fileName, String filePattern, int maxBackupSize, long maxFileSize ) {
            mLogConfigrator.setFileName( fileName );
            mLogConfigrator.setMaxFileSize( maxFileSize );
            mLogConfigrator.setFilePattern(filePattern);
            mLogConfigrator.setMaxBackupSize(maxBackupSize);
            mLogConfigrator.setUseLogCatAppender(true);
            mLogConfigrator.configure();
    
        }
    
        public static org.apache.log4j.Logger getLogger( String name ) {
            org.apache.log4j.Logger logger = org.apache.log4j.Logger.getLogger( name );
            return logger;
        }
    }
  4. Trong lớp Hoạt động

    org.apache.log4j.Logger log= Log4jHelper.getLogger( "YourActivity" );
    log.error("Error");
    log.info("Info");
    log.warn("Warn");

Nguồn ví dụ . Lưu ý rằng, log4j 2.x (chức năng được cải thiện) được viết lại từ đầu không thể kết hợp ngược với log4j 1.x. Vì vậy, bạn phải sử dụng jar log4j 1.2.x với jar android-log-log4j. Tôi đã có thể đăng nhập vào tệp nội bộ của ứng dụng và sau đó gửi email tệp đó vớisetReadable(true, false)


24

microlog4android làm việc cho tôi nhưng tài liệu khá kém. Tất cả những gì họ cần thêm là đây là một hướng dẫn bắt đầu nhanh .

Đây là một hướng dẫn nhanh tôi tìm thấy.

  1. Thêm biến tĩnh sau vào Hoạt động chính của bạn:

    private static final Logger logger = LoggerFactory.getLogger();
  2. Thêm phần sau vào onCreate()phương thức của bạn :

    PropertyConfigurator.getConfigurator(this).configure();
  3. Tạo một tập tin có tên microlog.propertiesvà lưu trữ nó trong assetsthư mục

  4. Chỉnh sửa microlog.propertiestập tin như sau:

    microlog.level=DEBUG
    microlog.appender=LogCatAppender;FileAppender
    microlog.formatter=PatternFormatter
    microlog.formatter.PatternFormatter.pattern=%c [%P] %m %T
  5. Thêm báo cáo đăng nhập như thế này:

    logger.debug("M4A");

Đối với mỗi lớp, bạn tạo một đối tượng logger như được chỉ định trong 1)

6. Bạn có thể được thêm các quyền sau:

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

Đây là nguồn để hướng dẫn


4
FileAppender sẽ tạo một tệp "microlog.txt" trên sdcard của bạn (đường dẫn đầy đủ trong trường hợp của tôi là "/sdcard/microlog.txt).
Ruslan Yanchyshyn 22/214

2
Hướng dẫn nhanh này nên là một phần của tài liệu chính thức của Microlog4Android.
Johan Karlsson

Lỗi: Android Dex: [project] com.android.dex.DexException: Nhiều tệp dex xác định Lcom / google / code / microlog4android / Level; ?

10

Cảnh báo: Tôi có thể hoàn toàn hiểu lầm bạn, nhưng nếu tất cả những gì bạn muốn là một tệp nhật ký, tại sao lại đổ mồ hôi?

Đặt cái này trong một tập tin bat (thay đổi đường dẫn đến thư mục công cụ của bạn và yourappname dĩ nhiên là tên ứng dụng của bạn):

cd "C:\devAndroid\Software\android-sdk-windows-1.6_r1\android-sdk-windows-1.6_r1\tools"
adb logcat -v time   ActivityManager:W  yourappname:D  *:W >"C:\devAndroid\log\yourappname.log"

Sau đó, trong mã của bạn chỉ cần làm một cái gì đó tương tự như thế này:

Log.d("yourappname", "Your message");

Để tạo nhật ký, kết nối cáp USB và chạy tệp dơi của bạn.

Trân trọng


Đừng quên nhập android.util.Log.
Spidey

Nó trả về "lỗi: nhiều hơn một thiết bị và trình giả lập - đang chờ thiết bị -" và không có gì nữa. Có gì sai?
eros

20
Làm thế nào để nó hoạt động nếu bạn không ở gần PC trong khi chạy ứng dụng?
DDSports

@DDSports thì không. Nhưng anh ta không hiểu bản chất của câu hỏi
Ewoks

@DDSports bạn có thể sử dụng adb qua wifi.
Daniel F

8

Sử dụng lib slf4android .
Việc triển khai slf4j api đơn giản bằng android java.util.logging. *.

Đặc trưng:

  • đăng nhập để gửi ra khỏi hộp
  • đăng nhập vào bất kỳ điểm đến nào khác bằng cách LoggerConfiguration.configuration().addHandlerToLogger
  • lắc thiết bị của bạn để gửi nhật ký với ảnh chụp màn hình qua email
  • thực sự nhỏ, chỉ mất ~ 55kB

slf4android được duy trì chủ yếu bởi @miensol .

Tìm hiểu thêm về slf4android trên blog của chúng tôi:


7

Bạn nên xem microlog4android. Họ có một giải pháp sẵn sàng để đăng nhập vào một tập tin.

http://code.google.com.vn/p/microlog4android/


12
Đây có vẻ là một thư viện tốt, nhưng tài liệu rất tệ. Tôi chỉ đơn giản là không thể tìm ra cách để nó hoạt động và cuối cùng tự mình đặt một cái gì đó lại với nhau.
Paul Lammertsma

@PaulLammertsma: Tôi nghĩ thật tuyệt khi bạn đã đóng góp cho Microlog4Android thay thế. FYI: nó có cùng API với Log4j. Xin lỗi về các tài liệu xấu.
Johan Karlsson

@Paul Lammertsma Làm thế nào BẤT CỨ điều gì có thể tốt nếu tài liệu đó tệ đến mức bạn không thể sử dụng nó? Nếu bạn không thể làm cho nó hoạt động thì đó là chuyện tào lao.
Ngày

7

Điều này có thể bị trễ nhưng hy vọng điều này có thể giúp ích .. Hãy thử điều này ....

public void writefile()
    {
        File externalStorageDir = Environment.getExternalStorageDirectory();
        File myFile = new File(externalStorageDir , "yourfilename.txt");

        if(myFile.exists())
        {
           try
           {

        FileOutputStream fostream = new FileOutputStream(myFile);
        OutputStreamWriter oswriter = new OutputStreamWriter(fostream); 
        BufferedWriter bwriter = new BufferedWriter(oswriter);   
        bwriter.write("Hi welcome ");
        bwriter.newLine();            
        bwriter.close(); 
        oswriter.close(); 
        fostream.close();
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }
        else
        {
            try {
                myFile.createNewFile();
            }
            catch (IOException e) 
            {
                e.printStackTrace();
            }
        }

ở đây bfwritter.newlineviết văn bản của bạn vào tập tin. Và thêm sự cho phép

 <uses-permission android:name = "android.permission.WRITE_EXTERNAL_STORAGE"/>

trong tệp kê khai của bạn mà không thất bại.


6

Tôi đã giải quyết vấn đề này bằng đoạn mã sau theo cách dòng lệnh:

File outputFile = new File("pathToFile"); 
Runtime.getRuntime().exec("logcat -c");
Runtime.getRuntime().exec("logcat -v time -f " + outputFile.getAbsolutePath())

Trong đó tùy chọn "thời gian" thêm chi tiết trường siêu dữ liệu cho ngày, thời gian gọi, mức độ ưu tiên / thẻ và PID của quy trình phát hành thông báo.

Sau đó, trong mã của bạn chỉ cần làm một cái gì đó tương tự như thế này (sử dụng android.util.Log):

Log.d("yourappname", "Your message");

4
Hoạt động độc đáo khi adbđược kết nối, nhưng không hoạt động khi thiết bị ở chế độ độc lập. Tôi sẽ không sử dụng nó trong sản xuất, nhưng nó là một giải pháp đơn giản trong chế độ gỡ lỗi.
Julien Kronegg

2

Nói chung, bạn phải có một tệp xử lý trước khi mở luồng. Bạn có một tệp xử lý fileOutputStream trước khi tạo NewNewFile () trong khối khác. Luồng không tạo tệp nếu nó không tồn tại.

Không thực sự cụ thể về Android, nhưng đó là rất nhiều IO cho mục đích này. Điều gì xảy ra nếu bạn thực hiện nhiều thao tác "viết" lần lượt? Bạn sẽ đọc toàn bộ nội dung và viết toàn bộ nội dung, dành thời gian và quan trọng hơn là thời lượng pin.

Tôi đề nghị sử dụng java.io.RandomAccessFile, tìm kiếm () 'đến cuối, sau đó writeChars () để chắp thêm. Nó sẽ là mã sạch hơn nhiều và có khả năng nhanh hơn nhiều.


2

Tôi đã tạo một lớp đơn giản, nhẹ (khoảng 260 LoC) mở rộng việc triển khai android.util.Log tiêu chuẩn với ghi nhật ký dựa trên tệp:
Mọi thông điệp tường trình được ghi lại thông qua android.util.Log và cũng được ghi vào tệp văn bản trên thiết bị.

Bạn có thể tìm thấy nó trên github:
https://github.com/volkerv/FileLog


1
không phải điều bạn cần để đảm bảo các thao tác tệp không được thực hiện trên luồng UI. Trong trường hợp chúng ta có số lượng lớn tệp nhật ký?
Jayshil Dave

2

Sau một thời gian dài điều tra tôi thấy rằng:

  • android.util.Log theo mặc định sử dụng java.util.logging.Logger
  • LogCat sử dụng loggervới tên "", để lấy ví dụ của nó sử dụngLogManager.getLogManager().getLogger("")
  • Thiết bị Android thêm vào loggerphiên bản LogCat com.android.internal.logging.AndroidHandlersau khi chạy ứng dụng gỡ lỗi
  • Nhưng!!! com.android.internal.logging.AndroidHandlerchỉ in các thông điệp tới logcat với các mức nhiều hơn java.util.logging.Level.INFOnhư ( Level.INFO, Level.WARNING, Level.SEVERE, Level.OFF)

Vì vậy, để ghi nhật ký vào tập tin chỉ đơn giản để rootLogger "" thêm a java.util.logging.FileHandler:

  class App : Application{
    override fun onCreate() {
      super.onCreate()
      Log.d(TAG, printLoggers("before setup"))

      val rootLogger = java.util.logging.LogManager.getLogManager().getLogger("")
      val dirFile = destinationFolder
      val file = File(dirFile,"logFile.txt")
      val handler = java.util.logging.FileHandler(file.absolutePath, 5* 1024 * 1024/*5Mb*/, 1, true)
      handler.formatter = AndroidLogFormatter(filePath = file.absolutePath)

      rootLogger?.addHandler(handler)

      Log.d(TAG, printLoggers("after setup"))
    }
  }

val destinationFolder: File
        get() {
            val parent = 
                Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).absoluteFile
            val destinationFolder = File(parent, "MyApp")
            if (!destinationFolder.exists()) {
                destinationFolder.mkdirs()
                destinationFolder.mkdir()
            }
            return destinationFolder
        }
class AndroidLogFormatter(val filePath: String = "", var tagPrefix: String = "") : Formatter() {

    override fun format(record: LogRecord): String {
        val tag = record.getTag(tagPrefix)
        val date = record.getDate()
        val level = record.getLogCatLevel()
        val message = record.getLogCatMessage()
        return "$date $level$tag: $message\n"
    }
}

fun LogRecord.getTag(tagPrefix: String): String {
    val name = loggerName
    val maxLength = 30
    val tag = tagPrefix + (if (name.length > maxLength) name.substring(name.length - maxLength) else name)
    return tag
}

fun LogRecord.getDate(): String? {
    return Date(millis).formatedBy("yyyy-MM-dd HH:mm:ss.SSS")
}

fun Date?.formatedBy(dateFormat: String): String? {
    val date = this
    date ?: return null
    val writeFormat = SimpleDateFormat(dateFormat, Locale.getDefault()) //MM в HH:mm
    return writeFormat.format(date)
}

fun LogRecord.getLogCatMessage(): String {
    var message = message

    if (thrown != null) {
        message += Log.getStackTraceString(thrown)
    }
    return message
}

fun Int.getAndroidLevel(): Int {
    return when {
        this >= Level.SEVERE.intValue() -> { // SEVERE
            Log.ERROR
        }
        this >= Level.WARNING.intValue() -> { // WARNING
            Log.WARN
        }
        this >= Level.INFO.intValue() -> { // INFO
            Log.INFO
        }
        else -> {
            Log.DEBUG
        }
    }
}

fun LogRecord.getLogCatLevel(): String {
    return when (level.intValue().getAndroidLevel()) {
        Log.ERROR -> { // SEVERE
            "E/"
        }
        Log.WARN -> { // WARNING
            "W/"
        }
        Log.INFO -> { // INFO
            "I/"
        }
        Log.DEBUG -> {
            "D/"
        }
        else -> {
            "D/"
        }
    }
}

fun getLoggerLevel(level: Int): Level {
    return when (level) {
        Log.ERROR -> { // SEVERE
            Level.SEVERE
        }
        Log.WARN -> { // WARNING
            Level.WARNING
        }
        Log.INFO -> { // INFO
            Level.INFO
        }
        Log.DEBUG -> {
            Level.FINE
        }
        else -> {
            Level.FINEST
        }
    }
}

Để in tất cả loggers tại ứng dụng của bạn, hãy sử dụng:

Log.e(TAG, printLoggers("before setup"))

 private fun printLoggers(caller: String, printIfEmpty: Boolean = true): String {
        val builder = StringBuilder()
        val loggerNames = LogManager.getLogManager().loggerNames
        builder.appendln("--------------------------------------------------------------")
        builder.appendln("printLoggers: $caller")
        while (loggerNames.hasMoreElements()) {
            val element = loggerNames.nextElement()
            val logger = LogManager.getLogManager().getLogger(element)
            val parentLogger: Logger? = logger.parent
            val handlers = logger.handlers
            val level = logger?.level
            if (!printIfEmpty && handlers.isEmpty()) {
                continue
            }
            val handlersNames = handlers.map {
                val handlerName = it.javaClass.simpleName
                val formatter: Formatter? = it.formatter
                val formatterName = if (formatter is AndroidLogFormatter) {
                    "${formatter.javaClass.simpleName}(${formatter.filePath})"
                } else {
                    formatter?.javaClass?.simpleName
                }
                "$handlerName($formatterName)"
            }
            builder.appendln("level: $level logger: \"$element\" handlers: $handlersNames parentLogger: ${parentLogger?.name}")
        }
        builder.appendln("--------------------------------------------------------------")
        return builder.toString()
    }

Chào. Lợi ích của việc sử dụng logger "" này là gì. Tôi thấy không có sự khác biệt với một logger tùy chỉnh. hoặc tôi đang thiếu một cái gì đó?
Abins Shaji

1

Biến thể này ngắn hơn nhiều

thử {
    đường dẫn tệp cuối cùng = tệp mới (
            Môi trường.getExternalStorageDirectory (), "DBO_logs5");
    if (! path.exists ()) {
        đường dẫn.mkdir ();
    }
    Runtime.getR.78 (). Exec (
            "logcat -d -f" + đường dẫn + File.separator
                    + "dbo_logcat"
                    + ".txt");
} bắt (IOException e) {
    e.printStackTrace ();
}

1

Bạn có thể sử dụng thư viện tôi đã viết. Nó rất dễ sử dụng:

Thêm phụ thuộc này vào tập tin lớp của bạn:

dependencies {
    compile 'com.github.danylovolokh:android-logger:1.0.2'
}

Khởi tạo thư viện trong lớp Ứng dụng:

File logsDirectory = AndroidLogger.getDefaultLogFilesDirectory(this);
    int logFileMaxSizeBytes = 2 * 1024 * 1024; // 2Mb
    try {
        AndroidLogger.initialize(
                this,
                logsDirectory,
                "Log_File_Name",
                logFileMaxSizeBytes,
                false
                );
    } catch (IOException e) {
        // Some error happened - most likely there is no free space on the system
    }

Đây là cách bạn sử dụng thư viện:

AndroidLogger.v("TAG", "Verbose Message");

Và đây là cách lấy lại nhật ký:

AndroidLogger.processPendingLogsStopAndGetLogFiles(new AndroidLogger.GetFilesCallback() {
        @Override
        public void onFiles(File[] logFiles) {
            // get everything you need from these files
            try {
                AndroidLogger.reinitAndroidLogger();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    });

Đây là liên kết đến trang github với nhiều thông tin hơn: https://github.com/danylovolokh/AndroidLogger

Hy vọng nó giúp.


1

Nhiều phiên bản trước trên log4j không hoạt động ngay bây giờ (05/2019). Nhưng bạn có thể sử dụng Hyperlog - Tôi có thể xác nhận nó hoạt động.

  1. Thêm dòng này vào dự án đồng bộ và phụ thuộc của bạn

    implementation 'com.hypertrack:hyperlog:0.0.10'
  2. Tạo một lớp ứng dụng mới (tạo một lớp java mới và mở rộng Ứng dụng). Sau đó, trong phương thức onCreate thêm các dòng sau:

    HyperLog.initialize(this);
    HyperLog.setLogLevel(Log.VERBOSE);
    
    HyperLog.getDeviceLogsInFile(this);
  3. Thay đổi tệp kê khai để có tệp ứng dụng được xác định.

    <application
        android:name=".AppClass"
        .....
  4. Các cách khác nhau để đăng nhập:

    HyperLog.d(TAG,"debug");
    HyperLog.i(TAG,"information");
    HyperLog.e(TAG,"error");
    HyperLog.v(TAG,"verbose");
    HyperLog.w(TAG,"warning");
    HyperLog.a(TAG,"assert");
    HyperLog.exception(TAG,"exception",throwable);
  5. Tìm tệp nhật ký của bạn. Hướng đến

    RootFolder/android/data/"appPackageName/LogFiles/

Tôi đã thử thư viện này, trong tệp được tạo bằng HyperLog.getDeviceLogsInFile (cái này); chỉ có nhật ký được tạo cuối cùng ở đó
mad_greasemonkey
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.