Image for post
Image for post

Logging in Java libraries for Firebase and Google Cloud Platform

When developing server-side Firebase applications using the Firebase Admin SDK for Java, it is sometimes useful to look at the debug logs produced by the SDK. The FirebaseDatabase class provides a setLogLevel() method that facilitates this to a certain extent. However, setLogLevel() method was deprecated in the version 5.3.0 release of the Admin Java SDK. The new recommended approach for obtaining debug logs from the SDK is via SLF4J. This change comes with several advantages that enterprise Java developers would find useful.

  1. It is now possible to configure logging independent of the application code. Enabling debug logs no longer requires code changes.
  2. SLF4J, being a high-level facade, supports a number of logging frameworks. We can log to the console, Log4J, Logback or any other logging framework compatible with SLF4J.
  3. A wide range of options to configure logging levels (debug, info, warn etc.), patterns, rotation schedules and more.

Logging in enterprise applications is a complicated matter. There are many aspects to consider including persistence, log rotation, formatting and performance. Fortunately, there are many powerful logging frameworks such as Logback, Apache Log4J, and Java’s built-in java.util.logging (JUL) that take care of most of these concerns. However, each of these frameworks have a slightly different API and a configuration model. Therefore if you write an application using any one framework, you will be tied to that framework. This is particularly problematic for library developers, since the users of the library may want to use different logging frameworks.

This is where the logging facades come in. A logging facade is a high-level logging API that acts as a mediator between application/library code, and the logging frameworks. We write our code using a logging facade, instead of a specific logging framework. Then at the deployment time we can decide what concrete logging framework to use (i.e. late binding the logging framework). By decoupling code from concrete logging frameworks, facades yield greater levels of code portability and maintainability.

SLF4J is one such logging facade. It supports all the common logging frameworks, and is favored by many Java developers due to its simple API and the performant implementation. Starting from version 5.3.0, Firebase Admin SDK for Java uses SLF4J as its logging facade. This enables developers using the Admin Java SDK to use any concrete logging framework of their choice. SLF4J can be easily wired up to a logging framework by dropping a binding library to the classpath. It has been tested in many environments, and also works well in Google App Engine.

This is mainly useful when testing or debugging an application locally. Simply add the slf4j-simple binding to the application classpath, and set the -Dorg.slf4j.simpleLogger.defaultLogLevel=debug system property. The Admin Java SDK will start printing debug log statements to your console:

[FirebaseDatabaseWorker] DEBUG - pc_0 - Scheduling connection attempt[FirebaseDatabaseWorker] DEBUG - Scheduling retry in 0ms[FirebaseDatabaseWorker] DEBUG - pc_0 - Trying to fetch auth token[FirebaseDatabaseWorker] DEBUG - pc_0 - Successfully fetched token, opening connection[FirebaseDatabaseWorker] DEBUG io.netty.util.internal.logging.InternalLoggerFactory - Using SLF4J as the default logging framework[FirebaseDatabaseWorker] DEBUG io.netty.util.internal.InternalThreadLocalMap - -Dio.netty.threadLocalMap.stringBuilder.initialSize: 1024[FirebaseDatabaseWorker] DEBUG io.netty.util.internal.InternalThreadLocalMap - -Dio.netty.threadLocalMap.stringBuilder.maxSize: 4096[FirebaseDatabaseWorker] DEBUG io.netty.util.internal.PlatformDependent0 - Java version: 8[firebase-websocket-worker] DEBUG - ws_0 - websocket opened[FirebaseDatabaseWorker] DEBUG - ws_0 - Reset keepAlive[firebase-websocket-worker] DEBUG io.netty.handler.codec.http.websocketx.WebSocket08FrameDecoder - Decoding WebSocket Frame opCode=1[firebase-websocket-worker] DEBUG io.netty.handler.codec.http.websocketx.WebSocket08FrameDecoder - Decoding WebSocket Frame length=132[firebase-websocket-worker] DEBUG - ws_0 - ws message: {"t":"c","d":{"t":"h","d":{"ts":1517693580787,"v":"5","h":"","s":"nPyuogc4ji6AURBD8pKEUPzdBsOedoCP"}}}[FirebaseDatabaseWorker] DEBUG - ws_0 - Reset keepAlive. Remaining: 44998[FirebaseDatabaseWorker] DEBUG - ws_0 - HandleNewFrameCount: 1

Take a look at the API docs of the SimpleLogger for additional options you can set to further configure the log output. Instead of setting them as individual system properties, you can also save them to a file named, and put it in your application classpath to be auto discovered.

The deprecated setLogLevel() method also prints logs to the console. Therefore, the approach described here can be used as an alternative to calling setLogLevel(Level.DEBUG) in your application code.

Most Google Cloud Platform (GCP) libraries for Java do not use a logging facade yet, and instead directly log to the JUL framework. Personally I’m not a fan of JUL, but one cannot ignore the appeal of an API that is built into the language. You can configure how JUL operates by creating a file for your app. Just remember to set the -Djava.util.logging.config.file system property to point to that file when executing the application. If you are not familiar with files, listing 1 shows an example.

Listing 1: file for getting debug logs from GCP libraries

Configuration in listing 1 instructs JUL to log to the console. It enables debug logging for* packages, which includes all GCP libraries. Then it sets a pattern for the log statements. Some sample JUL logs captured using listing 1 are shown below. These logs were produced by the Google API client library — a dependency used by the Admin SDK to make HTTP calls. This specific interaction was triggered by a call to the FirebaseAuth.getUser() API in the Admin SDK:

2018-02-03 14:01:31 CONFIG execute -------------- REQUEST  --------------
Accept-Encoding: gzip
Authorization: <Not Logged>
User-Agent: Google-HTTP-Java-Client/1.23.0 (gzip)
x-client-version: Java/Admin/5.8.0
Content-Type: application/json; charset=UTF-8
Content-Length: 19
2018-02-03 14:01:31 CONFIG execute curl -v --compressed -X POST -H 'Accept-Encoding: gzip' -H 'Authorization: <Not Logged>' -H 'User-Agent: Google-HTTP-Java-Client/1.23.0 (gzip)' -H 'x-client-version: Java/Admin/5.8.0' -H 'Content-Type: application/json; charset=UTF-8' -d '@-' -- '' << $$$
2018-02-03 14:01:31 CONFIG close Total: 19 bytes
2018-02-03 14:01:31 CONFIG close {"localId":["uid"]}
2018-02-03 14:01:31 CONFIG <init> -------------- RESPONSE --------------
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Alt-Svc: hq=":443"; ma=2592000; quic=51303431; quic=51303339; quic=51303338; quic=51303337; quic=51303335,quic=":443"; ma=2592000; v="41,39,38,37,35"
Server: GSE
X-Content-Type-Options: nosniff
Pragma: no-cache
Date: Sat, 03 Feb 2018 22:01:31 GMT
X-Frame-Options: SAMEORIGIN
Cache-Control: no-cache, no-store, max-age=0, must-revalidate
ETag: "MXZTd1FuXGy6b-RgGu8L_ikGax4/vyGp6PvFo4RvsFtPoIWeCReyIC8"
Content-Encoding: gzip
Vary: X-Origin
Vary: Origin
Expires: Mon, 01 Jan 1990 00:00:00 GMT
X-XSS-Protection: 1; mode=block
Content-Type: application/json; charset=UTF-8
2018-02-03 14:01:31 CONFIG close Total: 54 bytes
2018-02-03 14:01:31 CONFIG close {
"kind": "identitytoolkit#GetAccountInfoResponse"

If you wish to also direct the Firebase Admin SDK logs to JUL, you just need to drop the slf4j-jdk14 binding to the application classpath. Here are some Realtime Database logs captured in this manner:

2018-02-03 14:01:31 FINE$1 newThread Creating new thread for: FirebaseDatabaseWorker
2018-02-03 14:01:31 FINE debug pc_0 - Scheduling connection attempt
2018-02-03 14:01:31 FINE debug Scheduling retry in 0ms
2018-02-03 14:01:31 FINE debug pc_0 - Trying to fetch auth token
2018-02-03 14:01:31 FINE debug pc_0 - Successfully fetched token, opening connection
2018-02-03 14:01:31 FINE debug conn_0 - Opening a connection
2018-02-03 14:01:32 FINE$1 newThread Creating new thread for: firebase-websocket-worker
2018-02-03 14:01:32 FINE debug ws_0 - websocket opened
2018-02-03 14:01:32 FINE debug ws_0 - Reset keepAlive
2018-02-03 14:01:32 FINE debug ws_0 - ws message: {"t":"c","d":{"t":"h","d":{"ts":1517695292399,"v":"5","h":"","s":"TOY9NrzcsYiZ0S0b4oHYVZdaeuMCLQkf"}}}
2018-02-03 14:01:32 FINE debug ws_0 - Reset keepAlive. Remaining: 44998
2018-02-03 14:01:32 FINE debug ws_0 - HandleNewFrameCount: 1

Since listing 1 enables debug logging for all* packages, it activates the debug mode for Firebase Admin SDK as well, which uses as its package name.

Google App Engine (GAE) expects applications and libraries to log to the JUL framework. You can configure logging by creating a file in the WEB-INF/ directory of your app. Listing 2 shows a configuration that enables debug logs for the Firebase Admin SDK.

Listing 2: for getting Firebase Admin SDK debug logs in App Engine

You also need to reference the configuration file in your appengine-web.xml, as illustrated in listing 3.

Listing 3: Referencing the file from the GAE app descriptor

Finally, to actually direct Admin SDK logs to JUL, bundle the slf4j-jdk14 binding with your app (i.e. make sure it goes in the WEB-INF/lib directory of the app). Now when you deploy the app in the cloud, GAE will capture the debug logs produced by the Admin SDK, and make them accessible through the GCP web console.

Image for post
Image for post
Figure 1: Firebase debug logs captured in App Engine

Logging is an important aspect in enterprise Java applications that often comes in handy when testing or debugging code. GCP libraries log to Java’s built-in JUL framework, which can be configured via a file. Firebase Admin SDK for Java goes one step further by using SLF4J as a logging facade, which enables developers to use any logging framework of their choice (including JUL). Logs produced by these libraries can be directed to the console, the file system or any other persistent storage suitable for logs. When deployed in the App Engine environment, the logs are automatically collected, parsed and sent to the GCP console for presentation.

I hope this answers most of the questions Java developers may have on obtaining logs from server-side Firebase and GCP libraries. If you have come across any interesting logging-related use cases, feel free to share them. Happy coding with Firebase and Google Cloud Platform!

Written by

Software engineer at Google. Enjoys working at the intersection of cloud, mobile and programming languages. Fan of all things tech and open source.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store