Android Local Remote Logger
Tags: android, kotlin, tooling, logcat
Published: 2021-08-10 07:13:34

# Respect the Logcat
The primary Android developer tool - Logcat. It is so widely used, always at hand, that it is difficult to imagine a situation where you have to work without it. However, I've found myself in such a situation. And I decided to write and share a tool that helped me survive this drama.

# The challenge
I was given a task to test a certain library, and since I work in `#fintech`, there often are some additional security checks. This library failed to initialize if:
- the application was built in `debug` mode
- was installed by Android Studio, or `adb install`
- it was unsigned (this is due to installing the application in the `release` version)
- the "developer options" were enabled
- was running on the device or OS version that was not approved

No developer options mean **no logs**. Just building the application in the `release` version limits the logs, but at least exceptions are logged. The application was crashing quite often, I suspected some standard NPE because, for example, I forgot to initialize or stub something, etc. but I had no idea where. I couldn't just write tests or stub the library, because that was my taks - to check how it works in closest to "real" environment.

Crashlytics or some other remote crash reporting tool could help somewhat. However, reporting is not immediate, only application crashes and some metrics are reported, and I would also like to log regular events.

# The solution
If only you could send Logcat logs, e.g. via WebSocket, and then catch them on your computer... I did not find such a tool, so I've created one.

## Server
I needed something simple, which would allow me to set up a WebSocket server and display logs as HTML, or log in to a file. I chose [ExpressJS](https://expressjs.com/) which I had to deal with a bit as a hobby.

### WebSocket
This is all it takes to get a WebSocket server:
```javascript
const ws = require('ws');
const wsServer = new ws.Server({ noServer: true});

wsServer.on('connection', socket => {
	events.clear();
	console.log("clearing events");
	socket.on('message', message =>{
		events.add(JSON.parse(message))
	})
});

const server = app.listen(process.env.WS_PORT || '1234')

console.log("Remote Logger is starting on port: ", process.env.WS_PORT || '1234');

server.on('upgrade', (request, socket, head) =>{
	wsServer.handleUpgrade(request, socket, head, socket =>{
		wsServer.emit('connection', socket, request);
	});
});
```
The server listens for events on provided port in the startup parameter `WS_PORT` or default `1234`, and then aggregates them in the `events` object.

### Web UI
Having logs in JavaScript object I could display them as HTML. I used [pug](https://pugjs.org/api/getting-started.html) templates. All it takes to display logs is adding this in ExpressJS app:
```javascript
app.set('view engine', 'pug')
app.get('/', function (req, res) {
  res.render('index', { title: 'Remote Logger', message: 'Remote Logger!', data: events.toArray() })
})
```
And a Pug template:
```pug
html
  head
    title= title
    style
      include style.css
  body
    h1= message
    ul
      each event in data
          li(class=event.level)!= event.time+' <b>['+event.level+'] '+event.tag+'</b>  : ' + '<span style="white-space: pre-wrap">'+event.message+'</span>'
```
So as simple unordered list feeded with elements of the `events` object, with very basic CSS styles for log level colors.


This is how it looks:
![Remote Logger web interface](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ejeksihso8n0t9wgc617.png)

### Extra
I just couldn't let go of not adding something extra just for fun, so after starting the server, we are greeted by information about the port used, the server IP to be entered in the Android code, and the name of the tool. Additionally, the log page opens automatically in your browser.
```bash
❯ node index.js
Remote Logger is starting on port:  1234
Application started on port 3000 at IP: 192.168.1.87
██████╗ ███████╗███╗   ███╗ ██████╗ ████████╗███████╗
██╔══██╗██╔════╝████╗ ████║██╔═══██╗╚══██╔══╝██╔════╝
██████╔╝█████╗  ██╔████╔██║██║   ██║   ██║   █████╗
██╔══██╗██╔══╝  ██║╚██╔╝██║██║   ██║   ██║   ██╔══╝
██║  ██║███████╗██║ ╚═╝ ██║╚██████╔╝   ██║   ███████╗
╚═╝  ╚═╝╚══════╝╚═╝     ╚═╝ ╚═════╝    ╚═╝   ╚══════╝

██╗      ██████╗  ██████╗  ██████╗ ███████╗██████╗
██║     ██╔═══██╗██╔════╝ ██╔════╝ ██╔════╝██╔══██╗
██║     ██║   ██║██║  ███╗██║  ███╗█████╗  ██████╔╝
██║     ██║   ██║██║   ██║██║   ██║██╔══╝  ██╔══██╗
███████╗╚██████╔╝╚██████╔╝╚██████╔╝███████╗██║  ██║
╚══════╝ ╚═════╝  ╚═════╝  ╚═════╝ ╚══════╝╚═╝  ╚═╝
```
## Android Lib
Initialization of the android part of the tool is trivial, it is best to do it in the `onCreate()` method of the class that inherits from `Application`:
```kotlin
class App : Application() {

    override fun onCreate() {
        super.onCreate()
        RemoteLogger().initialize(
            Config(
                "192.168.1.87",
                1234,
                applicationContext.packageName
            )
        )
    }
}
```

You just initialize the library and forget about the thing, the logs go to your local server. This tool will not replace remotely collecting application crashes or Logcat itself. It is only about solving a specific problem, where the application must run in an environment that does not allow the use of Logcat, but still, in the development phase, when you need access to logs. Logger initialization and the library itself should not be included in the publicly released version of the application.

# Usage
0. Download the [repo](https://github.com/asvid/RemoteLogger)
1. Add the library to the project [JitPack](https://jitpack.io/#asvid/RemoteLogger)
2. Fire up the server in the /server directory `node index.js`
3. Launch your application with the added library
4. Refresh the page of [remote logger](http://localhost:3000/)

The library itself will connect to the server when the application starts, so the server must be launched earlier. For now, I didn't feel like doing it nicer is required. This tool is intended for developers and to solve a specific problem, not for everyday use in every project :)

All logs from Android are being collected from the moment the application starts, even before calling `onCreate ()` in `App`. Usually the first entry is `Fri Aug 06 08:58:54 GMT + 02: 00 2021 [DEBUG] LOGGER: --------- beginning of system`. Logs are collected on one thread and after connecting to the WS server they are sent on another one. Logs are collected only for the application package, so it must be specified in the `initialize()` method.

RemoteLogger takes over the handling of uncaught exceptions by setting `Thread.setDefaultUncaughtExceptionHandler`. When an exception occurs that crashes the application, it is logged on the server and the application crashes with error code `1`. I didn't have a chance to test it with Firebase Crashlytics, but if you already have it configured you probably don't need my tool.

## You don't want to use it anyway
I realize this tool is rather rough... but it helped me a lot, and I didn't want to spend more than one afternoon on it. I just wish you never need to use it :) Enjoy Logcat.