();
62 | FileInputStream input = null;
63 |
64 | try {
65 | input = context.openFileInput(STORAGE_FILE_NAME);
66 | DataInputStream inputStream = new DataInputStream(input);
67 | BufferedReader bufReader = new BufferedReader(new InputStreamReader(inputStream));
68 |
69 | String logLine = bufReader.readLine();
70 | while (logLine != null) {
71 | logs.offer(logLine);
72 | logLine = bufReader.readLine();
73 | }
74 |
75 | if (needToRemoveStorageFile) {
76 | removeStorageFile();
77 | }
78 |
79 | } catch (IOException ex) {
80 | Log.e(TAG, "Cannot load logs from the local storage: " + ex.getMessage());
81 | // Basically, ignore the exception - if something has gone wrong - just return empty
82 | // logs list.
83 | } finally {
84 | try {
85 | if (input != null) {
86 | input.close();
87 | }
88 | } catch (IOException ex2) {
89 | Log.e(TAG, "Cannot close the local storage file: " + ex2.getMessage());
90 | }
91 | }
92 |
93 | return logs;
94 | }
95 |
96 | public void removeStorageFile() throws IOException {
97 | if (!storageFilePtr.delete()) {
98 | throw new IOException("Cannot delete " + STORAGE_FILE_NAME);
99 | }
100 | }
101 |
102 | public void reCreateStorageFile() throws IOException {
103 | Log.d(TAG, "Log storage has been re-created.");
104 | if (storageFilePtr == null) {
105 | storageFilePtr = create();
106 | } else {
107 | removeStorageFile();
108 | }
109 | storageFilePtr = create();
110 | }
111 |
112 | private File create() throws IOException {
113 | return new File(context.getFilesDir(), STORAGE_FILE_NAME);
114 | }
115 |
116 | private long getCurrentStorageFileSize() throws IOException {
117 | if (storageFilePtr == null) {
118 | storageFilePtr = create();
119 | }
120 |
121 | return storageFilePtr.length();
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/logentries/misc/Utils.java:
--------------------------------------------------------------------------------
1 | package com.logentries.misc;
2 |
3 | import android.os.Build;
4 | import android.util.Log;
5 |
6 | import java.lang.reflect.Method;
7 | import java.net.InetAddress;
8 | import java.net.UnknownHostException;
9 | import java.security.MessageDigest;
10 | import java.security.NoSuchAlgorithmException;
11 | import java.util.ArrayList;
12 | import java.util.UUID;
13 | import java.util.regex.Pattern;
14 |
15 | import org.json.JSONArray;
16 | import org.json.JSONException;
17 | import org.json.JSONObject;
18 |
19 | public class Utils {
20 |
21 | private static final String TAG = "LogentriesAndroidLogger";
22 |
23 | /**
24 | * Reg.ex. that is used to check correctness of HostName if it is defined by user
25 | */
26 | private static final Pattern HOSTNAME_REGEX = Pattern.compile("[$/\\\"&+,:;=?#|<>_* \\[\\]]");
27 |
28 | private static String traceID = "";
29 | private static String hostName = "";
30 |
31 | // Requires at least API level 9 (v. >= 2.3).
32 | static {
33 | try {
34 | traceID = computeTraceID();
35 | } catch (NoSuchAlgorithmException ex) {
36 | Log.e(TAG, "Cannot get traceID from device's properties!");
37 | traceID = "unknown";
38 | }
39 |
40 | try {
41 | hostName = getProp("net.hostname");
42 | if (hostName.equals("")) { // We have failed to get the real host name
43 | // so, use the default one.
44 | hostName = InetAddress.getLocalHost().getHostName();
45 | }
46 | } catch (UnknownHostException e) {
47 | // We cannot resolve local host name - so won't use it at all.
48 | }
49 | }
50 |
51 | private static String getProp(String propertyName) {
52 |
53 | if (propertyName == null || propertyName.isEmpty()) {
54 | return "";
55 | }
56 |
57 | try {
58 | Method getString = Build.class.getDeclaredMethod("getString", String.class);
59 | getString.setAccessible(true);
60 | return getString.invoke(null, propertyName).toString();
61 | } catch (Exception ex) {
62 | // Ignore the exception - we simply couldn't access the property;
63 | Log.e(TAG, ex.getMessage());
64 | }
65 |
66 | return "";
67 | }
68 |
69 | private static String computeTraceID() throws NoSuchAlgorithmException {
70 |
71 | String fingerprint = getProp("ro.build.fingerprint");
72 | String displayId = getProp("ro.build.display.id");
73 | String hardware = getProp("ro.hardware");
74 | String device = getProp("ro.product.device");
75 | String rilImei = getProp("ril.IMEI");
76 |
77 | MessageDigest hashGen = MessageDigest.getInstance("MD5");
78 | byte[] digest = null;
79 | if (fingerprint.isEmpty() & displayId.isEmpty() & hardware.isEmpty() & device.isEmpty() & rilImei.isEmpty()) {
80 | Log.e(TAG, "Cannot obtain any of device's properties - will use default Trace ID source.");
81 |
82 | Double randomTrace = Math.random() + Math.PI;
83 | String defaultValue = randomTrace.toString();
84 | randomTrace = Math.random() + Math.PI;
85 | defaultValue += randomTrace.toString().replace(".", "");
86 | // The code below fixes one strange bug, when call to a freshly installed app crashes at this
87 | // point, because random() produces too short sequence. Note, that this behavior does not
88 | // occur for the second and all further launches.
89 | defaultValue = defaultValue.length() >= 36 ? defaultValue.substring(2, 34) :
90 | defaultValue.substring(2);
91 |
92 | hashGen.update(defaultValue.getBytes());
93 | } else {
94 | StringBuilder sb = new StringBuilder();
95 | sb.append(fingerprint).append(displayId).append(hardware).append(device).append(rilImei);
96 | hashGen.update(sb.toString().getBytes());
97 | }
98 |
99 | digest = hashGen.digest();
100 | StringBuilder conv = new StringBuilder();
101 | for (byte b : digest) {
102 | conv.append(String.format("%02x", b & 0xff).toUpperCase());
103 | }
104 |
105 | return conv.toString();
106 | }
107 |
108 | public static String getTraceID() {
109 | return traceID;
110 | }
111 |
112 |
113 | private static String getFormattedDeviceId(boolean toJSON) {
114 | if (toJSON) {
115 | return "\"DeviceId\": \"" + Build.SERIAL + "\"";
116 | }
117 | return "DeviceId=" + Build.SERIAL;
118 | }
119 |
120 | public static String getFormattedTraceID(boolean toJSON) {
121 | if (toJSON) {
122 | return "\"TraceID\": \"" + traceID + "\"";
123 | }
124 | return "TraceID=" + traceID;
125 | }
126 |
127 | public static String getHostName() {
128 | return hostName;
129 | }
130 |
131 | public static String getFormattedHostName(boolean toJSON) {
132 | if (toJSON) {
133 | return "\"Host\": \"" + hostName + "\"";
134 | }
135 | return "Host=" + hostName;
136 | }
137 |
138 | /**
139 | * Via http://stackoverflow.com/a/10174938
140 | */
141 | public static boolean isJSONValid(String message) {
142 | try {
143 | new JSONObject(message);
144 | } catch (JSONException ex) {
145 | try {
146 | new JSONArray(message);
147 | } catch (JSONException ex1) {
148 | return false;
149 | }
150 | }
151 | return true;
152 | }
153 |
154 | /**
155 | * Formats given message to make it suitable for ingestion by Logentris endpoint.
156 | * If isUsingHttp == true, the method produces such structure:
157 | * {"event": {"Host": "SOMEHOST", "Timestamp": 12345, "DeviceID": "DEV_ID", "Message": "MESSAGE"}}
158 | *
159 | * If isUsingHttp == false the output will be like this:
160 | * Host=SOMEHOST Timestamp=12345 DeviceID=DEV_ID MESSAGE
161 | *
162 | * @param message Message to be sent to Logentries
163 | * @param logHostName - if set to true - "Host"=HOSTNAME parameter is appended to the message.
164 | * @param isUsingHttp will be using http
165 | * @return
166 | */
167 | public static String formatMessage(String message, boolean logHostName, boolean isUsingHttp) {
168 | StringBuilder sb = new StringBuilder();
169 |
170 | if (isUsingHttp) {
171 | // Add 'event' structure.
172 | sb.append("{\"event\": {");
173 | }
174 |
175 | if (logHostName) {
176 | sb.append(Utils.getFormattedHostName(isUsingHttp));
177 | sb.append(isUsingHttp ? ", " : " ");
178 | }
179 |
180 | sb.append(Utils.getFormattedTraceID(isUsingHttp)).append(" ");
181 | sb.append(isUsingHttp ? ", " : " ");
182 |
183 |
184 | sb.append(Utils.getFormattedDeviceId(isUsingHttp)).append(" ");
185 | sb.append(isUsingHttp ? ", " : " ");
186 |
187 | long timestamp = System.currentTimeMillis(); // Current time in UTC in milliseconds.
188 | if (isUsingHttp) {
189 | sb.append("\"Timestamp\": ").append(Long.toString(timestamp)).append(", ");
190 | } else {
191 | sb.append("Timestamp=").append(Long.toString(timestamp)).append(" ");
192 | }
193 |
194 | // Append the event data
195 | if (isUsingHttp) {
196 | if (Utils.isJSONValid(message)) {
197 | sb.append("\"Message\":").append(message);
198 | sb.append("}}");
199 | } else {
200 | sb.append("\"Message\": \"").append(message);
201 | sb.append("\"}}");
202 | }
203 |
204 | } else {
205 | sb.append(message);
206 | }
207 |
208 | return sb.toString();
209 | }
210 |
211 | public static boolean checkValidUUID(String uuid) {
212 | if (uuid != null && !uuid.isEmpty()) {
213 | try {
214 |
215 | UUID u = UUID.fromString(uuid);
216 | return true;
217 |
218 | } catch (IllegalArgumentException e) {
219 | return false;
220 | }
221 | }
222 | return false;
223 | }
224 |
225 | public static boolean checkIfHostNameValid(String hostName) {
226 | return !HOSTNAME_REGEX.matcher(hostName).find();
227 | }
228 |
229 | public static String[] splitStringToChunks(String source, int chunkLength) {
230 | if (chunkLength < 0) {
231 | throw new IllegalArgumentException("Chunk length must be greater or equal to zero!");
232 | }
233 |
234 | int srcLength = source.length();
235 | if (chunkLength == 0 || srcLength <= chunkLength) {
236 | return new String[]{source};
237 | }
238 |
239 | ArrayList chunkBuffer = new ArrayList();
240 | int splitSteps = srcLength / chunkLength + (srcLength % chunkLength > 0 ? 1 : 0);
241 |
242 | int lastCutPosition = 0;
243 | for (int i = 0; i < splitSteps; ++i) {
244 |
245 | if (i < splitSteps - 1) {
246 | // Cut out the chunk of the requested size.
247 | chunkBuffer.add(source.substring(lastCutPosition, lastCutPosition + chunkLength));
248 | } else {
249 | // Cut out all that left to the end of the string.
250 | chunkBuffer.add(source.substring(lastCutPosition));
251 | }
252 |
253 | lastCutPosition += chunkLength;
254 | }
255 |
256 | return chunkBuffer.toArray(new String[chunkBuffer.size()]);
257 | }
258 | }
259 |
--------------------------------------------------------------------------------
/lib/src/main/java/com/logentries/net/LogentriesClient.java:
--------------------------------------------------------------------------------
1 | package com.logentries.net;
2 |
3 | import org.apache.http.client.HttpClient;
4 | import org.apache.http.client.HttpResponseException;
5 | import org.apache.http.client.methods.HttpPost;
6 | import org.apache.http.entity.StringEntity;
7 | import org.apache.http.impl.client.DefaultHttpClient;
8 |
9 | import java.io.IOException;
10 | import java.io.OutputStream;
11 | import java.net.Socket;
12 | import java.nio.charset.Charset;
13 |
14 | import javax.net.ssl.SSLSocket;
15 | import javax.net.ssl.SSLSocketFactory;
16 |
17 | import android.util.Log;
18 |
19 | public class LogentriesClient {
20 | // Logentries server endpoints for logs data.
21 | private static final String LE_TOKEN_API = "data.logentries.com"; // For token-based stream input
22 |
23 | private static final String LE_HTTP_API = "http://webhook.logentries.com/noformat/logs/"; // For HTTP-based input.
24 | private static final String LE_HTTPS_API = "https://webhook.logentries.com/noformat/logs/"; // For HTTP-based input.
25 |
26 | // Port number for unencrypted HTTP PUT/Token TCP logging on Logentries server.
27 | private static final int LE_PORT = 80;
28 |
29 | // Port number for SSL HTTP PUT/TLS Token TCP logging on Logentries server.
30 | private static final int LE_SSL_PORT = 443;
31 |
32 | static final Charset UTF8 = Charset.forName("UTF-8");
33 |
34 | private final SSLSocketFactory sslFactory;
35 |
36 | private Socket socket; // The socket, connected to the Token API endpoint (Token-based input only!)
37 | private OutputStream stream; // Data stream to the endpoint, where log messages go (Token-based input only!)
38 |
39 | private HttpClient httpClient; // HTTP client, used for communicating with HTTP API endpoint.
40 | private HttpPost postRequest; // Request object, used to forward data put requests.
41 |
42 | private String endpointToken; // Token, that points to the exact endpoint - the log object, where the data goes.
43 |
44 | private boolean sslChoice = false; // Use SSL layering for the Socket?
45 | private boolean httpChoice = false; // Use HTTP input instead of token-based stream input?
46 |
47 | // Datahub-related attributes.
48 | private String dataHubServer = null;
49 | private int dataHubPort = 0;
50 | private boolean useDataHub = false;
51 |
52 | // The formatter used to prepend logs with the endpoint token for Token-based input.
53 | private StringBuilder streamFormatter = new StringBuilder();
54 |
55 | public LogentriesClient(boolean useHttpPost, boolean useSsl, boolean isUsingDataHub, String server, int port,
56 | String token)
57 | throws InstantiationException, IllegalArgumentException {
58 |
59 | if (useHttpPost && isUsingDataHub) {
60 | throw new IllegalArgumentException("'httpPost' parameter cannot be set to true if 'isUsingDataHub' " +
61 | "is set to true.");
62 | }
63 |
64 | if (token == null || token.isEmpty()) {
65 | throw new IllegalArgumentException("Token parameter cannot be empty!");
66 | }
67 |
68 | useDataHub = isUsingDataHub;
69 | sslChoice = useSsl;
70 | httpChoice = useHttpPost;
71 | endpointToken = token;
72 |
73 | if (useDataHub) {
74 | if (server == null || server.isEmpty()) {
75 | throw new InstantiationException("'server' parameter is mandatory if 'isUsingDatahub' parameter " +
76 | "is set to true.");
77 | }
78 | if (port <= 0 || port > 65535) {
79 | throw new InstantiationException("Incorrect port number " + Integer.toString(port) + ". Port number must " +
80 | "be greater than zero and less than 65535.");
81 | }
82 | dataHubServer = server;
83 | dataHubPort = port;
84 | }
85 | if (useSsl) {
86 | try {
87 | sslFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
88 | } catch (Exception e) {
89 | throw new InstantiationException("Cannot create LogentriesClient instance. Error: " + e.getMessage());
90 | }
91 | } else {
92 | sslFactory = null;
93 | }
94 | }
95 |
96 | public int getPort() {
97 | if (useDataHub) {
98 | return dataHubPort;
99 | } else {
100 | return sslChoice ? LE_SSL_PORT : LE_PORT;
101 | }
102 | }
103 |
104 | public String getAddress() {
105 | if (useDataHub) {
106 | return dataHubServer;
107 | } else {
108 | if (httpChoice) {
109 | return sslChoice ? LE_HTTPS_API : LE_HTTP_API;
110 | }
111 | return LE_TOKEN_API;
112 | }
113 | }
114 |
115 | public void connect() throws IOException, IllegalArgumentException {
116 | if (httpChoice) {
117 | httpClient = new DefaultHttpClient();
118 | postRequest = new HttpPost(getAddress() + endpointToken);
119 | } else {
120 | Socket s = new Socket(getAddress(), getPort());
121 | if (sslChoice) {
122 | if (sslFactory == null) {
123 | throw new IllegalArgumentException("SSL Socket Factory is not initialized!");
124 | }
125 | SSLSocket sslSocket = (SSLSocket) sslFactory.createSocket(s, getAddress(), getPort(), true);
126 | sslSocket.setTcpNoDelay(true);
127 | socket = sslSocket;
128 | } else {
129 | socket = s;
130 | }
131 | stream = socket.getOutputStream();
132 | }
133 | }
134 |
135 | public void write(String data) throws IOException {
136 | if (!httpChoice) {
137 | // Token-based or DataHub output mode - we're using plain stream forwarding via the socket.
138 | if (stream == null) {
139 | throw new IOException("OutputStream is not initialized!");
140 | }
141 | streamFormatter.setLength(0); // Erase all previous data.
142 | streamFormatter.append(endpointToken).append(" ");
143 | streamFormatter.append(data);
144 | // For Token-based input it is mandatory for the message to has '\n' at the end to be
145 | // ingested by the endpoint correctly.
146 | if (!data.endsWith("\n")) {
147 | streamFormatter.append("\n");
148 | }
149 | stream.write(streamFormatter.toString().getBytes(UTF8));
150 | stream.flush();
151 | } else {
152 | // HTTP input mode.
153 | postRequest.setEntity(new StringEntity(data, "UTF8"));
154 | try {
155 | httpClient.execute(postRequest);
156 | } catch (HttpResponseException ex) {
157 | Log.e("LogentriesAndroidLogger", "Received status code:" + ex.getStatusCode());
158 | Log.e("LogentriesAndroidLogger", "Error message:" + ex.getMessage());
159 | }
160 | }
161 | }
162 |
163 | public void close() {
164 | try {
165 | if (socket != null) {
166 | socket.close();
167 | socket = null;
168 | }
169 | } catch (Exception e) {
170 | // Just hide the exception - we cannot throw here.
171 | }
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':lib'
2 |
--------------------------------------------------------------------------------