From 860cd457d8769bd475347defbc7538d86d1bd5d8 Mon Sep 17 00:00:00 2001 From: "lukasz.suski" Date: Mon, 20 Jun 2016 10:38:03 +0200 Subject: [PATCH 1/5] Added possibility to have many Sentry instances and different http request mechanism --- .idea/gradle.xml | 4 + .idea/modules.xml | 2 + build.gradle | 26 +- sentry-android-okhttp-client/.gitignore | 1 + sentry-android-okhttp-client/build.gradle | 33 + .../sentry-android-okhttp-client.iml | 111 ++ .../src/main/AndroidManifest.xml | 3 + .../sentry/okhttp/OkHttpRequestSender.java | 118 ++ sentry-android/build.gradle | 22 +- sentry-android/sentry-android.iml | 14 +- .../joshdholtz/sentry/BaseHttpBuilder.java | 44 + .../joshdholtz/sentry/HttpRequestSender.java | 34 + .../java/com/joshdholtz/sentry/Sentry.java | 1596 ++++++++--------- .../com/joshdholtz/sentry/SentryInstance.java | 26 + sentry-apache-http-client/.gitignore | 1 + sentry-apache-http-client/build.gradle | 32 + .../sentry-apache-http-client.iml | 107 ++ .../src/main/AndroidManifest.xml | 3 + .../http/apache/ApacheHttpRequestSender.java | 214 +++ sentry-app/build.gradle | 2 +- sentry-app/sentry-app.iml | 32 +- sentry-app/src/main/AndroidManifest.xml | 1 + .../joshdholtz/sentryapp/MainActivity.java | 87 +- .../sentryapp/SentryApplication.java | 17 + settings.gradle | 2 +- 25 files changed, 1578 insertions(+), 954 deletions(-) create mode 100644 sentry-android-okhttp-client/.gitignore create mode 100644 sentry-android-okhttp-client/build.gradle create mode 100644 sentry-android-okhttp-client/sentry-android-okhttp-client.iml create mode 100644 sentry-android-okhttp-client/src/main/AndroidManifest.xml create mode 100644 sentry-android-okhttp-client/src/main/java/com/joshdholtz/sentry/okhttp/OkHttpRequestSender.java create mode 100644 sentry-android/src/main/java/com/joshdholtz/sentry/BaseHttpBuilder.java create mode 100644 sentry-android/src/main/java/com/joshdholtz/sentry/HttpRequestSender.java create mode 100644 sentry-android/src/main/java/com/joshdholtz/sentry/SentryInstance.java create mode 100644 sentry-apache-http-client/.gitignore create mode 100644 sentry-apache-http-client/build.gradle create mode 100644 sentry-apache-http-client/sentry-apache-http-client.iml create mode 100644 sentry-apache-http-client/src/main/AndroidManifest.xml create mode 100644 sentry-apache-http-client/src/main/java/com/joshdholtz/sentry/http/apache/ApacheHttpRequestSender.java create mode 100644 sentry-app/src/main/java/com/joshdholtz/sentryapp/SentryApplication.java diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 76067f3..71ad73e 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -10,6 +10,8 @@ @@ -17,6 +19,8 @@ diff --git a/.idea/modules.xml b/.idea/modules.xml index 8503bf1..b911b92 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -4,6 +4,8 @@ + + diff --git a/build.gradle b/build.gradle index 9718b7a..a70ea05 100644 --- a/build.gradle +++ b/build.gradle @@ -5,11 +5,10 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:2.0.0' + classpath 'com.android.tools.build:gradle:2.1.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files - classpath 'com.android.tools.build:gradle:1.2.3' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.3' } @@ -18,8 +17,31 @@ buildscript { allprojects { repositories { jcenter() + mavenLocal() maven { url 'https://dl.bintray.com/joshdholtz/maven/' } } + + + ext { + bintrayRepo = 'maven' + bintrayName = 'sentry-android' + + publishedGroupId = 'com.joshdholtz.sentry' + + + siteUrl = 'https://github.com/nuuneoi/FBLikeAndroid' + gitUrl = 'https://github.com/nuuneoi/FBLikeAndroid.git' + + libraryVersion = '1.4.2' + + developerId = 'joshdholtz' + developerName = 'Josh Holtz' + developerEmail = 'josh@rokkincat.com' + + licenseName = 'The MIT License (MIT)' + licenseUrl = 'http://opensource.org/licenses/mit-license.php' + allLicenses = ["MIT"] + } } diff --git a/sentry-android-okhttp-client/.gitignore b/sentry-android-okhttp-client/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/sentry-android-okhttp-client/.gitignore @@ -0,0 +1 @@ +/build diff --git a/sentry-android-okhttp-client/build.gradle b/sentry-android-okhttp-client/build.gradle new file mode 100644 index 0000000..4512c14 --- /dev/null +++ b/sentry-android-okhttp-client/build.gradle @@ -0,0 +1,33 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.3" + + defaultConfig { + minSdkVersion 10 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +ext { + libraryName = 'sentry-android-okhttp-client' + artifact = 'sentry-android-okhttp-client' + libraryDescription = 'A Sentry okhttp client for Android' +} + +dependencies { + compile "com.squareup.okhttp3:okhttp:3.3.1" + compile "com.joshdholtz.sentry:sentry-android:${libraryVersion}" +} + +apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' +apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' diff --git a/sentry-android-okhttp-client/sentry-android-okhttp-client.iml b/sentry-android-okhttp-client/sentry-android-okhttp-client.iml new file mode 100644 index 0000000..096aef7 --- /dev/null +++ b/sentry-android-okhttp-client/sentry-android-okhttp-client.iml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sentry-android-okhttp-client/src/main/AndroidManifest.xml b/sentry-android-okhttp-client/src/main/AndroidManifest.xml new file mode 100644 index 0000000..4360b07 --- /dev/null +++ b/sentry-android-okhttp-client/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/sentry-android-okhttp-client/src/main/java/com/joshdholtz/sentry/okhttp/OkHttpRequestSender.java b/sentry-android-okhttp-client/src/main/java/com/joshdholtz/sentry/okhttp/OkHttpRequestSender.java new file mode 100644 index 0000000..8497ea1 --- /dev/null +++ b/sentry-android-okhttp-client/src/main/java/com/joshdholtz/sentry/okhttp/OkHttpRequestSender.java @@ -0,0 +1,118 @@ +package com.joshdholtz.sentry.okhttp; + +import com.joshdholtz.sentry.HttpRequestSender; + +import okhttp3.Call; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.RequestBody; +import okhttp3.ResponseBody; + +public class OkHttpRequestSender implements HttpRequestSender { + + private static class OkBuilder implements Builder { + + private static class OkResponse implements Response { + + private final int code; + private final String bodyContent; + + public OkResponse(int code, String bodyContent) { + this.code = code; + this.bodyContent = bodyContent; + } + + @Override + public int getStatusCode() { + return code; + } + + @Override + public String getContent() { + return bodyContent; + } + } + + private final okhttp3.Request.Builder builder; + private OkHttpClient okHttpClient; + private boolean useHttps; + private String url; + + private OkBuilder(OkHttpClient okHttpClient) { + this.okHttpClient = okHttpClient; + builder = new okhttp3.Request.Builder(); + } + + @Override + public Request build() throws Exception { + final Call call = okHttpClient.newCall(builder.build()); + return new Request() { + @Override + public Response execute() throws Exception { + okhttp3.Response response = call.execute(); + try { + ResponseBody body = response.body(); + String bodyContent = null; + if (body != null) { + bodyContent = body.string(); + } + + return new OkResponse(response.code(), bodyContent); + } finally { + if (response.body() != null) { + response.body().close(); + } + } + } + }; + } + + @Override + public Builder useHttps() { + useHttps = true; + if (url != null) { + addHttps(); + } + return this; + } + + private void addHttps() { + if (!url.startsWith("http")) { + url = "https://" + url; + } + } + + @Override + public Builder url(String url) { + this.url = url; + if (useHttps) { + addHttps(); + } + builder.url(this.url); + return this; + } + + @Override + public Builder header(String headerName, String headerValue) { + builder.header(headerName, headerValue); + return this; + } + + @Override + public Builder content(String requestData, String mediaType) { + builder.post(RequestBody.create(MediaType.parse(mediaType), requestData)); + return this; + } + } + + private OkHttpClient okHttpClient; + + public OkHttpRequestSender(OkHttpClient okHttpClient) { + this.okHttpClient = okHttpClient; + } + + @Override + public Builder newBuilder() { + return new OkBuilder(okHttpClient); + } +} diff --git a/sentry-android/build.gradle b/sentry-android/build.gradle index 10ee9d8..8c6c710 100644 --- a/sentry-android/build.gradle +++ b/sentry-android/build.gradle @@ -2,9 +2,7 @@ apply plugin: 'com.android.library' android { compileSdkVersion 23 - buildToolsVersion "23.0.2" - - useLibrary 'org.apache.http.legacy' + buildToolsVersion "23.0.3" defaultConfig { minSdkVersion 3 @@ -22,31 +20,13 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - provided 'org.jbundle.util.osgi.wrapped:org.jbundle.util.osgi.wrapped.org.apache.http.client:4.1.2' } ext { - bintrayRepo = 'maven' - bintrayName = 'sentry-android' - - publishedGroupId = 'com.joshdholtz.sentry' libraryName = 'sentry-android' artifact = 'sentry-android' libraryDescription = 'A Sentry client for Android' - - siteUrl = 'https://github.com/nuuneoi/FBLikeAndroid' - gitUrl = 'https://github.com/nuuneoi/FBLikeAndroid.git' - - libraryVersion = '1.4.1' - - developerId = 'joshdholtz' - developerName = 'Josh Holtz' - developerEmail = 'josh@rokkincat.com' - - licenseName = 'The MIT License (MIT)' - licenseUrl = 'http://opensource.org/licenses/mit-license.php' - allLicenses = ["MIT"] } apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' diff --git a/sentry-android/sentry-android.iml b/sentry-android/sentry-android.iml index 6a7f0d8..5fe1fa7 100644 --- a/sentry-android/sentry-android.iml +++ b/sentry-android/sentry-android.iml @@ -1,5 +1,5 @@ - + @@ -48,6 +48,7 @@ + @@ -55,6 +56,7 @@ + @@ -62,6 +64,7 @@ + @@ -69,6 +72,7 @@ + @@ -76,24 +80,28 @@ + + + + + + - - \ No newline at end of file diff --git a/sentry-android/src/main/java/com/joshdholtz/sentry/BaseHttpBuilder.java b/sentry-android/src/main/java/com/joshdholtz/sentry/BaseHttpBuilder.java new file mode 100644 index 0000000..367050b --- /dev/null +++ b/sentry-android/src/main/java/com/joshdholtz/sentry/BaseHttpBuilder.java @@ -0,0 +1,44 @@ +package com.joshdholtz.sentry; + +import java.util.HashMap; +import java.util.Map; + +public abstract class BaseHttpBuilder implements HttpRequestSender.Builder { + private String url; + private Map headers = new HashMap<>(); + private String requestData; + private String mediaType; + private boolean useHttps; + + @Override + public HttpRequestSender.Request build() throws Exception { + return build(url, headers, requestData, mediaType, useHttps); + } + + protected abstract HttpRequestSender.Request build(String url, Map headers, String requestData, String mediaType, boolean useHttps) throws Exception; + + @Override + public HttpRequestSender.Builder useHttps() { + useHttps = true; + return this; + } + + @Override + public HttpRequestSender.Builder url(String url) { + this.url = url; + return this; + } + + @Override + public HttpRequestSender.Builder header(String headerName, String headerValue) { + headers.put(headerName, headerValue); + return this; + } + + @Override + public HttpRequestSender.Builder content(String requestData, String mediaType) { + this.requestData = requestData; + this.mediaType = mediaType; + return this; + } +} diff --git a/sentry-android/src/main/java/com/joshdholtz/sentry/HttpRequestSender.java b/sentry-android/src/main/java/com/joshdholtz/sentry/HttpRequestSender.java new file mode 100644 index 0000000..6ae2b29 --- /dev/null +++ b/sentry-android/src/main/java/com/joshdholtz/sentry/HttpRequestSender.java @@ -0,0 +1,34 @@ +package com.joshdholtz.sentry; + +public interface HttpRequestSender +{ + interface Builder + { + Request build() throws Exception; + + Builder useHttps(); + + Builder url(String url); + + Builder header(String headerName, String headerValue); + + Builder content(String requestData, String mediaType); + } + + interface Response + { + + int getStatusCode(); + + String getContent(); + } + + interface Request + { + Response execute() throws Exception; + } + + + Builder newBuilder(); + +} diff --git a/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java b/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java index cc41e84..204ab8e 100755 --- a/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java +++ b/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java @@ -1,890 +1,748 @@ package com.joshdholtz.sentry; -import java.io.ByteArrayOutputStream; +import android.app.Application; +import android.content.Context; +import android.content.pm.PackageManager; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; +import android.net.Uri; +import android.os.Bundle; +import android.util.Log; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.FilenameFilter; import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; +import java.io.InputStreamReader; import java.io.ObjectOutputStream; +import java.io.OutputStream; import java.io.PrintWriter; -import java.io.Serializable; -import java.io.StreamCorruptedException; +import java.io.Reader; import java.io.StringWriter; import java.io.Writer; import java.lang.Thread.UncaughtExceptionHandler; -import java.net.Socket; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.UnknownHostException; -import java.nio.ByteBuffer; -import java.nio.CharBuffer; -import java.nio.charset.CharacterCodingException; -import java.nio.charset.Charset; -import java.nio.charset.CharsetDecoder; -import java.security.KeyManagementException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; -import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.TimeZone; import java.util.UUID; -import java.util.concurrent.CopyOnWriteArrayList; - -import javax.net.ssl.SSLContext; -import javax.net.ssl.TrustManager; -import javax.net.ssl.X509TrustManager; - -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.ClientProtocolException; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.utils.URLEncodedUtils; -import org.apache.http.conn.ClientConnectionManager; -import org.apache.http.conn.scheme.Scheme; -import org.apache.http.conn.scheme.SchemeRegistry; -import org.apache.http.conn.ssl.SSLSocketFactory; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.HttpConnectionParams; -import org.apache.http.params.HttpParams; -import org.apache.http.params.HttpProtocolParams; -import org.apache.http.protocol.HTTP; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public final class Sentry { + + private static final String TAG = "Sentry"; + private static final String VERSION = "0.2.0"; + private static final String VERIFY_SSL = "verify_ssl"; + private static final String MEDIA_TYPE = "application/json; charset=utf-8"; + private static final int HTTP_OK = 200; + private static final String API = "/api/"; + private static final String STORE = "/store/"; + private static final int DEFAULT_BUFFER_SIZE = 1024 * 4; + private static final String EVENT_ID = "event_id"; + private Context context; + private String sentryVersion = "7"; + private String url; + private Uri dsn; + private String packageName; + private int verifySsl; + private SentryEventCaptureListener captureListener; + private HttpRequestSender httpRequestSender; + private ExecutorService executorService = Executors.newSingleThreadExecutor(); + private InternalStorage internalStorage; + private Runnable sendUnsentRequests = new Runnable() { + @Override + public void run() { + List unsentRequests = internalStorage.getUnsentRequests(); + logD("Sending up " + unsentRequests.size() + " cached response(s)"); + for (JSONObject request : unsentRequests) { + sendRequest(request); + } + } + }; + private boolean enableDebugLogging; + private SentryUncaughtExceptionHandler uncaughtExceptionHandler; + + private Sentry(Context applicationContext, Uri dsnUri, String url, int verifySsl, String storageFileName, HttpRequestSender httpRequestSender) { + context = applicationContext; + dsn = dsnUri; + this.url = url; + this.verifySsl = verifySsl; + this.httpRequestSender = httpRequestSender; + packageName = applicationContext.getPackageName(); + internalStorage = new InternalStorage(storageFileName); + } -import com.joshdholtz.sentry.Sentry.SentryEventBuilder.SentryEventLevel; + /** + * This method returns new {@code Sentry} instance which can operate separately with other instances. Should be called once, usually in {@link Application#onCreate()} and obtained from some singleton. If you have only one instance than you can use {@link SentryInstance}. In {@link android.app.Activity#onCreate(Bundle)} you should call at least {@link #sendAllCachedCapturedEvents()} to try to send cached events + * + * @param context this can be any context because {@link Context#getApplicationContext()} is used to avoid memory leak + * @param dsn DSN of your project + * @param httpRequestSender used for sending events to Sentry server + * @param storageFileName unsent requests storage file - must be different for different instances + * @return new {@code Sentry} instance + */ + public static Sentry newInstance(Context context, String dsn, HttpRequestSender httpRequestSender, String storageFileName) { + Uri dsnUri = Uri.parse(dsn); + + Sentry sentry = new Sentry(context.getApplicationContext(), dsnUri, getUrl(dsnUri), getVerifySsl(dsnUri), storageFileName, httpRequestSender); + sentry.setupUncaughtExceptionHandler(); + return sentry; + } -import android.content.Context; -import android.content.pm.PackageManager; -import android.net.ConnectivityManager; -import android.net.NetworkInfo; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.util.Log; + private static String getUrl(Uri uri) { + String port = ""; + if (uri.getPort() >= 0) { + port = ":" + uri.getPort(); + } + + String path = uri.getPath(); + + String projectId = path.substring(path.lastIndexOf('/') + 1); + return uri.getScheme() + "://" + uri.getHost() + port + API + projectId + STORE; + } + + private static int getVerifySsl(Uri uri) { + int verifySsl = 1; + String queryParameter = uri.getQueryParameter(VERIFY_SSL); + return queryParameter == null ? verifySsl : Integer.parseInt(queryParameter); + } + + public void setSentryVersion(String sentryVersion) { + this.sentryVersion = sentryVersion; + } + + public void setupUncaughtExceptionHandler() { + + UncaughtExceptionHandler currentHandler = Thread.getDefaultUncaughtExceptionHandler(); + if (currentHandler != null) { + logD("current handler class=" + currentHandler.getClass().getName()); + if (currentHandler == uncaughtExceptionHandler) { + return; + } + } + + //disable existing handler because we don't know if someone wrapped us + if (uncaughtExceptionHandler != null) { + uncaughtExceptionHandler.disabled = true; + } + //register new handler + uncaughtExceptionHandler = new SentryUncaughtExceptionHandler(currentHandler); + Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler); + + sendAllCachedCapturedEvents(); + } + + private String createXSentryAuthHeader() { + String header = ""; + + String authority = dsn.getAuthority().replace("@" + dsn.getHost(), ""); + + String[] authorityParts = authority.split(":"); + String publicKey = authorityParts[0]; + String secretKey = authorityParts[1]; + + header += "Sentry sentry_version=" + sentryVersion + ","; + header += "sentry_client=sentry-android/" + VERSION + ","; + header += "sentry_timestamp=" + System.currentTimeMillis() + ","; + header += "sentry_key=" + publicKey + ","; + header += "sentry_secret=" + secretKey; + + return header; + } + + public void sendAllCachedCapturedEvents() { + if (shouldAttemptPost()) { + submit(sendUnsentRequests); + } + } + + /** + * @param captureListener the captureListener to set + */ + public void setCaptureListener(SentryEventCaptureListener captureListener) { + this.captureListener = captureListener; + } + + public void captureMessage(String message) { + captureMessage(message, SentryEventBuilder.SentryEventLevel.INFO); + } + + public void captureMessage(String message, SentryEventBuilder.SentryEventLevel level) { + captureEvent(newEventBuilder().setMessage(message).setLevel(level)); + } + + public SentryEventBuilder newEventBuilder() { + return new SentryEventBuilder(enableDebugLogging); + } + + public void captureException(Throwable t) { + captureException(t, SentryEventBuilder.SentryEventLevel.ERROR); + } + + public void captureException(Throwable t, SentryEventBuilder.SentryEventLevel level) { + String culprit = getCause(t, t.getMessage()); + + captureEvent(newEventBuilder().setMessage(t.getMessage()).setCulprit(culprit).setLevel(level).setException(t)); + } + + public void captureUncaughtException(Context context, Throwable t) { + final Writer result = new StringWriter(); + final PrintWriter printWriter = new PrintWriter(result); + t.printStackTrace(printWriter); + try { + // Random number to avoid duplicate files + long random = System.currentTimeMillis(); + + // Embed version in stacktrace filename + File stacktrace = new File(getStacktraceLocation(context), "raven-" + random + ".stacktrace"); + logD("Writing unhandled exception to: " + stacktrace.getAbsolutePath()); + + // Write the stacktrace to disk + ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(stacktrace)); + oos.writeObject(t); + oos.flush(); + // Close up everything + oos.close(); + } catch (Exception ebos) { + // Nothing much we can do about this - the game is over + logW(ebos); + } + logD(result.toString()); + } -public class Sentry { - - private final static String VERSION = "0.2.0"; - - private Context context; - - public static String sentryVersion = "7"; - - private String baseUrl; - private String dsn; - private String packageName; - private int verifySsl; - private SentryEventCaptureListener captureListener; - - private static final String TAG = "Sentry"; - private static final String DEFAULT_BASE_URL = "https://app.getsentry.com"; - - private Sentry() { - - } - - private static Sentry getInstance() { - return LazyHolder.instance; - } - - private static class LazyHolder { - private static Sentry instance = new Sentry(); - } - - public static void init(Context context, String dsn) { - Sentry.getInstance().context = context.getApplicationContext(); - - Uri uri = Uri.parse(dsn); - String port = ""; - if (uri.getPort() >= 0) { - port = ":" + uri.getPort(); - } - - Sentry.getInstance().baseUrl = uri.getScheme() + "://" + uri.getHost() + port; - Sentry.getInstance().dsn = dsn; - Sentry.getInstance().packageName = context.getPackageName(); - Sentry.getInstance().verifySsl = getVerifySsl(dsn); - - Sentry.getInstance().setupUncaughtExceptionHandler(); - } - - private static int getVerifySsl(String dsn) { - int verifySsl = 1; - List params = getAllGetParams(dsn); - for (NameValuePair param : params) { - if(param.getName().equals("verify_ssl")) - return Integer.parseInt(param.getValue()); - } - return verifySsl; - } - - private static List getAllGetParams(String dsn) { - List params = null; - try { - params = URLEncodedUtils.parse(new URI(dsn), HTTP.UTF_8); - } catch (URISyntaxException e) { - e.printStackTrace(); - } - return params; - } - - private void setupUncaughtExceptionHandler() { - - UncaughtExceptionHandler currentHandler = Thread.getDefaultUncaughtExceptionHandler(); - if (currentHandler != null) { - Log.d("Debugged", "current handler class="+currentHandler.getClass().getName()); - } - - // don't register again if already registered - if (!(currentHandler instanceof SentryUncaughtExceptionHandler)) { - // Register default exceptions handler - Thread.setDefaultUncaughtExceptionHandler( - new SentryUncaughtExceptionHandler(currentHandler, context)); - } - - sendAllCachedCapturedEvents(); - } - - private static String createXSentryAuthHeader() { - String header = ""; - - Uri uri = Uri.parse(Sentry.getInstance().dsn); - - String authority = uri.getAuthority().replace("@" + uri.getHost(), ""); - - String[] authorityParts = authority.split(":"); - String publicKey = authorityParts[0]; - String secretKey = authorityParts[1]; - - header += "Sentry sentry_version=" + sentryVersion + ","; - header += "sentry_client=sentry-android/" + VERSION + ","; - header += "sentry_timestamp=" + System.currentTimeMillis() +","; - header += "sentry_key=" + publicKey + ","; - header += "sentry_secret=" + secretKey; - - return header; - } - - private static String getProjectId() { - Uri uri = Uri.parse(Sentry.getInstance().dsn); - String path = uri.getPath(); - String projectId = path.substring(path.lastIndexOf("/") + 1); - - return projectId; - } - - public static void sendAllCachedCapturedEvents() { - List unsentRequests = InternalStorage.getInstance().getUnsentRequests(); - Log.d(Sentry.TAG, "Sending up " + unsentRequests.size() + " cached response(s)"); - for (SentryEventRequest request : unsentRequests) { - Sentry.doCaptureEventPost(request); - } - } - - /** - * @param captureListener the captureListener to set - */ - public static void setCaptureListener(SentryEventCaptureListener captureListener) { - Sentry.getInstance().captureListener = captureListener; - } - - public static void captureMessage(String message) { - Sentry.captureMessage(message, SentryEventLevel.INFO); - } - - public static void captureMessage(String message, SentryEventLevel level) { - Sentry.captureEvent(new SentryEventBuilder() - .setMessage(message) - .setLevel(level) - ); - } - - public static void captureException(Throwable t) { - Sentry.captureException(t, SentryEventLevel.ERROR); - } - - public static void captureException(Throwable t, SentryEventLevel level) { - String culprit = getCause(t, t.getMessage()); - - Sentry.captureEvent(new SentryEventBuilder() - .setMessage(t.getMessage()) - .setCulprit(culprit) - .setLevel(level) - .setException(t) - ); - - } - - public static void captureUncaughtException(Context context, Throwable t) { - final Writer result = new StringWriter(); - final PrintWriter printWriter = new PrintWriter(result); - t.printStackTrace(printWriter); - try { - // Random number to avoid duplicate files - long random = System.currentTimeMillis(); - - // Embed version in stacktrace filename - File stacktrace = new File(getStacktraceLocation(context), "raven-" + String.valueOf(random) + ".stacktrace"); - Log.d(TAG, "Writing unhandled exception to: " + stacktrace.getAbsolutePath()); - - // Write the stacktrace to disk - ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(stacktrace)); - oos.writeObject(t); - oos.flush(); - // Close up everything - oos.close(); - } catch (Exception ebos) { - // Nothing much we can do about this - the game is over - ebos.printStackTrace(); - } - - Log.d(TAG, result.toString()); - } - - private static String getCause(Throwable t, String culprit) { - for (StackTraceElement stackTrace : t.getStackTrace()) { - if (stackTrace.toString().contains(Sentry.getInstance().packageName)) { - culprit = stackTrace.toString(); - break; - } - } - - return culprit; - } - - private static File getStacktraceLocation(Context context) { - return new File(context.getCacheDir(), "crashes"); - } - - private static String getStackTrace(Throwable t) { - StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); - t.printStackTrace(pw); - return sw.toString(); - } - - public static void captureEvent(SentryEventBuilder builder) { - final SentryEventRequest request; - if (Sentry.getInstance().captureListener != null) { - - builder = Sentry.getInstance().captureListener.beforeCapture(builder); - if (builder == null) { - Log.e(Sentry.TAG, "SentryEventBuilder in captureEvent is null"); - return; - } - - request = new SentryEventRequest(builder); - } else { - request = new SentryEventRequest(builder); - } - - Log.d(TAG, "Request - " + request.getRequestData()); - - // Check if on main thread - if not, run on main thread - if (Looper.myLooper() == Looper.getMainLooper()) { - doCaptureEventPost(request); - } else if (Sentry.getInstance().context != null) { - - HandlerThread thread = new HandlerThread("SentryThread") {}; - thread.start(); - Runnable runnable = new Runnable() { - @Override - public void run() { - doCaptureEventPost(request); - } - }; - Handler h = new Handler(thread.getLooper()); - h.post(runnable); - - } - - } - - private static boolean shouldAttemptPost() { - PackageManager pm = Sentry.getInstance().context.getPackageManager(); - int hasPerm = pm.checkPermission(android.Manifest.permission.ACCESS_NETWORK_STATE, Sentry.getInstance().context.getPackageName()); - if (hasPerm == PackageManager.PERMISSION_DENIED) { - return true; - } - - ConnectivityManager connectivityManager = (ConnectivityManager) Sentry.getInstance().context.getSystemService(Context.CONNECTIVITY_SERVICE); - NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); - return activeNetworkInfo != null && activeNetworkInfo.isConnected(); - } - - public static class ExSSLSocketFactory extends SSLSocketFactory { - SSLContext sslContext = SSLContext.getInstance("TLS"); - - public ExSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { - super(truststore); - TrustManager x509TrustManager = new X509TrustManager() { - public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { - } - - public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - } - - public X509Certificate[] getAcceptedIssuers() { - return null; - } - }; - - sslContext.init(null, new TrustManager[] { x509TrustManager }, null); - } - - public ExSSLSocketFactory(SSLContext context) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { - super(null); - sslContext = context; - } - - @Override - public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { - return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); - } - - @Override - public Socket createSocket() throws IOException { - return sslContext.getSocketFactory().createSocket(); - } - } - - public static HttpClient getHttpsClient(HttpClient client) { - try { - X509TrustManager x509TrustManager = new X509TrustManager() { - @Override - public void checkClientTrusted(X509Certificate[] chain, - String authType) throws CertificateException { - } - - @Override - public void checkServerTrusted(X509Certificate[] chain, - String authType) throws CertificateException { - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return null; - } - }; - - SSLContext sslContext = SSLContext.getInstance("TLS"); - sslContext.init(null, new TrustManager[]{x509TrustManager}, null); - SSLSocketFactory sslSocketFactory = new ExSSLSocketFactory(sslContext); - sslSocketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); - ClientConnectionManager clientConnectionManager = client.getConnectionManager(); - SchemeRegistry schemeRegistry = clientConnectionManager.getSchemeRegistry(); - schemeRegistry.register(new Scheme("https", sslSocketFactory, 443)); - return new DefaultHttpClient(clientConnectionManager, client.getParams()); - } catch (Exception ex) { - return null; - } - } - - private static void doCaptureEventPost(final SentryEventRequest request) { - - if (!shouldAttemptPost()) { - InternalStorage.getInstance().addRequest(request); - return; - } - - new AsyncTask(){ - @Override - protected Void doInBackground(Void... params) { - - int projectId = Integer.parseInt(getProjectId()); - String url = Sentry.getInstance().baseUrl + "/api/" + projectId + "/store/"; - - Log.d(TAG, "Sending to URL - " + url); - - HttpClient httpClient; - if(Sentry.getInstance().verifySsl != 0) { - Log.d(TAG, "Using http client"); - httpClient = new DefaultHttpClient(); - } - else { - Log.d(TAG, "Using https client"); - httpClient = getHttpsClient(new DefaultHttpClient()); - } - - HttpPost httpPost = new HttpPost(url); - - int TIMEOUT_MILLISEC = 10000; // = 20 seconds - HttpParams httpParams = httpPost.getParams(); - HttpConnectionParams.setConnectionTimeout(httpParams, TIMEOUT_MILLISEC); - HttpConnectionParams.setSoTimeout(httpParams, TIMEOUT_MILLISEC); - - HttpProtocolParams.setContentCharset(httpParams, HTTP.UTF_8); - HttpProtocolParams.setHttpElementCharset(httpParams, HTTP.UTF_8); - - boolean success = false; - try { - httpPost.setHeader("X-Sentry-Auth", createXSentryAuthHeader()); - httpPost.setHeader("User-Agent", "sentry-android/" + VERSION); - httpPost.setHeader("Content-Type", "application/json; charset=utf-8"); - - httpPost.setEntity(new StringEntity(request.getRequestData(), HTTP.UTF_8)); - HttpResponse httpResponse = httpClient.execute(httpPost); - - int status = httpResponse.getStatusLine().getStatusCode(); - byte[] byteResp = null; - - // Gets the input stream and unpackages the response into a command - if (httpResponse.getEntity() != null) { - try { - InputStream in = httpResponse.getEntity().getContent(); - byteResp = this.readBytes(in); - - } catch (IOException e) { - e.printStackTrace(); - } - } - - String stringResponse = null; - Charset charsetInput = Charset.forName("UTF-8"); - CharsetDecoder decoder = charsetInput.newDecoder(); - CharBuffer cbuf = null; - try { - cbuf = decoder.decode(ByteBuffer.wrap(byteResp)); - stringResponse = cbuf.toString(); - } catch (CharacterCodingException e) { - e.printStackTrace(); - } - - success = (status == 200); - - Log.d(TAG, "SendEvent - " + status + " " + stringResponse); - } catch (ClientProtocolException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (Exception e) { - e.printStackTrace(); - } - - if (success) { - InternalStorage.getInstance().removeBuilder(request); - } else { - InternalStorage.getInstance().addRequest(request); - } - - return null; - } - - private byte[] readBytes(InputStream inputStream) throws IOException { - // this dynamically extends to take the bytes you read - ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); - - // this is storage overwritten on each iteration with bytes - int bufferSize = 1024; - byte[] buffer = new byte[bufferSize]; - - // we need to know how may bytes were read to write them to the byteBuffer - int len = 0; - while ((len = inputStream.read(buffer)) != -1) { - byteBuffer.write(buffer, 0, len); - } - - // and then we can return your byte array. - return byteBuffer.toByteArray(); - } - - }.execute(); - - } - - private class SentryUncaughtExceptionHandler implements UncaughtExceptionHandler { - - private UncaughtExceptionHandler defaultExceptionHandler; - private Context context; - - // constructor - public SentryUncaughtExceptionHandler(UncaughtExceptionHandler pDefaultExceptionHandler, Context context) { - defaultExceptionHandler = pDefaultExceptionHandler; - this.context = context; - } - - @Override - public void uncaughtException(Thread thread, Throwable e) { - // Here you should have a more robust, permanent record of problems - SentryEventBuilder builder = new SentryEventBuilder(e, SentryEventLevel.FATAL); - if (Sentry.getInstance().captureListener != null) { - builder = Sentry.getInstance().captureListener.beforeCapture(builder); - } + private String getCause(Throwable t, String culprit) { + for (StackTraceElement stackTrace : t.getStackTrace()) { + if (stackTrace.toString().contains(packageName)) { + return stackTrace.toString(); + } + } + + return culprit; + } + + private static File getStacktraceLocation(Context context) { + return new File(context.getCacheDir(), "crashes"); + } + + public void captureEvent(SentryEventBuilder builder) { + JSONObject request; + if (captureListener != null) { + + builder = captureListener.beforeCapture(builder); + if (builder == null) { + logW("SentryEventBuilder in captureEvent is null"); + return; + } + + request = createRequest(builder); + } else { + request = createRequest(builder); + } + + if (enableDebugLogging) { + //this string can be really big so there is no need to log if logging is disabled + logD("Request - " + request); + } + + doCaptureEventPost(request); + } + + private boolean shouldAttemptPost() { + PackageManager pm = context.getPackageManager(); + int hasPerm = pm.checkPermission(android.Manifest.permission.ACCESS_NETWORK_STATE, packageName); + if (hasPerm == PackageManager.PERMISSION_DENIED) { + return true; + } + + ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo(); + return activeNetworkInfo != null && activeNetworkInfo.isConnected(); + } + + private void doCaptureEventPost(final JSONObject request) { + + if (shouldAttemptPost()) { + submit(new Runnable() { + @Override + public void run() { + sendRequest(request); + } + }); + } else { + submit(new Runnable() { + @Override + public void run() { + addRequest(request); + } + }); + } + } + + private void submit(Runnable task) { + executorService.submit(task); + } + + private void addRequest(JSONObject request) { + internalStorage.addRequest(request); + } + + private void sendRequest(JSONObject request) { + logD("Sending to URL - " + url); + + HttpRequestSender.Builder builder = httpRequestSender.newBuilder(); + if (verifySsl != 0) { + logD("Using http client"); + } else { + logD("Using https client"); + builder.useHttps(); + } + + builder.url(url); + boolean success = false; + try { + builder.header("X-Sentry-Auth", createXSentryAuthHeader()); + builder.header("User-Agent", "sentry-android/" + VERSION); + builder.header("Content-Type", MEDIA_TYPE); + + builder.content(request.toString(), MEDIA_TYPE); + HttpRequestSender.Response httpResponse = builder.build().execute(); + + int status = httpResponse.getStatusCode(); + success = status == HTTP_OK; + + logD("SendEvent - " + status + " " + httpResponse.getContent()); + } catch (Exception e) { + logW(e); + } + + if (success) { + internalStorage.removeBuilder(request); + } else { + addRequest(request); + } + } + + public void setDebugLogging(boolean enableDebugLogging) { + this.enableDebugLogging = enableDebugLogging; + } + + private void logD(String message) { + if (enableDebugLogging) { + Log.d(TAG, message); + } + } + + private void logW(String message) { + if (enableDebugLogging) { + Log.w(TAG, message); + } + } + + private void logW(Exception ex) { + if (enableDebugLogging) { + Log.w(TAG, ex); + } + } + + private void logW(String message, Exception ex) { + logW(message, ex, enableDebugLogging); + } + + private static void logW(String message, Exception ex, boolean enableDebugLogging) { + if (enableDebugLogging) { + Log.w(TAG, message, ex); + } + } + + private class SentryUncaughtExceptionHandler implements UncaughtExceptionHandler { + + UncaughtExceptionHandler defaultExceptionHandler; + boolean disabled; + + // constructor + public SentryUncaughtExceptionHandler(UncaughtExceptionHandler pDefaultExceptionHandler) { + defaultExceptionHandler = pDefaultExceptionHandler; + } + + @Override + public void uncaughtException(Thread thread, Throwable e) { + if (disabled) { + return; + } + // Here you should have a more robust, permanent record of problems + SentryEventBuilder builder = newEventBuilder(e, SentryEventBuilder.SentryEventLevel.FATAL); + if (captureListener != null) { + builder = captureListener.beforeCapture(builder); + } if (builder != null) { - InternalStorage.getInstance().addRequest(new SentryEventRequest(builder)); + addRequest(createRequest(builder)); } else { - Log.e(Sentry.TAG, "SentryEventBuilder in uncaughtException is null"); + logW("SentryEventBuilder in uncaughtException is null"); } - //call original handler - defaultExceptionHandler.uncaughtException(thread, e); - } - - } - - private static class InternalStorage { - - private final static String FILE_NAME = "unsent_requests"; - private List unsentRequests; - - private static InternalStorage getInstance() { - return LazyHolder.instance; - } - - private static class LazyHolder { - private static InternalStorage instance = new InternalStorage(); - } - - private InternalStorage() { - Context context = Sentry.getInstance().context; - try { - File unsetRequestsFile = new File(context.getFilesDir(), FILE_NAME); - if (!unsetRequestsFile.exists()) { - writeObject(context, new ArrayList()); - } - } catch (Exception e) { - e.printStackTrace(); - } - this.unsentRequests = this.readObject(context); - } - - /** - * @return the unsentRequests - */ - public List getUnsentRequests() { - final List copy = new ArrayList<>(); - synchronized (this) { - copy.addAll(unsentRequests); - } - return copy; - } - - public void addRequest(SentryEventRequest request) { - synchronized(this) { - Log.d(Sentry.TAG, "Adding request - " + request.uuid); - if (!this.unsentRequests.contains(request)) { - this.unsentRequests.add(request); - this.writeObject(Sentry.getInstance().context, this.unsentRequests); - } - } - } - - public void removeBuilder(SentryEventRequest request) { - synchronized(this) { - Log.d(Sentry.TAG, "Removing request - " + request.uuid); - this.unsentRequests.remove(request); - this.writeObject(Sentry.getInstance().context, this.unsentRequests); - } - } - - private void writeObject(Context context, List requests) { - try { - FileOutputStream fos = context.openFileOutput(FILE_NAME, Context.MODE_PRIVATE); - ObjectOutputStream oos = new ObjectOutputStream(fos); - oos.writeObject(requests); - oos.close(); - fos.close(); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - } - - private List readObject(Context context) { - try { - FileInputStream fis = context.openFileInput(FILE_NAME); - ObjectInputStream ois = new ObjectInputStream(fis); - List requests = (ArrayList) ois.readObject(); - ois.close(); - fis.close(); - return requests; - } catch (FileNotFoundException e) { - e.printStackTrace(); - } catch (StreamCorruptedException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - } - return new ArrayList(); - } - } - - public abstract static class SentryEventCaptureListener { - - public abstract SentryEventBuilder beforeCapture(SentryEventBuilder builder); - - } - - public static class SentryEventRequest implements Serializable { - private String requestData; - private UUID uuid; - - public SentryEventRequest(SentryEventBuilder builder) { - this.requestData = new JSONObject(builder.event).toString(); - this.uuid = UUID.randomUUID(); - } - - /** - * @return the requestData - */ - public String getRequestData() { - return requestData; - } - - /** - * @return the uuid - */ - public UUID getUuid() { - return uuid; - } - - @Override - public boolean equals(Object other) { - SentryEventRequest otherRequest = (SentryEventRequest) other; - - if (this.uuid != null && otherRequest.uuid != null) { - return uuid.equals(otherRequest.uuid); - } - - return false; - } - - } - - public static class SentryEventBuilder implements Serializable { - - private static final long serialVersionUID = -8589756678369463988L; - - private final static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); - static { - sdf.setTimeZone(TimeZone.getTimeZone("GMT")); - } - - private Map event; - - public static enum SentryEventLevel { - - FATAL("fatal"), - ERROR("error"), - WARNING("warning"), - INFO("info"), - DEBUG("debug"); - - private String value; - SentryEventLevel(String value) { - this.value = value; - } - - } - - public SentryEventBuilder() { - event = new HashMap(); - event.put("event_id", UUID.randomUUID().toString().replace("-", "")); - event.put("platform", "java"); - this.setTimestamp(System.currentTimeMillis()); - } - - public SentryEventBuilder(Throwable t, SentryEventLevel level) { - this(); - - String culprit = getCause(t, t.getMessage()); - - this.setMessage(t.getMessage()) - .setCulprit(culprit) - .setLevel(level) - .setException(t); - } - - /** - * "message": "SyntaxError: Wattttt!" - * @param message Message - * @return SentryEventBuilder - */ - public SentryEventBuilder setMessage(String message) { - event.put("message", message); - return this; - } - - /** - * "timestamp": "2011-05-02T17:41:36" - * @param timestamp Timestamp - * @return SentryEventBuilder - */ - public SentryEventBuilder setTimestamp(long timestamp) { - event.put("timestamp", sdf.format(new Date(timestamp))); - return this; - } - - /** - * "level": "warning" - * @param level Level - * @return SentryEventBuilder - */ - public SentryEventBuilder setLevel(SentryEventLevel level) { - event.put("level", level.value); - return this; - } - - /** - * "logger": "my.logger.name" - * @param logger Logger - * @return SentryEventBuilder - */ - public SentryEventBuilder setLogger(String logger) { - event.put("logger", logger); - return this; - } - - /** - * "culprit": "my.module.function_name" - * @param culprit Culprit - * @return SentryEventBuilder - */ - public SentryEventBuilder setCulprit(String culprit) { - event.put("culprit", culprit); - return this; - } - - /** - * - * @param user User - * @return SentryEventBuilder - */ - public SentryEventBuilder setUser(Map user) { - setUser(new JSONObject(user)); - return this; - } - - public SentryEventBuilder setUser(JSONObject user) { - event.put("user", user); - return this; - } - - public JSONObject getUser() { - if (!event.containsKey("user")) { - setTags(new HashMap()); - } - - return (JSONObject) event.get("user"); - } - - /** - * - * @param tags Tags - * @return SentryEventBuilder - */ - public SentryEventBuilder setTags(Map tags) { - setTags(new JSONObject(tags)); - return this; - } - - public SentryEventBuilder setTags(JSONObject tags) { - event.put("tags", tags); - return this; - } - - public JSONObject getTags() { - if (!event.containsKey("tags")) { - setTags(new HashMap()); - } - - return (JSONObject) event.get("tags"); - } - - /** - * - * @param serverName Server name - * @return SentryEventBuilder - */ - public SentryEventBuilder setServerName(String serverName) { - event.put("server_name", serverName); - return this; - } - - /** - * - * @param release Release - * @return SentryEventBuilder - */ - public SentryEventBuilder setRelease(String release) { - event.put("release", release); - return this; - } - - /** - * - * @param name Name - * @param version Version - * @return SentryEventBuilder - */ - public SentryEventBuilder addModule(String name, String version) { - JSONArray modules; - if (!event.containsKey("modules")) { - modules = new JSONArray(); - event.put("modules", modules); - } else { - modules = (JSONArray)event.get("modules"); - } - - if (name != null && version != null) { - String[] module = {name, version}; - modules.put(new JSONArray(Arrays.asList(module))); - } - - return this; - } - - /** - * - * @param extra Extra - * @return SentryEventBuilder - */ - public SentryEventBuilder setExtra(Map extra) { - setExtra(new JSONObject(extra)); - return this; - } - - public SentryEventBuilder setExtra(JSONObject extra) { - event.put("extra", extra); - return this; - } - - public JSONObject getExtra() { - if (!event.containsKey("extra")) { - setExtra(new HashMap()); - } - - return (JSONObject) event.get("extra"); - } - - /** - * - * @param t Throwable - * @return SentryEventBuilder - */ + //call original handler + if (defaultExceptionHandler != null) { + defaultExceptionHandler.uncaughtException(thread, e); + } + } + } + + private static JSONObject createRequest(SentryEventBuilder builder) { + return builder.toJson(); + } + + public SentryEventBuilder newEventBuilder(Throwable e, SentryEventBuilder.SentryEventLevel level) { + return new SentryEventBuilder(e, level, getCause(e, e.getMessage()), enableDebugLogging); + } + + private final class InternalStorage { + + private List unsentRequests; + private String fileName; + + private InternalStorage(String fileName) { + this.fileName = fileName; + } + + /** + * @return the unsentRequests + */ + public List getUnsentRequests() { + synchronized (this) { + return new ArrayList(lazyGetUnsentRequests()); + } + } + + private List lazyGetUnsentRequests() { + if (unsentRequests == null) { + unsentRequests = readRequests(); + } + return unsentRequests; + } + + public void addRequest(JSONObject request) { + synchronized (this) { + logD("Adding request - " + getEventId(request)); + if (!contains(request)) { + lazyGetUnsentRequests().add(request); + writeRequests(); + } + } + } + + private boolean contains(JSONObject request) { + List list = lazyGetUnsentRequests(); + for (JSONObject object : list) { + if (getEventId(object).equals(getEventId(request))) { + return true; + } + } + return false; + } + + public void removeBuilder(JSONObject request) { + synchronized (this) { + logD("Removing request - " + getEventId(request)); + lazyGetUnsentRequests().remove(request); + writeRequests(); + } + } + + private void writeRequests() { + OutputStream fos = null; + try { + + JSONArray jsonArray = new JSONArray(); + Iterable requests = lazyGetUnsentRequests(); + for (JSONObject request : requests) { + jsonArray.put(request); + } + String s = jsonArray.toString(); + fos = context.openFileOutput(fileName, Context.MODE_PRIVATE); + fos.write(s.getBytes("UTF-8")); + } catch (IOException e) { + logW(e); + } finally { + try { + if (fos != null) { + fos.close(); + } + } catch (IOException e) { + logW(e); + } + } + } + + private List readRequests() { + Reader reader = null; + try { + StringWriter sw = new StringWriter(); + reader = new InputStreamReader(context.openFileInput(fileName)); + char[] buffer = new char[DEFAULT_BUFFER_SIZE]; + int n; + while (-1 != (n = reader.read(buffer))) { + sw.write(buffer, 0, n); + } + JSONArray jsonArray = new JSONArray(sw.toString()); + List requests = new ArrayList<>(jsonArray.length()); + for (int i = 0; i < jsonArray.length(); i++) { + requests.add(jsonArray.getJSONObject(i)); + } + return requests; + } catch (Exception e) { + logW(e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + logW(e); + } + } + } + return new ArrayList(); + } + } + + private static String getEventId(JSONObject object) { + return object.optString(EVENT_ID); + } + + public interface SentryEventCaptureListener { + + SentryEventBuilder beforeCapture(SentryEventBuilder builder); + } + + public static final class SentryEventBuilder { + + private static final ThreadLocal sdf = new ThreadLocal() { + @Override + protected SimpleDateFormat initialValue() { + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US); + format.setTimeZone(TimeZone.getTimeZone("GMT")); + return format; + } + }; + private JSONObject event; + + public enum SentryEventLevel { + + FATAL("fatal"), + ERROR("error"), + WARNING("warning"), + INFO("info"), + DEBUG("debug"); + private String value; + + SentryEventLevel(String value) { + this.value = value; + } + + } + + private boolean enableLogging; + + private SentryEventBuilder(boolean enableLogging) { + this.enableLogging = enableLogging; + event = new JSONObject(); + setString(EVENT_ID, UUID.randomUUID().toString().replace("-", "")); + setString("platform", "java"); + setTimestamp(System.currentTimeMillis()); + } + + private SentryEventBuilder(Throwable t, SentryEventLevel level, String cause, boolean enableLogging) { + this(enableLogging); + + setMessage(t.getMessage()).setCulprit(cause).setLevel(level).setException(t); + } + + /** + * "message": "SyntaxError: Wattttt!" + * + * @param message Message + * @return SentryEventBuilder + */ + public SentryEventBuilder setMessage(String message) { + return setString("message", message); + } + + /** + * "timestamp": "2011-05-02T17:41:36" + * + * @param timestamp Timestamp + * @return SentryEventBuilder + */ + public SentryEventBuilder setTimestamp(long timestamp) { + return setString("timestamp", sdf.get().format(new Date(timestamp))); + } + + /** + * "level": "warning" + * + * @param level Level + * @return SentryEventBuilder + */ + public SentryEventBuilder setLevel(SentryEventLevel level) { + return setString("level", level.value); + } + + /** + * "logger": "my.logger.name" + * + * @param logger Logger + * @return SentryEventBuilder + */ + public SentryEventBuilder setLogger(String logger) { + return setString("logger", logger); + } + + /** + * "culprit": "my.module.function_name" + * + * @param culprit Culprit + * @return SentryEventBuilder + */ + public SentryEventBuilder setCulprit(String culprit) { + return setString("culprit", culprit); + } + + private SentryEventBuilder setString(String key, String culprit) { + try { + event.put(key, culprit); + } catch (JSONException e) { + //there should be no exception + logW("", e, enableLogging); + } + return this; + } + + /** + * @param user User + * @return SentryEventBuilder + */ + public SentryEventBuilder setUser(Map user) { + setUser(new JSONObject(user)); + return this; + } + + public SentryEventBuilder setUser(JSONObject user) { + return setJsonObject("user", user); + } + + public JSONObject getUser() { + if (!event.has("user")) { + setTags(new JSONObject()); + } + + return (JSONObject) event.opt("user"); + } + + /** + * @param tags Tags + * @return SentryEventBuilder + */ + public SentryEventBuilder setTags(Map tags) { + setTags(new JSONObject(tags)); + return this; + } + + public SentryEventBuilder setTags(JSONObject tags) { + return setJsonObject("tags", tags); + } + + private SentryEventBuilder setJsonObject(String key, JSONObject object) { + try { + event.put(key, object); + } catch (JSONException e) { + //there should be no exception + logW("", e, enableLogging); + } + return this; + } + + public JSONObject getTags() { + if (!event.has("tags")) { + setTags(new JSONObject()); + } + + return (JSONObject) event.opt("tags"); + } + + /** + * @param serverName Server name + * @return SentryEventBuilder + */ + public SentryEventBuilder setServerName(String serverName) { + return setString("server_name", serverName); + } + + /** + * @param release Release + * @return SentryEventBuilder + */ + public SentryEventBuilder setRelease(String release) { + return setString("release", release); + } + + /** + * @param name Name + * @param version Version + * @return SentryEventBuilder + */ + public SentryEventBuilder addModule(String name, String version) { + JSONArray modules; + try { + + if (!event.has("modules")) { + modules = new JSONArray(); + event.put("modules", modules); + } else { + modules = (JSONArray) event.get("modules"); + } + + if (name != null && version != null) { + String[] module = {name, version}; + modules.put(new JSONArray(Arrays.asList(module))); + } + } catch (JSONException e) { + logW("", e, enableLogging); + } + return this; + } + + /** + * @param extra Extra + * @return SentryEventBuilder + */ + public SentryEventBuilder setExtra(Map extra) { + return setExtra(new JSONObject(extra)); + } + + public SentryEventBuilder setExtra(JSONObject extra) { + return setJsonObject("extra", extra); + } + + public JSONObject getExtra() { + if (!event.has("extra")) { + setExtra(new JSONObject()); + } + + return (JSONObject) event.opt("extra"); + } + + /** + * @param t Throwable + * @return SentryEventBuilder + */ public SentryEventBuilder setException(Throwable t) { JSONArray values = new JSONArray(); @@ -899,7 +757,7 @@ public SentryEventBuilder setException(Throwable t) { values.put(exception); } catch (JSONException e) { - Log.e(TAG, "Failed to build sentry report for " + t, e); + logW("Failed to build sentry report for " + t, e, enableLogging); } t = t.getCause(); @@ -911,7 +769,7 @@ public SentryEventBuilder setException(Throwable t) { exceptionReport.put("values", values); event.put("exception", exceptionReport); } catch (JSONException e) { - Log.e(TAG, "Unable to attach exception to event " + values, e); + logW("Unable to attach exception to event " + values, e, enableLogging); } return this; @@ -939,13 +797,10 @@ public static JSONObject getStackTrace(Throwable t) throws JSONException { frame.put("module", className); // Take out some of the system packages to improve the exception folding on the sentry server - if (className.startsWith("android.") - || className.startsWith("java.") - || className.startsWith("dalvik.") - || className.startsWith("com.android.")) { + if (className.startsWith("android.") || className.startsWith("java.") || className.startsWith("dalvik.") || className.startsWith("com.android.")) { inApp = false; - } + } frame.put("in_app", inApp); @@ -957,6 +812,9 @@ public static JSONObject getStackTrace(Throwable t) throws JSONException { return frameHash; } - } + private JSONObject toJson() { + return event; + } + } } diff --git a/sentry-android/src/main/java/com/joshdholtz/sentry/SentryInstance.java b/sentry-android/src/main/java/com/joshdholtz/sentry/SentryInstance.java new file mode 100644 index 0000000..9a17d39 --- /dev/null +++ b/sentry-android/src/main/java/com/joshdholtz/sentry/SentryInstance.java @@ -0,0 +1,26 @@ +package com.joshdholtz.sentry; + +import android.content.Context; + +public final class SentryInstance { + + private static final String FILE_NAME = "unsent_requests"; + private static SentryInstance ourInstance = new SentryInstance(); + private Sentry sentry; + + public static void init(Context context, String dsn, HttpRequestSender httpRequestSender) { + ourInstance.sentry = Sentry.newInstance(context, dsn, httpRequestSender, FILE_NAME); + } + + /** + * {@link #init(Context, String, HttpRequestSender)} must be called before this + * + * @return {@code Sentry} instance created in {@link #init(Context, String, HttpRequestSender)} + */ + public static Sentry getInstance() { + return ourInstance.sentry; + } + + private SentryInstance() { + } +} diff --git a/sentry-apache-http-client/.gitignore b/sentry-apache-http-client/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/sentry-apache-http-client/.gitignore @@ -0,0 +1 @@ +/build diff --git a/sentry-apache-http-client/build.gradle b/sentry-apache-http-client/build.gradle new file mode 100644 index 0000000..fed02c6 --- /dev/null +++ b/sentry-apache-http-client/build.gradle @@ -0,0 +1,32 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 23 + buildToolsVersion "23.0.3" + + useLibrary 'org.apache.http.legacy' + + defaultConfig { + minSdkVersion 3 + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } +} + +ext { + libraryName = 'sentry-android-apache-http-client' + artifact = 'sentry-android-apache-http-client' + libraryDescription = 'A Sentry Apache http client for Android' +} + +dependencies { + provided 'org.jbundle.util.osgi.wrapped:org.jbundle.util.osgi.wrapped.org.apache.http.client:4.1.2' + compile "com.joshdholtz.sentry:sentry-android:${libraryVersion}" +} diff --git a/sentry-apache-http-client/sentry-apache-http-client.iml b/sentry-apache-http-client/sentry-apache-http-client.iml new file mode 100644 index 0000000..1aadfb6 --- /dev/null +++ b/sentry-apache-http-client/sentry-apache-http-client.iml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sentry-apache-http-client/src/main/AndroidManifest.xml b/sentry-apache-http-client/src/main/AndroidManifest.xml new file mode 100644 index 0000000..9c929cc --- /dev/null +++ b/sentry-apache-http-client/src/main/AndroidManifest.xml @@ -0,0 +1,3 @@ + + + diff --git a/sentry-apache-http-client/src/main/java/com/joshdholtz/sentry/http/apache/ApacheHttpRequestSender.java b/sentry-apache-http-client/src/main/java/com/joshdholtz/sentry/http/apache/ApacheHttpRequestSender.java new file mode 100644 index 0000000..5d0a8b7 --- /dev/null +++ b/sentry-apache-http-client/src/main/java/com/joshdholtz/sentry/http/apache/ApacheHttpRequestSender.java @@ -0,0 +1,214 @@ +package com.joshdholtz.sentry.http.apache; + +import com.joshdholtz.sentry.BaseHttpBuilder; +import com.joshdholtz.sentry.HttpRequestSender; + +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ssl.SSLSocketFactory; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpProtocolParams; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.Socket; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.security.KeyManagementException; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.UnrecoverableKeyException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.Map; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +public class ApacheHttpRequestSender implements HttpRequestSender { + + private static final int TIMEOUT = 10000; + private static final String UTF_8 = "utf-8"; + + @Override + public Builder newBuilder() { + return new ApacheBuilder(); + } + + static class ApacheBuilder extends BaseHttpBuilder { + + private static class ApacheResponse implements Response { + + private final HttpResponse response; + + public ApacheResponse(HttpResponse response) { + this.response = response; + } + + @Override + public int getStatusCode() { + return response.getStatusLine().getStatusCode(); + } + + @Override + public String getContent() { + // Gets the input stream and unpackages the response into a command + byte[] byteResp = null; + if (response.getEntity() != null) { + try { + InputStream in = response.getEntity().getContent(); + byteResp = this.readBytes(in); + + } catch (IOException e) { + e.printStackTrace(); + } + } + + String stringResponse = null; + Charset charsetInput = Charset.forName("UTF-8"); + CharsetDecoder decoder = charsetInput.newDecoder(); + CharBuffer cbuf = null; + try { + cbuf = decoder.decode(ByteBuffer.wrap(byteResp)); + stringResponse = cbuf.toString(); + } catch (CharacterCodingException e) { + e.printStackTrace(); + } + return stringResponse; + + } + + private byte[] readBytes(InputStream inputStream) throws IOException { + // this dynamically extends to take the bytes you read + ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream(); + + // this is storage overwritten on each iteration with bytes + int bufferSize = 1024; + byte[] buffer = new byte[bufferSize]; + + // we need to know how may bytes were read to write them to the byteBuffer + int len = 0; + while ((len = inputStream.read(buffer)) != -1) { + byteBuffer.write(buffer, 0, len); + } + + // and then we can return your byte array. + return byteBuffer.toByteArray(); + } + } + + @Override + protected Request build(String url, Map headers, String requestData, String mediaType, boolean useHttps) throws Exception { + final HttpClient client; + if (useHttps) { + client = getHttpsClient(); + } else { + client = new DefaultHttpClient(); + } + final HttpPost httpPost = new HttpPost(url); + + HttpParams httpParams = httpPost.getParams(); + HttpConnectionParams.setConnectionTimeout(httpParams, TIMEOUT); + HttpConnectionParams.setSoTimeout(httpParams, TIMEOUT); + + HttpProtocolParams.setContentCharset(httpParams, UTF_8); + HttpProtocolParams.setHttpElementCharset(httpParams, UTF_8); + + for (Map.Entry header : headers.entrySet()) { + httpPost.setHeader(header.getKey(), header.getValue()); + } + + httpPost.setEntity(new StringEntity(requestData, UTF_8)); + + return new Request() { + @Override + public Response execute() throws Exception { + final HttpResponse response = client.execute(httpPost); + return new ApacheResponse(response); + } + }; + } + + private static class ExSSLSocketFactory extends SSLSocketFactory { + + SSLContext sslContext = SSLContext.getInstance("TLS"); + + public ExSSLSocketFactory(KeyStore truststore) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException, UnrecoverableKeyException { + super(truststore); + TrustManager x509TrustManager = new X509TrustManager() { + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public X509Certificate[] getAcceptedIssuers() { + return null; + } + }; + + sslContext.init(null, new TrustManager[]{x509TrustManager}, null); + } + + public ExSSLSocketFactory(SSLContext context) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException, UnrecoverableKeyException { + super(null); + sslContext = context; + } + + @Override + public Socket createSocket(Socket socket, String host, int port, boolean autoClose) throws IOException, UnknownHostException { + return sslContext.getSocketFactory().createSocket(socket, host, port, autoClose); + } + + @Override + public Socket createSocket() throws IOException { + return sslContext.getSocketFactory().createSocket(); + } + } + + public static HttpClient getHttpsClient() { + DefaultHttpClient defaultHttpClient = new DefaultHttpClient(); + try { + X509TrustManager x509TrustManager = new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + }; + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[]{x509TrustManager}, null); + SSLSocketFactory sslSocketFactory = new ExSSLSocketFactory(sslContext); + sslSocketFactory.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + ClientConnectionManager clientConnectionManager = defaultHttpClient.getConnectionManager(); + SchemeRegistry schemeRegistry = clientConnectionManager.getSchemeRegistry(); + schemeRegistry.register(new Scheme("https", sslSocketFactory, 443)); + return new DefaultHttpClient(clientConnectionManager, defaultHttpClient.getParams()); + } catch (Exception ex) { + return defaultHttpClient; + } + } + } +} diff --git a/sentry-app/build.gradle b/sentry-app/build.gradle index d539c96..dc2e857 100644 --- a/sentry-app/build.gradle +++ b/sentry-app/build.gradle @@ -24,5 +24,5 @@ android { dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:23.1.1' - compile project(':sentry-android') + compile project(':sentry-apache-http-client') } \ No newline at end of file diff --git a/sentry-app/sentry-app.iml b/sentry-app/sentry-app.iml index 3b35d2c..8ced4b0 100644 --- a/sentry-app/sentry-app.iml +++ b/sentry-app/sentry-app.iml @@ -47,6 +47,7 @@ + @@ -54,6 +55,7 @@ + @@ -61,13 +63,7 @@ - - - - - - - + @@ -75,25 +71,31 @@ + + + + + + + + + - - - - + - + - + @@ -101,10 +103,10 @@ - + - + \ No newline at end of file diff --git a/sentry-app/src/main/AndroidManifest.xml b/sentry-app/src/main/AndroidManifest.xml index 902db6f..2c35531 100644 --- a/sentry-app/src/main/AndroidManifest.xml +++ b/sentry-app/src/main/AndroidManifest.xml @@ -9,6 +9,7 @@ android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" + android:name=".SentryApplication" android:theme="@style/AppTheme" > Date: Wed, 10 Aug 2016 11:52:07 +0200 Subject: [PATCH 2/5] Changed the way of passing credentials --- build.gradle | 2 +- sentry-android-okhttp-client/build.gradle | 20 +++++++++++++++- .../sentry-android-okhttp-client.iml | 10 ++++---- sentry-android/build.gradle | 19 ++++++++++++++- sentry-android/sentry-android.iml | 3 ++- .../java/com/joshdholtz/sentry/Sentry.java | 24 ++++++++++--------- .../com/joshdholtz/sentry/SentryInstance.java | 9 +++---- .../sentry-apache-http-client.iml | 18 +++++++------- sentry-app/sentry-app.iml | 16 ++++++------- 9 files changed, 80 insertions(+), 41 deletions(-) diff --git a/build.gradle b/build.gradle index a70ea05..0c12e3c 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,7 @@ allprojects { siteUrl = 'https://github.com/nuuneoi/FBLikeAndroid' gitUrl = 'https://github.com/nuuneoi/FBLikeAndroid.git' - libraryVersion = '1.4.2' + libraryVersion = '1.4.2-finanteq' developerId = 'joshdholtz' developerName = 'Josh Holtz' diff --git a/sentry-android-okhttp-client/build.gradle b/sentry-android-okhttp-client/build.gradle index 4512c14..27dc032 100644 --- a/sentry-android-okhttp-client/build.gradle +++ b/sentry-android-okhttp-client/build.gradle @@ -1,4 +1,8 @@ +group = 'com.joshdholtz.sentry' +version = '1.4.2-finanteq' apply plugin: 'com.android.library' +apply plugin: 'com.github.dcendents.android-maven' + android { compileSdkVersion 23 @@ -25,9 +29,23 @@ ext { } dependencies { - compile "com.squareup.okhttp3:okhttp:3.3.1" + compile "com.squareup.okhttp3:okhttp:3.4.1" +// compile project(":sentry-android") compile "com.joshdholtz.sentry:sentry-android:${libraryVersion}" } apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' + +uploadArchives { + repositories { + mavenDeployer { + repository(url: nexusReleaseRepository) { + authentication(userName: nexusUserName, password: nexusPassword) + } + snapshotRepository(url: nexusSnapshotRepository) { + authentication(userName: nexusUserName, password: nexusPassword) + } + } + } +} \ No newline at end of file diff --git a/sentry-android-okhttp-client/sentry-android-okhttp-client.iml b/sentry-android-okhttp-client/sentry-android-okhttp-client.iml index 096aef7..f671aaa 100644 --- a/sentry-android-okhttp-client/sentry-android-okhttp-client.iml +++ b/sentry-android-okhttp-client/sentry-android-okhttp-client.iml @@ -1,5 +1,5 @@ - + @@ -87,7 +87,6 @@ - @@ -97,6 +96,7 @@ + @@ -104,8 +104,8 @@ - - - + + + \ No newline at end of file diff --git a/sentry-android/build.gradle b/sentry-android/build.gradle index 8c6c710..d0797fd 100644 --- a/sentry-android/build.gradle +++ b/sentry-android/build.gradle @@ -1,11 +1,14 @@ +group = 'com.joshdholtz.sentry' +version = '1.4.2-finanteq' apply plugin: 'com.android.library' +apply plugin: 'com.github.dcendents.android-maven' android { compileSdkVersion 23 buildToolsVersion "23.0.3" defaultConfig { - minSdkVersion 3 + minSdkVersion 5 targetSdkVersion 23 versionCode 1 versionName "1.0" @@ -31,3 +34,17 @@ ext { apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' + + +uploadArchives { + repositories { + mavenDeployer { + repository(url: nexusReleaseRepository) { + authentication(userName: nexusUserName, password: nexusPassword) + } + snapshotRepository(url: nexusSnapshotRepository) { + authentication(userName: nexusUserName, password: nexusPassword) + } + } + } +} diff --git a/sentry-android/sentry-android.iml b/sentry-android/sentry-android.iml index 5fe1fa7..735716a 100644 --- a/sentry-android/sentry-android.iml +++ b/sentry-android/sentry-android.iml @@ -1,5 +1,5 @@ - + @@ -96,6 +96,7 @@ + diff --git a/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java b/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java index 204ab8e..13674c7 100755 --- a/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java +++ b/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java @@ -8,6 +8,7 @@ import android.net.Uri; import android.os.Bundle; import android.util.Log; +import android.util.Pair; import org.json.JSONArray; import org.json.JSONException; @@ -51,6 +52,7 @@ public final class Sentry { private String sentryVersion = "7"; private String url; private Uri dsn; + private Pair credentials; private String packageName; private int verifySsl; private SentryEventCaptureListener captureListener; @@ -70,10 +72,11 @@ public void run() { private boolean enableDebugLogging; private SentryUncaughtExceptionHandler uncaughtExceptionHandler; - private Sentry(Context applicationContext, Uri dsnUri, String url, int verifySsl, String storageFileName, HttpRequestSender httpRequestSender) { + private Sentry(Context applicationContext, Uri dsnUri, String url, Pair credentials, int verifySsl, String storageFileName, HttpRequestSender httpRequestSender) { context = applicationContext; dsn = dsnUri; this.url = url; + this.credentials = credentials; this.verifySsl = verifySsl; this.httpRequestSender = httpRequestSender; packageName = applicationContext.getPackageName(); @@ -84,15 +87,16 @@ private Sentry(Context applicationContext, Uri dsnUri, String url, int verifySsl * This method returns new {@code Sentry} instance which can operate separately with other instances. Should be called once, usually in {@link Application#onCreate()} and obtained from some singleton. If you have only one instance than you can use {@link SentryInstance}. In {@link android.app.Activity#onCreate(Bundle)} you should call at least {@link #sendAllCachedCapturedEvents()} to try to send cached events * * @param context this can be any context because {@link Context#getApplicationContext()} is used to avoid memory leak - * @param dsn DSN of your project + * @param dsnWithoutCredentials DSN of your project - remove credentials from it to avoid warning in Google Play console * @param httpRequestSender used for sending events to Sentry server * @param storageFileName unsent requests storage file - must be different for different instances + * @param credentials credentials from DSN * @return new {@code Sentry} instance */ - public static Sentry newInstance(Context context, String dsn, HttpRequestSender httpRequestSender, String storageFileName) { - Uri dsnUri = Uri.parse(dsn); + public static Sentry newInstance(Context context, String dsnWithoutCredentials, HttpRequestSender httpRequestSender, String storageFileName, Pair credentials) { + Uri dsnUri = Uri.parse(dsnWithoutCredentials); - Sentry sentry = new Sentry(context.getApplicationContext(), dsnUri, getUrl(dsnUri), getVerifySsl(dsnUri), storageFileName, httpRequestSender); + Sentry sentry = new Sentry(context.getApplicationContext(), dsnUri, getUrl(dsnUri), credentials, getVerifySsl(dsnUri), storageFileName, httpRequestSender); sentry.setupUncaughtExceptionHandler(); return sentry; } @@ -143,21 +147,19 @@ public void setupUncaughtExceptionHandler() { private String createXSentryAuthHeader() { String header = ""; - String authority = dsn.getAuthority().replace("@" + dsn.getHost(), ""); - - String[] authorityParts = authority.split(":"); - String publicKey = authorityParts[0]; - String secretKey = authorityParts[1]; + String publicKey = credentials.first; + String secretKey = credentials.second; header += "Sentry sentry_version=" + sentryVersion + ","; header += "sentry_client=sentry-android/" + VERSION + ","; header += "sentry_timestamp=" + System.currentTimeMillis() + ","; - header += "sentry_key=" + publicKey + ","; + header += "sentry_key=" + publicKey+","; header += "sentry_secret=" + secretKey; return header; } + public void sendAllCachedCapturedEvents() { if (shouldAttemptPost()) { submit(sendUnsentRequests); diff --git a/sentry-android/src/main/java/com/joshdholtz/sentry/SentryInstance.java b/sentry-android/src/main/java/com/joshdholtz/sentry/SentryInstance.java index 9a17d39..bd27b49 100644 --- a/sentry-android/src/main/java/com/joshdholtz/sentry/SentryInstance.java +++ b/sentry-android/src/main/java/com/joshdholtz/sentry/SentryInstance.java @@ -1,6 +1,7 @@ package com.joshdholtz.sentry; import android.content.Context; +import android.util.Pair; public final class SentryInstance { @@ -8,14 +9,14 @@ public final class SentryInstance { private static SentryInstance ourInstance = new SentryInstance(); private Sentry sentry; - public static void init(Context context, String dsn, HttpRequestSender httpRequestSender) { - ourInstance.sentry = Sentry.newInstance(context, dsn, httpRequestSender, FILE_NAME); + public static void init(Context context, String dsnWithoutCredentials, HttpRequestSender httpRequestSender, Pair credentials) { + ourInstance.sentry = Sentry.newInstance(context, dsnWithoutCredentials, httpRequestSender, FILE_NAME, credentials); } /** - * {@link #init(Context, String, HttpRequestSender)} must be called before this + * {@link #init(Context, String, HttpRequestSender, Pair)} must be called before this * - * @return {@code Sentry} instance created in {@link #init(Context, String, HttpRequestSender)} + * @return {@code Sentry} instance created in {@link #init(Context, String, HttpRequestSender, Pair)} (Context, String, HttpRequestSender)} */ public static Sentry getInstance() { return ourInstance.sentry; diff --git a/sentry-apache-http-client/sentry-apache-http-client.iml b/sentry-apache-http-client/sentry-apache-http-client.iml index 1aadfb6..1be95a4 100644 --- a/sentry-apache-http-client/sentry-apache-http-client.iml +++ b/sentry-apache-http-client/sentry-apache-http-client.iml @@ -65,14 +65,6 @@ - - - - - - - - @@ -81,6 +73,14 @@ + + + + + + + + @@ -100,8 +100,8 @@ - + \ No newline at end of file diff --git a/sentry-app/sentry-app.iml b/sentry-app/sentry-app.iml index 8ced4b0..34a1428 100644 --- a/sentry-app/sentry-app.iml +++ b/sentry-app/sentry-app.iml @@ -64,14 +64,6 @@ - - - - - - - - @@ -80,6 +72,14 @@ + + + + + + + + From df098cde6c1f9b93573f69806dfd16a58ed70408 Mon Sep 17 00:00:00 2001 From: "lukasz.suski" Date: Fri, 3 Feb 2017 15:35:15 +0100 Subject: [PATCH 3/5] Merge changes from version 1.5.4 --- .gitignore | 7 +- build.gradle | 28 ++ gradle/wrapper/gradle-wrapper.properties | 2 +- sentry-android-okhttp-client/build.gradle | 23 +- sentry-android/build.gradle | 31 +- .../sentry/SentryEventBuilderTest.java | 25 +- .../sentry/AppInfoSentryCaptureListener.java | 169 +++++++ .../java/com/joshdholtz/sentry/Sentry.java | 456 ++++++++++++++---- .../com/joshdholtz/sentry/SentryInstance.java | 9 +- sentry-apache-http-client/build.gradle | 15 +- sentry-app/build.gradle | 17 +- .../joshdholtz/sentryapp/MainActivity.java | 18 +- .../sentryapp/SentryApplication.java | 5 +- 13 files changed, 638 insertions(+), 167 deletions(-) create mode 100644 sentry-android/src/main/java/com/joshdholtz/sentry/AppInfoSentryCaptureListener.java diff --git a/.gitignore b/.gitignore index 5377aa5..021a2cd 100644 --- a/.gitignore +++ b/.gitignore @@ -20,9 +20,6 @@ local.properties .classpath .gradle -/local.properties -/.idea/workspace.xml -/.idea/misc.xml -/.idea/libraries .DS_Store -/build +*.iml +.idea/ diff --git a/build.gradle b/build.gradle index 2f769d4..6243a6f 100644 --- a/build.gradle +++ b/build.gradle @@ -18,4 +18,32 @@ allprojects { url 'https://dl.bintray.com/joshdholtz/maven/' } } + + ext { + bintrayRepo = 'maven' + bintrayName = 'sentry-android' + + publishedGroupId = 'com.joshdholtz.sentry' + + + siteUrl = 'https://github.com/nuuneoi/FBLikeAndroid' + gitUrl = 'https://github.com/nuuneoi/FBLikeAndroid.git' + + libraryVersion = '1.5.5' + + developerId = 'joshdholtz' + developerName = 'Josh Holtz' + developerEmail = 'josh@rokkincat.com' + + licenseName = 'The MIT License (MIT)' + licenseUrl = 'http://opensource.org/licenses/mit-license.php' + allLicenses = ["MIT"] + + ext_compileSdkVersion = 25 + ext_buildToolsVersion = "25.0.2" + + ext_minSdkVersion = 10 + ext_targetSdkVersion = 25 + } + } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index bb7f185..54ff6f2 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip diff --git a/sentry-android-okhttp-client/build.gradle b/sentry-android-okhttp-client/build.gradle index 27dc032..3cd22a1 100644 --- a/sentry-android-okhttp-client/build.gradle +++ b/sentry-android-okhttp-client/build.gradle @@ -1,19 +1,21 @@ -group = 'com.joshdholtz.sentry' -version = '1.4.2-finanteq' +plugins { + id "com.jfrog.bintray" version "1.7.3" +} apply plugin: 'com.android.library' apply plugin: 'com.github.dcendents.android-maven' android { - compileSdkVersion 23 - buildToolsVersion "23.0.3" + compileSdkVersion ext_compileSdkVersion + buildToolsVersion ext_buildToolsVersion defaultConfig { - minSdkVersion 10 - targetSdkVersion 23 + minSdkVersion ext_minSdkVersion + targetSdkVersion ext_targetSdkVersion versionCode 1 versionName "1.0" } + buildTypes { release { minifyEnabled false @@ -22,6 +24,7 @@ android { } } + ext { libraryName = 'sentry-android-okhttp-client' artifact = 'sentry-android-okhttp-client' @@ -29,9 +32,9 @@ ext { } dependencies { - compile "com.squareup.okhttp3:okhttp:3.4.1" -// compile project(":sentry-android") - compile "com.joshdholtz.sentry:sentry-android:${libraryVersion}" + compile "com.squareup.okhttp3:okhttp:3.5.0" + compile project(":sentry-android") +// compile "com.joshdholtz.sentry:sentry-android:${libraryVersion}" } apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' @@ -48,4 +51,4 @@ uploadArchives { } } } -} \ No newline at end of file +} diff --git a/sentry-android/build.gradle b/sentry-android/build.gradle index a485c5d..f36ac2f 100644 --- a/sentry-android/build.gradle +++ b/sentry-android/build.gradle @@ -1,24 +1,22 @@ plugins { - id "com.jfrog.bintray" version "1.7" + id "com.jfrog.bintray" version "1.7.3" } apply plugin: 'com.android.library' - -def SentryAndroidVersion = "1.5.4-finanteq" - android { - compileSdkVersion 25 - buildToolsVersion "25.0.3" + compileSdkVersion ext_compileSdkVersion + buildToolsVersion ext_buildToolsVersion defaultConfig { - minSdkVersion 5 - targetSdkVersion 25 + minSdkVersion ext_minSdkVersion + targetSdkVersion ext_targetSdkVersion versionCode 1 versionName "1.0" - buildConfigField "String", "SENTRY_ANDROID_VERSION", "\"${SentryAndroidVersion}\"" + buildConfigField "String", "SENTRY_ANDROID_VERSION", "\"${libraryVersion}\"" } + buildTypes { release { minifyEnabled false @@ -39,19 +37,6 @@ ext { libraryDescription = 'A Sentry client for Android' } + apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle' apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle' - - -uploadArchives { - repositories { - mavenDeployer { - repository(url: nexusReleaseRepository) { - authentication(userName: nexusUserName, password: nexusPassword) - } - snapshotRepository(url: nexusSnapshotRepository) { - authentication(userName: nexusUserName, password: nexusPassword) - } - } - } -} diff --git a/sentry-android/src/androidTest/java/com/joshdholtz/sentry/SentryEventBuilderTest.java b/sentry-android/src/androidTest/java/com/joshdholtz/sentry/SentryEventBuilderTest.java index 48d0784..613206f 100644 --- a/sentry-android/src/androidTest/java/com/joshdholtz/sentry/SentryEventBuilderTest.java +++ b/sentry-android/src/androidTest/java/com/joshdholtz/sentry/SentryEventBuilderTest.java @@ -1,7 +1,5 @@ package com.joshdholtz.sentry; -import android.provider.Telephony; - import com.google.common.base.Joiner; import junit.framework.TestCase; @@ -11,7 +9,6 @@ import java.util.HashMap; import java.util.Map; -import java.util.regex.Pattern; public class SentryEventBuilderTest extends TestCase { @@ -21,7 +18,7 @@ public void testAddExtra() throws JSONException { initialExtra.put("key1", "value1"); initialExtra.put("key2", "value2"); - Sentry.SentryEventBuilder builder = new Sentry.SentryEventBuilder() + Sentry.SentryEventBuilder builder = createSentryEventBuilder() .setMessage("Being awesome") .setExtra(initialExtra); @@ -41,20 +38,24 @@ public void testAddTag() throws JSONException { initialTags.put("tag1", "value1"); initialTags.put("tag2", "value2"); - Sentry.SentryEventBuilder builder = new Sentry.SentryEventBuilder() + Sentry.SentryEventBuilder builder = createSentryEventBuilder() .setMessage("Being awesome") .setTags(initialTags); JSONObject tags = builder.getTags(); - assertEquals("value1", tags.getString("key1")); - assertEquals("value2", tags.getString("key2")); + assertEquals("value1", tags.getString("tag1")); + assertEquals("value2", tags.getString("tag2")); builder.addTag("key3", "value3"); - JSONObject moreTags = builder.getExtra(); + JSONObject moreTags = builder.getTags(); assertEquals("value3", moreTags.getString("key3")); } + private Sentry.SentryEventBuilder createSentryEventBuilder() { + return new Sentry.SentryEventBuilder(true); + } + // Since regexes are very hard to read, we build the regex to recognize an internal package // name programatically. // Given a list of packages, return a regex to match them. @@ -99,7 +100,7 @@ public void testInternalPackageNameRegex() throws Exception { "com.android", "com.google.android", "dalvik.system"}; - assertEquals(Sentry.SentryEventBuilder.isInternalPackage, toPackageRegex(internalPackages)); + assertEquals(Sentry.SentryEventBuilder.IS_INTERNAL_PACKAGE.pattern(), toPackageRegex(internalPackages)); final String[] internalClasses = { @@ -110,7 +111,7 @@ public void testInternalPackageNameRegex() throws Exception { "dalvik.system.console" }; for (String c : internalClasses) { - assertTrue(c, c.matches(Sentry.SentryEventBuilder.isInternalPackage)); + assertTrue(c, Sentry.SentryEventBuilder.IS_INTERNAL_PACKAGE.matcher(c).matches()); } final String[] userClasses= { @@ -119,7 +120,7 @@ public void testInternalPackageNameRegex() throws Exception { }; for (String c : userClasses) { - assertFalse(c, c.matches(Sentry.SentryEventBuilder.isInternalPackage)); + assertFalse(c, Sentry.SentryEventBuilder.IS_INTERNAL_PACKAGE.matcher(c).matches()); } } -} \ No newline at end of file +} diff --git a/sentry-android/src/main/java/com/joshdholtz/sentry/AppInfoSentryCaptureListener.java b/sentry-android/src/main/java/com/joshdholtz/sentry/AppInfoSentryCaptureListener.java new file mode 100644 index 0000000..9122c7b --- /dev/null +++ b/sentry-android/src/main/java/com/joshdholtz/sentry/AppInfoSentryCaptureListener.java @@ -0,0 +1,169 @@ +package com.joshdholtz.sentry; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.res.Configuration; +import android.os.Build; +import android.text.TextUtils; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.WindowManager; + +import org.json.JSONException; +import org.json.JSONObject; + +public class AppInfoSentryCaptureListener implements Sentry.SentryEventCaptureListener { + + + private final JSONObject contexts; + private Sentry.SentryEventCaptureListener otherListener; + + public AppInfoSentryCaptureListener(Context context,Sentry.SentryEventCaptureListener otherListener) { + this.otherListener = otherListener; + AppInfo appInfo = AppInfo.read(context); + contexts = readContexts(context, appInfo); + } + + public AppInfoSentryCaptureListener(Context context) { + this(context,null); + } + + @Override + public Sentry.SentryEventBuilder beforeCapture(Sentry.SentryEventBuilder builder) { + Sentry.SentryEventBuilder eventBuilder = builder.setContexts(contexts); + if (otherListener == null) { + return eventBuilder; + } + return otherListener.beforeCapture(eventBuilder); + } + + private static JSONObject readContexts(Context context, AppInfo appInfo) { + final JSONObject contexts = new JSONObject(); + try { + contexts.put("os", osContext()); + contexts.put("device", deviceContext(context)); + contexts.put("package", packageContext(appInfo)); + } catch (JSONException e) { + Log.e(Sentry.TAG, "Failed to build device contexts", e); + } + return contexts; + } + + /** + * Read the package data into map to be sent as an event context item. + * This is not a built-in context type. + */ + private static JSONObject packageContext(AppInfo appInfo) { + final JSONObject pack = new JSONObject(); + try { + pack.put("type", "package"); + pack.put("name", appInfo.name); + pack.put("version_name", appInfo.versionName); + pack.put("version_code", Integer.toString(appInfo.versionCode)); + } catch (JSONException e) { + Log.e(Sentry.TAG, "Error reading package context", e); + } + return pack; + } + + + /** + * Read the device and build into a map. + *

+ * Not implemented: + * - battery_level + * If the device has a battery this can be an integer defining the battery level (in + * the range 0-100). (Android requires registration of an intent to query the battery). + * - name + * The name of the device. This is typically a hostname. + *

+ * See https://docs.getsentry.com/hosted/clientdev/interfaces/#context-types + */ + private static JSONObject deviceContext(Context context) { + final JSONObject device = new JSONObject(); + try { + // The family of the device. This is normally the common part of model names across + // generations. For instance iPhone would be a reasonable family, so would be Samsung Galaxy. + device.put("family", Build.BRAND); + + // The model name. This for instance can be Samsung Galaxy S3. + device.put("model", Build.PRODUCT); + + // An internal hardware revision to identify the device exactly. + device.put("model_id", Build.MODEL); + + final String architecture = System.getProperty("os.arch"); + if (!TextUtils.isEmpty(architecture)) { + device.put("arch", architecture); + } + + final int orient = context.getResources().getConfiguration().orientation; + device.put("orientation", orient == Configuration.ORIENTATION_LANDSCAPE ? + "landscape" : "portrait"); + + // Read screen resolution in the format "800x600" + // Normalised to have wider side first. + final Object windowManager = context.getSystemService(Context.WINDOW_SERVICE); + if (windowManager instanceof WindowManager) { + final DisplayMetrics metrics = new DisplayMetrics(); + ((WindowManager) windowManager).getDefaultDisplay().getMetrics(metrics); + device.put("screen_resolution", + String.format("%sx%s", + Math.max(metrics.widthPixels, metrics.heightPixels), + Math.min(metrics.widthPixels, metrics.heightPixels))); + } + + } catch (Exception e) { + Log.e(Sentry.TAG, "Error reading device context", e); + } + return device; + } + + + private static JSONObject osContext() { + final JSONObject os = new JSONObject(); + try { + os.put("type", "os"); + os.put("name", "Android"); + os.put("version", Build.VERSION.RELEASE); + os.put("build", Integer.toString(Build.VERSION.SDK_INT)); + + final String kernelVersion = System.getProperty("os.version"); + if (!TextUtils.isEmpty(kernelVersion)) { + os.put("kernel_version", kernelVersion); + } + + } catch (Exception e) { + Log.e(Sentry.TAG, "Error reading OS context", e); + } + return os; + } + + /** + * Store a tuple of package version information captured from PackageInfo + * + * @see PackageInfo + */ + private final static class AppInfo { + static final AppInfo Empty = new AppInfo("", "", 0); + final String name; + final String versionName; + final int versionCode; + + AppInfo(String name, String versionName, int versionCode) { + this.name = name; + this.versionName = versionName; + this.versionCode = versionCode; + } + + static AppInfo read(final Context context) { + try { + final PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + return new AppInfo(info.packageName, info.versionName, info.versionCode); + } catch (Exception e) { + Log.e(Sentry.TAG, "Error reading package context", e); + return Empty; + } + } + } +} diff --git a/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java b/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java index 13674c7..5c336cd 100755 --- a/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java +++ b/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java @@ -1,5 +1,7 @@ package com.joshdholtz.sentry; +import android.Manifest; +import android.app.Activity; import android.app.Application; import android.content.Context; import android.content.pm.PackageManager; @@ -7,8 +9,8 @@ import android.net.NetworkInfo; import android.net.Uri; import android.os.Bundle; +import android.text.TextUtils; import android.util.Log; -import android.util.Pair; import org.json.JSONArray; import org.json.JSONException; @@ -25,22 +27,33 @@ import java.io.StringWriter; import java.io.Writer; import java.lang.Thread.UncaughtExceptionHandler; +import java.net.UnknownHostException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; import java.util.UUID; +import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public final class Sentry { - private static final String TAG = "Sentry"; - private static final String VERSION = "0.2.0"; + static final String TAG = "Sentry"; + private static final int MAX_QUEUE_LENGTH = 50; private static final String VERIFY_SSL = "verify_ssl"; private static final String MEDIA_TYPE = "application/json; charset=utf-8"; private static final int HTTP_OK = 200; @@ -51,13 +64,13 @@ public final class Sentry { private Context context; private String sentryVersion = "7"; private String url; - private Uri dsn; - private Pair credentials; private String packageName; + private final String publicKey; + private final String secretKey; private int verifySsl; private SentryEventCaptureListener captureListener; private HttpRequestSender httpRequestSender; - private ExecutorService executorService = Executors.newSingleThreadExecutor(); + private ExecutorService executorService = fixedQueueDiscardingExecutor(); private InternalStorage internalStorage; private Runnable sendUnsentRequests = new Runnable() { @Override @@ -71,12 +84,13 @@ public void run() { }; private boolean enableDebugLogging; private SentryUncaughtExceptionHandler uncaughtExceptionHandler; + private final Breadcrumbs breadcrumbs = new Breadcrumbs(); - private Sentry(Context applicationContext, Uri dsnUri, String url, Pair credentials, int verifySsl, String storageFileName, HttpRequestSender httpRequestSender) { + private Sentry(Context applicationContext, String url, String publicKey,String secretKey, int verifySsl, String storageFileName, HttpRequestSender httpRequestSender) { context = applicationContext; - dsn = dsnUri; this.url = url; - this.credentials = credentials; + this.publicKey = publicKey; + this.secretKey = secretKey; this.verifySsl = verifySsl; this.httpRequestSender = httpRequestSender; packageName = applicationContext.getPackageName(); @@ -84,19 +98,20 @@ private Sentry(Context applicationContext, Uri dsnUri, String url, Pair credentials) { + public static Sentry newInstance(Context context, String dsnWithoutCredentials, HttpRequestSender httpRequestSender, String storageFileName, String publicKey,String secretKey) { Uri dsnUri = Uri.parse(dsnWithoutCredentials); - Sentry sentry = new Sentry(context.getApplicationContext(), dsnUri, getUrl(dsnUri), credentials, getVerifySsl(dsnUri), storageFileName, httpRequestSender); + Sentry sentry = new Sentry(context.getApplicationContext(), getUrl(dsnUri), publicKey, secretKey, getVerifySsl(dsnUri), storageFileName, httpRequestSender); sentry.setupUncaughtExceptionHandler(); return sentry; } @@ -113,6 +128,26 @@ private static String getUrl(Uri uri) { return uri.getScheme() + "://" + uri.getHost() + port + API + projectId + STORE; } + private static ExecutorService fixedQueueDiscardingExecutor() { + // Name our threads so that it is easy for app developers to see who is creating threads. + final ThreadFactory threadFactory = new ThreadFactory() { + private final AtomicLong count = new AtomicLong(); + + @Override + public Thread newThread(Runnable runnable) { + final Thread thread = new Thread(runnable); + thread.setName(String.format(Locale.US, "Sentry HTTP Thread %d", count.incrementAndGet())); + return thread; + } + }; + + return new ThreadPoolExecutor( + 0, 1, // Keep 0 threads alive. Max pool size is 1. + 60, TimeUnit.SECONDS, // Kill unused threads after this length. + new ArrayBlockingQueue(MAX_QUEUE_LENGTH), + threadFactory, new ThreadPoolExecutor.DiscardPolicy()); // Discard exceptions + } + private static int getVerifySsl(Uri uri) { int verifySsl = 1; String queryParameter = uri.getQueryParameter(VERIFY_SSL); @@ -145,18 +180,12 @@ public void setupUncaughtExceptionHandler() { } private String createXSentryAuthHeader() { - String header = ""; - - String publicKey = credentials.first; - String secretKey = credentials.second; - - header += "Sentry sentry_version=" + sentryVersion + ","; - header += "sentry_client=sentry-android/" + VERSION + ","; - header += "sentry_timestamp=" + System.currentTimeMillis() + ","; - header += "sentry_key=" + publicKey+","; - header += "sentry_secret=" + secretKey; - return header; + return "Sentry " + + String.format("sentry_version=%s,", sentryVersion) + + String.format("sentry_client=sentry-android/%s,", BuildConfig.SENTRY_ANDROID_VERSION) + + String.format("sentry_key=%s,", publicKey) + + String.format("sentry_secret=%s", secretKey); } @@ -195,6 +224,22 @@ public void captureException(Throwable t, SentryEventBuilder.SentryEventLevel le captureEvent(newEventBuilder().setMessage(t.getMessage()).setCulprit(culprit).setLevel(level).setException(t)); } + public void captureException(Throwable t, String message) { + captureException(t, message, SentryEventBuilder.SentryEventLevel.ERROR); + } + + public void captureException(Throwable t, String message, SentryEventBuilder.SentryEventLevel level) { + String culprit = getCause(t, t.getMessage()); + + captureEvent(newEventBuilder() + .setMessage(message) + .setCulprit(culprit) + .setLevel(level) + .setException(t) + ); + + } + public void captureUncaughtException(Context context, Throwable t) { final Writer result = new StringWriter(); final PrintWriter printWriter = new PrintWriter(result); @@ -235,19 +280,8 @@ private static File getStacktraceLocation(Context context) { } public void captureEvent(SentryEventBuilder builder) { - JSONObject request; - if (captureListener != null) { + JSONObject request = createEvent(builder); - builder = captureListener.beforeCapture(builder); - if (builder == null) { - logW("SentryEventBuilder in captureEvent is null"); - return; - } - - request = createRequest(builder); - } else { - request = createRequest(builder); - } if (enableDebugLogging) { //this string can be really big so there is no need to log if logging is disabled @@ -257,9 +291,22 @@ public void captureEvent(SentryEventBuilder builder) { doCaptureEventPost(request); } + private JSONObject createEvent(SentryEventBuilder builder) { + builder.setJsonArray("breadcrumbs", breadcrumbs.current()); + if (captureListener != null) { + + builder = captureListener.beforeCapture(builder); + if (builder == null) { + logW("SentryEventBuilder in captureEvent is null"); + return null; + } + } + return createRequest(builder); + } + private boolean shouldAttemptPost() { PackageManager pm = context.getPackageManager(); - int hasPerm = pm.checkPermission(android.Manifest.permission.ACCESS_NETWORK_STATE, packageName); + int hasPerm = pm.checkPermission(Manifest.permission.ACCESS_NETWORK_STATE, packageName); if (hasPerm == PackageManager.PERMISSION_DENIED) { return true; } @@ -311,7 +358,7 @@ private void sendRequest(JSONObject request) { boolean success = false; try { builder.header("X-Sentry-Auth", createXSentryAuthHeader()); - builder.header("User-Agent", "sentry-android/" + VERSION); + builder.header("User-Agent", "sentry-android/" + BuildConfig.SENTRY_ANDROID_VERSION); builder.header("Content-Type", MEDIA_TYPE); builder.content(request.toString(), MEDIA_TYPE); @@ -321,7 +368,11 @@ private void sendRequest(JSONObject request) { success = status == HTTP_OK; logD("SendEvent - " + status + " " + httpResponse.getContent()); - } catch (Exception e) { + }catch (UnknownHostException unh) { + //LogCat does not log UnknownHostException + logW("UnknownHostException on sending event"); + } + catch (Exception e) { logW(e); } @@ -381,12 +432,10 @@ public void uncaughtException(Thread thread, Throwable e) { } // Here you should have a more robust, permanent record of problems SentryEventBuilder builder = newEventBuilder(e, SentryEventBuilder.SentryEventLevel.FATAL); - if (captureListener != null) { - builder = captureListener.beforeCapture(builder); - } - if (builder != null) { - addRequest(createRequest(builder)); + JSONObject event = createEvent(builder); + if (event != null) { + addRequest(event); } else { logW("SentryEventBuilder in uncaughtException is null"); } @@ -524,18 +573,61 @@ public interface SentryEventCaptureListener { SentryEventBuilder beforeCapture(SentryEventBuilder builder); } + + /** + * The Sentry server assumes the time is in UTC. + * The timestamp should be in ISO 8601 format, without a timezone. + */ + private static SimpleDateFormat iso8601() { + final SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US); + format.setTimeZone(TimeZone.getTimeZone("UTC")); + return format; + } + public static final class SentryEventBuilder { + static final Pattern IS_INTERNAL_PACKAGE = Pattern.compile("^(java|android|com\\.android|com\\.google\\.android|dalvik\\.system)\\..*"); private static final ThreadLocal sdf = new ThreadLocal() { @Override protected SimpleDateFormat initialValue() { - SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US); - format.setTimeZone(TimeZone.getTimeZone("GMT")); - return format; + return iso8601(); } }; + private static final Pattern PATTERN = Pattern.compile("-", Pattern.LITERAL); private JSONObject event; + // Convert a StackTraceElement to a sentry.interfaces.stacktrace.Stacktrace JSON object. + static JSONObject frameJson(StackTraceElement ste) throws JSONException { + final JSONObject frame = new JSONObject(); + + final String method = ste.getMethodName(); + if (!TextUtils.isEmpty(method)) { + frame.put("function", method); + } + + final String fileName = ste.getFileName(); + if (!TextUtils.isEmpty(fileName)) { + frame.put("filename", fileName); + } + + int lineno = ste.getLineNumber(); + if (!ste.isNativeMethod() && lineno >= 0) { + frame.put("lineno", lineno); + } + + String className = ste.getClassName(); + frame.put("module", className); + + // Take out some of the system packages to improve the exception folding on the sentry server + frame.put("in_app", !IS_INTERNAL_PACKAGE.matcher(className).matches()); + + return frame; + } + + public SentryEventBuilder setContexts(JSONObject contexts) { + return setJsonObject("contexts", contexts); + } + public enum SentryEventLevel { FATAL("fatal"), @@ -553,10 +645,10 @@ public enum SentryEventLevel { private boolean enableLogging; - private SentryEventBuilder(boolean enableLogging) { + SentryEventBuilder(boolean enableLogging) { this.enableLogging = enableLogging; event = new JSONObject(); - setString(EVENT_ID, UUID.randomUUID().toString().replace("-", "")); + setString(EVENT_ID, PATTERN.matcher(UUID.randomUUID().toString()).replaceAll(Matcher.quoteReplacement(""))); setString("platform", "java"); setTimestamp(System.currentTimeMillis()); } @@ -618,8 +710,12 @@ public SentryEventBuilder setCulprit(String culprit) { } private SentryEventBuilder setString(String key, String culprit) { + return putSafely(key, culprit); + } + + private SentryEventBuilder putSafely(String key, Object object) { try { - event.put(key, culprit); + event.put(key, object); } catch (JSONException e) { //there should be no exception logW("", e, enableLogging); @@ -657,20 +753,38 @@ public SentryEventBuilder setTags(Map tags) { return this; } - public SentryEventBuilder setTags(JSONObject tags) { - return setJsonObject("tags", tags); + public SentryEventBuilder addTag(String key, String value) { + try { + getTags().put(key, value); + } catch (JSONException e) { + Log.e(Sentry.TAG, "Error adding tag in SentryEventBuilder"); + } + + return this; } - private SentryEventBuilder setJsonObject(String key, JSONObject object) { + public SentryEventBuilder addExtra(String key, String value) { try { - event.put(key, object); + getExtra().put(key, value); } catch (JSONException e) { - //there should be no exception - logW("", e, enableLogging); + Log.e(Sentry.TAG, "Error adding extra in SentryEventBuilder"); } + return this; } + public SentryEventBuilder setTags(JSONObject tags) { + return setJsonObject("tags", tags); + } + + public SentryEventBuilder setJsonObject(String key, JSONObject object) { + return putSafely(key, object); + } + + public SentryEventBuilder setJsonArray(String key, JSONArray object) { + return putSafely(key, object); + } + public JSONObject getTags() { if (!event.has("tags")) { setTags(new JSONObject()); @@ -687,6 +801,14 @@ public SentryEventBuilder setServerName(String serverName) { return setString("server_name", serverName); } + /** + * @param environment The environment name, such as production or staging + * @return SentryEventBuilder + */ + public SentryEventBuilder setEnvironment(String environment) { + return setString("environment", environment); + } + /** * @param release Release * @return SentryEventBuilder @@ -755,11 +877,11 @@ public SentryEventBuilder setException(Throwable t) { exception.put("type", t.getClass().getSimpleName()); exception.put("value", t.getMessage()); exception.put("module", t.getClass().getPackage().getName()); - exception.put("stacktrace", getStackTrace(t)); + exception.put("stacktrace", getStackTrace(t.getStackTrace())); values.put(exception); } catch (JSONException e) { - logW("Failed to build sentry report for " + t, e, enableLogging); + Log.e(TAG, "Failed to build sentry report for " + t, e); } t = t.getCause(); @@ -771,52 +893,208 @@ public SentryEventBuilder setException(Throwable t) { exceptionReport.put("values", values); event.put("exception", exceptionReport); } catch (JSONException e) { - logW("Unable to attach exception to event " + values, e, enableLogging); + Log.e(TAG, "Unable to attach exception to event " + values, e); } return this; } - public static JSONObject getStackTrace(Throwable t) throws JSONException { - JSONArray frameList = new JSONArray(); + /** + * Add a stack trace to the event. + * A stack trace for the current thread can be obtained by using + * `Thread.currentThread().getStackTrace()`. + * + * @param stackTrace stacktrace + * @return same builder + * @see Thread#currentThread() + * @see Thread#getStackTrace() + */ + public SentryEventBuilder setStackTrace(StackTraceElement[] stackTrace) { + setJsonObject("stacktrace", getStackTrace(stackTrace)); + return this; + } + + private static JSONObject getStackTrace(StackTraceElement[] stackFrames) { - for (StackTraceElement ste : t.getStackTrace()) { - JSONObject frame = new JSONObject(); + JSONObject stacktrace = new JSONObject(); - String method = ste.getMethodName(); - if (method.length() != 0) { - frame.put("function", method); + try { + JSONArray frameList = new JSONArray(); + + // Java stack frames are in the opposite order from what the Sentry client API expects. + // > The zeroth element of the array (assuming the array's length is non-zero) + // > represents the top of the stack, which is the last method invocation in the + // > sequence. + // See: + // https://docs.oracle.com/javase/7/docs/api/java/lang/Throwable.html#getStackTrace() + // https://docs.sentry.io/clientdev/interfaces/#failure-interfaces + // + // This code uses array indices rather a foreach construct since there is no built-in + // reverse iterator in the Java standard library. To use a foreach loop would require + // calling Collections.reverse which would require copying the array to a list. + for (int i = stackFrames.length - 1; i >= 0; i--) { + frameList.put(frameJson(stackFrames[i])); } - int lineno = ste.getLineNumber(); - if (!ste.isNativeMethod() && lineno >= 0) { - frame.put("lineno", lineno); - } + stacktrace.put("frames", frameList); + } catch (JSONException e) { + Log.e(TAG, "Error serializing stack frames", e); + } - boolean inApp = true; + return stacktrace; + } - String className = ste.getClassName(); - frame.put("module", className); + private JSONObject toJson() { + return event; + } + } - // Take out some of the system packages to improve the exception folding on the sentry server - if (className.startsWith("android.") || className.startsWith("java.") || className.startsWith("dalvik.") || className.startsWith("com.android.")) { + private final static class Breadcrumb { - inApp = false; - } + enum Type { + + Default("default"), + HTTP("http"), + Navigation("navigation"); - frame.put("in_app", inApp); + private final String value; - frameList.put(frame); + Type(String value) { + this.value = value; } + } - JSONObject frameHash = new JSONObject(); - frameHash.put("frames", frameList); + final long timestamp; + final Type type; + final String message; + final String category; + final SentryEventBuilder.SentryEventLevel level; + final Map data = new HashMap<>(); + + Breadcrumb(long timestamp, Type type, String message, String category, SentryEventBuilder.SentryEventLevel level) { + this.timestamp = timestamp; + this.type = type; + this.message = message; + this.category = category; + this.level = level; + } + } - return frameHash; + private static class Breadcrumbs { + + // The max number of breadcrumbs that will be tracked at any one time. + private static final int MAX_BREADCRUMBS = 10; + + + // Access to this list must be thread-safe. + // See GitHub Issue #110 + // This list is protected by the provided ReadWriteLock. + private final LinkedList breadcrumbs = new LinkedList<>(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(); + + void push(Breadcrumb b) { + try { + lock.writeLock().lock(); + while (breadcrumbs.size() >= MAX_BREADCRUMBS) { + breadcrumbs.removeFirst(); + } + breadcrumbs.add(b); + } finally { + lock.writeLock().unlock(); + } } - private JSONObject toJson() { - return event; + JSONArray current() { + final JSONArray crumbs = new JSONArray(); + try { + lock.readLock().lock(); + for (Breadcrumb breadcrumb : breadcrumbs) { + final JSONObject json = new JSONObject(); + json.put("timestamp", breadcrumb.timestamp); + json.put("type", breadcrumb.type.value); + json.put("message", breadcrumb.message); + json.put("category", breadcrumb.category); + json.put("level", breadcrumb.level.value); + json.put("data", new JSONObject(breadcrumb.data)); + crumbs.put(json); + } + } catch (Exception e) { + Log.e(TAG, "Error serializing breadcrumbs", e); + } finally { + lock.readLock().unlock(); + } + return crumbs; } + } + + /** + * Record a breadcrumb to log a navigation from `from` to `to`. + * + * @param category A category to label the event under. This generally is similar to a logger + * name, and will let you more easily understand the area an event took place, such as auth. + * @param from A string representing the original application state / location. + * @param to A string representing the new application state / location. + * @see com.joshdholtz.sentry.Sentry#addHttpBreadcrumb(String, String, int) + */ + public void addNavigationBreadcrumb(String category, String from, String to) { + final Breadcrumb b = new Breadcrumb( + System.currentTimeMillis() / 1000, + Breadcrumb.Type.Navigation, + "", + category, + SentryEventBuilder.SentryEventLevel.INFO); + + b.data.put("from", from); + b.data.put("to", to); + breadcrumbs.push(b); + } + + /** + * Record a HTTP request breadcrumb. This represents an HTTP request transmitted from your + * application. This could be an AJAX request from a web application, or a server-to-server HTTP + * request to an API service provider, etc. + * + * @param url The request URL. + * @param method The HTTP request method. + * @param statusCode The HTTP status code of the response. + * @see com.joshdholtz.sentry.Sentry#addHttpBreadcrumb(String, String, int) + */ + public void addHttpBreadcrumb(String url, String method, int statusCode) { + final Breadcrumb b = new Breadcrumb( + System.currentTimeMillis() / 1000, + Breadcrumb.Type.HTTP, + "", + String.format("http.%s", method.toLowerCase()), + SentryEventBuilder.SentryEventLevel.INFO); + + b.data.put("url", url); + b.data.put("method", method); + b.data.put("status_code", Integer.toString(statusCode)); + breadcrumbs.push(b); + } + + /** + * Sentry supports a concept called Breadcrumbs, which is a trail of events which happened prior + * to an issue. Often times these events are very similar to traditional logs, but also have the + * ability to record more rich structured data. + * + * @param category A category to label the event under. This generally is similar to a logger + * name, and will let you more easily understand the area an event took place, + * such as auth. + * @param message A string describing the event. The most common vector, often used as a drop-in + * for a traditional log message. + *

+ * See https://docs.sentry.io/hosted/learn/breadcrumbs/ + */ + public void addBreadcrumb(String category, String message) { + breadcrumbs.push(new Breadcrumb( + System.currentTimeMillis() / 1000, + Breadcrumb.Type.Default, + message, + category, + SentryEventBuilder.SentryEventLevel.INFO)); + } + + } diff --git a/sentry-android/src/main/java/com/joshdholtz/sentry/SentryInstance.java b/sentry-android/src/main/java/com/joshdholtz/sentry/SentryInstance.java index bd27b49..8c21461 100644 --- a/sentry-android/src/main/java/com/joshdholtz/sentry/SentryInstance.java +++ b/sentry-android/src/main/java/com/joshdholtz/sentry/SentryInstance.java @@ -1,7 +1,6 @@ package com.joshdholtz.sentry; import android.content.Context; -import android.util.Pair; public final class SentryInstance { @@ -9,14 +8,14 @@ public final class SentryInstance { private static SentryInstance ourInstance = new SentryInstance(); private Sentry sentry; - public static void init(Context context, String dsnWithoutCredentials, HttpRequestSender httpRequestSender, Pair credentials) { - ourInstance.sentry = Sentry.newInstance(context, dsnWithoutCredentials, httpRequestSender, FILE_NAME, credentials); + public static void init(Context context, String dsnWithoutCredentials, HttpRequestSender httpRequestSender, String publicKey,String secretKey) { + ourInstance.sentry = Sentry.newInstance(context, dsnWithoutCredentials, httpRequestSender, FILE_NAME, publicKey, secretKey); } /** - * {@link #init(Context, String, HttpRequestSender, Pair)} must be called before this + * {@link #init(Context, String, HttpRequestSender, String, String)} must be called before this * - * @return {@code Sentry} instance created in {@link #init(Context, String, HttpRequestSender, Pair)} (Context, String, HttpRequestSender)} + * @return {@code Sentry} instance created in {@link #init(Context, String, HttpRequestSender, String, String)} (Context, String, HttpRequestSender, Pair)} (Context, String, HttpRequestSender)} */ public static Sentry getInstance() { return ourInstance.sentry; diff --git a/sentry-apache-http-client/build.gradle b/sentry-apache-http-client/build.gradle index fed02c6..553873c 100644 --- a/sentry-apache-http-client/build.gradle +++ b/sentry-apache-http-client/build.gradle @@ -1,17 +1,20 @@ apply plugin: 'com.android.library' + android { - compileSdkVersion 23 - buildToolsVersion "23.0.3" + compileSdkVersion ext_compileSdkVersion + buildToolsVersion ext_buildToolsVersion useLibrary 'org.apache.http.legacy' + defaultConfig { - minSdkVersion 3 - targetSdkVersion 23 + minSdkVersion ext_minSdkVersion + targetSdkVersion ext_targetSdkVersion versionCode 1 versionName "1.0" } + buildTypes { release { minifyEnabled false @@ -28,5 +31,7 @@ ext { dependencies { provided 'org.jbundle.util.osgi.wrapped:org.jbundle.util.osgi.wrapped.org.apache.http.client:4.1.2' - compile "com.joshdholtz.sentry:sentry-android:${libraryVersion}" +// compile "com.joshdholtz.sentry:sentry-android:${libraryVersion}" + compile project(":sentry-android") + } diff --git a/sentry-app/build.gradle b/sentry-app/build.gradle index dc2e857..c47d7a1 100644 --- a/sentry-app/build.gradle +++ b/sentry-app/build.gradle @@ -1,28 +1,31 @@ apply plugin: 'com.android.application' android { - compileSdkVersion 23 - buildToolsVersion "23.0.2" - useLibrary 'org.apache.http.legacy' + compileSdkVersion ext_compileSdkVersion + buildToolsVersion ext_buildToolsVersion defaultConfig { applicationId "com.joshdholtz.sentryapp" - minSdkVersion 15 - targetSdkVersion 23 + minSdkVersion ext_minSdkVersion + targetSdkVersion ext_targetSdkVersion versionCode 1 versionName "1.0" } + buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + + useLibrary 'org.apache.http.legacy' + } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:23.1.1' + compile 'com.android.support:appcompat-v7:25.1.1' compile project(':sentry-apache-http-client') -} \ No newline at end of file +} diff --git a/sentry-app/src/main/java/com/joshdholtz/sentryapp/MainActivity.java b/sentry-app/src/main/java/com/joshdholtz/sentryapp/MainActivity.java index 0105351..e945141 100644 --- a/sentry-app/src/main/java/com/joshdholtz/sentryapp/MainActivity.java +++ b/sentry-app/src/main/java/com/joshdholtz/sentryapp/MainActivity.java @@ -7,6 +7,7 @@ import android.view.View; import com.joshdholtz.sentry.Sentry; +import com.joshdholtz.sentry.SentryInstance; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -15,20 +16,19 @@ public class MainActivity extends AppCompatActivity { private final ExecutorService executor = Executors.newSingleThreadExecutor(); + private Sentry sentry; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - String yourDSN = "your-dsn"; - Sentry.init(this, yourDSN); - Sentry.debug = true; + sentry = SentryInstance.getInstance(); - Sentry.addNavigationBreadcrumb("activity.main", "here", "there"); - Sentry.addHttpBreadcrumb("http://example.com", "GET", 202); + sentry.addNavigationBreadcrumb("activity.main", "here", "there"); + sentry.addHttpBreadcrumb("http://example.com", "GET", 202); - Sentry.captureEvent(new Sentry.SentryEventBuilder() + sentry.captureEvent(sentry.newEventBuilder() .setMessage("OMG this works woooo") .setStackTrace(Thread.currentThread().getStackTrace()) ); @@ -57,17 +57,17 @@ public void run() { } public void onClickBreak(View view) { - Sentry.addBreadcrumb("button.click", "break button"); + sentry.addBreadcrumb("button.click", "break button"); crash(); } public void onClickCapture(View view) { - Sentry.addBreadcrumb("button.click", "capture button"); + sentry.addBreadcrumb("button.click", "capture button"); try { crash(); } catch (Exception e) { - Sentry.captureException(e, "Exception caught in click handler"); + sentry.captureException(e, "Exception caught in click handler"); } } diff --git a/sentry-app/src/main/java/com/joshdholtz/sentryapp/SentryApplication.java b/sentry-app/src/main/java/com/joshdholtz/sentryapp/SentryApplication.java index b669082..66d07ee 100644 --- a/sentry-app/src/main/java/com/joshdholtz/sentryapp/SentryApplication.java +++ b/sentry-app/src/main/java/com/joshdholtz/sentryapp/SentryApplication.java @@ -1,6 +1,8 @@ package com.joshdholtz.sentryapp; import android.app.Application; + +import com.joshdholtz.sentry.AppInfoSentryCaptureListener; import com.joshdholtz.sentry.SentryInstance; import com.joshdholtz.sentry.http.apache.ApacheHttpRequestSender; @@ -11,7 +13,8 @@ public class SentryApplication extends Application public void onCreate() { super.onCreate(); - SentryInstance.init(this, "your-dsn",new ApacheHttpRequestSender()); + SentryInstance.init(this, "your-dsn-without-credentials",new ApacheHttpRequestSender(), "login","password"); SentryInstance.getInstance().setDebugLogging(BuildConfig.DEBUG); + SentryInstance.getInstance().setCaptureListener(new AppInfoSentryCaptureListener(this)); } } From be85ae8c56d3e01d91e333925da7f244bf16f8cd Mon Sep 17 00:00:00 2001 From: "lukasz.suski" Date: Mon, 6 Feb 2017 15:19:47 +0100 Subject: [PATCH 4/5] fixed getUser() method in SentryEventBuilder --- sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java b/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java index 5c336cd..99c7109 100755 --- a/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java +++ b/sentry-android/src/main/java/com/joshdholtz/sentry/Sentry.java @@ -738,7 +738,7 @@ public SentryEventBuilder setUser(JSONObject user) { public JSONObject getUser() { if (!event.has("user")) { - setTags(new JSONObject()); + setUser(new JSONObject()); } return (JSONObject) event.opt("user"); From 913794623182558b2291336f27865190fa830d3e Mon Sep 17 00:00:00 2001 From: "lukasz.suski" Date: Mon, 6 Feb 2017 15:20:16 +0100 Subject: [PATCH 5/5] updated okhttp to 3.6.0 --- sentry-android-okhttp-client/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-android-okhttp-client/build.gradle b/sentry-android-okhttp-client/build.gradle index 3cd22a1..adbee6f 100644 --- a/sentry-android-okhttp-client/build.gradle +++ b/sentry-android-okhttp-client/build.gradle @@ -32,7 +32,7 @@ ext { } dependencies { - compile "com.squareup.okhttp3:okhttp:3.5.0" + compile "com.squareup.okhttp3:okhttp:3.6.0" compile project(":sentry-android") // compile "com.joshdholtz.sentry:sentry-android:${libraryVersion}" }