Remote Logger

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 which I had to deal with a bit as a hobby.

WebSocket

This is all it takes to get a WebSocket server:

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 templates. All it takes to display logs is adding this in ExpressJS app:

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:

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

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.

❯ 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:

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

  1. Download the repo
  2. Add the library to the project JitPack
  3. Fire up the server in the /server directory node index.js
  4. Launch your application with the added library
  5. Refresh the page of remote logger

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.


Adam Świderski

software engineer