diff --git a/README.md b/README.md index 9659640..23b943a 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,7 @@ Add the following inside the application manifest (inside ``): * **LogseneRequiresUnmeteredNetwork**: if logs should be shipped only on unmetered network connection * **LogseneRequiresDeviceIdle**: if logs should be shipped only when device is idle * **LogseneRequiresBatteryNotLow**: if logs should be shipped only when battery is not low + * **LogseneAutomaticLocationEnabled**: if logs should be automatically enriched with device location information Example Application @@ -99,6 +100,22 @@ To see how some basic use cases are actually implemented, checkout the bundled ` **Note** that it's highly recommended that you use one instance of Logsene at any time in your app. +Initializing Logsene +-------------------- + +Starting with version **3.0.0** of the Android library `Logsene` needs to be initialized in a **static** way in order to be used. This allows usage in classes that don't have `Context` available. To do that, you need to call the following in your application code. Keep in mind this is only needed **once**: + +```java +Logsene.init(this); +``` + +To get the instance of the `Logsene` object that supports logging you need to call the `getInstance()` method, for example: + +```java +Logsene logsene = Logsene.getInstance(); +logsene.info("Hello World!"); +``` + Mobile Application Analytics ---------------------------- @@ -109,7 +126,7 @@ try { JSONObject event = new JSONObject(); event.put("activity", this.getClass().getSimpleName()); event.put("action", "started"); - Logsene logsene = new Logsene(this); + Logsene logsene = Logsene.getInstance(); logsene.event(event); } catch (JSONException e) { Log.e("myapp", "Unable to construct json", e); @@ -214,7 +231,8 @@ Because of the automatic retrieval of location from the device the `ACCESS_COARS If your application uses JUL (java.util.logging) loggers, you can use the provided custom Handler for Logsene. You will need to configure it through code, since we need a reference to the `Context` object. If you configure your loggers to use the `LogseneHandler`, all log messages will be sent to Sematext for centralized logging. ```java -Logsene logsene = new Logsene(context); +Logsene.init(this); +Logsene logsene = Logsene.getInstance(); Logger logger = Logger.getLogger("mylogger"); logger.addHandler(new LogseneHandler(logsene)); ``` @@ -224,7 +242,8 @@ logger.addHandler(new LogseneHandler(logsene)); If you use JUL and the `LogseneHandler`, all logged exceptions will be sent to Sematext, no further configuration is needed. However, if you don't use JUL, the library provides a helper method to log exceptions: ```java -Logsene logsene = new Logsene(context); +Logsene.init(this); +Logsene logsene = Logsene.getInstance(); try { trySomeOperation(); } catch (IOException e) { @@ -243,7 +262,8 @@ public class TestApplication extends Application { @Override public void uncaughtException(Thread thread, Throwable ex) { // Send uncaught exception to Logsene. - Logsene logsene = new Logsene(TestApplication.this); + Logsene.init(TestApplication.this); + Logsene logsene = Logsene.getInstance(); logsene.error(ex); // Run the default android handler if one is set @@ -261,3 +281,28 @@ public class TestApplication extends Application { ``` Don't forget to declare the custom application class in your manifest (with `android:name` on `application` element). + +Migrating to version 3.x from 2.x +--------------------------------- + +Starting from version **3.0.0** Logsene Android SDK contains backwards incompatible changes related to how it is initilized. You no longer need to create the `Logsene` object everytime you would like to use it for logging. You no longer create the `Logsene` object itself like this: + +```java +Logsene logsene = new Logsene(context, true); + +``` + +Instead you call the `init(Context)` method once and retrieve the instance of `Logsene` object later: + +```java +Logsene.init(context); +Logsene logsene = Logsene.getInstance(); +``` + +You also need to include the `LogseneAutomaticLocationEnabled` property in your manifest file to enable automatic log enrichment with location data: + +```xml + +``` \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 4851699..94e5bca 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,7 +7,7 @@ android { minSdkVersion 19 targetSdkVersion 28 versionCode 1 - versionName "2.5.0" + versionName "3.0.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 59ad7ab..293ed10 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -18,12 +18,10 @@ - - @@ -33,7 +31,9 @@ - + \ No newline at end of file diff --git a/app/src/main/java/com/sematext/logseneapplication/MainActivity.java b/app/src/main/java/com/sematext/logseneapplication/MainActivity.java index 9463033..8ed7378 100644 --- a/app/src/main/java/com/sematext/logseneapplication/MainActivity.java +++ b/app/src/main/java/com/sematext/logseneapplication/MainActivity.java @@ -22,8 +22,8 @@ public class MainActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - - logsene = new Logsene(this, true); + Logsene.init(this); + logsene = logsene.getInstance(); Log.e("INFO", "Android version: " + Build.VERSION.RELEASE); diff --git a/build.gradle b/build.gradle index b0a10a1..033cb20 100644 --- a/build.gradle +++ b/build.gradle @@ -7,7 +7,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.0.2' + classpath 'com.android.tools.build:gradle:4.1.1' classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8ce6d7d..a096c38 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Wed Jun 03 17:02:22 CEST 2020 +#Wed Dec 16 21:30:23 CET 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip diff --git a/logseneandroid/build.gradle b/logseneandroid/build.gradle index 9a799a0..4d70c86 100644 --- a/logseneandroid/build.gradle +++ b/logseneandroid/build.gradle @@ -9,7 +9,7 @@ android { minSdkVersion 19 targetSdkVersion 28 versionCode 4 - versionName "2.5.0" + versionName "3.0.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" diff --git a/logseneandroid/src/main/java/com/sematext/logseneandroid/Logsene.java b/logseneandroid/src/main/java/com/sematext/logseneandroid/Logsene.java index f5ef2d5..5b05c78 100644 --- a/logseneandroid/src/main/java/com/sematext/logseneandroid/Logsene.java +++ b/logseneandroid/src/main/java/com/sematext/logseneandroid/Logsene.java @@ -58,9 +58,9 @@ public class Logsene { */ private static final int DEFAULT_TIME_INTERVAL = 15 * 60 * 1000; - private static JSONObject defaultMeta; - private final Context context; + private static boolean initialized = false; + private static Logsene self; private String versionName; private Integer versionCode; private String uuid; @@ -84,78 +84,82 @@ public class Logsene { private boolean sendRequiresDeviceIdle; private boolean sendRequiresBatteryNotLow; private boolean isActive; + private boolean automaticLocationEnabled; private LogseneLocationListener locationListener; - public Logsene(Context context) { - this(context, false); + // Private constructor - no instances. + private Logsene() { } - public Logsene(Context context, boolean automaticLocationEnabled) { - Utils.requireNonNull(context); - this.context = context; - this.uuid = Installation.id(context); - config(); - this.preflightQueue = new SqliteObjectQueue(Logsene.this.context, maxOfflineMessages); - this.lastScheduled = SystemClock.elapsedRealtime(); - if (automaticLocationEnabled) { - this.locationListener = new LogseneLocationListener(context); - } - this.isActive = true; - schedulePeriodicWorker(); - } + /** + * Initializes Logsene object. + * @param context Context + */ + public static void init(Context context) { + if (!Logsene.isInitialized()) { + Utils.requireNonNull(context); + Logsene logsene = new Logsene(); + logsene.uuid = Installation.id(context); + logsene.config(context); + logsene.preflightQueue = new SqliteObjectQueue(context, logsene.maxOfflineMessages); + logsene.lastScheduled = SystemClock.elapsedRealtime(); + if (logsene.automaticLocationEnabled) { + logsene.locationListener = new LogseneLocationListener(context); + } + logsene.isActive = true; + logsene.schedulePeriodicWorker(); - public long getQueueSize() { - return preflightQueue.size(); + // finally set the static values + Logsene.initialized = true; + Logsene.self = logsene; + } } - public LogseneLocationListener getLocationListener() { - return locationListener; + /** + * Checks if the Logsene object is initialized. + * @return true if initialized, false otherwise + */ + public static boolean isInitialized() { + return Logsene.initialized; } - private void config() { - Bundle data = null; - try { - data = context.getPackageManager().getApplicationInfo(context.getPackageName(), - PackageManager.GET_META_DATA).metaData; - } catch (PackageManager.NameNotFoundException e) { - throw new RuntimeException(e); - } - - // required fields - if (!data.containsKey("LogseneAppToken")) { - throw new RuntimeException("Please provide "); - } else if (!data.containsKey("LogseneType")) { - throw new RuntimeException("Please provide "); + /** + * Returns instance of Logsene object if it is initialized. + * @return Logsene instance + */ + public static Logsene getInstance() { + if (Logsene.isInitialized()) { + return Logsene.self; } - appToken = data.getString("LogseneAppToken"); - type = data.getString("LogseneType"); + throw new NullPointerException("Logsene is not initialized"); + } - // optional fields - receiverUrl = data.getString("LogseneReceiverUrl", RECEIVER_URL); - maxOfflineMessages = data.getInt("LogseneMaxOfflineMessages", DEFAULT_MAX_OFFLINE_MESSAGES); - minTimeDelay = (long)(data.getInt("LogseneMinTimeDelay", DEFAULT_MIN_TIME_DELAY)); - timeInterval = (long)(data.getInt("LogseneInterval", DEFAULT_TIME_INTERVAL)); - sendRequiresUnmeteredNetwork = data.getBoolean("LogseneSendRequiresUnmeteredNetwork", false); - sendRequiresDeviceIdle = data.getBoolean("LogseneSendRequiresDeviceIdle", false); - sendRequiresBatteryNotLow = data.getBoolean("LogseneSendRequiresBatteryNotLow", false); + /** + * Sets the default meta properties. These will be included with every request. + * @param metadata the default meta properties, use null to disable. + */ + public static void setDefaultMeta(JSONObject metadata) { + defaultMeta = metadata; + } - Log.d(TAG, String.format("Logsene is configured:\n" - + " Type: %s\n" - + " Receiver URL: %s\n" - + " Max Offline Messages: %d\n" - + " Min Time Trigger: %d\n" - + " Max Time Trigger: %d\n" - + " Send logs only on unmetered network: %s\n" - + " Send logs only when device is idle: %s\n" - + " Send logs only when battery is not low: %s", - type, receiverUrl, maxOfflineMessages, minTimeDelay, timeInterval, - sendRequiresUnmeteredNetwork, sendRequiresDeviceIdle, sendRequiresBatteryNotLow)); + /** + * Returns LogseneLocationListener for location related functionality. + * @return LogseneLocationListener + */ + public LogseneLocationListener getLocationListener() { + return locationListener; } + /** + * Pauses sending of the data. + */ public void pause() { this.isActive = false; } + /** + * Resumes sending of the data. + */ public void resume() { this.isActive = true; } @@ -343,17 +347,6 @@ public void event(JSONObject object) { addToQueue(object); } - /** - * Sets the default meta properties. - * - * These will be included with every request. - * - * @param metadata the default meta properties, use null to disable. - */ - public static void setDefaultMeta(JSONObject metadata) { - defaultMeta = metadata; - } - /** * Flush the message queue. * @@ -366,6 +359,60 @@ public void flushMessageQueue() { scheduleUnconstrainedWorker(); } + private void config(Context context) { + Bundle data = null; + try { + data = context.getPackageManager().getApplicationInfo(context.getPackageName(), + PackageManager.GET_META_DATA).metaData; + } catch (PackageManager.NameNotFoundException e) { + throw new RuntimeException(e); + } + + // required fields + if (!data.containsKey("LogseneAppToken")) { + throw new RuntimeException("Please provide "); + } else if (!data.containsKey("LogseneType")) { + throw new RuntimeException("Please provide "); + } + appToken = data.getString("LogseneAppToken"); + type = data.getString("LogseneType"); + + // retrieve version name and version code + PackageInfo pInfo = null; + try { + pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + versionName = pInfo.versionName; + versionCode = pInfo.versionCode; + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, e.getMessage(), e); + versionName = "n/a"; + versionCode = -1; + } + + // optional fields + receiverUrl = data.getString("LogseneReceiverUrl", RECEIVER_URL); + maxOfflineMessages = data.getInt("LogseneMaxOfflineMessages", DEFAULT_MAX_OFFLINE_MESSAGES); + minTimeDelay = (long)(data.getInt("LogseneMinTimeDelay", DEFAULT_MIN_TIME_DELAY)); + timeInterval = (long)(data.getInt("LogseneInterval", DEFAULT_TIME_INTERVAL)); + sendRequiresUnmeteredNetwork = data.getBoolean("LogseneSendRequiresUnmeteredNetwork", false); + sendRequiresDeviceIdle = data.getBoolean("LogseneSendRequiresDeviceIdle", false); + sendRequiresBatteryNotLow = data.getBoolean("LogseneSendRequiresBatteryNotLow", false); + automaticLocationEnabled = data.getBoolean("LogseneAutomaticLocationEnabled", false); + + Log.d(TAG, String.format("Logsene is configured:\n" + + " Type: %s\n" + + " Receiver URL: %s\n" + + " Max Offline Messages: %d\n" + + " Min Time Trigger: %d\n" + + " Max Time Trigger: %d\n" + + " Automatic location enabled: %b\n" + + " Send logs only on unmetered network: %s\n" + + " Send logs only when device is idle: %s\n" + + " Send logs only when battery is not low: %s", + type, receiverUrl, maxOfflineMessages, minTimeDelay, timeInterval, automaticLocationEnabled, + sendRequiresUnmeteredNetwork, sendRequiresDeviceIdle, sendRequiresBatteryNotLow)); + } + private Data getWorkerData() { return new Data.Builder() .putString(KEY_RECEIVERURL, receiverUrl) @@ -451,34 +498,10 @@ private void addToQueue(JSONObject obj) { } private String getVersionName() { - if (versionName == null) { - PackageInfo pInfo = null; - try { - pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); - versionName = pInfo.versionName; - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, e.getMessage(), e); - // set to n/a so we don't try again - versionName = "n/a"; - } - } - return versionName; } private int getVersionCode() { - if (versionCode == null) { - PackageInfo pInfo = null; - try { - pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); - versionCode = pInfo.versionCode; - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, e.getMessage(), e); - // set to n/a so we don't try again - versionCode = -1; - } - } - return versionCode; }