hzy 7 bulan lalu
melakukan
c7df889abd
100 mengubah file dengan 5758 tambahan dan 0 penghapusan
  1. 38 0
      .gitignore
  2. 49 0
      CONTRIBUTING.md
  3. 9 0
      Example/Logan-Android/.gitignore
  4. 99 0
      Example/Logan-Android/READMD-zh.md
  5. 101 0
      Example/Logan-Android/README.md
  6. 1 0
      Example/Logan-Android/app/.gitignore
  7. 32 0
      Example/Logan-Android/app/build.gradle
  8. 25 0
      Example/Logan-Android/app/proguard-rules.pro
  9. 26 0
      Example/Logan-Android/app/src/androidTest/java/test/logan/dianping/com/logan/ExampleInstrumentedTest.java
  10. 25 0
      Example/Logan-Android/app/src/main/AndroidManifest.xml
  11. 8 0
      Example/Logan-Android/app/src/main/gen/test/logan/dianping/com/logan/BuildConfig.java
  12. 7 0
      Example/Logan-Android/app/src/main/gen/test/logan/dianping/com/logan/Manifest.java
  13. 7 0
      Example/Logan-Android/app/src/main/gen/test/logan/dianping/com/logan/R.java
  14. 166 0
      Example/Logan-Android/app/src/main/java/test/logan/dianping/com/logan/MainActivity.java
  15. 44 0
      Example/Logan-Android/app/src/main/java/test/logan/dianping/com/logan/MyApplication.java
  16. 187 0
      Example/Logan-Android/app/src/main/java/test/logan/dianping/com/logan/RealSendLogRunnable.java
  17. 73 0
      Example/Logan-Android/app/src/main/res/layout/activity_main.xml
  18. TEMPAT SAMPAH
      Example/Logan-Android/app/src/main/res/mipmap-hdpi/ic_launcher.png
  19. TEMPAT SAMPAH
      Example/Logan-Android/app/src/main/res/mipmap-mdpi/ic_launcher.png
  20. TEMPAT SAMPAH
      Example/Logan-Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
  21. TEMPAT SAMPAH
      Example/Logan-Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
  22. TEMPAT SAMPAH
      Example/Logan-Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
  23. 3 0
      Example/Logan-Android/app/src/main/res/values/strings.xml
  24. 17 0
      Example/Logan-Android/app/src/test/java/test/logan/dianping/com/logan/ExampleUnitTest.java
  25. 54 0
      Example/Logan-Android/bintrayUpload.gradle
  26. 34 0
      Example/Logan-Android/build.gradle
  27. 20 0
      Example/Logan-Android/gradle.properties
  28. TEMPAT SAMPAH
      Example/Logan-Android/gradle/wrapper/gradle-wrapper.jar
  29. 6 0
      Example/Logan-Android/gradle/wrapper/gradle-wrapper.properties
  30. 160 0
      Example/Logan-Android/gradlew
  31. 90 0
      Example/Logan-Android/gradlew.bat
  32. 1 0
      Example/Logan-Android/logan/.gitignore
  33. 27 0
      Example/Logan-Android/logan/CMakeLists.txt
  34. 49 0
      Example/Logan-Android/logan/build.gradle
  35. 25 0
      Example/Logan-Android/logan/proguard-rules.pro
  36. 143 0
      Example/Logan-Android/logan/src/androidTest/java/com/dianping/logan/LoganTest.java
  37. 2 0
      Example/Logan-Android/logan/src/main/AndroidManifest.xml
  38. 8 0
      Example/Logan-Android/logan/src/main/gen/com/dianping/logan/BuildConfig.java
  39. 7 0
      Example/Logan-Android/logan/src/main/gen/com/dianping/logan/Manifest.java
  40. 7 0
      Example/Logan-Android/logan/src/main/gen/com/dianping/logan/R.java
  41. 196 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/CLoganProtocol.java
  42. 56 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/ConstantCode.java
  43. 158 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/Logan.java
  44. 144 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/LoganConfig.java
  45. 163 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/LoganControlCenter.java
  46. 50 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/LoganModel.java
  47. 96 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/LoganProtocol.java
  48. 40 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/LoganProtocolHandler.java
  49. 367 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/LoganThread.java
  50. 28 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/OnLoganProtocolStatus.java
  51. 44 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/SendAction.java
  52. 14 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/SendLogCallback.java
  53. 201 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/SendLogDefaultRunnable.java
  54. 78 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/SendLogRunnable.java
  55. 47 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/Util.java
  56. 48 0
      Example/Logan-Android/logan/src/main/java/com/dianping/logan/WriteAction.java
  57. 84 0
      Example/Logan-Android/logan/src/main/jni/clogan_protocol.c
  58. 76 0
      Example/Logan-Android/logan/src/main/jni/clogan_protocol.h
  59. 3 0
      Example/Logan-Android/logan/src/main/res/values/strings.xml
  60. 17 0
      Example/Logan-Android/logan/src/test/java/com/dianping/logan/ExampleUnitTest.java
  61. 1 0
      Example/Logan-Android/settings.gradle
  62. 61 0
      Example/Logan-NodeServer/.gitignore
  63. 42 0
      Example/Logan-NodeServer/README.md
  64. 97 0
      Example/Logan-NodeServer/app.ts
  65. 24 0
      Example/Logan-NodeServer/package.json
  66. 13 0
      Example/Logan-NodeServer/tsconfig.json
  67. 510 0
      Example/Logan-NodeServer/yarn.lock
  68. 3 0
      Example/Logan-WebSDK/.gitignore
  69. 1 0
      Example/Logan-WebSDK/.npmrc
  70. 2 0
      Example/Logan-WebSDK/README.md
  71. 0 0
      Example/Logan-WebSDK/demo/js/report_log.00379bd039ba0d23e095.js
  72. 0 0
      Example/Logan-WebSDK/demo/js/report_log.540408c7d6a5382cf118.js
  73. 0 0
      Example/Logan-WebSDK/demo/js/test.js
  74. 0 0
      Example/Logan-WebSDK/demo/js/vendors~encryption.b5564d7d68255d790409.js
  75. 0 0
      Example/Logan-WebSDK/demo/js/vendors~encryption.ed2aadee96f4380db40e.js
  76. 0 0
      Example/Logan-WebSDK/demo/js/vendors~report_log~save_log.1130f9f2be3cde3d0b2d.js
  77. 0 0
      Example/Logan-WebSDK/demo/js/vendors~report_log~save_log.4f48e20a6aa09ee73148.js
  78. 0 0
      Example/Logan-WebSDK/demo/js/vendors~save_log.81bc5a66cd212fb25cd1.js
  79. 0 0
      Example/Logan-WebSDK/demo/js/vendors~save_log.e6c9cfa9a3e7e0214368.js
  80. 15 0
      Example/Logan-WebSDK/demo/test.html
  81. 19 0
      Example/Logan-WebSDK/package.json
  82. 42 0
      Example/Logan-WebSDK/src/test.js
  83. 24 0
      Example/Logan-WebSDK/webpack.config.js
  84. 68 0
      Example/Logan-iOS/.gitignore
  85. 608 0
      Example/Logan-iOS/Logan-iOS.xcodeproj/project.pbxproj
  86. 144 0
      Example/Logan-iOS/Logan-iOS.xcodeproj/xcshareddata/xcschemes/Logan-iOS-Example.xcscheme
  87. 31 0
      Example/Logan-iOS/Logan-iOS/Base.lproj/LaunchScreen.storyboard
  88. 82 0
      Example/Logan-iOS/Logan-iOS/Base.lproj/Main.storyboard
  89. 98 0
      Example/Logan-iOS/Logan-iOS/Images.xcassets/AppIcon.appiconset/Contents.json
  90. 30 0
      Example/Logan-iOS/Logan-iOS/LGAppDelegate.h
  91. 55 0
      Example/Logan-iOS/Logan-iOS/LGAppDelegate.m
  92. 28 0
      Example/Logan-iOS/Logan-iOS/LGViewController.h
  93. 97 0
      Example/Logan-iOS/Logan-iOS/LGViewController.m
  94. 54 0
      Example/Logan-iOS/Logan-iOS/Logan-iOS-Info.plist
  95. 16 0
      Example/Logan-iOS/Logan-iOS/Logan-iOS-Prefix.pch
  96. 2 0
      Example/Logan-iOS/Logan-iOS/en.lproj/InfoPlist.strings
  97. 30 0
      Example/Logan-iOS/Logan-iOS/main.m
  98. 7 0
      Example/Logan-iOS/Podfile
  99. 18 0
      Example/Logan-iOS/Podfile.lock
  100. 76 0
      Example/Logan-iOS/Tests/LoganTests.m

+ 38 - 0
.gitignore

@@ -0,0 +1,38 @@
+# OS X
+.DS_Store
+
+# Xcode
+build/
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+xcuserdata/
+*.xccheckout
+profile
+*.moved-aside
+DerivedData
+*.hmap
+*.ipa
+
+# Bundler
+.bundle
+clogan/cmake-build-debug
+
+**/.idea/*
+**/node_modules/*
+
+Carthage
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# http://guides.cocoapods.org/using/using-cocoapods.html#should-i-ignore-the-pods-directory-in-source-control
+# 
+# Note: if you ignore the Pods directory, make sure to uncomment
+# `pod install` in .travis.yml
+#
+# Pods/
+.data/

+ 49 - 0
CONTRIBUTING.md

@@ -0,0 +1,49 @@
+# Contributing to Logan
+
+Logan is a lightweight case logging system based on mobile platform developed by Meituan-Dianping
+
+## Development Process
+
+### Pull Requests
+
+Please submit your pull request on the *master* branch. 
+
+*Before* submitting a pull request, please make sure the following is done…
+
+1. Fork the repo and create your branch from `master`.
+2. Add the copyright notice to the top of any new files you've added.
+3. **Format your code**.
+4. **Make sure your code can compile**.
+5. Squash your commits (`git rebase -i`).
+
+#### Copyright Notice for files
+
+Copy and paste this to the top of your new file(s):
+
+```
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+```
+
+## License
+
+By contributing to Logan, you agree that your contributions will be licensed under its MIT License.

+ 9 - 0
Example/Logan-Android/.gitignore

@@ -0,0 +1,9 @@
+*.iml
+.gradle
+/local.properties
+/.idea/workspace.xml
+/.idea/libraries
+.DS_Store
+/build
+/captures
+.externalNativeBuild

+ 99 - 0
Example/Logan-Android/READMD-zh.md

@@ -0,0 +1,99 @@
+### Prerequisites
+
+如果你想编译源代码,请确保NDK版本不高于**16.1.4479499**。
+
+### Installation
+
+在项目的`build.gradle`文件中添加:
+
+```groovy
+compile 'com.dianping.android.sdk:logan:1.2.4'
+```
+
+### Usage
+
+在使用之前,必须初始化Logan,例如:
+
+```java
+LoganConfig config = new LoganConfig.Builder()
+        .setCachePath(getApplicationContext().getFilesDir().getAbsolutePath())
+        .setPath(getApplicationContext().getExternalFilesDir(null).getAbsolutePath()
+                + File.separator + "logan_v1")
+        .setEncryptKey16("0123456789012345".getBytes())
+        .setEncryptIV16("0123456789012345".getBytes())
+        .build();
+Logan.init(config);
+```
+
+初始化之后,就可以愉快的写日志了,例如这样写一条日志:
+
+```java
+Logan.w("test logan", 1);
+```
+
+Logan.w方法有两个参数,详解如下:
+
+- **String log**:写入的日志内容;
+- **int type**:写入的日志类型,这非常重要,在下文的最佳实践内容会详细讲述如何优雅利用日志类型参数。
+注意:type值1已被logan内部占用,建议业务方的日志类型使用新的type值。
+
+如果你想立即写入日志文件,需要调用flush方法:
+
+```java
+Logan.f();
+```
+
+如果你想查看所有日志文件的信息,需要调用getAllFilesInfo方法:
+
+```java
+Map<String, Long> map = Logan.getAllFilesInfo();
+```
+
+其中key为日期,value为日志文件大小(Bytes)。
+
+#### Upload
+
+推荐使用这个接口上传数据,我们开源了Logan后台日志解析和展示的部分,只要部署好服务器就可以用这个接口直接上报日志到后端。
+```java
+final String url = "https://openlogan.inf.test.sankuai.com/logan/upload.json";
+Logan.s(url, loganTodaysDate(), "testAppId", "testUnionid", "testdDviceId", "testBuildVersion", "testAppVersion", new SendLogCallback() {
+    @Override
+    public void onLogSendCompleted(int statusCode, byte[] data) {
+        final String resultData = data != null ? new String(data) : "";
+        Log.d(TAG, "日志上传结果, http状态码: " + statusCode + ", 详细: " + resultData);
+    }
+});
+```
+
+Logan内部提供了日志上传机制,对需要上传的日志做了预处理操作。如果你需要上传日志功能,首先需要实现一个自己的SendLogRunnable:
+
+```java
+public class RealSendLogRunnable extends SendLogRunnable {
+
+    @Override
+    public void sendLog(File logFile) {
+      // logFile为预处理过后即将要上传的日志文件
+      // 在此方法最后必须调用finish方法
+      finish();
+      if (logFile.getName().contains(".copy")) {
+				logFile.delete();
+			}
+    }
+}
+```
+
+**注意:在sendLog方法的最后必须调用finish方法**。如上面代码所示。
+
+最后需要调用Logan的send方法:
+
+```java
+Logan.s(date, mSendLogRunnable);
+```
+
+其中第一个参数为日期数组(yyyy-MM-dd)。
+
+### Permission
+
+如果你需要上传日志到服务器,需要申请 INTERNET 权限。
+
+如果你需要写日志到外部存储,或者从外部存储读取日志信息,则需要 WRITE_EXTERNAL_STORAGE 权限或者 READ_EXTERNAL_STORAGE 权限。

+ 101 - 0
Example/Logan-Android/README.md

@@ -0,0 +1,101 @@
+### Prerequisites
+
+If you want to build the source, make sure your NDK version is not higher than **16.1.4479499**.
+
+### Installation
+
+Add the following content in the project `build.gradle` file:
+
+```groovy
+compile 'com.dianping.android.sdk:logan:1.2.4'
+```
+
+### Usage
+
+**You must init Logan before you use it**. For example:
+
+```java
+LoganConfig config = new LoganConfig.Builder()
+        .setCachePath(getApplicationContext().getFilesDir().getAbsolutePath())
+        .setPath(getApplicationContext().getExternalFilesDir(null).getAbsolutePath()
+                + File.separator + "logan_v1")
+        .setEncryptKey16("0123456789012345".getBytes())
+        .setEncryptIV16("0123456789012345".getBytes())
+        .build();
+Logan.init(config);
+```
+
+After you init Logan, you can use Logan to write a log. Like this:
+
+```java
+Logan.w("test logan", 1);
+```
+
+Logan.w method has two parameters:
+
+- **String log**: What you want to write;
+- **int type**: Log type. This is very important, best practices below content will show you how to using log type parameter.
+
+Note: The type value "1" is already used internally by logan. It is recommended that the business party's log type use the new type value.
+
+If you want to write log to file immediately, you need to call flush function:
+
+```java
+Logan.f();
+```
+
+If you want to see all of the log file information, you need to call getAllFilesInfo function:
+
+```java
+Map<String, Long> map = Logan.getAllFilesInfo();
+```
+
+- key: Log file date;
+- value: Log file size(Bytes).
+
+#### Upload
+
+this upload method is recommend, you can use this method upload your logs directly into your server. we also provide logan server source code ,you can find it in Logan open souce Repository.
+```java
+final String url = "https://openlogan.inf.test.sankuai.com/logan/upload.json";
+Logan.s(url, loganTodaysDate(), "testAppId", "testUnionid", "testdDviceId", "testBuildVersion", "testAppVersion", new SendLogCallback() {
+    @Override
+    public void onLogSendCompleted(int statusCode, byte[] data) {
+        final String resultData = data != null ? new String(data) : "";
+        Log.d(TAG, "upload result, httpCode: " + statusCode + ", details: " + resultData);
+    }
+});
+```
+
+Logan internal provides logging upload mechanism, in view of the need to upload the log to do the preprocessing. If you want to upload log file, you need to implement a SendLogRunnable:
+
+```java
+public class RealSendLogRunnable extends SendLogRunnable {
+
+    @Override
+    public void sendLog(File logFile) {
+      // logFile: After the pretreatment is going to upload the log file
+      // Must Call finish after send log
+      finish();
+      if (logFile.getName().contains(".copy")) {
+				logFile.delete();
+			}
+    }
+}
+```
+
+**NOTE: You must call finish method after send log**. As written in the code above
+
+Finally you need to call Logan.s method:
+
+```java
+Logan.s(date, mSendLogRunnable);
+```
+
+One of the first parameter is date array(yyyy-MM-dd).
+
+### Permission
+
+If you upload log file to server, you need INTERNET permission.
+
+If you write log to SD card or read log info from SD card, you need WRITE_EXTERNAL_STORAGE and READ_EXTERNAL_STORAGE permission

+ 1 - 0
Example/Logan-Android/app/.gitignore

@@ -0,0 +1 @@
+/build

+ 32 - 0
Example/Logan-Android/app/build.gradle

@@ -0,0 +1,32 @@
+apply plugin: 'com.android.application'
+
+android {
+    compileSdkVersion 23
+    buildToolsVersion "25.0.2"
+    defaultConfig {
+        applicationId "test.logan.dianping.com.logan"
+        minSdkVersion 14
+        targetSdkVersion 23
+        versionCode 1
+        versionName "1.0"
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+    }
+    buildTypes {
+        release {
+            minifyEnabled true
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+    }
+}
+
+dependencies {
+    compile fileTree(dir: 'libs', include: ['*.jar'])
+    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+    testCompile 'junit:junit:4.12'
+    compile project(':logan')
+//    compile 'com.dianping.android.sdk:logan:1.1.0'
+//    releaseCompile project(path: ':logan', configuration: 'release')
+//    debugCompile project(path: ':logan', configuration: 'debug')
+}

+ 25 - 0
Example/Logan-Android/app/proguard-rules.pro

@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/baitian0521/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 26 - 0
Example/Logan-Android/app/src/androidTest/java/test/logan/dianping/com/logan/ExampleInstrumentedTest.java

@@ -0,0 +1,26 @@
+package test.logan.dianping.com.logan;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+    @Test
+    public void useAppContext() throws Exception {
+        // Context of the app under test.
+        Context appContext = InstrumentationRegistry.getTargetContext();
+
+        assertEquals("test.logan.dianping.com.logan", appContext.getPackageName());
+    }
+}

+ 25 - 0
Example/Logan-Android/app/src/main/AndroidManifest.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="test.logan.dianping.com.logan">
+
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
+
+
+    <application
+        android:name=".MyApplication"
+        android:allowBackup="true"
+        android:icon="@mipmap/ic_launcher"
+        android:label="@string/app_name"
+        android:supportsRtl="true">
+        <activity android:name=".MainActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+</manifest>

+ 8 - 0
Example/Logan-Android/app/src/main/gen/test/logan/dianping/com/logan/BuildConfig.java

@@ -0,0 +1,8 @@
+/*___Generated_by_IDEA___*/
+
+package test.logan.dianping.com.logan;
+
+/* This stub is only used by the IDE. It is NOT the BuildConfig class actually packed into the APK */
+public final class BuildConfig {
+  public final static boolean DEBUG = Boolean.parseBoolean(null);
+}

+ 7 - 0
Example/Logan-Android/app/src/main/gen/test/logan/dianping/com/logan/Manifest.java

@@ -0,0 +1,7 @@
+/*___Generated_by_IDEA___*/
+
+package test.logan.dianping.com.logan;
+
+/* This stub is only used by the IDE. It is NOT the Manifest class actually packed into the APK */
+public final class Manifest {
+}

+ 7 - 0
Example/Logan-Android/app/src/main/gen/test/logan/dianping/com/logan/R.java

@@ -0,0 +1,7 @@
+/*___Generated_by_IDEA___*/
+
+package test.logan.dianping.com.logan;
+
+/* This stub is only used by the IDE. It is NOT the R class actually packed into the APK */
+public final class R {
+}

+ 166 - 0
Example/Logan-Android/app/src/main/java/test/logan/dianping/com/logan/MainActivity.java

@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package test.logan.dianping.com.logan;
+
+import android.app.Activity;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+import com.dianping.logan.Logan;
+import com.dianping.logan.SendLogCallback;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+
+public class MainActivity extends Activity {
+
+    private static final String TAG = MainActivity.class.getName();
+
+    private TextView mTvInfo;
+    private EditText mEditIp;
+    private RealSendLogRunnable mSendLogRunnable;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_main);
+        initView();
+        mSendLogRunnable = new RealSendLogRunnable();
+    }
+
+    private void initView() {
+        Button button = (Button) findViewById(R.id.write_btn);
+        Button batchBtn = (Button) findViewById(R.id.write_batch_btn);
+        Button sendBtn = (Button) findViewById(R.id.send_btn);
+        Button logFileBtn = (Button) findViewById(R.id.show_log_file_btn);
+        mTvInfo = (TextView) findViewById(R.id.info);
+        mEditIp = (EditText) findViewById(R.id.send_ip);
+
+        button.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                Logan.w("啊哈哈哈哈66666", 2);
+            }
+        });
+        batchBtn.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                loganTest();
+            }
+        });
+        sendBtn.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                loganSend();
+            }
+        });
+        logFileBtn.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                loganFilesInfo();
+            }
+        });
+        findViewById(R.id.send_btn_default).setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                loganSendByDefault();
+            }
+        });
+    }
+
+    private void loganTest() {
+        new Thread() {
+            @Override
+            public void run() {
+                super.run();
+                try {
+                    for (int i = 0; i < 9; i++) {
+                        Log.d(TAG, "times : " + i);
+                        Logan.w(String.valueOf(i), 1);
+                        Thread.sleep(5);
+                    }
+                    Log.d(TAG, "write log end");
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                }
+            }
+        }.start();
+    }
+
+    private void loganSend() {
+        SimpleDateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd");
+        String d = dataFormat.format(new Date(System.currentTimeMillis()));
+        String[] temp = new String[1];
+        temp[0] = d;
+        String ip = mEditIp.getText().toString().trim();
+        if (!TextUtils.isEmpty(ip)) {
+            mSendLogRunnable.setIp(ip);
+        }
+        Logan.s(temp, mSendLogRunnable);
+    }
+
+    private void loganFilesInfo() {
+        Map<String, Long> map = Logan.getAllFilesInfo();
+        if (map != null) {
+            StringBuilder info = new StringBuilder();
+            for (Map.Entry<String, Long> entry : map.entrySet()) {
+                info.append("文件日期:").append(entry.getKey()).append("  文件大小(bytes):").append(
+                        entry.getValue()).append("\n");
+            }
+            mTvInfo.setText(info.toString());
+        }
+    }
+
+    private void loganSendByDefault() {
+        String buildVersion = "";
+        String appVersion = "";
+        try {
+            PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
+            appVersion = pInfo.versionName;
+            buildVersion = String.valueOf(pInfo.versionCode);
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+        }
+        final String url = "https://openlogan.inf.test.sankuai.com/logan/upload.json";
+        SimpleDateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd");
+        final String date = dataFormat.format(new Date(System.currentTimeMillis()));
+        Logan.s(url, date, "1", "logan-test-unionid", "deviceId", buildVersion, appVersion, new SendLogCallback() {
+            @Override
+            public void onLogSendCompleted(int statusCode, byte[] data) {
+                final String resultData = data != null ? new String(data) : "";
+                Log.d(TAG, "日志上传结果, http状态码: " + statusCode + ", 详细: " + resultData);
+            }
+        });
+    }
+}

+ 44 - 0
Example/Logan-Android/app/src/main/java/test/logan/dianping/com/logan/MyApplication.java

@@ -0,0 +1,44 @@
+package test.logan.dianping.com.logan;
+
+import android.app.Application;
+import android.util.Log;
+
+import com.dianping.logan.Logan;
+import com.dianping.logan.LoganConfig;
+import com.dianping.logan.OnLoganProtocolStatus;
+
+import java.io.File;
+
+public class MyApplication extends Application {
+
+    private static final String TAG = MyApplication.class.getName();
+    private static final String FILE_NAME = "logan_v1";
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        initLogan();
+        Logan.w("MyApplication onCreate", 3);
+        Logan.w("MyApplication onCreate", 3);
+        Logan.w("MyApplication onCreate", 3);
+    }
+
+    private void initLogan() {
+        LoganConfig config = new LoganConfig.Builder()
+                .setCachePath(getApplicationContext().getFilesDir().getAbsolutePath())
+                .setPath(getApplicationContext().getExternalFilesDir(null).getAbsolutePath()
+                        + File.separator + FILE_NAME)
+                .setEncryptKey16("0123456789012345".getBytes())
+                .setEncryptIV16("0123456789012345".getBytes())
+                .build();
+        Logan.init(config);
+        Logan.setDebug(true);
+        Logan.setOnLoganProtocolStatus(new OnLoganProtocolStatus() {
+            @Override
+            public void loganProtocolStatus(String cmd, int code) {
+                Log.d(TAG, "clogan > cmd : " + cmd + " | " + "code : " + code);
+            }
+        });
+
+    }
+}

+ 187 - 0
Example/Logan-Android/app/src/main/java/test/logan/dianping/com/logan/RealSendLogRunnable.java

@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package test.logan.dianping.com.logan;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.dianping.logan.SendLogRunnable;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
+
+public class RealSendLogRunnable extends SendLogRunnable {
+    private String mUploadLogUrl = "http://localhost:3000/logupload";
+
+    @Override
+    public void sendLog(File logFile) {
+        boolean success = doSendFileByAction(logFile);
+        Log.d("上传日志测试", "日志上传测试结果:" + success);
+        // Must Call finish after send log
+        finish();
+        if (logFile.getName().contains(".copy")) {
+            logFile.delete();
+        }
+    }
+
+    public void setIp(String ip) {
+        mUploadLogUrl = "http://" + ip + ":3000/logupload";
+    }
+
+    private HashMap<String, String> getActionHeader() {
+        HashMap<String, String> map = new HashMap<>();
+        map.put("Content-Type", "binary/octet-stream"); //二进制上传
+        map.put("client", "android");
+        return map;
+    }
+
+    /**
+     * 主动上报
+     */
+    private boolean doSendFileByAction(File logFile) {
+        boolean isSuccess = false;
+        try {
+            FileInputStream fileStream = new FileInputStream(logFile);
+            byte[] backData = doPostRequest(mUploadLogUrl, fileStream, getActionHeader());
+            isSuccess = handleSendLogBackData(backData);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (JSONException e) {
+            e.printStackTrace();
+        }
+        return isSuccess;
+    }
+
+    private byte[] doPostRequest(String url, InputStream inputData, Map<String, String> headerMap) {
+        byte[] data = null;
+        OutputStream outputStream = null;
+        InputStream inputStream = null;
+        HttpURLConnection c = null;
+        ByteArrayOutputStream back;
+        byte[] Buffer = new byte[2048];
+        try {
+            java.net.URL u = new URL(url);
+            c = (HttpURLConnection) u.openConnection();
+            if (c instanceof HttpsURLConnection) {
+                ((HttpsURLConnection) c).setHostnameVerifier(new HostnameVerifier() {
+                    @Override
+                    public boolean verify(String hostname, SSLSession session) {
+                        return true;
+                    }
+                });
+            }
+            Set<Map.Entry<String, String>> entrySet = headerMap.entrySet();
+            for (Map.Entry<String, String> tempEntry : entrySet) {
+                c.addRequestProperty(tempEntry.getKey(), tempEntry.getValue());
+            }
+            c.setReadTimeout(15000);
+            c.setConnectTimeout(15000);
+            c.setDoInput(true);
+            c.setDoOutput(true);
+            c.setRequestMethod("POST");
+            outputStream = c.getOutputStream();
+            int i;
+            while ((i = inputData.read(Buffer)) != -1) {
+                outputStream.write(Buffer, 0, i);
+            }
+            outputStream.flush();
+            int res = c.getResponseCode();
+            if (res == 200) {
+                back = new ByteArrayOutputStream();
+                inputStream = c.getInputStream();
+                while ((i = inputStream.read(Buffer)) != -1) {
+                    back.write(Buffer, 0, i);
+                }
+                data = back.toByteArray();
+            }
+        } catch (ProtocolException e) {
+            e.printStackTrace();
+        } catch (MalformedURLException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            if (outputStream != null) {
+                try {
+                    outputStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (inputData != null) {
+                try {
+                    inputData.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (c != null) {
+                c.disconnect();
+            }
+        }
+        return data;
+    }
+
+    /**
+     * 处理上传日志接口返回的数据
+     */
+    private boolean handleSendLogBackData(byte[] backData) throws JSONException {
+        boolean isSuccess = false;
+        if (backData != null) {
+            String data = new String(backData);
+            if (!TextUtils.isEmpty(data)) {
+                JSONObject jsonObj = new JSONObject(data);
+                if (jsonObj.optBoolean("success", false)) {
+                    isSuccess = true;
+                }
+            }
+        }
+        return isSuccess;
+    }
+}

+ 73 - 0
Example/Logan-Android/app/src/main/res/layout/activity_main.xml

@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:background="@android:color/white"
+    android:orientation="vertical">
+
+    <Button
+        android:id="@+id/write_batch_btn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="10dp"
+        android:background="@android:color/background_dark"
+        android:text="测试Logan批量写入"
+        android:textColor="@android:color/white" />
+
+    <Button
+        android:id="@+id/write_btn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="10dp"
+        android:background="@android:color/background_dark"
+        android:text="测试Logan单条写入"
+        android:textColor="@android:color/white" />
+
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="10dp"
+        android:orientation="horizontal">
+
+        <Button
+            android:id="@+id/send_btn"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:background="@android:color/background_dark"
+            android:text="测试Logan上报"
+            android:textColor="@android:color/white" />
+
+        <EditText
+            android:id="@+id/send_ip"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="10dp"
+            android:hint="请输入server的ip地址"
+            android:textColor="@android:color/black" />
+
+    </LinearLayout>
+
+    <Button
+        android:id="@+id/send_btn_default"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="10dp"
+        android:background="@android:color/background_dark"
+        android:text="测试Logan默认上报方式"
+        android:textColor="@android:color/white" />
+
+    <Button
+        android:id="@+id/show_log_file_btn"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_margin="10dp"
+        android:background="@android:color/background_dark"
+        android:text="查看所有日志文件信息"
+        android:textColor="@android:color/white" />
+
+    <TextView
+        android:id="@+id/info"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:textColor="@android:color/black" />
+</LinearLayout>

TEMPAT SAMPAH
Example/Logan-Android/app/src/main/res/mipmap-hdpi/ic_launcher.png


TEMPAT SAMPAH
Example/Logan-Android/app/src/main/res/mipmap-mdpi/ic_launcher.png


TEMPAT SAMPAH
Example/Logan-Android/app/src/main/res/mipmap-xhdpi/ic_launcher.png


TEMPAT SAMPAH
Example/Logan-Android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png


TEMPAT SAMPAH
Example/Logan-Android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png


+ 3 - 0
Example/Logan-Android/app/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">Logan Demo</string>
+</resources>

+ 17 - 0
Example/Logan-Android/app/src/test/java/test/logan/dianping/com/logan/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package test.logan.dianping.com.logan;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.Test;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() throws Exception {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 54 - 0
Example/Logan-Android/bintrayUpload.gradle

@@ -0,0 +1,54 @@
+apply plugin: 'com.github.dcendents.android-maven'
+apply plugin: 'com.jfrog.bintray'
+group = groupId
+artifactId = artifactId
+version = versionCode
+
+def getPropertyFromLocalProperties(key) {
+    File file = project.rootProject.file('local.properties')
+    if (file.exists()) {
+        Properties properties = new Properties()
+        properties.load(file.newDataInputStream())
+        return properties.getProperty(key)
+    }
+}
+
+def siteUrl = 'https://tech.meituan.com/Logan.html'
+def gitUrl = 'https://github.com/Meituan-Dianping/Logan'
+
+bintray {
+    user = getPropertyFromLocalProperties("bintray.user")
+    key = getPropertyFromLocalProperties("bintray.apikey")
+    configurations = ['archives']
+    pkg {
+        repo = 'maven'
+        name = "${project.group}:${project.name}"
+        userOrg = 'dianping'
+        licenses = ['MIT']
+        websiteUrl = siteUrl
+        vcsUrl = gitUrl
+        publish = true
+    }
+}
+
+install {
+    repositories.mavenInstaller {
+        pom {
+            project {
+                packaging 'aar'
+                groupId groupId
+                artifactId artifactId
+                version versionCode
+            }
+        }
+    }
+}
+
+task sourcesJar(type: Jar) {
+    from android.sourceSets.main.java.srcDirs
+    classifier = 'sources'
+}
+
+artifacts {
+    archives sourcesJar
+}

+ 34 - 0
Example/Logan-Android/build.gradle

@@ -0,0 +1,34 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+    repositories {
+        jcenter()
+        maven {
+            url 'https://maven.google.com/'
+            name 'Google'
+        }
+    }
+    dependencies {
+        classpath 'com.android.tools.build:gradle:2.3.3'
+        classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5'
+        classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4'
+
+        // NOTE: Do not place your application dependencies here; they belong
+        // in the individual module build.gradle files
+    }
+}
+
+allprojects {
+    repositories {
+        jcenter()
+        maven {
+            url 'https://maven.google.com/'
+            name 'Google'
+        }
+    }
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
+

+ 20 - 0
Example/Logan-Android/gradle.properties

@@ -0,0 +1,20 @@
+# Project-wide Gradle settings.
+
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+groupId=com.dianping.android.sdk
+artifactId=logan
+versionCode=1.2.4

TEMPAT SAMPAH
Example/Logan-Android/gradle/wrapper/gradle-wrapper.jar


+ 6 - 0
Example/Logan-Android/gradle/wrapper/gradle-wrapper.properties

@@ -0,0 +1,6 @@
+#Thu Aug 10 17:35:04 CST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip

+ 160 - 0
Example/Logan-Android/gradlew

@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+##  Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+    echo "$*"
+}
+
+die ( ) {
+    echo
+    echo "$*"
+    echo
+    exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+  CYGWIN* )
+    cygwin=true
+    ;;
+  Darwin* )
+    darwin=true
+    ;;
+  MINGW* )
+    msys=true
+    ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+    ls=`ls -ld "$PRG"`
+    link=`expr "$ls" : '.*-> \(.*\)$'`
+    if expr "$link" : '/.*' > /dev/null; then
+        PRG="$link"
+    else
+        PRG=`dirname "$PRG"`"/$link"
+    fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+        # IBM's JDK on AIX uses strange locations for the executables
+        JAVACMD="$JAVA_HOME/jre/sh/java"
+    else
+        JAVACMD="$JAVA_HOME/bin/java"
+    fi
+    if [ ! -x "$JAVACMD" ] ; then
+        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+    fi
+else
+    JAVACMD="java"
+    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+    MAX_FD_LIMIT=`ulimit -H -n`
+    if [ $? -eq 0 ] ; then
+        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+            MAX_FD="$MAX_FD_LIMIT"
+        fi
+        ulimit -n $MAX_FD
+        if [ $? -ne 0 ] ; then
+            warn "Could not set maximum file descriptor limit: $MAX_FD"
+        fi
+    else
+        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+    fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+    JAVACMD=`cygpath --unix "$JAVACMD"`
+
+    # We build the pattern for arguments to be converted via cygpath
+    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+    SEP=""
+    for dir in $ROOTDIRSRAW ; do
+        ROOTDIRS="$ROOTDIRS$SEP$dir"
+        SEP="|"
+    done
+    OURCYGPATTERN="(^($ROOTDIRS))"
+    # Add a user-defined pattern to the cygpath arguments
+    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+    fi
+    # Now convert the arguments - kludge to limit ourselves to /bin/sh
+    i=0
+    for arg in "$@" ; do
+        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option
+
+        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
+            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+        else
+            eval `echo args$i`="\"$arg\""
+        fi
+        i=$((i+1))
+    done
+    case $i in
+        (0) set -- ;;
+        (1) set -- "$args0" ;;
+        (2) set -- "$args0" "$args1" ;;
+        (3) set -- "$args0" "$args1" "$args2" ;;
+        (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+        (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+        (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+        (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+        (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+        (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+    esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+    JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"

+ 90 - 0
Example/Logan-Android/gradlew.bat

@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem  Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega

+ 1 - 0
Example/Logan-Android/logan/.gitignore

@@ -0,0 +1 @@
+/build

+ 27 - 0
Example/Logan-Android/logan/CMakeLists.txt

@@ -0,0 +1,27 @@
+cmake_minimum_required(VERSION 3.4.1)
+
+SET (CMAKE_C_FLAGS_DEBUG          "-g")
+SET (CMAKE_C_FLAGS_RELEASE        "-O2 -DNDEBUG")
+
+set(EXTERN_DIR ../../../Logan/Clogan)
+
+add_subdirectory(${EXTERN_DIR} clogan.out)
+
+include_directories(${EXTERN_DIR})
+
+link_directories(clogan.out)
+
+
+find_library( # Sets the name of the path variable.
+              log-lib
+
+              # Specifies the name of the NDK library that
+              # you want CMake to locate.
+              log )
+
+# Specifies libraries CMake should link to your target library. You
+# can link multiple libraries, such as libraries you define in this
+# build script, prebuilt third-party libraries, or system libraries.
+
+add_library(logan SHARED src/main/jni/clogan_protocol.c)
+target_link_libraries(logan ${log-lib} z clogan)

+ 49 - 0
Example/Logan-Android/logan/build.gradle

@@ -0,0 +1,49 @@
+apply plugin: 'com.android.library'
+apply from: rootProject.file("bintrayUpload.gradle")
+
+android {
+//    publishNonDefault true
+
+    compileSdkVersion 23
+    buildToolsVersion "25.0.2"
+
+    defaultConfig {
+        minSdkVersion 14
+        targetSdkVersion 23
+        versionCode 1
+        versionName "1.0"
+
+        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+
+        externalNativeBuild {
+            cmake {
+                arguments '-DBUILD_TESTING=OFF', '-DANDROID_TOOLCHAIN=gcc'
+                cFlags "-std=c11"
+                abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
+            }
+        }
+    }
+    buildTypes {
+        release {
+            minifyEnabled false
+            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+        }
+        debug {
+            jniDebuggable true
+            testCoverageEnabled true
+        }
+    }
+    externalNativeBuild {
+        cmake {
+            path "CMakeLists.txt"
+        }
+    }
+}
+
+dependencies {
+    compile fileTree(include: ['*.jar'], dir: 'libs')
+    androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
+        exclude group: 'com.android.support', module: 'support-annotations'
+    })
+    testCompile 'junit:junit:4.12'
+}

+ 25 - 0
Example/Logan-Android/logan/proguard-rules.pro

@@ -0,0 +1,25 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in /Users/baitian0521/Library/Android/sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile

+ 143 - 0
Example/Logan-Android/logan/src/androidTest/java/com/dianping/logan/LoganTest.java

@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.MethodSorters;
+
+import java.io.File;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Instrumentation test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class LoganTest {
+    private static final String TAG = LoganTest.class.getName();
+    private static final String FILE_NAME = "logan" + "_vtest";
+
+    private SimpleDateFormat mDateFormat = new SimpleDateFormat("yyyy-MM-dd");
+    private CountDownLatch mLatch;
+
+    @Before
+    public void setUp() throws Exception {
+        mLatch = new CountDownLatch(1);
+    }
+
+    @Test
+    public void test001Init() {
+        Context applicationContext = InstrumentationRegistry.getTargetContext();
+        LoganConfig config = new LoganConfig.Builder()
+                .setCachePath(applicationContext.getFilesDir().getAbsolutePath())
+                .setPath(applicationContext.getExternalFilesDir(null).getAbsolutePath()
+                        + File.separator + FILE_NAME)
+                .setEncryptKey16("0123456789012345".getBytes())
+                .setEncryptIV16("0123456789012345".getBytes())
+                .build();
+        Logan.init(config);
+        Logan.setDebug(true);
+        Logan.setOnLoganProtocolStatus(new OnLoganProtocolStatus() {
+            @Override
+            public void loganProtocolStatus(String cmd, int code) {
+                Log.d(TAG, "clogan > cmd : " + cmd + " | " + "code : " + code);
+            }
+        });
+    }
+
+    @Test
+    public void test002LoganW() throws InterruptedException {
+        Logan.w("Logan junit test write function", 1);
+        assertWriteLog();
+    }
+
+    @Test
+    public void test003LoganF() {
+        Logan.f();
+    }
+
+    @Test
+    public void test004LoganS() {
+        SendLogRunnable sendLogRunnable = new SendLogRunnable() {
+            @Override
+            public void sendLog(File logFile) {
+
+            }
+        };
+        Logan.s(getTodayDate(), sendLogRunnable);
+    }
+
+    @Test
+    public void test005LoganFilesInfo() {
+        Map<String, Long> map = Logan.getAllFilesInfo();
+        if (map != null) {
+            StringBuilder info = new StringBuilder();
+            for (Map.Entry<String, Long> entry : map.entrySet()) {
+                info.append("文件日期:").append(entry.getKey()).append("  文件大小(bytes):").append(
+                        entry.getValue()).append("\n");
+            }
+            Log.d(TAG, info.toString());
+        }
+    }
+
+    // Functions
+
+    private String[] getTodayDate() {
+        String d = mDateFormat.format(new Date(System.currentTimeMillis()));
+        String[] temp = new String[1];
+        temp[0] = d;
+        return temp;
+    }
+
+    private void assertWriteLog() throws InterruptedException {
+        final int[] statusCode = new int[1];
+        Logan.setOnLoganProtocolStatus(new OnLoganProtocolStatus() {
+            @Override
+            public void loganProtocolStatus(String cmd, int code) {
+                statusCode[0] = code;
+                if (cmd.equals(ConstantCode.CloganStatus.CLOGAN_WRITE_STATUS)) {
+                    mLatch.countDown();
+                    assertEquals(ConstantCode.CloganStatus.CLOGAN_WRITE_SUCCESS, code);
+                }
+            }
+        });
+        mLatch.await(2333, TimeUnit.MILLISECONDS);
+        assertEquals("write状态码", ConstantCode.CloganStatus.CLOGAN_WRITE_SUCCESS, statusCode[0]);
+    }
+}

+ 2 - 0
Example/Logan-Android/logan/src/main/AndroidManifest.xml

@@ -0,0 +1,2 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.dianping.logan"/>

+ 8 - 0
Example/Logan-Android/logan/src/main/gen/com/dianping/logan/BuildConfig.java

@@ -0,0 +1,8 @@
+/*___Generated_by_IDEA___*/
+
+package com.dianping.logan;
+
+/* This stub is only used by the IDE. It is NOT the BuildConfig class actually packed into the APK */
+public final class BuildConfig {
+  public final static boolean DEBUG = Boolean.parseBoolean(null);
+}

+ 7 - 0
Example/Logan-Android/logan/src/main/gen/com/dianping/logan/Manifest.java

@@ -0,0 +1,7 @@
+/*___Generated_by_IDEA___*/
+
+package com.dianping.logan;
+
+/* This stub is only used by the IDE. It is NOT the Manifest class actually packed into the APK */
+public final class Manifest {
+}

+ 7 - 0
Example/Logan-Android/logan/src/main/gen/com/dianping/logan/R.java

@@ -0,0 +1,7 @@
+/*___Generated_by_IDEA___*/
+
+package com.dianping.logan;
+
+/* This stub is only used by the IDE. It is NOT the R class actually packed into the APK */
+public final class R {
+}

+ 196 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/CLoganProtocol.java

@@ -0,0 +1,196 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+class CLoganProtocol implements LoganProtocolHandler {
+
+    private static final String LIBRARY_NAME = "logan";
+
+    private static CLoganProtocol sCLoganProtocol;
+    private static boolean sIsCloganOk;
+
+    private boolean mIsLoganInit;
+    private boolean mIsLoganOpen;
+    private OnLoganProtocolStatus mLoganProtocolStatus;
+    private Set<Integer> mArraySet = Collections.synchronizedSet(new HashSet<Integer>());
+
+    static {
+        try {
+            System.loadLibrary(LIBRARY_NAME);
+            sIsCloganOk = true;
+        } catch (Throwable e) {
+            e.printStackTrace();
+            sIsCloganOk = false;
+        }
+    }
+
+    static boolean isCloganSuccess() {
+        return sIsCloganOk;
+    }
+
+    static CLoganProtocol newInstance() {
+        if (sCLoganProtocol == null) {
+            synchronized (CLoganProtocol.class) {
+                if (sCLoganProtocol == null) {
+                    sCLoganProtocol = new CLoganProtocol();
+                }
+            }
+        }
+        return sCLoganProtocol;
+    }
+
+    /**
+     * 初始化Clogan
+     *
+     * @param dir_path 目录路径
+     * @param max_file 最大文件值
+     */
+    private native int clogan_init(String cache_path, String dir_path, int max_file,
+            String encrypt_key_16, String encrypt_iv_16);
+
+    private native int clogan_open(String file_name);
+
+    private native void clogan_debug(boolean is_debug);
+
+    /**
+     * @param flag        日志类型
+     * @param log         日志内容
+     * @param local_time  本地时间
+     * @param thread_name 线程名称
+     * @param thread_id   线程ID
+     * @param is_main     是否主线程
+     */
+    private native int clogan_write(int flag, String log, long local_time, String thread_name,
+            long thread_id, int is_main);
+
+    private native void clogan_flush();
+
+    @Override
+    public void logan_init(String cache_path, String dir_path, int max_file, String encrypt_key_16,
+            String encrypt_iv_16) {
+        if (mIsLoganInit) {
+            return;
+        }
+        if (!sIsCloganOk) {
+            loganStatusCode(ConstantCode.CloganStatus.CLOGAN_LOAD_SO,
+                    ConstantCode.CloganStatus.CLOGAN_LOAD_SO_FAIL);
+            return;
+        }
+
+        try {
+            int code = clogan_init(cache_path, dir_path, max_file, encrypt_key_16, encrypt_iv_16);
+            mIsLoganInit = true;
+            loganStatusCode(ConstantCode.CloganStatus.CLGOAN_INIT_STATUS, code);
+        } catch (UnsatisfiedLinkError e) {
+            e.printStackTrace();
+            loganStatusCode(ConstantCode.CloganStatus.CLGOAN_INIT_STATUS,
+                    ConstantCode.CloganStatus.CLOGAN_INIT_FAIL_JNI);
+        }
+    }
+
+    @Override
+    public void logan_debug(boolean debug) {
+        if (!mIsLoganInit || !sIsCloganOk) {
+            return;
+        }
+        try {
+            clogan_debug(debug);
+        } catch (UnsatisfiedLinkError e) {
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void setOnLoganProtocolStatus(OnLoganProtocolStatus listener) {
+        mLoganProtocolStatus = listener;
+    }
+
+    @Override
+    public void logan_open(String file_name) {
+        if (!mIsLoganInit || !sIsCloganOk) {
+            return;
+        }
+        try {
+            int code = clogan_open(file_name);
+            mIsLoganOpen = true;
+            loganStatusCode(ConstantCode.CloganStatus.CLOGAN_OPEN_STATUS, code);
+        } catch (UnsatisfiedLinkError e) {
+            e.printStackTrace();
+            loganStatusCode(ConstantCode.CloganStatus.CLOGAN_OPEN_STATUS,
+                    ConstantCode.CloganStatus.CLOGAN_OPEN_FAIL_JNI);
+        }
+    }
+
+    @Override
+    public void logan_flush() {
+        if (!mIsLoganOpen || !sIsCloganOk) {
+            return;
+        }
+        try {
+            clogan_flush();
+        } catch (UnsatisfiedLinkError e) {
+            e.printStackTrace();
+        }
+
+    }
+
+    @Override
+    public void logan_write(int flag, String log, long local_time, String thread_name,
+            long thread_id, boolean is_main) {
+        if (!mIsLoganOpen || !sIsCloganOk) {
+            return;
+        }
+        try {
+            int isMain = is_main ? 1 : 0;
+            int code = clogan_write(flag, log, local_time, thread_name, thread_id,
+                    isMain);
+            if (code != ConstantCode.CloganStatus.CLOGAN_WRITE_SUCCESS || Logan.sDebug) {
+                loganStatusCode(ConstantCode.CloganStatus.CLOGAN_WRITE_STATUS, code);
+            }
+        } catch (UnsatisfiedLinkError e) {
+            e.printStackTrace();
+            loganStatusCode(ConstantCode.CloganStatus.CLOGAN_WRITE_STATUS,
+                    ConstantCode.CloganStatus.CLOGAN_WRITE_FAIL_JNI);
+        }
+    }
+
+    private void loganStatusCode(String cmd, int code) {
+        if (code < 0) {
+            if (ConstantCode.CloganStatus.CLOGAN_WRITE_STATUS.endsWith(cmd)
+                    && code != ConstantCode.CloganStatus.CLOGAN_WRITE_FAIL_JNI) {
+                if (mArraySet.contains(code)) {
+                    return;
+                } else {
+                    mArraySet.add(code);
+                }
+            }
+            if (mLoganProtocolStatus != null) {
+                mLoganProtocolStatus.loganProtocolStatus(cmd, code);
+            }
+        }
+    }
+}

+ 56 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/ConstantCode.java

@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+public class ConstantCode {
+
+    public static class CloganStatus {
+        public static final String CLGOAN_INIT_STATUS = "clogan_init"; //初始化函数
+        public static final int CLOGAN_INIT_SUCCESS_MMAP = -1010; //初始化成功, mmap内存
+        public static final int CLOGAN_INIT_SUCCESS_MEMORY = -1020; //初始化成功, 堆内存
+        public static final int CLOGAN_INIT_FAIL_NOCACHE = -1030; //初始化失败 , 没有缓存
+        public static final int CLOGAN_INIT_FAIL_NOMALLOC = -1040; //初始化失败 , malloc失败
+        public static final int CLOGAN_INIT_FAIL_HEADER = -1050; //初始化头失败
+        public static final int CLOGAN_INIT_FAIL_JNI = -1060; //jni找不到对应C函数
+
+        public static final String CLOGAN_OPEN_STATUS = "clogan_open"; //打开文件函数
+        public static final int CLOGAN_OPEN_SUCCESS = -2010; //打开文件成功
+        public static final int CLOGAN_OPEN_FAIL_IO = -2020; //打开文件IO失败
+        public static final int CLOGAN_OPEN_FAIL_ZLIB = -2030; //打开文件zlib失败
+        public static final int CLOGAN_OPEN_FAIL_MALLOC = -2040; //打开文件malloc失败
+        public static final int CLOGAN_OPEN_FAIL_NOINIT = -2050; //打开文件没有初始化失败
+        public static final int CLOGAN_OPEN_FAIL_HEADER = -2060; //打开文件头失败
+        public static final int CLOGAN_OPEN_FAIL_JNI = -2070; //jni找不到对应C函数
+
+        public static final String CLOGAN_WRITE_STATUS = "clogan_write"; //写入函数
+        public static final int CLOGAN_WRITE_SUCCESS = -4010; //写入日志成功
+        public static final int CLOGAN_WRITE_FAIL_PARAM = -4020; //写入失败, 可变参数错误
+        public static final int CLOAGN_WRITE_FAIL_MAXFILE = -4030; //写入失败,超过文件最大值
+        public static final int CLOGAN_WRITE_FAIL_MALLOC = -4040; //写入失败,malloc失败
+        public static final int CLOGAN_WRITE_FAIL_HEADER = -4050; //写入头失败
+        public static final int CLOGAN_WRITE_FAIL_JNI = -4060; //jni找不到对应C函数
+
+        public static final String CLOGAN_LOAD_SO = "logan_loadso"; //Logan装载So;
+        public static final int CLOGAN_LOAD_SO_FAIL = -5020; //加载的SO失败
+    }
+}

+ 158 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/Logan.java

@@ -0,0 +1,158 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.Map;
+
+public class Logan {
+
+    private static OnLoganProtocolStatus sLoganProtocolStatus;
+    private static LoganControlCenter sLoganControlCenter;
+    static boolean sDebug = false;
+
+    /**
+     * @brief Logan初始化
+     */
+    public static void init(LoganConfig loganConfig) {
+        sLoganControlCenter = LoganControlCenter.instance(loganConfig);
+    }
+
+    /**
+     * @param log  表示日志内容
+     * @param type 表示日志类型
+     * @brief Logan写入日志
+     */
+    public static void w(String log, int type) {
+        if (sLoganControlCenter == null) {
+            throw new RuntimeException("Please initialize Logan first");
+        }
+        sLoganControlCenter.write(log, type);
+    }
+
+    /**
+     * @brief 立即写入日志文件
+     */
+    public static void f() {
+        if (sLoganControlCenter == null) {
+            throw new RuntimeException("Please initialize Logan first");
+        }
+        sLoganControlCenter.flush();
+    }
+
+    /**
+     * @param dates    日期数组,格式:“2018-07-27”
+     * @param runnable 发送操作
+     * @brief 发送日志
+     */
+    public static void s(String[] dates, SendLogRunnable runnable) {
+        if (sLoganControlCenter == null) {
+            throw new RuntimeException("Please initialize Logan first");
+        }
+        sLoganControlCenter.send(dates, runnable);
+    }
+
+    /**
+     * @param url             接受日志的服务器完整url.
+     * @param date            日志日期 格式:"2018-11-21".
+     * @param appId           当前应用的唯一标识,在多App时区分日志来源App.
+     * @param unionId         当前用户的唯一标识,用来区分日志来源用户.
+     * @param deviceId        设备id.
+     * @param buildVersion    上报源App的build号.
+     * @param appVersion      上报源的App版本.
+     * @param sendLogCallback 上报结果回调(子线程调用).
+     */
+    public static void s(String url, String date, String appId, String unionId, String deviceId,
+                         String buildVersion, String appVersion, SendLogCallback sendLogCallback) {
+        final Map<String, String> headers = new HashMap<>();
+        headers.put("fileDate", date);
+        headers.put("appId", appId);
+        headers.put("unionId", unionId);
+        headers.put("deviceId", deviceId);
+        headers.put("buildVersion", buildVersion);
+        headers.put("appVersion", appVersion);
+        headers.put("platform", "1");
+        s(url, date, headers, sendLogCallback);
+    }
+
+    /**
+     * @param url             接受日志的服务器完整url.
+     * @param date            日志日期 格式:"2018-11-21".
+     * @param headers         请求头信息.
+     * @param sendLogCallback 上报结果回调(子线程调用).
+     */
+    public static void s(String url, String date, Map<String, String> headers, SendLogCallback sendLogCallback) {
+        if (sLoganControlCenter == null) {
+            throw new RuntimeException("Please initialize Logan first");
+        }
+        final SendLogDefaultRunnable sendLogRunnable = new SendLogDefaultRunnable();
+        sendLogRunnable.setUrl(url);
+        sendLogRunnable.setSendLogCallback(sendLogCallback);
+        sendLogRunnable.setRequestHeader(headers);
+        sLoganControlCenter.send(new String[]{date}, sendLogRunnable);
+    }
+
+    /**
+     * @brief 返回所有日志文件信息
+     */
+    public static Map<String, Long> getAllFilesInfo() {
+        if (sLoganControlCenter == null) {
+            throw new RuntimeException("Please initialize Logan first");
+        }
+        File dir = sLoganControlCenter.getDir();
+        if (!dir.exists()) {
+            return null;
+        }
+        File[] files = dir.listFiles();
+        if (files == null) {
+            return null;
+        }
+        Map<String, Long> allFilesInfo = new HashMap<>();
+        for (File file : files) {
+            try {
+                allFilesInfo.put(Util.getDateStr(Long.parseLong(file.getName())), file.length());
+            } catch (NumberFormatException e) {
+                // ignore
+            }
+        }
+        return allFilesInfo;
+    }
+
+    /**
+     * @brief Logan Debug开关
+     */
+    public static void setDebug(boolean debug) {
+        Logan.sDebug = debug;
+    }
+
+    static void onListenerLogWriteStatus(String name, int status) {
+        if (sLoganProtocolStatus != null) {
+            sLoganProtocolStatus.loganProtocolStatus(name, status);
+        }
+    }
+
+    public static void setOnLoganProtocolStatus(OnLoganProtocolStatus listener) {
+        sLoganProtocolStatus = listener;
+    }
+}

+ 144 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/LoganConfig.java

@@ -0,0 +1,144 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+import android.text.TextUtils;
+
+public class LoganConfig {
+
+    private static final long DAYS = 24 * 60 * 60 * 1000; //天
+    private static final long M = 1024 * 1024; //M
+    private static final long DEFAULT_DAY = 7 * DAYS; //默认删除天数
+    private static final long DEFAULT_FILE_SIZE = 10 * M;
+    private static final long DEFAULT_MIN_SDCARD_SIZE = 50 * M; //最小的SD卡小于这个大小不写入
+    private static final int DEFAULT_QUEUE = 500;
+
+    String mCachePath; //mmap缓存路径
+    String mPathPath; //file文件路径
+
+    long mMaxFile = DEFAULT_FILE_SIZE; //删除文件最大值
+    long mDay = DEFAULT_DAY; //删除天数
+    long mMaxQueue = DEFAULT_QUEUE;
+    long mMinSDCard = DEFAULT_MIN_SDCARD_SIZE; //最小sdk卡大小
+
+    byte[] mEncryptKey16; //128位aes加密Key
+    byte[] mEncryptIv16; //128位aes加密IV
+
+    boolean isValid() {
+        boolean valid = false;
+        if (!TextUtils.isEmpty(mCachePath) && !TextUtils.isEmpty(mPathPath) && mEncryptKey16 != null
+                && mEncryptIv16 != null) {
+            valid = true;
+        }
+        return valid;
+    }
+
+    private LoganConfig() {
+
+    }
+
+    private void setCachePath(String cachePath) {
+        mCachePath = cachePath;
+    }
+
+    private void setPathPath(String pathPath) {
+        mPathPath = pathPath;
+    }
+
+    private void setMaxFile(long maxFile) {
+        mMaxFile = maxFile;
+    }
+
+    private void setDay(long day) {
+        mDay = day;
+    }
+
+    private void setMinSDCard(long minSDCard) {
+        mMinSDCard = minSDCard;
+    }
+
+    private void setEncryptKey16(byte[] encryptKey16) {
+        mEncryptKey16 = encryptKey16;
+    }
+
+    private void setEncryptIV16(byte[] encryptIv16) {
+        mEncryptIv16 = encryptIv16;
+    }
+
+    public static final class Builder {
+        String mCachePath; //mmap缓存路径
+        String mPath; //file文件路径
+        long mMaxFile = DEFAULT_FILE_SIZE; //删除文件最大值
+        long mDay = DEFAULT_DAY; //删除天数
+        byte[] mEncryptKey16; //128位ase加密Key
+        byte[] mEncryptIv16; //128位aes加密IV
+        long mMinSDCard = DEFAULT_MIN_SDCARD_SIZE;
+
+        public Builder setCachePath(String cachePath) {
+            mCachePath = cachePath;
+            return this;
+        }
+
+        public Builder setPath(String path) {
+            mPath = path;
+            return this;
+        }
+
+        public Builder setMaxFile(long maxFile) {
+            mMaxFile = maxFile * M;
+            return this;
+        }
+
+        public Builder setDay(long day) {
+            mDay = day * DAYS;
+            return this;
+        }
+
+        public Builder setEncryptKey16(byte[] encryptKey16) {
+            mEncryptKey16 = encryptKey16;
+            return this;
+        }
+
+        public Builder setEncryptIV16(byte[] encryptIv16) {
+            mEncryptIv16 = encryptIv16;
+            return this;
+        }
+
+        public Builder setMinSDCard(long minSDCard) {
+            this.mMinSDCard = minSDCard;
+            return this;
+        }
+
+        public LoganConfig build() {
+            LoganConfig config = new LoganConfig();
+            config.setCachePath(mCachePath);
+            config.setPathPath(mPath);
+            config.setMaxFile(mMaxFile);
+            config.setMinSDCard(mMinSDCard);
+            config.setDay(mDay);
+            config.setEncryptKey16(mEncryptKey16);
+            config.setEncryptIV16(mEncryptIv16);
+            return config;
+        }
+    }
+}

+ 163 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/LoganControlCenter.java

@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+import android.os.Looper;
+import android.text.TextUtils;
+
+import java.io.File;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+class LoganControlCenter {
+
+    private static LoganControlCenter sLoganControlCenter;
+
+    private ConcurrentLinkedQueue<LoganModel> mCacheLogQueue = new ConcurrentLinkedQueue<>();
+    private String mCachePath; // 缓存文件路径
+    private String mPath; //文件路径
+    private long mSaveTime; //存储时间
+    private long mMaxLogFile;//最大文件大小
+    private long mMinSDCard;
+    private long mMaxQueue; //最大队列数
+    private String mEncryptKey16;
+    private String mEncryptIv16;
+    private LoganThread mLoganThread;
+    private SimpleDateFormat dataFormat = new SimpleDateFormat("yyyy-MM-dd");
+
+    private LoganControlCenter(LoganConfig config) {
+        if (!config.isValid()) {
+            throw new NullPointerException("config's param is invalid");
+        }
+
+        mPath = config.mPathPath;
+        mCachePath = config.mCachePath;
+        mSaveTime = config.mDay;
+        mMinSDCard = config.mMinSDCard;
+        mMaxLogFile = config.mMaxFile;
+        mMaxQueue = config.mMaxQueue;
+        mEncryptKey16 = new String(config.mEncryptKey16);
+        mEncryptIv16 = new String(config.mEncryptIv16);
+
+        init();
+    }
+
+    private void init() {
+        if (mLoganThread == null) {
+            mLoganThread = new LoganThread(mCacheLogQueue, mCachePath, mPath, mSaveTime,
+                    mMaxLogFile, mMinSDCard, mEncryptKey16, mEncryptIv16);
+            mLoganThread.setName("logan-thread");
+            mLoganThread.start();
+        }
+    }
+
+    static LoganControlCenter instance(LoganConfig config) {
+        if (sLoganControlCenter == null) {
+            synchronized (LoganControlCenter.class) {
+                if (sLoganControlCenter == null) {
+                    sLoganControlCenter = new LoganControlCenter(config);
+                }
+            }
+        }
+        return sLoganControlCenter;
+    }
+
+    void write(String log, int flag) {
+        if (TextUtils.isEmpty(log)) {
+            return;
+        }
+        LoganModel model = new LoganModel();
+        model.action = LoganModel.Action.WRITE;
+        WriteAction action = new WriteAction();
+        String threadName = Thread.currentThread().getName();
+        long threadLog = Thread.currentThread().getId();
+        boolean isMain = false;
+        if (Looper.getMainLooper() == Looper.myLooper()) {
+            isMain = true;
+        }
+        action.log = log;
+        action.localTime = System.currentTimeMillis();
+        action.flag = flag;
+        action.isMainThread = isMain;
+        action.threadId = threadLog;
+        action.threadName = threadName;
+        model.writeAction = action;
+        if (mCacheLogQueue.size() < mMaxQueue) {
+            mCacheLogQueue.add(model);
+            if (mLoganThread != null) {
+                mLoganThread.notifyRun();
+            }
+        }
+    }
+
+    void send(String dates[], SendLogRunnable runnable) {
+        if (TextUtils.isEmpty(mPath) || dates == null || dates.length == 0) {
+            return;
+        }
+        for (String date : dates) {
+            if (TextUtils.isEmpty(date)) {
+                continue;
+            }
+            long time = getDateTime(date);
+            if (time > 0) {
+                LoganModel model = new LoganModel();
+                SendAction action = new SendAction();
+                model.action = LoganModel.Action.SEND;
+                action.date = String.valueOf(time);
+                action.sendLogRunnable = runnable;
+                model.sendAction = action;
+                mCacheLogQueue.add(model);
+                if (mLoganThread != null) {
+                    mLoganThread.notifyRun();
+                }
+            }
+        }
+    }
+
+    void flush() {
+        if (TextUtils.isEmpty(mPath)) {
+            return;
+        }
+        LoganModel model = new LoganModel();
+        model.action = LoganModel.Action.FLUSH;
+        mCacheLogQueue.add(model);
+        if (mLoganThread != null) {
+            mLoganThread.notifyRun();
+        }
+    }
+
+    File getDir() {
+        return new File(mPath);
+    }
+
+    private long getDateTime(String time) {
+        long tempTime = 0;
+        try {
+            tempTime = dataFormat.parse(time).getTime();
+        } catch (ParseException e) {
+            e.printStackTrace();
+        }
+        return tempTime;
+    }
+}

+ 50 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/LoganModel.java

@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+class LoganModel {
+
+    enum Action {
+        WRITE, SEND, FLUSH
+    }
+
+    Action action;
+
+    WriteAction writeAction;
+
+    SendAction sendAction;
+
+    boolean isValid() {
+        boolean valid = false;
+        if (action != null) {
+            if (action == Action.SEND && sendAction != null && sendAction.isValid()) {
+                valid = true;
+            } else if (action == Action.WRITE && writeAction != null && writeAction.isValid()) {
+                valid = true;
+            } else if (action == Action.FLUSH) {
+                valid = true;
+            }
+        }
+        return valid;
+    }
+}

+ 96 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/LoganProtocol.java

@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+class LoganProtocol implements LoganProtocolHandler {
+
+    private static LoganProtocol sLoganProtocol;
+
+    private LoganProtocolHandler mCurProtocol;
+    private boolean mIsInit;
+    private OnLoganProtocolStatus mLoganProtocolStatus;
+
+    private LoganProtocol() {
+
+    }
+
+    static LoganProtocol newInstance() {
+        if (sLoganProtocol == null) {
+            synchronized (LoganProtocol.class) {
+                sLoganProtocol = new LoganProtocol();
+            }
+        }
+        return sLoganProtocol;
+    }
+
+    @Override
+    public void logan_flush() {
+        if (mCurProtocol != null) {
+            mCurProtocol.logan_flush();
+        }
+    }
+
+    @Override
+    public void logan_write(int flag, String log, long local_time, String thread_name,
+            long thread_id, boolean is_main) {
+        if (mCurProtocol != null) {
+            mCurProtocol.logan_write(flag, log, local_time, thread_name, thread_id,
+                    is_main);
+        }
+    }
+
+    @Override
+    public void logan_open(String file_name) {
+        if (mCurProtocol != null) {
+            mCurProtocol.logan_open(file_name);
+        }
+    }
+
+    @Override
+    public void logan_init(String cache_path, String dir_path, int max_file, String encrypt_key_16,
+            String encrypt_iv_16) {
+        if (mIsInit) {
+            return;
+        }
+        if (CLoganProtocol.isCloganSuccess()) {
+            mCurProtocol = CLoganProtocol.newInstance();
+            mCurProtocol.setOnLoganProtocolStatus(mLoganProtocolStatus);
+            mCurProtocol.logan_init(cache_path, dir_path, max_file, encrypt_key_16, encrypt_iv_16);
+            mIsInit = true;
+        } else {
+            mCurProtocol = null;
+        }
+    }
+
+    @Override
+    public void logan_debug(boolean debug) {
+        if (mCurProtocol != null) {
+            mCurProtocol.logan_debug(debug);
+        }
+    }
+
+    @Override
+    public void setOnLoganProtocolStatus(OnLoganProtocolStatus listener) {
+        mLoganProtocolStatus = listener;
+    }
+}

+ 40 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/LoganProtocolHandler.java

@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+public interface LoganProtocolHandler {
+
+    void logan_flush();
+
+    void logan_write(int flag, String log, long local_time, String thread_name,
+            long thread_id, boolean is_main);
+
+    void logan_open(String file_name);
+
+    void logan_init(String cache_path, String dir_path, int max_file, String encrypt_key_16,
+            String encrypt_iv_16);
+
+    void logan_debug(boolean debug);
+
+    void setOnLoganProtocolStatus(OnLoganProtocolStatus listener);
+}

+ 367 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/LoganThread.java

@@ -0,0 +1,367 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+import android.os.StatFs;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+
+class LoganThread extends Thread {
+
+    private static final String TAG = "LoganThread";
+    private static final int MINUTE = 60 * 1000;
+    private static final long LONG = 24 * 60 * 60 * 1000;
+    private static final int CACHE_SIZE = 1024;
+
+    private final Object sync = new Object();
+    private final Object sendSync = new Object();
+    private volatile boolean mIsRun = true;
+
+    private long mCurrentDay;
+    private volatile boolean mIsWorking;
+    private File mFileDirectory;
+    private boolean mIsSDCard;
+    private long mLastTime;
+    private LoganProtocol mLoganProtocol;
+    private ConcurrentLinkedQueue<LoganModel> mCacheLogQueue;
+    private String mCachePath; // 缓存文件路径
+    private String mPath; //文件路径
+    private long mSaveTime; //存储时间
+    private long mMaxLogFile;//最大文件大小
+    private long mMinSDCard;
+    private String mEncryptKey16;
+    private String mEncryptIv16;
+    private int mSendLogStatusCode;
+    // 发送缓存队列
+    private ConcurrentLinkedQueue<LoganModel> mCacheSendQueue = new ConcurrentLinkedQueue<>();
+    private ExecutorService mSingleThreadExecutor;
+
+    LoganThread(
+            ConcurrentLinkedQueue<LoganModel> cacheLogQueue, String cachePath,
+            String path, long saveTime, long maxLogFile, long minSDCard, String encryptKey16,
+            String encryptIv16) {
+        mCacheLogQueue = cacheLogQueue;
+        mCachePath = cachePath;
+        mPath = path;
+        mSaveTime = saveTime;
+        mMaxLogFile = maxLogFile;
+        mMinSDCard = minSDCard;
+        mEncryptKey16 = encryptKey16;
+        mEncryptIv16 = encryptIv16;
+    }
+
+    void notifyRun() {
+        if (!mIsWorking) {
+            synchronized (sync) {
+                sync.notify();
+            }
+        }
+    }
+
+    void quit() {
+        mIsRun = false;
+        if (!mIsWorking) {
+            synchronized (sync) {
+                sync.notify();
+            }
+        }
+    }
+
+    @Override
+    public void run() {
+        super.run();
+        while (mIsRun) {
+            synchronized (sync) {
+                mIsWorking = true;
+                try {
+                    LoganModel model = mCacheLogQueue.poll();
+                    if (model == null) {
+                        mIsWorking = false;
+                        sync.wait();
+                        mIsWorking = true;
+                    } else {
+                        action(model);
+                    }
+                } catch (InterruptedException e) {
+                    e.printStackTrace();
+                    mIsWorking = false;
+                }
+            }
+        }
+    }
+
+    private void action(LoganModel model) {
+        if (model == null || !model.isValid()) {
+            return;
+        }
+        if (mLoganProtocol == null) {
+            mLoganProtocol = LoganProtocol.newInstance();
+            mLoganProtocol.setOnLoganProtocolStatus(new OnLoganProtocolStatus() {
+                @Override
+                public void loganProtocolStatus(String cmd, int code) {
+                    Logan.onListenerLogWriteStatus(cmd, code);
+                }
+            });
+            mLoganProtocol.logan_init(mCachePath, mPath, (int) mMaxLogFile, mEncryptKey16,
+                    mEncryptIv16);
+            mLoganProtocol.logan_debug(Logan.sDebug);
+        }
+
+        if (model.action == LoganModel.Action.WRITE) {
+            doWriteLog2File(model.writeAction);
+        } else if (model.action == LoganModel.Action.SEND) {
+            if (model.sendAction.sendLogRunnable != null) {
+                // 是否正在发送
+                synchronized (sendSync) {
+                    if (mSendLogStatusCode == SendLogRunnable.SENDING) {
+                        mCacheSendQueue.add(model);
+                    } else {
+                        doSendLog2Net(model.sendAction);
+                    }
+                }
+            }
+        } else if (model.action == LoganModel.Action.FLUSH) {
+            doFlushLog2File();
+        }
+    }
+
+    private void doFlushLog2File() {
+        if (Logan.sDebug) {
+            Log.d(TAG, "Logan flush start");
+        }
+        if (mLoganProtocol != null) {
+            mLoganProtocol.logan_flush();
+        }
+    }
+
+    private boolean isDay() {
+        long currentTime = System.currentTimeMillis();
+        return mCurrentDay < currentTime && mCurrentDay + LONG > currentTime;
+    }
+
+    private void deleteExpiredFile(long deleteTime) {
+        File dir = new File(mPath);
+        if (dir.isDirectory()) {
+            String[] files = dir.list();
+            if (files != null) {
+                for (String item : files) {
+                    try {
+                        if (TextUtils.isEmpty(item)) {
+                            continue;
+                        }
+                        String[] longStrArray = item.split("\\.");
+                        if (longStrArray.length > 0) {  //小于时间就删除
+                            long longItem = Long.valueOf(longStrArray[0]);
+                            if (longItem <= deleteTime && longStrArray.length == 1) {
+                                new File(mPath, item).delete(); //删除文件
+                            }
+                        }
+                    } catch (Exception e) {
+                        e.printStackTrace();
+                    }
+                }
+            }
+        }
+    }
+
+    private void doWriteLog2File(WriteAction action) {
+        if (Logan.sDebug) {
+            Log.d(TAG, "Logan write start");
+        }
+        if (mFileDirectory == null) {
+            mFileDirectory = new File(mPath);
+        }
+
+        if (!isDay()) {
+            long tempCurrentDay = Util.getCurrentTime();
+            //save时间
+            long deleteTime = tempCurrentDay - mSaveTime;
+            deleteExpiredFile(deleteTime);
+            mCurrentDay = tempCurrentDay;
+            mLoganProtocol.logan_open(String.valueOf(mCurrentDay));
+        }
+
+        long currentTime = System.currentTimeMillis(); //每隔1分钟判断一次
+        if (currentTime - mLastTime > MINUTE) {
+            mIsSDCard = isCanWriteSDCard();
+        }
+        mLastTime = System.currentTimeMillis();
+
+        if (!mIsSDCard) { //如果大于50M 不让再次写入
+            return;
+        }
+        mLoganProtocol.logan_write(action.flag, action.log, action.localTime, action.threadName,
+                action.threadId, action.isMainThread);
+    }
+
+    private void doSendLog2Net(SendAction action) {
+        if (Logan.sDebug) {
+            Log.d(TAG, "Logan send start");
+        }
+        if (TextUtils.isEmpty(mPath) || action == null || !action.isValid()) {
+            return;
+        }
+        boolean success = prepareLogFile(action);
+        if (!success) {
+            if (Logan.sDebug) {
+                Log.d(TAG, "Logan prepare log file failed, can't find log file");
+            }
+            return;
+        }
+        action.sendLogRunnable.setSendAction(action);
+        action.sendLogRunnable.setCallBackListener(
+                new SendLogRunnable.OnSendLogCallBackListener() {
+                    @Override
+                    public void onCallBack(int statusCode) {
+                        synchronized (sendSync) {
+                            mSendLogStatusCode = statusCode;
+                            if (statusCode == SendLogRunnable.FINISH) {
+                                mCacheLogQueue.addAll(mCacheSendQueue);
+                                mCacheSendQueue.clear();
+                                notifyRun();
+                            }
+                        }
+                    }
+                });
+        mSendLogStatusCode = SendLogRunnable.SENDING;
+        if (mSingleThreadExecutor == null) {
+            mSingleThreadExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
+                @Override
+                public Thread newThread(Runnable r) {
+                    // Just rename Thread
+                    Thread t = new Thread(Thread.currentThread().getThreadGroup(), r,
+                            "logan-thread-send-log", 0);
+                    if (t.isDaemon()) {
+                        t.setDaemon(false);
+                    }
+                    if (t.getPriority() != Thread.NORM_PRIORITY) {
+                        t.setPriority(Thread.NORM_PRIORITY);
+                    }
+                    return t;
+                }
+            });
+        }
+        mSingleThreadExecutor.execute(action.sendLogRunnable);
+    }
+
+    /**
+     * 发送日志前的预处理操作
+     */
+    private boolean prepareLogFile(SendAction action) {
+        if (Logan.sDebug) {
+            Log.d(TAG, "prepare log file");
+        }
+        if (isFile(action.date)) { //是否有日期文件
+            String src = mPath + File.separator + action.date;
+            if (action.date.equals(String.valueOf(Util.getCurrentTime()))) {
+                doFlushLog2File();
+                String des = mPath + File.separator + action.date + ".copy";
+                if (copyFile(src, des)) {
+                    action.uploadPath = des;
+                    return true;
+                }
+            } else {
+                action.uploadPath = src;
+                return true;
+            }
+        } else {
+            action.uploadPath = "";
+        }
+        return false;
+    }
+
+    private boolean copyFile(String src, String des) {
+        boolean back = false;
+        FileInputStream inputStream = null;
+        FileOutputStream outputStream = null;
+        try {
+            inputStream = new FileInputStream(new File(src));
+            outputStream = new FileOutputStream(new File(des));
+            byte[] buffer = new byte[CACHE_SIZE];
+            int i;
+            while ((i = inputStream.read(buffer)) >= 0) {
+                outputStream.write(buffer, 0, i);
+                outputStream.flush();
+            }
+            back = true;
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                if (inputStream != null) {
+                    inputStream.close();
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+            try {
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return back;
+    }
+
+    private boolean isCanWriteSDCard() {
+        boolean item = false;
+        try {
+            StatFs stat = new StatFs(mPath);
+            long blockSize = stat.getBlockSize();
+            long availableBlocks = stat.getAvailableBlocks();
+            long total = availableBlocks * blockSize;
+            if (total > mMinSDCard) { //判断SDK卡
+                item = true;
+            }
+        } catch (IllegalArgumentException e) {
+            e.printStackTrace();
+        }
+        return item;
+    }
+
+    private boolean isFile(String name) {
+        boolean isExist = false;
+        if (TextUtils.isEmpty(mPath)) {
+            return false;
+        }
+        File file = new File(mPath + File.separator + name);
+        if (file.exists() && file.isFile()) {
+            isExist = true;
+        }
+        return isExist;
+    }
+}

+ 28 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/OnLoganProtocolStatus.java

@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+public interface OnLoganProtocolStatus {
+    void loganProtocolStatus(String cmd, int code);
+}
+

+ 44 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/SendAction.java

@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+class SendAction {
+
+    long fileSize; //文件大小
+
+    String date; //日期
+
+    String uploadPath;
+
+    SendLogRunnable sendLogRunnable;
+
+    boolean isValid() {
+        boolean valid = false;
+        if (sendLogRunnable != null) {
+            valid = true;
+        } else if (fileSize > 0) {
+            valid = true;
+        }
+        return valid;
+    }
+}

+ 14 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/SendLogCallback.java

@@ -0,0 +1,14 @@
+package com.dianping.logan;
+
+/**
+ * Create by luoheng on 2019-11-20.
+ */
+public interface SendLogCallback {
+    /**
+     * 日志上传结果回调方法.
+     *
+     * @param statusCode 对应http状态码.
+     * @param data       http返回的data.
+     */
+    void onLogSendCompleted(int statusCode, byte[] data);
+}

+ 201 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/SendLogDefaultRunnable.java

@@ -0,0 +1,201 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+import android.util.Log;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.ProtocolException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLSession;
+
+public class SendLogDefaultRunnable extends SendLogRunnable {
+
+    private static final String TAG = "SendLogDefaultRunnable";
+
+    private final Map<String, String> mRequestHeaders;
+    private String mUploadLogUrl;
+    private SendLogCallback mSendLogCallback;
+
+    public SendLogDefaultRunnable() {
+        mRequestHeaders = new HashMap<>();
+    }
+
+    @Override
+    public void sendLog(File logFile) {
+        doSendFileByAction(logFile, mRequestHeaders, mUploadLogUrl);
+        // Must Call finish after send log
+        finish();
+        if (logFile.getName().contains(".copy")) {
+            logFile.delete();
+        }
+    }
+
+    /**
+     * set upload log url.
+     *
+     * @param uploadLogUrl
+     */
+    public void setUrl(String uploadLogUrl) {
+        mUploadLogUrl = uploadLogUrl;
+    }
+
+    /**
+     * set request header.
+     *
+     * @param headers
+     */
+    public void setRequestHeader(Map<String, String> headers) {
+        mRequestHeaders.clear();
+        if (headers != null) {
+            mRequestHeaders.putAll(headers);
+        }
+    }
+
+    /**
+     * SendLogCallback.
+     *
+     * @param sendLogCallback
+     */
+    public void setSendLogCallback(SendLogCallback sendLogCallback) {
+        mSendLogCallback = sendLogCallback;
+    }
+
+    /**
+     * 主动上报
+     */
+    private void doSendFileByAction(File logFile, Map<String, String> headers, String url) {
+        try {
+            FileInputStream fileStream = new FileInputStream(logFile);
+            doPostRequest(url, fileStream, headers);
+        } catch (FileNotFoundException e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void doPostRequest(String url, InputStream inputData, Map<String, String> headerMap) {
+        byte[] data = null;
+        OutputStream outputStream = null;
+        InputStream inputStream = null;
+        HttpURLConnection c = null;
+        ByteArrayOutputStream back;
+        byte[] buffer = new byte[2048];
+        int statusCode = -1;
+        try {
+            URL u = new URL(url);
+            c = (HttpURLConnection) u.openConnection();
+            if (c instanceof HttpsURLConnection) {
+                ((HttpsURLConnection) c).setHostnameVerifier(new HostnameVerifier() {
+                    @Override
+                    public boolean verify(String hostname, SSLSession session) {
+                        return true;
+                    }
+                });
+            }
+            Set<Map.Entry<String, String>> entrySet = headerMap.entrySet();
+            for (Map.Entry<String, String> tempEntry : entrySet) {
+                c.addRequestProperty(tempEntry.getKey(), tempEntry.getValue());
+            }
+            c.setReadTimeout(15000);
+            c.setConnectTimeout(15000);
+            c.setDoInput(true);
+            c.setDoOutput(true);
+            c.setRequestMethod("POST");
+            outputStream = c.getOutputStream();
+            int i;
+            final ByteArrayOutputStream out = new ByteArrayOutputStream(2048);
+            byte[] tmp = null;
+            try {
+                while ((i = inputData.read(buffer)) != -1) {
+                    out.write(buffer, 0, i);
+                }
+                tmp = out.toByteArray();
+            } finally {
+                out.close();
+            }
+            outputStream.write(tmp);
+            outputStream.flush();
+            statusCode = c.getResponseCode();
+            if (statusCode / 100 == 2) {
+                back = new ByteArrayOutputStream();
+                inputStream = c.getInputStream();
+                while ((i = inputStream.read(buffer)) != -1) {
+                    back.write(buffer, 0, i);
+                }
+                data = back.toByteArray();
+            }
+        } catch (ProtocolException e) {
+            e.printStackTrace();
+        } catch (MalformedURLException e) {
+            e.printStackTrace();
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch (Exception e) {
+            e.printStackTrace();
+        } finally {
+            if (outputStream != null) {
+                try {
+                    outputStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (inputStream != null) {
+                try {
+                    inputStream.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (inputData != null) {
+                try {
+                    inputData.close();
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+            }
+            if (c != null) {
+                c.disconnect();
+            }
+        }
+        Log.d(TAG, "log send completed, http statusCode : " + statusCode);
+        if (mSendLogCallback != null) {
+            mSendLogCallback.onLogSendCompleted(statusCode, data);
+        }
+    }
+
+}

+ 78 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/SendLogRunnable.java

@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+import android.text.TextUtils;
+
+import java.io.File;
+
+public abstract class SendLogRunnable implements Runnable {
+    public static final int SENDING = 10001;
+    public static final int FINISH = 10002;
+
+    private SendAction mSendAction;
+    private OnSendLogCallBackListener mCallBackListener;
+
+    /**
+     * 真正发送上传日志文件的方法,留给外部实现
+     *
+     * @param logFile 日志文件
+     */
+    public abstract void sendLog(File logFile);
+
+    void setSendAction(SendAction action) {
+        mSendAction = action;
+    }
+
+    @Override
+    public void run() {
+        if (mSendAction == null || TextUtils.isEmpty(mSendAction.date)) {
+            finish();
+            return;
+        }
+
+        if (TextUtils.isEmpty(mSendAction.uploadPath)) {
+            finish();
+            return;
+        }
+        File file = new File(mSendAction.uploadPath);
+        sendLog(file);
+    }
+
+    void setCallBackListener(OnSendLogCallBackListener callBackListener) {
+        mCallBackListener = callBackListener;
+    }
+
+    /**
+     * Must call this method after send log finish!
+     */
+    protected void finish() {
+        if (mCallBackListener != null) {
+            mCallBackListener.onCallBack(FINISH);
+        }
+    }
+
+    interface OnSendLogCallBackListener {
+        void onCallBack(int statusCode);
+    }
+}

+ 47 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/Util.java

@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+public class Util {
+
+    private static final SimpleDateFormat sDateFormat = new SimpleDateFormat("yyyy-MM-dd");
+
+    public static long getCurrentTime() {
+        long currentTime = System.currentTimeMillis();
+        long tempTime = 0;
+        try {
+            String dataStr = sDateFormat.format(new Date(currentTime));
+            tempTime = sDateFormat.parse(dataStr).getTime();
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+        return tempTime;
+    }
+
+    public static String getDateStr(long time) {
+        return sDateFormat.format(new Date(time));
+    }
+}

+ 48 - 0
Example/Logan-Android/logan/src/main/java/com/dianping/logan/WriteAction.java

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package com.dianping.logan;
+
+import android.text.TextUtils;
+
+class WriteAction {
+
+    String log; //日志
+
+    boolean isMainThread;
+
+    long threadId;
+
+    String threadName = "";
+
+    long localTime;
+
+    int flag;
+
+    boolean isValid() {
+        boolean valid = false;
+        if (!TextUtils.isEmpty(log)) {
+            valid = true;
+        }
+        return valid;
+    }
+}

+ 84 - 0
Example/Logan-Android/logan/src/main/jni/clogan_protocol.c

@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "clogan_protocol.h"
+
+JNIEXPORT jint JNICALL
+Java_com_dianping_logan_CLoganProtocol_clogan_1write(JNIEnv *env, jobject instance, jint flag,
+                                                     jstring log_, jlong local_time,
+                                                     jstring thread_name_, jlong thread_id,
+                                                     jint is_main) {
+    const char *log = (*env)->GetStringUTFChars(env, log_, 0);
+    const char *thread_name = (*env)->GetStringUTFChars(env, thread_name_, 0);
+
+    jint code = (jint) clogan_write(flag, log, local_time, thread_name, thread_id, is_main);
+
+    (*env)->ReleaseStringUTFChars(env, log_, log);
+    (*env)->ReleaseStringUTFChars(env, thread_name_, thread_name);
+    return code;
+
+}
+
+JNIEXPORT jint JNICALL
+Java_com_dianping_logan_CLoganProtocol_clogan_1init(JNIEnv *env, jobject instance,
+                                                    jstring cache_path_,
+                                                    jstring dir_path_, jint max_file,
+                                                    jstring encrypt_key16_, jstring encrypt_iv16_) {
+    const char *dir_path = (*env)->GetStringUTFChars(env, dir_path_, 0);
+    const char *cache_path = (*env)->GetStringUTFChars(env, cache_path_, 0);
+    const char *encrypt_key16 = (*env)->GetStringUTFChars(env, encrypt_key16_, 0);
+    const char *encrypt_iv16 = (*env)->GetStringUTFChars(env, encrypt_iv16_, 0);
+
+    jint code = (jint) clogan_init(cache_path, dir_path, max_file, encrypt_key16, encrypt_iv16);
+
+    (*env)->ReleaseStringUTFChars(env, dir_path_, dir_path);
+    (*env)->ReleaseStringUTFChars(env, cache_path_, cache_path);
+    (*env)->ReleaseStringUTFChars(env, encrypt_key16_, encrypt_key16);
+    (*env)->ReleaseStringUTFChars(env, encrypt_iv16_, encrypt_iv16);
+    return code;
+}
+
+JNIEXPORT jint JNICALL
+Java_com_dianping_logan_CLoganProtocol_clogan_1open(JNIEnv *env, jobject instance,
+                                                    jstring file_name_) {
+    const char *file_name = (*env)->GetStringUTFChars(env, file_name_, 0);
+
+    jint code = (jint) clogan_open(file_name);
+
+    (*env)->ReleaseStringUTFChars(env, file_name_, file_name);
+    return code;
+}
+
+JNIEXPORT void JNICALL
+Java_com_dianping_logan_CLoganProtocol_clogan_1flush(JNIEnv *env, jobject instance) {
+    clogan_flush();
+}
+
+JNIEXPORT void JNICALL
+Java_com_dianping_logan_CLoganProtocol_clogan_1debug(JNIEnv *env, jobject instance,
+                                                     jboolean is_debug) {
+    int i = 1;
+    if (!is_debug) {
+        i = 0;
+    }
+    clogan_debug(i);
+}

+ 76 - 0
Example/Logan-Android/logan/src/main/jni/clogan_protocol.h

@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#ifndef ANDROID_NOVA_LOGAN_CLOGAN_PROTOCOL_H_H
+#define ANDROID_NOVA_LOGAN_CLOGAN_PROTOCOL_H_H
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include <jni.h>
+#include <clogan_core.h>
+
+/**
+ * JNI write interface
+ */
+JNIEXPORT jint JNICALL
+Java_com_dianping_logan_CLoganProtocol_clogan_1write(JNIEnv *env, jobject instance,
+                                                          jint flag, jstring log_,
+                                                          jlong local_time, jstring thread_name_,
+                                                          jlong thread_id, jint ismain);
+/**
+ * JNI init interface
+ */
+JNIEXPORT jint JNICALL
+Java_com_dianping_logan_CLoganProtocol_clogan_1init(JNIEnv *env, jobject instance,
+                                                         jstring cache_path_,
+                                                         jstring dir_path_, jint max_file,
+                                                         jstring encrypt_key16_,
+                                                         jstring encrypt_iv16_);
+
+/**
+ * JNI open interface
+ */
+JNIEXPORT jint JNICALL
+Java_com_dianping_logan_CLoganProtocol_clogan_1open(JNIEnv *env, jobject instance,
+                                                         jstring file_name_);
+
+/**
+ * JNI flush interface
+ */
+JNIEXPORT void JNICALL
+Java_com_dianping_logan_CLoganProtocol_clogan_1flush(JNIEnv *env, jobject instance);
+
+/**
+ * JNI debug interface
+ */
+JNIEXPORT void JNICALL
+Java_com_dianping_logan_CLoganProtocol_clogan_1debug(JNIEnv *env, jobject instance,
+                                                          jboolean is_debug);
+
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //ANDROID_NOVA_LOGAN_CLOGAN_PROTOCOL_H_H

+ 3 - 0
Example/Logan-Android/logan/src/main/res/values/strings.xml

@@ -0,0 +1,3 @@
+<resources>
+    <string name="app_name">logan</string>
+</resources>

+ 17 - 0
Example/Logan-Android/logan/src/test/java/com/dianping/logan/ExampleUnitTest.java

@@ -0,0 +1,17 @@
+package com.dianping.logan;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+    @Test
+    public void addition_isCorrect() throws Exception {
+        assertEquals(4, 2 + 2);
+    }
+}

+ 1 - 0
Example/Logan-Android/settings.gradle

@@ -0,0 +1 @@
+include ':app', ':logan'

+ 61 - 0
Example/Logan-NodeServer/.gitignore

@@ -0,0 +1,61 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Runtime data
+pids
+*.pid
+*.seed
+*.pid.lock
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# nyc test coverage
+.nyc_output
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# Bower dependency directory (https://bower.io/)
+bower_components
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (https://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directories
+node_modules/
+jspm_packages/
+
+# Typescript v1 declaration files
+typings/
+
+# Optional npm cache directory
+.npm
+
+# Optional eslint cache
+.eslintcache
+
+# Optional REPL history
+.node_repl_history
+
+# Output of 'npm pack'
+*.tgz
+
+# Yarn Integrity file
+.yarn-integrity
+
+# dotenv environment variables file
+.env
+
+# next.js build output
+.next

+ 42 - 0
Example/Logan-NodeServer/README.md

@@ -0,0 +1,42 @@
+### Logan-Web nodejs解码实践
+
+
+
+##### 部署步骤
+
+1. `yarn install`
+
+2. `yarn run start`
+
+
+
+##### 测试
+
+```javascript
+import Logan from "logan-web";
+
+(async () => {
+    Logan.initConfig({
+        reportUrl: 'https://yourServerAddressToAcceptLogs',
+        publicKey: '-----BEGIN PUBLIC KEY-----\n' +
+            'MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgG2m5VVtZ4mHml3FB9foDRpDW7Pw\n' +
+            'Foa+1eYN777rNmIdnmezQqHWIRVcnTRVjrgGt2ndP2cYT7MgmWpvr8IjgN0PZ6ng\n' +
+            'MmKYGpapMqkxsnS/6Q8UZO4PQNlnsK2hSPoIDeJcHxDvo6Nelg+mRHEpD6K+1FIq\n' +
+            'zvdwVPCcgK7UbZElAgMBAAE=\n' +
+            '-----END PUBLIC KEY-----',
+        errorHandler(e: any) { console.error(e); }
+    });
+    const reportResult = await Logan.report({
+        reportUrl: 'http://localhost:9002',
+        deviceId: 'LocalDeviceIdOrUnionId',
+        fromDayString: '2020-05-01',
+        toDayString: '2020-05-10',
+        webSource: '',
+        environment: '',
+        customInfo: ''
+    });
+
+    console.log(reportResult);
+})();
+
+```

+ 97 - 0
Example/Logan-NodeServer/app.ts

@@ -0,0 +1,97 @@
+import express from "express";
+import logger from "morgan";
+import { AES, enc, mode, pad } from "crypto-js";
+const { JSEncrypt } = require("js-encrypt");
+
+interface IReportLog {
+    client: string;
+    webSource?: string;
+    deviceId: string;
+    environment?: string;
+    customInfo: string;
+    logPageNo: number;
+    fileDate: string;
+    logArray: string;
+}
+
+interface ILogItem {
+    l: string;
+    iv?: string;
+    k?: string;
+    v?: number;
+}
+
+interface ILogContent {
+    /**log type */
+    t: number;
+
+    /**log content */
+    c: string;
+
+    /**js timestamp */
+    d: string;
+}
+
+const port = '9002';
+
+const privateKey = '-----BEGIN PRIVATE KEY-----\n' +
+    'MIICXAIBAAKBgG2m5VVtZ4mHml3FB9foDRpDW7PwFoa+1eYN777rNmIdnmezQqHW\n' +
+    'IRVcnTRVjrgGt2ndP2cYT7MgmWpvr8IjgN0PZ6ngMmKYGpapMqkxsnS/6Q8UZO4P\n' +
+    'QNlnsK2hSPoIDeJcHxDvo6Nelg+mRHEpD6K+1FIqzvdwVPCcgK7UbZElAgMBAAEC\n' +
+    'gYAXQM9dgGf2iGU6AXCaXsF4klQ+ImoEhS/DK61t5V+RCwrunttAirJVX2CPGp27\n' +
+    'dOEseBjb+hHcwMsIAUtadkD7VqDoLg0C63pP6Yr91zoLSq7ru7FL4j8ZDGgHV2tE\n' +
+    '6TbtIRGbxuuF+EmztKqrMCvN4qcxqDvTtU6Xq9Us7xC+uQJBANoFtsuTqDaFFOJ0\n' +
+    'p0S3+w4lzUcfp+XboVb4+q7wcFumfDCLIuvOTEiCFj5Tj0o2eHtEo3ARHWIcNZqB\n' +
+    'OgYPPdMCQQCAwJzubpjr7oXxINLERcQ1PXvjD5HD9Q4A20p6pFkcEYTlDYW/nm60\n' +
+    'PMr7JWG54TH0e6w8IfJZVR2xonVasoInAkEAjdIfuUdgqa5iCnkFgb8IEYjngneG\n' +
+    'GRCIX/Hv57JB9GxU5qLrYWa92oC8hWiHkifisZTRmAmaCoL9H3cmTmDFvwJAJjwM\n' +
+    '3mmNlBLDR/YdYRfuyni1v5oyCWVOgUad+YmwxLsXIgY//8WGzpN3G9ngCZksgpPv\n' +
+    'c/QIyiqSpNu/ye1U5QJBAIgSfWXvx+varXagGojcCH8mVtT/E4/w3R+QTLAp6s0L\n' +
+    'QTQUDPnDGrxvT4sDoU6ib+nn0FAr/kTyJptdlvaXfeo=' +
+    '-----END PRIVATE KEY-----';
+
+const app = express();
+
+app.use(logger('dev'));
+app.use(express.json());
+app.use(express.urlencoded({ extended: false }));
+
+app.use('/', (req, res, next) => {
+    const reportLog: IReportLog = req.body;
+    const logArray: Array<ILogItem> = reportLog.logArray.split(',').map(it => JSON.parse(decodeURIComponent(it))) as Array<ILogItem>;
+
+    let logContent: Array<ILogContent> = logArray.map(it => {
+        let logContent: ILogContent, content: string;
+
+        if (it.iv && it.k && it.v) {
+
+            /**
+             * 如果加密过
+             * 则先使用RSA解密出AES加密的key
+             * 再使用得到的key使用AES解密得到内容
+             */
+
+            /**
+             * if log with encryption
+             * first, use RSA decrypt to get the AES encryption key
+             * then, use the key do AES decrypt can get our log content
+             */
+
+            const en = new JSEncrypt();
+            en.setPrivateKey(privateKey);
+            const key = en.decrypt(it.k).toString(enc.Utf8);
+
+            content = AES.decrypt(it.l, enc.Utf8.parse(key), { iv: enc.Utf8.parse(it.iv), mode: mode.CTR, padding: pad.NoPadding }).toString(enc.Utf8);
+        } else {
+            content = enc.Base64.parse(it.l).toString(enc.Utf8);
+        }
+
+        logContent = JSON.parse(content);
+        logContent.c = decodeURIComponent(logContent.c);
+        return logContent;
+    });
+
+    res.status(200).json(logContent);
+});
+
+app.listen(port, () => { console.log(`server run at ${port}`); });

+ 24 - 0
Example/Logan-NodeServer/package.json

@@ -0,0 +1,24 @@
+{
+  "name": "logan-node-example",
+  "version": "1.0.0",
+  "main": "app.ts",
+  "author": "westinchen",
+  "license": "MIT",
+  "scripts": {
+    "start": "./node_modules/.bin/ts-node ./app.ts"
+  },
+  "dependencies": {
+    "crypto-js": "^4.0.0",
+    "express": "~4.16.1",
+    "js-encrypt": "^2.3.4",
+    "morgan": "^1.10.0"
+  },
+  "devDependencies": {
+    "@types/crypto-js": "^3.1.45",
+    "@types/express": "^4.17.6",
+    "@types/morgan": "^1.9.0",
+    "@types/node": "^13.13.5",
+    "ts-node": "^8.10.1",
+    "typescript": "^3.8.3"
+  }
+}

+ 13 - 0
Example/Logan-NodeServer/tsconfig.json

@@ -0,0 +1,13 @@
+{
+  "compilerOptions": {
+    "target": "ESNext",
+    "module": "commonjs",
+    "allowJs": false,
+    "strict": true,
+    "noImplicitAny": true,
+    "moduleResolution": "node",
+    "allowSyntheticDefaultImports": true,
+    "esModuleInterop": true,
+    "forceConsistentCasingInFileNames": true
+  }
+}

+ 510 - 0
Example/Logan-NodeServer/yarn.lock

@@ -0,0 +1,510 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@types/body-parser@*":
+  version "1.19.0"
+  resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.0.tgz#0685b3c47eb3006ffed117cdd55164b61f80538f"
+  integrity sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==
+  dependencies:
+    "@types/connect" "*"
+    "@types/node" "*"
+
+"@types/connect@*":
+  version "3.4.33"
+  resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.33.tgz#31610c901eca573b8713c3330abc6e6b9f588546"
+  integrity sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==
+  dependencies:
+    "@types/node" "*"
+
+"@types/crypto-js@^3.1.45":
+  version "3.1.45"
+  resolved "https://registry.yarnpkg.com/@types/crypto-js/-/crypto-js-3.1.45.tgz#d2ccd23ae926ff05e3a99b11945e0435185cc1f1"
+  integrity sha512-/4yiILxQnVfpAtrVctuEl0qwqAToqayoPEvutyWhHPslr2D9sx85KRmIq7etC0ZYKCK8ppucoLzJukjJdAtfdA==
+
+"@types/express-serve-static-core@*":
+  version "4.17.6"
+  resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.6.tgz#ec825455acb075e7fc804f4f7b7734e043003f43"
+  integrity sha512-U2oynuRIB17GIbEdvjFrrEACOy7GQkzsX7bPEBz1H41vZYEU4j0fLL97sawmHDwHUXpUQDBMHIyM9vejqP9o1A==
+  dependencies:
+    "@types/node" "*"
+    "@types/qs" "*"
+    "@types/range-parser" "*"
+
+"@types/express@*", "@types/express@^4.17.6":
+  version "4.17.6"
+  resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.6.tgz#6bce49e49570507b86ea1b07b806f04697fac45e"
+  integrity sha512-n/mr9tZI83kd4azlPG5y997C/M4DNABK9yErhFM6hKdym4kkmd9j0vtsJyjFIwfRBxtrxZtAfGZCNRIBMFLK5w==
+  dependencies:
+    "@types/body-parser" "*"
+    "@types/express-serve-static-core" "*"
+    "@types/qs" "*"
+    "@types/serve-static" "*"
+
+"@types/mime@*":
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.1.tgz#dc488842312a7f075149312905b5e3c0b054c79d"
+  integrity sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==
+
+"@types/morgan@^1.9.0":
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/@types/morgan/-/morgan-1.9.0.tgz#342119ae57fe67d36b91537143fc5aef16c2479f"
+  integrity sha512-warrzirh5dlTMaETytBTKR886pRXwr+SMZD87ZE13gLMR8Pzz69SiYFkvoDaii78qGP1iyBIUYz5GiXyryO//A==
+  dependencies:
+    "@types/express" "*"
+
+"@types/node@*", "@types/node@^13.13.5":
+  version "13.13.5"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.5.tgz#96ec3b0afafd64a4ccea9107b75bf8489f0e5765"
+  integrity sha512-3ySmiBYJPqgjiHA7oEaIo2Rzz0HrOZ7yrNO5HWyaE5q0lQ3BppDZ3N53Miz8bw2I7gh1/zir2MGVZBvpb1zq9g==
+
+"@types/qs@*":
+  version "6.9.2"
+  resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.2.tgz#faab98ec4f96ee72c829b7ec0983af4f4d343113"
+  integrity sha512-a9bDi4Z3zCZf4Lv1X/vwnvbbDYSNz59h3i3KdyuYYN+YrLjSeJD0dnphdULDfySvUv6Exy/O0K6wX/kQpnPQ+A==
+
+"@types/range-parser@*":
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.3.tgz#7ee330ba7caafb98090bece86a5ee44115904c2c"
+  integrity sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==
+
+"@types/serve-static@*":
+  version "1.13.3"
+  resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.13.3.tgz#eb7e1c41c4468272557e897e9171ded5e2ded9d1"
+  integrity sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==
+  dependencies:
+    "@types/express-serve-static-core" "*"
+    "@types/mime" "*"
+
+accepts@~1.3.5:
+  version "1.3.7"
+  resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
+  integrity sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==
+  dependencies:
+    mime-types "~2.1.24"
+    negotiator "0.6.2"
+
+arg@^4.1.0:
+  version "4.1.3"
+  resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089"
+  integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==
+
+array-flatten@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
+  integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=
+
+basic-auth@~2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/basic-auth/-/basic-auth-2.0.1.tgz#b998279bf47ce38344b4f3cf916d4679bbf51e3a"
+  integrity sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==
+  dependencies:
+    safe-buffer "5.1.2"
+
+body-parser@1.18.3:
+  version "1.18.3"
+  resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4"
+  integrity sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=
+  dependencies:
+    bytes "3.0.0"
+    content-type "~1.0.4"
+    debug "2.6.9"
+    depd "~1.1.2"
+    http-errors "~1.6.3"
+    iconv-lite "0.4.23"
+    on-finished "~2.3.0"
+    qs "6.5.2"
+    raw-body "2.3.3"
+    type-is "~1.6.16"
+
+buffer-from@^1.0.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
+  integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
+
+bytes@3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048"
+  integrity sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=
+
+content-disposition@0.5.2:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
+  integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ=
+
+content-type@~1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
+  integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
+
+cookie-signature@1.0.6:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
+  integrity sha1-4wOogrNCzD7oylE6eZmXNNqzriw=
+
+cookie@0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb"
+  integrity sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=
+
+crypto-js@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.0.0.tgz#2904ab2677a9d042856a2ea2ef80de92e4a36dcc"
+  integrity sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==
+
+debug@2.6.9:
+  version "2.6.9"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+  integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
+  dependencies:
+    ms "2.0.0"
+
+depd@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
+  integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
+
+depd@~2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df"
+  integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==
+
+destroy@~1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
+  integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
+
+diff@^4.0.1:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
+  integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
+
+ee-first@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
+  integrity sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=
+
+encodeurl@~1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59"
+  integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
+
+escape-html@~1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
+  integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
+
+etag@~1.8.1:
+  version "1.8.1"
+  resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887"
+  integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=
+
+express@~4.16.1:
+  version "4.16.4"
+  resolved "https://registry.yarnpkg.com/express/-/express-4.16.4.tgz#fddef61926109e24c515ea97fd2f1bdbf62df12e"
+  integrity sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==
+  dependencies:
+    accepts "~1.3.5"
+    array-flatten "1.1.1"
+    body-parser "1.18.3"
+    content-disposition "0.5.2"
+    content-type "~1.0.4"
+    cookie "0.3.1"
+    cookie-signature "1.0.6"
+    debug "2.6.9"
+    depd "~1.1.2"
+    encodeurl "~1.0.2"
+    escape-html "~1.0.3"
+    etag "~1.8.1"
+    finalhandler "1.1.1"
+    fresh "0.5.2"
+    merge-descriptors "1.0.1"
+    methods "~1.1.2"
+    on-finished "~2.3.0"
+    parseurl "~1.3.2"
+    path-to-regexp "0.1.7"
+    proxy-addr "~2.0.4"
+    qs "6.5.2"
+    range-parser "~1.2.0"
+    safe-buffer "5.1.2"
+    send "0.16.2"
+    serve-static "1.13.2"
+    setprototypeof "1.1.0"
+    statuses "~1.4.0"
+    type-is "~1.6.16"
+    utils-merge "1.0.1"
+    vary "~1.1.2"
+
+finalhandler@1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105"
+  integrity sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==
+  dependencies:
+    debug "2.6.9"
+    encodeurl "~1.0.2"
+    escape-html "~1.0.3"
+    on-finished "~2.3.0"
+    parseurl "~1.3.2"
+    statuses "~1.4.0"
+    unpipe "~1.0.0"
+
+forwarded@~0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
+  integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=
+
+fresh@0.5.2:
+  version "0.5.2"
+  resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
+  integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
+
+http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3:
+  version "1.6.3"
+  resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d"
+  integrity sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=
+  dependencies:
+    depd "~1.1.2"
+    inherits "2.0.3"
+    setprototypeof "1.1.0"
+    statuses ">= 1.4.0 < 2"
+
+iconv-lite@0.4.23:
+  version "0.4.23"
+  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63"
+  integrity sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==
+  dependencies:
+    safer-buffer ">= 2.1.2 < 3"
+
+inherits@2.0.3:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
+  integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
+
+ipaddr.js@1.9.1:
+  version "1.9.1"
+  resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
+  integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==
+
+js-encrypt@^2.3.4:
+  version "2.3.4"
+  resolved "https://registry.yarnpkg.com/js-encrypt/-/js-encrypt-2.3.4.tgz#97ac02cf81005676d3265dc40c4d670e8ff35113"
+  integrity sha1-l6wCz4EAVnbTJl3EDE1nDo/zURM=
+
+make-error@^1.1.1:
+  version "1.3.6"
+  resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
+  integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
+
+media-typer@0.3.0:
+  version "0.3.0"
+  resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
+  integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
+
+merge-descriptors@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
+  integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
+
+methods@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
+  integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
+
+mime-db@1.44.0:
+  version "1.44.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92"
+  integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==
+
+mime-types@~2.1.24:
+  version "2.1.27"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f"
+  integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==
+  dependencies:
+    mime-db "1.44.0"
+
+mime@1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6"
+  integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==
+
+morgan@^1.10.0:
+  version "1.10.0"
+  resolved "https://registry.yarnpkg.com/morgan/-/morgan-1.10.0.tgz#091778abc1fc47cd3509824653dae1faab6b17d7"
+  integrity sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==
+  dependencies:
+    basic-auth "~2.0.1"
+    debug "2.6.9"
+    depd "~2.0.0"
+    on-finished "~2.3.0"
+    on-headers "~1.0.2"
+
+ms@2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+  integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+
+negotiator@0.6.2:
+  version "0.6.2"
+  resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
+  integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==
+
+on-finished@~2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947"
+  integrity sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=
+  dependencies:
+    ee-first "1.1.1"
+
+on-headers@~1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
+  integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
+
+parseurl@~1.3.2:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
+  integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==
+
+path-to-regexp@0.1.7:
+  version "0.1.7"
+  resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
+  integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
+
+proxy-addr@~2.0.4:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.6.tgz#fdc2336505447d3f2f2c638ed272caf614bbb2bf"
+  integrity sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==
+  dependencies:
+    forwarded "~0.1.2"
+    ipaddr.js "1.9.1"
+
+qs@6.5.2:
+  version "6.5.2"
+  resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
+  integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==
+
+range-parser@~1.2.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
+  integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==
+
+raw-body@2.3.3:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3"
+  integrity sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==
+  dependencies:
+    bytes "3.0.0"
+    http-errors "1.6.3"
+    iconv-lite "0.4.23"
+    unpipe "1.0.0"
+
+safe-buffer@5.1.2:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
+  integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
+
+"safer-buffer@>= 2.1.2 < 3":
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+  integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+send@0.16.2:
+  version "0.16.2"
+  resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1"
+  integrity sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==
+  dependencies:
+    debug "2.6.9"
+    depd "~1.1.2"
+    destroy "~1.0.4"
+    encodeurl "~1.0.2"
+    escape-html "~1.0.3"
+    etag "~1.8.1"
+    fresh "0.5.2"
+    http-errors "~1.6.2"
+    mime "1.4.1"
+    ms "2.0.0"
+    on-finished "~2.3.0"
+    range-parser "~1.2.0"
+    statuses "~1.4.0"
+
+serve-static@1.13.2:
+  version "1.13.2"
+  resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1"
+  integrity sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==
+  dependencies:
+    encodeurl "~1.0.2"
+    escape-html "~1.0.3"
+    parseurl "~1.3.2"
+    send "0.16.2"
+
+setprototypeof@1.1.0:
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656"
+  integrity sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==
+
+source-map-support@^0.5.17:
+  version "0.5.19"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61"
+  integrity sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==
+  dependencies:
+    buffer-from "^1.0.0"
+    source-map "^0.6.0"
+
+source-map@^0.6.0:
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+  integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+"statuses@>= 1.4.0 < 2":
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
+  integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
+
+statuses@~1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087"
+  integrity sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==
+
+ts-node@^8.10.1:
+  version "8.10.1"
+  resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.1.tgz#77da0366ff8afbe733596361d2df9a60fc9c9bd3"
+  integrity sha512-bdNz1L4ekHiJul6SHtZWs1ujEKERJnHs4HxN7rjTyyVOFf3HaJ6sLqe6aPG62XTzAB/63pKRh5jTSWL0D7bsvw==
+  dependencies:
+    arg "^4.1.0"
+    diff "^4.0.1"
+    make-error "^1.1.1"
+    source-map-support "^0.5.17"
+    yn "3.1.1"
+
+type-is@~1.6.16:
+  version "1.6.18"
+  resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"
+  integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==
+  dependencies:
+    media-typer "0.3.0"
+    mime-types "~2.1.24"
+
+typescript@^3.8.3:
+  version "3.8.3"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061"
+  integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w==
+
+unpipe@1.0.0, unpipe@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec"
+  integrity sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=
+
+utils-merge@1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713"
+  integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=
+
+vary@~1.1.2:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
+  integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
+
+yn@3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"
+  integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==

+ 3 - 0
Example/Logan-WebSDK/.gitignore

@@ -0,0 +1,3 @@
+.DS_Store
+.vscode
+node_modules/

+ 1 - 0
Example/Logan-WebSDK/.npmrc

@@ -0,0 +1 @@
+registry="https://registry.npmjs.org/"

+ 2 - 0
Example/Logan-WebSDK/README.md

@@ -0,0 +1,2 @@
+# Logan Web SDK Example
+Show how to use [logan-web](https://www.npmjs.com/package/logan-web) with [webpack](https://webpack.js.org/).

File diff ditekan karena terlalu besar
+ 0 - 0
Example/Logan-WebSDK/demo/js/report_log.00379bd039ba0d23e095.js


File diff ditekan karena terlalu besar
+ 0 - 0
Example/Logan-WebSDK/demo/js/report_log.540408c7d6a5382cf118.js


File diff ditekan karena terlalu besar
+ 0 - 0
Example/Logan-WebSDK/demo/js/test.js


File diff ditekan karena terlalu besar
+ 0 - 0
Example/Logan-WebSDK/demo/js/vendors~encryption.b5564d7d68255d790409.js


File diff ditekan karena terlalu besar
+ 0 - 0
Example/Logan-WebSDK/demo/js/vendors~encryption.ed2aadee96f4380db40e.js


File diff ditekan karena terlalu besar
+ 0 - 0
Example/Logan-WebSDK/demo/js/vendors~report_log~save_log.1130f9f2be3cde3d0b2d.js


File diff ditekan karena terlalu besar
+ 0 - 0
Example/Logan-WebSDK/demo/js/vendors~report_log~save_log.4f48e20a6aa09ee73148.js


File diff ditekan karena terlalu besar
+ 0 - 0
Example/Logan-WebSDK/demo/js/vendors~save_log.81bc5a66cd212fb25cd1.js


File diff ditekan karena terlalu besar
+ 0 - 0
Example/Logan-WebSDK/demo/js/vendors~save_log.e6c9cfa9a3e7e0214368.js


+ 15 - 0
Example/Logan-WebSDK/demo/test.html

@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <meta http-equiv="X-UA-Compatible" content="ie=edge">
+    <title>Logan Web Demo</title>
+</head>
+<body>
+    <button id="log">Log</button>
+    <button id="logWithEncryption">LogWithEncryption</button>
+    <button id="report">Report</button>
+    <script type="text/javascript" src="js/test.js"></script>
+</body>
+</html>

+ 19 - 0
Example/Logan-WebSDK/package.json

@@ -0,0 +1,19 @@
+{
+  "name": "logan-web-demo",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "scripts": {
+    "demo": "rm -rf demo/js && webpack"
+  },
+  "author": "sylvia",
+  "license": "MIT",
+  "dependencies": {
+    "logan-web": "^1.0.3"
+  },
+  "devDependencies": {
+    "webpack": "^4.41.2",
+    "webpack-cli": "^3.3.10",
+    "serialize-javascript": "^2.1.1"
+  }
+}

+ 42 - 0
Example/Logan-WebSDK/src/test.js

@@ -0,0 +1,42 @@
+var Logan = require('logan-web');
+Logan.initConfig({
+    /* Demo Key */
+    publicKey:
+        '-----BEGIN PUBLIC KEY-----\n' +
+        'MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgG2m5VVtZ4mHml3FB9foDRpDW7Pw\n' +
+        'Foa+1eYN777rNmIdnmezQqHWIRVcnTRVjrgGt2ndP2cYT7MgmWpvr8IjgN0PZ6ng\n' +
+        'MmKYGpapMqkxsnS/6Q8UZO4PQNlnsK2hSPoIDeJcHxDvo6Nelg+mRHEpD6K+1FIq\n' +
+        'zvdwVPCcgK7UbZElAgMBAAE=\n' +
+        '-----END PUBLIC KEY-----'
+});
+function timeFormat2Day(date) {
+    var Y = date.getFullYear();
+    var M = date.getMonth() + 1;
+    var D = date.getDate();
+    return Y + '-' + (M < 10 ? '0' + M : M) + '-' + (D < 10 ? '0' + D : D);
+}
+document.getElementById('log').onclick = log;
+document.getElementById('logWithEncryption').onclick = logWithEncryption;
+document.getElementById('report').onclick = report;
+
+function log() {
+    Logan.log('Hello World!', 1);
+}
+
+function logWithEncryption() {
+    Logan.logWithEncryption('Hello World!', 2);
+}
+
+function report() {
+    var now = new Date();
+    var sevenDaysAgo = new Date(+now - 6 * 24 * 3600 * 1000);
+    Logan.report({
+        reportUrl: 'https://yourServerAddressToAcceptLogs',
+        deviceId: 'test-logan-web',
+        fromDayString: timeFormat2Day(sevenDaysAgo),
+        toDayString: timeFormat2Day(now),
+        webSource: 'browser',
+        environment: navigator.userAgent,
+        customInfo: JSON.stringify({ userId: 123456, biz: 'Live Better' })
+    });
+}

+ 24 - 0
Example/Logan-WebSDK/webpack.config.js

@@ -0,0 +1,24 @@
+var path = require('path');
+const DEMO_PATH = './demo';
+const STATIC_JS = 'js';
+const PUBLIC_PATH = './' + STATIC_JS + '/';
+module.exports = [
+    {
+        mode: 'production',
+        entry: {
+            'test': ['./src/test.js']
+        },
+        output: {
+            path: path.join(__dirname, DEMO_PATH, STATIC_JS),
+            publicPath: PUBLIC_PATH,
+            filename: '[name].js',
+            chunkFilename: '[name].[chunkhash].js',
+        },
+        resolve: {
+            extensions: ['.js']
+        },
+        module: {
+            rules: []
+        }
+    }
+];

+ 68 - 0
Example/Logan-iOS/.gitignore

@@ -0,0 +1,68 @@
+# Xcode
+#
+# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
+
+## User settings
+xcuserdata/
+
+## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
+*.xcscmblueprint
+*.xccheckout
+
+## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
+build/
+DerivedData/
+*.moved-aside
+*.pbxuser
+!default.pbxuser
+*.mode1v3
+!default.mode1v3
+*.mode2v3
+!default.mode2v3
+*.perspectivev3
+!default.perspectivev3
+
+## Obj-C/Swift specific
+*.hmap
+
+## App packaging
+*.ipa
+*.dSYM.zip
+*.dSYM
+
+# CocoaPods
+#
+# We recommend against adding the Pods directory to your .gitignore. However
+# you should judge for yourself, the pros and cons are mentioned at:
+# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
+#
+Pods/
+#
+# Add this line if you want to avoid checking in source code from the Xcode workspace
+*.xcworkspace
+
+# Carthage
+#
+# Add this line if you want to avoid checking in source code from Carthage dependencies.
+# Carthage/Checkouts
+
+Carthage/Build/
+
+# fastlane
+#
+# It is recommended to not store the screenshots in the git repo.
+# Instead, use fastlane to re-generate the screenshots whenever they are needed.
+# For more information about the recommended setup visit:
+# https://docs.fastlane.tools/best-practices/source-control/#source-control
+
+fastlane/report.xml
+fastlane/Preview.html
+fastlane/screenshots/**/*.png
+fastlane/test_output
+
+# Code Injection
+#
+# After new code Injection tools there's a generated folder /iOSInjectionProject
+# https://github.com/johnno1962/injectionforxcode
+
+iOSInjectionProject/

+ 608 - 0
Example/Logan-iOS/Logan-iOS.xcodeproj/project.pbxproj

@@ -0,0 +1,608 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		6003F58E195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
+		6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58F195388D20070C39A /* CoreGraphics.framework */; };
+		6003F592195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
+		6003F598195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F596195388D20070C39A /* InfoPlist.strings */; };
+		6003F59A195388D20070C39A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F599195388D20070C39A /* main.m */; };
+		6003F59E195388D20070C39A /* LGAppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F59D195388D20070C39A /* LGAppDelegate.m */; };
+		6003F5A7195388D20070C39A /* LGViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5A6195388D20070C39A /* LGViewController.m */; };
+		6003F5A9195388D20070C39A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5A8195388D20070C39A /* Images.xcassets */; };
+		6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F5AF195388D20070C39A /* XCTest.framework */; };
+		6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F58D195388D20070C39A /* Foundation.framework */; };
+		6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6003F591195388D20070C39A /* UIKit.framework */; };
+		6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6003F5B8195388D20070C39A /* InfoPlist.strings */; };
+		6003F5BC195388D20070C39A /* Tests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6003F5BB195388D20070C39A /* Tests.m */; };
+		71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */; };
+		873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 873B8AEA1B1F5CCA007FD442 /* Main.storyboard */; };
+		A706F9E521145B9D00F819F4 /* LoganTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A706F9E421145B9D00F819F4 /* LoganTests.m */; };
+		A74F422F20D0FEAB0008E4F8 /* Logan.podspec in Resources */ = {isa = PBXBuildFile; fileRef = A74F422E20D0FEAB0008E4F8 /* Logan.podspec */; };
+		D1C53C4214E25E27A0D5FB82 /* Pods_Logan_iOS_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DF957FBE944AABF7798BD54E /* Pods_Logan_iOS_Example.framework */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+		6003F5B3195388D20070C39A /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 6003F582195388D10070C39A /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 6003F589195388D20070C39A;
+			remoteInfo = "Logan-iOS";
+		};
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXFileReference section */
+		04FA3BEF6F33F169BA9754D0 /* Pods-Logan-iOS_Example.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Logan-iOS_Example.debug.xcconfig"; path = "Target Support Files/Pods-Logan-iOS_Example/Pods-Logan-iOS_Example.debug.xcconfig"; sourceTree = "<group>"; };
+		2243F3ED99B45EA641651CBD /* Pods-Logan-iOS_Example.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Logan-iOS_Example.release.xcconfig"; path = "Target Support Files/Pods-Logan-iOS_Example/Pods-Logan-iOS_Example.release.xcconfig"; sourceTree = "<group>"; };
+		6003F58A195388D20070C39A /* Logan-iOS_Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Logan-iOS_Example.app"; sourceTree = BUILT_PRODUCTS_DIR; };
+		6003F58D195388D20070C39A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; };
+		6003F58F195388D20070C39A /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; };
+		6003F591195388D20070C39A /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; };
+		6003F595195388D20070C39A /* Logan-iOS-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Logan-iOS-Info.plist"; sourceTree = "<group>"; };
+		6003F597195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+		6003F599195388D20070C39A /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+		6003F59B195388D20070C39A /* Logan-iOS-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Logan-iOS-Prefix.pch"; sourceTree = "<group>"; };
+		6003F59C195388D20070C39A /* LGAppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LGAppDelegate.h; sourceTree = "<group>"; };
+		6003F59D195388D20070C39A /* LGAppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LGAppDelegate.m; sourceTree = "<group>"; };
+		6003F5A5195388D20070C39A /* LGViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LGViewController.h; sourceTree = "<group>"; };
+		6003F5A6195388D20070C39A /* LGViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LGViewController.m; sourceTree = "<group>"; };
+		6003F5A8195388D20070C39A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
+		6003F5AE195388D20070C39A /* Logan-iOS_Tests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Logan-iOS_Tests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
+		6003F5AF195388D20070C39A /* XCTest.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCTest.framework; path = Library/Frameworks/XCTest.framework; sourceTree = DEVELOPER_DIR; };
+		6003F5B7195388D20070C39A /* Tests-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "Tests-Info.plist"; sourceTree = "<group>"; };
+		6003F5B9195388D20070C39A /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = "<group>"; };
+		6003F5BB195388D20070C39A /* Tests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = Tests.m; sourceTree = "<group>"; };
+		606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Tests-Prefix.pch"; sourceTree = "<group>"; };
+		71719F9E1E33DC2100824A3D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+		873B8AEA1B1F5CCA007FD442 /* Main.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = Main.storyboard; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+		A67F38E0FC0E4DD9FE651A11 /* Pods_Logan_iOS_Tests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Logan_iOS_Tests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+		A706F9E421145B9D00F819F4 /* LoganTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LoganTests.m; sourceTree = "<group>"; };
+		A74516AB20EF2EE200D2B849 /* libz.1.2.5.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.1.2.5.tbd; path = usr/lib/libz.1.2.5.tbd; sourceTree = SDKROOT; };
+		A74F422E20D0FEAB0008E4F8 /* Logan.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = Logan.podspec; path = ../../Logan.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
+		DF957FBE944AABF7798BD54E /* Pods_Logan_iOS_Example.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Logan_iOS_Example.framework; sourceTree = BUILT_PRODUCTS_DIR; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		6003F587195388D20070C39A /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				6003F590195388D20070C39A /* CoreGraphics.framework in Frameworks */,
+				6003F592195388D20070C39A /* UIKit.framework in Frameworks */,
+				6003F58E195388D20070C39A /* Foundation.framework in Frameworks */,
+				D1C53C4214E25E27A0D5FB82 /* Pods_Logan_iOS_Example.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		6003F5AB195388D20070C39A /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				6003F5B0195388D20070C39A /* XCTest.framework in Frameworks */,
+				6003F5B2195388D20070C39A /* UIKit.framework in Frameworks */,
+				6003F5B1195388D20070C39A /* Foundation.framework in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		6003F581195388D10070C39A = {
+			isa = PBXGroup;
+			children = (
+				A74F422E20D0FEAB0008E4F8 /* Logan.podspec */,
+				6003F593195388D20070C39A /* Example for Logan-iOS */,
+				6003F5B5195388D20070C39A /* Tests */,
+				6003F58C195388D20070C39A /* Frameworks */,
+				6003F58B195388D20070C39A /* Products */,
+				8C7684273B940DE7FF3048A2 /* Pods */,
+			);
+			sourceTree = "<group>";
+		};
+		6003F58B195388D20070C39A /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				6003F58A195388D20070C39A /* Logan-iOS_Example.app */,
+				6003F5AE195388D20070C39A /* Logan-iOS_Tests.xctest */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		6003F58C195388D20070C39A /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				A74516AB20EF2EE200D2B849 /* libz.1.2.5.tbd */,
+				6003F58D195388D20070C39A /* Foundation.framework */,
+				6003F58F195388D20070C39A /* CoreGraphics.framework */,
+				6003F591195388D20070C39A /* UIKit.framework */,
+				6003F5AF195388D20070C39A /* XCTest.framework */,
+				DF957FBE944AABF7798BD54E /* Pods_Logan_iOS_Example.framework */,
+				A67F38E0FC0E4DD9FE651A11 /* Pods_Logan_iOS_Tests.framework */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+		6003F593195388D20070C39A /* Example for Logan-iOS */ = {
+			isa = PBXGroup;
+			children = (
+				6003F59C195388D20070C39A /* LGAppDelegate.h */,
+				6003F59D195388D20070C39A /* LGAppDelegate.m */,
+				873B8AEA1B1F5CCA007FD442 /* Main.storyboard */,
+				6003F5A5195388D20070C39A /* LGViewController.h */,
+				6003F5A6195388D20070C39A /* LGViewController.m */,
+				71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */,
+				6003F5A8195388D20070C39A /* Images.xcassets */,
+				6003F594195388D20070C39A /* Supporting Files */,
+			);
+			name = "Example for Logan-iOS";
+			path = "Logan-iOS";
+			sourceTree = "<group>";
+		};
+		6003F594195388D20070C39A /* Supporting Files */ = {
+			isa = PBXGroup;
+			children = (
+				6003F595195388D20070C39A /* Logan-iOS-Info.plist */,
+				6003F596195388D20070C39A /* InfoPlist.strings */,
+				6003F599195388D20070C39A /* main.m */,
+				6003F59B195388D20070C39A /* Logan-iOS-Prefix.pch */,
+			);
+			name = "Supporting Files";
+			sourceTree = "<group>";
+		};
+		6003F5B5195388D20070C39A /* Tests */ = {
+			isa = PBXGroup;
+			children = (
+				A706F9E421145B9D00F819F4 /* LoganTests.m */,
+				6003F5BB195388D20070C39A /* Tests.m */,
+				6003F5B6195388D20070C39A /* Supporting Files */,
+			);
+			path = Tests;
+			sourceTree = "<group>";
+		};
+		6003F5B6195388D20070C39A /* Supporting Files */ = {
+			isa = PBXGroup;
+			children = (
+				6003F5B7195388D20070C39A /* Tests-Info.plist */,
+				6003F5B8195388D20070C39A /* InfoPlist.strings */,
+				606FC2411953D9B200FFA9A0 /* Tests-Prefix.pch */,
+			);
+			name = "Supporting Files";
+			sourceTree = "<group>";
+		};
+		8C7684273B940DE7FF3048A2 /* Pods */ = {
+			isa = PBXGroup;
+			children = (
+				04FA3BEF6F33F169BA9754D0 /* Pods-Logan-iOS_Example.debug.xcconfig */,
+				2243F3ED99B45EA641651CBD /* Pods-Logan-iOS_Example.release.xcconfig */,
+			);
+			path = Pods;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		6003F589195388D20070C39A /* Logan-iOS_Example */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "Logan-iOS_Example" */;
+			buildPhases = (
+				4C403F827A2C6BFB7D817B38 /* [CP] Check Pods Manifest.lock */,
+				6003F586195388D20070C39A /* Sources */,
+				6003F587195388D20070C39A /* Frameworks */,
+				6003F588195388D20070C39A /* Resources */,
+				A8B5E1EF13BDD1A4E5DA02A5 /* [CP] Embed Pods Frameworks */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = "Logan-iOS_Example";
+			productName = "Logan-iOS";
+			productReference = 6003F58A195388D20070C39A /* Logan-iOS_Example.app */;
+			productType = "com.apple.product-type.application";
+		};
+		6003F5AD195388D20070C39A /* Logan-iOS_Tests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "Logan-iOS_Tests" */;
+			buildPhases = (
+				6003F5AA195388D20070C39A /* Sources */,
+				6003F5AB195388D20070C39A /* Frameworks */,
+				6003F5AC195388D20070C39A /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				6003F5B4195388D20070C39A /* PBXTargetDependency */,
+			);
+			name = "Logan-iOS_Tests";
+			productName = "Logan-iOSTests";
+			productReference = 6003F5AE195388D20070C39A /* Logan-iOS_Tests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		6003F582195388D10070C39A /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				CLASSPREFIX = LG;
+				LastUpgradeCheck = 1420;
+				ORGANIZATIONNAME = jiangteng;
+				TargetAttributes = {
+					6003F5AD195388D20070C39A = {
+						TestTargetID = 6003F589195388D20070C39A;
+					};
+				};
+			};
+			buildConfigurationList = 6003F585195388D10070C39A /* Build configuration list for PBXProject "Logan-iOS" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = English;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				English,
+				en,
+				Base,
+			);
+			mainGroup = 6003F581195388D10070C39A;
+			productRefGroup = 6003F58B195388D20070C39A /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				6003F589195388D20070C39A /* Logan-iOS_Example */,
+				6003F5AD195388D20070C39A /* Logan-iOS_Tests */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		6003F588195388D20070C39A /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				873B8AEB1B1F5CCA007FD442 /* Main.storyboard in Resources */,
+				A74F422F20D0FEAB0008E4F8 /* Logan.podspec in Resources */,
+				71719F9F1E33DC2100824A3D /* LaunchScreen.storyboard in Resources */,
+				6003F5A9195388D20070C39A /* Images.xcassets in Resources */,
+				6003F598195388D20070C39A /* InfoPlist.strings in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		6003F5AC195388D20070C39A /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				6003F5BA195388D20070C39A /* InfoPlist.strings in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		4C403F827A2C6BFB7D817B38 /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-Logan-iOS_Example-checkManifestLockResult.txt",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			showEnvVarsInLog = 0;
+		};
+		A8B5E1EF13BDD1A4E5DA02A5 /* [CP] Embed Pods Frameworks */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-Logan-iOS_Example/Pods-Logan-iOS_Example-frameworks.sh",
+				"${BUILT_PRODUCTS_DIR}/Logan/Logan.framework",
+			);
+			name = "[CP] Embed Pods Frameworks";
+			outputPaths = (
+				"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Logan.framework",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Logan-iOS_Example/Pods-Logan-iOS_Example-frameworks.sh\"\n";
+			showEnvVarsInLog = 0;
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		6003F586195388D20070C39A /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				6003F59E195388D20070C39A /* LGAppDelegate.m in Sources */,
+				6003F5A7195388D20070C39A /* LGViewController.m in Sources */,
+				6003F59A195388D20070C39A /* main.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		6003F5AA195388D20070C39A /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				6003F5BC195388D20070C39A /* Tests.m in Sources */,
+				A706F9E521145B9D00F819F4 /* LoganTests.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+		6003F5B4195388D20070C39A /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 6003F589195388D20070C39A /* Logan-iOS_Example */;
+			targetProxy = 6003F5B3195388D20070C39A /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+		6003F596195388D20070C39A /* InfoPlist.strings */ = {
+			isa = PBXVariantGroup;
+			children = (
+				6003F597195388D20070C39A /* en */,
+			);
+			name = InfoPlist.strings;
+			sourceTree = "<group>";
+		};
+		6003F5B8195388D20070C39A /* InfoPlist.strings */ = {
+			isa = PBXVariantGroup;
+			children = (
+				6003F5B9195388D20070C39A /* en */,
+			);
+			name = InfoPlist.strings;
+			sourceTree = "<group>";
+		};
+		71719F9D1E33DC2100824A3D /* LaunchScreen.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				71719F9E1E33DC2100824A3D /* Base */,
+			);
+			name = LaunchScreen.storyboard;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		6003F5BD195388D20070C39A /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_SYMBOLS_PRIVATE_EXTERN = NO;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		6003F5BE195388D20070C39A /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = YES;
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 12.0;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Release;
+		};
+		6003F5C0195388D20070C39A /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 04FA3BEF6F33F169BA9754D0 /* Pods-Logan-iOS_Example.debug.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				GCC_PRECOMPILE_PREFIX_HEADER = YES;
+				GCC_PREFIX_HEADER = "Logan-iOS/Logan-iOS-Prefix.pch";
+				HEADER_SEARCH_PATHS = (
+					"../../Logan/Clogan/**",
+					"../../Logan/mbedtls/include/**",
+				);
+				INFOPLIST_FILE = "Logan-iOS/Logan-iOS-Info.plist";
+				IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+				MODULE_NAME = ExampleApp;
+				PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				WRAPPER_EXTENSION = app;
+			};
+			name = Debug;
+		};
+		6003F5C1195388D20070C39A /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 2243F3ED99B45EA641651CBD /* Pods-Logan-iOS_Example.release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				GCC_PRECOMPILE_PREFIX_HEADER = YES;
+				GCC_PREFIX_HEADER = "Logan-iOS/Logan-iOS-Prefix.pch";
+				HEADER_SEARCH_PATHS = (
+					"../../Logan/Clogan/**",
+					"../../Logan/mbedtls/include/**",
+				);
+				INFOPLIST_FILE = "Logan-iOS/Logan-iOS-Info.plist";
+				IPHONEOS_DEPLOYMENT_TARGET = 11.0;
+				MODULE_NAME = ExampleApp;
+				PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				WRAPPER_EXTENSION = app;
+			};
+			name = Release;
+		};
+		6003F5C3195388D20070C39A /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(SDKROOT)/Developer/Library/Frameworks",
+					"$(inherited)",
+					"$(DEVELOPER_FRAMEWORKS_DIR)",
+				);
+				GCC_GENERATE_TEST_COVERAGE_FILES = YES;
+				GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
+				GCC_PRECOMPILE_PREFIX_HEADER = YES;
+				GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				INFOPLIST_FILE = "Tests/Tests-Info.plist";
+				PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Logan-iOS_Example.app/Logan-iOS_Example";
+				WRAPPER_EXTENSION = xctest;
+			};
+			name = Debug;
+		};
+		6003F5C4195388D20070C39A /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(SDKROOT)/Developer/Library/Frameworks",
+					"$(inherited)",
+					"$(DEVELOPER_FRAMEWORKS_DIR)",
+				);
+				GCC_GENERATE_TEST_COVERAGE_FILES = YES;
+				GCC_INSTRUMENT_PROGRAM_FLOW_ARCS = YES;
+				GCC_PRECOMPILE_PREFIX_HEADER = YES;
+				GCC_PREFIX_HEADER = "Tests/Tests-Prefix.pch";
+				INFOPLIST_FILE = "Tests/Tests-Info.plist";
+				PRODUCT_BUNDLE_IDENTIFIER = "org.cocoapods.demo.${PRODUCT_NAME:rfc1034identifier}";
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Logan-iOS_Example.app/Logan-iOS_Example";
+				WRAPPER_EXTENSION = xctest;
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		6003F585195388D10070C39A /* Build configuration list for PBXProject "Logan-iOS" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				6003F5BD195388D20070C39A /* Debug */,
+				6003F5BE195388D20070C39A /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		6003F5BF195388D20070C39A /* Build configuration list for PBXNativeTarget "Logan-iOS_Example" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				6003F5C0195388D20070C39A /* Debug */,
+				6003F5C1195388D20070C39A /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		6003F5C2195388D20070C39A /* Build configuration list for PBXNativeTarget "Logan-iOS_Tests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				6003F5C3195388D20070C39A /* Debug */,
+				6003F5C4195388D20070C39A /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 6003F582195388D10070C39A /* Project object */;
+}

+ 144 - 0
Example/Logan-iOS/Logan-iOS.xcodeproj/xcshareddata/xcschemes/Logan-iOS-Example.xcscheme

@@ -0,0 +1,144 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1420"
+   version = "1.7">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "6003F589195388D20070C39A"
+               BuildableName = "Logan-iOS_Example.app"
+               BlueprintName = "Logan-iOS_Example"
+               ReferencedContainer = "container:Logan-iOS.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      codeCoverageEnabled = "YES"
+      onlyGenerateCoverageForSpecifiedTargets = "YES">
+      <PostActions>
+         <ExecutionAction
+            ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
+            <ActionContent
+               title = "Run Script"
+               scriptText = "${SRCROOT}/XcodeCoverage/run_code_coverage_post.sh/"
+               shellToInvoke = "/bin/bash">
+               <EnvironmentBuildable>
+                  <BuildableReference
+                     BuildableIdentifier = "primary"
+                     BlueprintIdentifier = "6003F589195388D20070C39A"
+                     BuildableName = "Logan-iOS_Example.app"
+                     BlueprintName = "Logan-iOS_Example"
+                     ReferencedContainer = "container:Logan-iOS.xcodeproj">
+                  </BuildableReference>
+               </EnvironmentBuildable>
+            </ActionContent>
+         </ExecutionAction>
+         <ExecutionAction
+            ActionType = "Xcode.IDEStandardExecutionActionsCore.ExecutionActionType.ShellScriptAction">
+            <ActionContent
+               title = "Run Script"
+               scriptText = "${SRCROOT}/Coverage/get_coverage_report.sh&#10;"
+               shellToInvoke = "/bin/bash">
+               <EnvironmentBuildable>
+                  <BuildableReference
+                     BuildableIdentifier = "primary"
+                     BlueprintIdentifier = "6003F589195388D20070C39A"
+                     BuildableName = "Logan-iOS_Example.app"
+                     BlueprintName = "Logan-iOS_Example"
+                     ReferencedContainer = "container:Logan-iOS.xcodeproj">
+                  </BuildableReference>
+               </EnvironmentBuildable>
+            </ActionContent>
+         </ExecutionAction>
+      </PostActions>
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "6003F589195388D20070C39A"
+            BuildableName = "Logan-iOS_Example.app"
+            BlueprintName = "Logan-iOS_Example"
+            ReferencedContainer = "container:Logan-iOS.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <CodeCoverageTargets>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "6003F589195388D20070C39A"
+            BuildableName = "Logan-iOS_Example.app"
+            BlueprintName = "Logan-iOS_Example"
+            ReferencedContainer = "container:Logan-iOS.xcodeproj">
+         </BuildableReference>
+      </CodeCoverageTargets>
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "6003F5AD195388D20070C39A"
+               BuildableName = "Logan-iOS_Tests.xctest"
+               BlueprintName = "Logan-iOS_Tests"
+               ReferencedContainer = "container:Logan-iOS.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "6003F589195388D20070C39A"
+            BuildableName = "Logan-iOS_Example.app"
+            BlueprintName = "Logan-iOS_Example"
+            ReferencedContainer = "container:Logan-iOS.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "6003F589195388D20070C39A"
+            BuildableName = "Logan-iOS_Example.app"
+            BlueprintName = "Logan-iOS_Example"
+            ReferencedContainer = "container:Logan-iOS.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>

+ 31 - 0
Example/Logan-iOS/Logan-iOS/Base.lproj/LaunchScreen.storyboard

@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13771" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+    <device id="retina4_7" orientation="portrait">
+        <adaptation id="fullscreen"/>
+    </device>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13772"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
+                        <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+</document>

+ 82 - 0
Example/Logan-iOS/Logan-iOS/Base.lproj/Main.storyboard

@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="whP-gf-Uak">
+    <device id="retina4_7" orientation="portrait">
+        <adaptation id="fullscreen"/>
+    </device>
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="wQg-tq-qST">
+            <objects>
+                <viewController id="whP-gf-Uak" customClass="LGViewController" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="uEw-UM-LJ8"/>
+                        <viewControllerLayoutGuide type="bottom" id="Mvr-aV-6Um"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="TpU-gO-2f1">
+                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="BpX-3e-jvr">
+                                <rect key="frame" x="36" y="69" width="92" height="30"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <state key="normal" title="测试日志写入"/>
+                                <connections>
+                                    <action selector="lllog:" destination="whP-gf-Uak" eventType="touchUpInside" id="n50-Xx-Bh5"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="b97-TW-WSZ">
+                                <rect key="frame" x="39" y="163" width="153" height="30"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <state key="normal" title="查看所有日志文件信息"/>
+                                <connections>
+                                    <action selector="allFilesInfo:" destination="whP-gf-Uak" eventType="touchUpInside" id="w4H-gm-KO0"/>
+                                </connections>
+                            </button>
+                            <button opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="kos-QU-zrA">
+                                <rect key="frame" x="39" y="115" width="92" height="30"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <state key="normal" title="测试日志上报"/>
+                                <connections>
+                                    <action selector="uploadFile:" destination="whP-gf-Uak" eventType="touchUpInside" id="Sy4-4v-Q93"/>
+                                </connections>
+                            </button>
+                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="server ip" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hiC-vX-vb6">
+                                <rect key="frame" x="144" y="120" width="70" height="22"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <fontDescription key="fontDescription" type="system" pointSize="18"/>
+                                <color key="textColor" white="0.33333333333333331" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                <nil key="highlightedColor"/>
+                            </label>
+                            <textField opaque="NO" contentMode="scaleToFill" fixedFrame="YES" contentHorizontalAlignment="left" contentVerticalAlignment="center" text="127.0.0.1" borderStyle="roundedRect" placeholder="请输入server ip地址" textAlignment="center" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="tsE-6W-owH">
+                                <rect key="frame" x="218" y="115" width="97" height="30"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <nil key="textColor"/>
+                                <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                <textInputTraits key="textInputTraits" keyboardType="numbersAndPunctuation"/>
+                            </textField>
+                            <textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" fixedFrame="YES" editable="NO" selectable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hsM-Sm-zNN">
+                                <rect key="frame" x="39" y="215" width="289" height="171"/>
+                                <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
+                                <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+                                <fontDescription key="fontDescription" type="system" pointSize="14"/>
+                                <textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
+                            </textView>
+                        </subviews>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                    </view>
+                    <connections>
+                        <outlet property="filesInfo" destination="hsM-Sm-zNN" id="58d-ba-lXX"/>
+                        <outlet property="ipText" destination="tsE-6W-owH" id="CVG-EQ-j8b"/>
+                    </connections>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="tc2-Qw-aMS" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="304.80000000000001" y="432.23388305847078"/>
+        </scene>
+    </scenes>
+</document>

+ 98 - 0
Example/Logan-iOS/Logan-iOS/Images.xcassets/AppIcon.appiconset/Contents.json

@@ -0,0 +1,98 @@
+{
+  "images" : [
+    {
+      "idiom" : "iphone",
+      "size" : "20x20",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "20x20",
+      "scale" : "3x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "29x29",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "29x29",
+      "scale" : "3x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "40x40",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "40x40",
+      "scale" : "3x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "60x60",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "iphone",
+      "size" : "60x60",
+      "scale" : "3x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "20x20",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "20x20",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "29x29",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "29x29",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "40x40",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "40x40",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "76x76",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "76x76",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ipad",
+      "size" : "83.5x83.5",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "ios-marketing",
+      "size" : "1024x1024",
+      "scale" : "1x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}

+ 30 - 0
Example/Logan-iOS/Logan-iOS/LGAppDelegate.h

@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+@import UIKit;
+
+
+@interface LGAppDelegate : UIResponder <UIApplicationDelegate>
+
+@property (strong, nonatomic) UIWindow *window;
+
+@end

+ 55 - 0
Example/Logan-iOS/Logan-iOS/LGAppDelegate.m

@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#import "LGAppDelegate.h"
+
+
+@implementation LGAppDelegate
+
+- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+    // Override point for customization after application launch.
+    return YES;
+}
+
+- (void)applicationWillResignActive:(UIApplication *)application {
+    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
+    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
+}
+
+- (void)applicationDidEnterBackground:(UIApplication *)application {
+    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
+    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
+}
+
+- (void)applicationWillEnterForeground:(UIApplication *)application {
+    // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
+}
+
+- (void)applicationDidBecomeActive:(UIApplication *)application {
+    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
+}
+
+- (void)applicationWillTerminate:(UIApplication *)application {
+    // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
+}
+
+@end

+ 28 - 0
Example/Logan-iOS/Logan-iOS/LGViewController.h

@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+@import UIKit;
+
+
+@interface LGViewController : UIViewController
+
+@end

+ 97 - 0
Example/Logan-iOS/Logan-iOS/LGViewController.m

@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#import "LGViewController.h"
+#import <Logan/Logan.h>
+#include <zlib.h>
+#import <CommonCrypto/CommonCryptor.h>
+
+typedef enum : NSUInteger {
+    LoganTypeAction = 1,  //用户行为日志
+    LoganTypeNetwork = 2, //网络级日志
+} LoganType;
+
+
+@interface LGViewController ()
+@property (nonatomic, assign) int count;
+@property (weak, nonatomic) IBOutlet UITextView *filesInfo;
+@property (weak, nonatomic) IBOutlet UITextField *ipText;
+@end
+
+
+@implementation LGViewController
+
+- (void)viewDidLoad {
+    [super viewDidLoad];
+
+    NSData *keydata = [@"0123456789012345" dataUsingEncoding:NSUTF8StringEncoding];
+    NSData *ivdata = [@"0123456789012345" dataUsingEncoding:NSUTF8StringEncoding];
+    uint64_t file_max = 10 * 1024 * 1024;
+    // logan初始化,传入16位key,16位iv,写入文件最大大小(byte)
+    loganInit(keydata, ivdata, file_max);
+    // 将日志输出至控制台
+    loganUseASL(YES);
+
+    self.view.backgroundColor = [UIColor whiteColor];
+}
+- (IBAction)lllog:(id)sender {
+    for (int i = 0; i < 10; i++) {
+        //行为日志
+        [self eventLogType:1 forLabel:[NSString stringWithFormat:@"click button %d", _count++]];
+    }
+}
+
+- (IBAction)allFilesInfo:(id)sender {
+    NSDictionary *files = loganAllFilesInfo();
+
+    NSMutableString *str = [[NSMutableString alloc] init];
+    for (NSString *k in files.allKeys) {
+        [str appendFormat:@"文件日期 %@,大小 %@byte\n", k, [files objectForKey:k]];
+    }
+
+    self.filesInfo.text = str;
+}
+
+- (IBAction)uploadFile:(id)sender {
+	// please repalce with your host
+	loganUpload(@"https://openlogan.inf.test.sankuai.com/logan/upload.json", loganTodaysDate(), @"testAppId", @"testUnionId",@"testDeviceId", ^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
+		if(error){
+			NSLog(@"%@",error);
+		}else{
+			NSLog(@"upload succeed");
+		}
+	});
+}
+
+/**
+ 用户行为日志
+
+ @param eventType 事件类型
+ @param label 描述
+ */
+- (void)eventLogType:(NSInteger)eventType forLabel:(NSString *)label {
+    NSMutableString *s = [NSMutableString string];
+    [s appendFormat:@"%d\t", (int)eventType];
+    [s appendFormat:@"%@\t", label];
+    logan(LoganTypeAction, s);
+}
+@end

+ 54 - 0
Example/Logan-iOS/Logan-iOS/Logan-iOS-Info.plist

@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleDisplayName</key>
+	<string>${PRODUCT_NAME}</string>
+	<key>CFBundleExecutable</key>
+	<string>${EXECUTABLE_NAME}</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>${PRODUCT_NAME}</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>1.0</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>NSAppTransportSecurity</key>
+	<dict>
+		<key>NSAllowsArbitraryLoads</key>
+		<false/>
+	</dict>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>UIMainStoryboardFile</key>
+	<string>Main</string>
+	<key>UIRequiredDeviceCapabilities</key>
+	<array>
+		<string>armv7</string>
+	</array>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+</dict>
+</plist>

+ 16 - 0
Example/Logan-iOS/Logan-iOS/Logan-iOS-Prefix.pch

@@ -0,0 +1,16 @@
+//
+//  Prefix header
+//
+//  The contents of this file are implicitly included at the beginning of every source file.
+//
+
+#import <Availability.h>
+
+#ifndef __IPHONE_5_0
+#warning "This project uses features only available in iOS SDK 5.0 and later."
+#endif
+
+#ifdef __OBJC__
+    @import UIKit;
+    @import Foundation;
+#endif

+ 2 - 0
Example/Logan-iOS/Logan-iOS/en.lproj/InfoPlist.strings

@@ -0,0 +1,2 @@
+/* Localized versions of Info.plist keys */
+

+ 30 - 0
Example/Logan-iOS/Logan-iOS/main.m

@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+@import UIKit;
+#import "LGAppDelegate.h"
+
+int main(int argc, char *argv[]) {
+    @autoreleasepool {
+        return UIApplicationMain(argc, argv, nil, NSStringFromClass([LGAppDelegate class]));
+    }
+}

+ 7 - 0
Example/Logan-iOS/Podfile

@@ -0,0 +1,7 @@
+platform :ios, '9.0'
+use_frameworks!
+
+target 'Logan-iOS_Example' do
+#  pod 'Logan', '~> 1.2.10'
+  pod 'Logan', :path => '../../'
+end

+ 18 - 0
Example/Logan-iOS/Podfile.lock

@@ -0,0 +1,18 @@
+PODS:
+  - Logan (1.2.10):
+    - Logan/mbedtls (= 1.2.10)
+  - Logan/mbedtls (1.2.10)
+
+DEPENDENCIES:
+  - Logan (from `../../`)
+
+EXTERNAL SOURCES:
+  Logan:
+    :path: "../../"
+
+SPEC CHECKSUMS:
+  Logan: 88a7f8103c2ec6b1d505b219c30e9245c755bfc6
+
+PODFILE CHECKSUM: 3b2aeec4fb7e1a3bc90b4b0cc926eadaabe1b207
+
+COCOAPODS: 1.11.3

+ 76 - 0
Example/Logan-iOS/Tests/LoganTests.m

@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2018-present, 美团点评
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#import <XCTest/XCTest.h>
+#import "Logan.h"
+
+
+@interface LoganTests : XCTestCase
+
+@end
+
+
+@implementation LoganTests
+
+- (void)setUp {
+    [super setUp];
+    NSData *keydata = [@"0123456789012345" dataUsingEncoding:NSUTF8StringEncoding];
+    NSData *ivdata = [@"0123456789012345" dataUsingEncoding:NSUTF8StringEncoding];
+    uint64_t file_max = 10 * 1024 * 1024;
+    loganInit(keydata, ivdata, file_max);
+    logan(1, @"test");
+    loganUseASL(YES);
+    loganPrintClibLog(YES);
+}
+
+- (void)tearDown {
+    [super tearDown];
+}
+
+- (void)testWirte {
+    logan(1, @"test");
+}
+
+- (void)testClear {
+    loganClearAllLogs();
+}
+
+- (void)testAllFilesInfo {
+    NSDictionary *info = loganAllFilesInfo();
+    XCTAssertNotNil(info, @"all files info should not be nil");
+}
+
+- (void)testTodaysDate {
+    XCTAssertNotNil(loganTodaysDate(), @"todays date should not be nil");
+}
+
+- (void)testUploadFilePath {
+    loganUploadFilePath(loganTodaysDate(), ^(NSString *_Nullable filePatch) {
+        XCTAssertNotNil(filePatch, @"file patch should not be nil");
+    });
+}
+
+- (void)testFlush {
+    loganFlush();
+}
+
+@end

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini