Dynamic Logback and Migrating Logback-Showcase

I did a couple of small projects over the holidays while drinking coquito, so here they are.

TL;DR I made a very smol dynamic logging project, and migrated terse-logback-showcase from Heroku to fly.io. It's now https://terse-logback-showcase.fly.dev/ and it has pictures of cats.

Dynamic Logback

The first is a "simplest possible dynamic logging" project called dynamic-logback. This is a project that sets up Logback, and then periodically refreshes log levels from a file. The functionality is in one file and is less than 100 lines of code. (After writing this, I did a search on "dynamic logback" on Github and discovered https://github.com/syamantm/dynamic-logback which is a more complete example.)

Anyway, the point is that dynamic logging is easy! You don't need a lot of infrastructure or to set up a database, you can just set up a timer task and be done with it.

There is an assumption that changing log levels requires a total configuration refresh. This is not helped by the documentation that only mentions autoscan as an option, and encourages the setting of levels directly in logback.xml. The reality is that reloading only applies to Logback appenders and their ancillary supports. Log levels can be queried and modified without any heavy lifting. In SQL terms, appenders and filters are the DDL statements, while querying and changing log levels are the DQL and DML statements.

To abstract it from Log4J/Logback APIs, you could add a LogLevelQuery/LogLevelResult interaction for querying log levels, and a LogLevelCommand/LogLevelEvent for modifying log levels, and that would allow for a CQRS style API. That's probably for another project though.

Migrating terse-logback-showcase from Heroku to fly.io

The showcase application used to be on Heroku, but they closed down their free tier. Luckily, fly.io has an option for deploying docker containers.

The application runs on Play Framework, which is JVM based and has built in docker deployment via sbt-native-packager, so the only thing I needed to for a Dockerfile was run sbt docker:publishLocal. Play itself takes up barely any memory, but the JVM is used to having a bunch of memory available – the free tier is only 256 MB, so it was time to get creative.

I found ibm-semeru-runtimes:open-17-jre-focal image with -XX:MaxRAM=70m was enough to get the JVM started, based on a tip from the community forums.

dockerBaseImage := "ibm-semeru-runtimes:open-17-jre-focal"
Universal / javaOptions += "-J-XX:MaxRAM=70m"

Then, I had to add the explicit add-opens required by JDK 17 as --illegal-access=permit is gone. I don't think there's any way to avoid this for now.

Universal / javaOptions ++= Seq(

I had to make sure the internal sqlite database used by Blacklite was writable:

dockerChmodType := DockerChmodType.UserGroupWriteExecute

And I had to disable the PID file:

Universal / javaOptions += "-Dpidfile.path=/dev/null"

You can see build.sbt for the actual implementation.

For some reason the logs directory wouldn't be created by sbt docker:publishLocal so I added it to dist directory for deployment:

mkdir -p dist/logs
touch dist/logs/.README

I didn't have to set up HTTPS or anything special for fly.toml. I did have to set up some fly secrets:

fly secrets set PLAY_APP_SECRET=<secret>
fly secrets set SENTRY_DSN=$SENTRY_DSN

and then it was just a case of creating docker images and running fly deploy:

sbt clean stage docker:publishLocal
cd target/docker/stage
fly deploy --app terse-logback-showcase

And now https://terse-logback-showcase.fly.dev/ is up! It has pictures of cats.