Debugging Live Scala Applications with Lightrun
Scala powers some of the world’s biggest websites. It’s normally very easy to debug Scala under normal conditions, but when it’s deployed in a remote environment this might be more challenging. Especially at scale.
This is true for a simple application or for a server application running Play or pretty much anything else out there. We need a way to observe live applications and debug issues without interrupting user flows.
This is where Lightrun steps in to make this process trivial and secure without risking your uptime. Notice that this short tutorial uses a simple “Game of Life” app for demonstration purposes. But you can use pretty much any application with Lightrun. The one constraint is that it’s “long running”, so a hello world application will end too quickly and we won’t have time to attach to it.
Notice that the game of life finishes a bit quickly so by the time I was able to walk through the demo I had to re-launch the app over and over.
In this tutorial, we’ll be using the “Game of Life” application written in Scala.
Step 1 – Build the Test Application
Download the project. Open the directory in IntelliJ/IDEA. Select Edit Configurations and add a new sbt task configuration called “Package” that invokes the package task. In the command line make sure you have Scala 2.12.x to match the version used by the demo.
Press Play to run that task in the IDE and package a jar file.
Step 2 – Install Lightrun & Run
If you didn’t do this yet, create a Lightrun account. Download the IDE plugin and set up the agent on your server. I won’t replicate the steps here as they are pretty clear on the website.
You can download the agent into the project directory, then run the app using:
java -agentpath:PATH_TO_AGENT_DIRECTORY/lightrun_agent.so -classpath PATH_TO_SCALA/scala-library-2.12.3.jar:target/scala-2.12/game-of-life-scala_2.12-0.1.0.jar de.l7r7.lab.conway.GameOfLife
Notice you need to replace PATH_TO_AGENT_DIRECTORY
with the right path to the agent. You also need to update the PATH_TO_SCALA
entry to point at a 2.12 install of Scala. Try to avoid shortcuts like ~
which might cause issues.
You can now install the Lightrun plugin and login via the IDE.
Step 3 – Inject a Log
In the IDE, open the GameOfLife.scala file. Go to line 75 and right click on it. Select Lightrun and Log.
In the Log dialog, type the following code into the format field: Prime number {cnt} is {i}
This injects a new log into the application. Notice the variables in curly braces. We can use any valid readonly Java expression, including method calls in those braces. Now if you look at the app you’ll notice printouts like this:
Oct 24, 2021 2:34:10 PM de.l7r7.lab.conway.GameOfLife.Board $anonfun$next$3
INFO: LOGPOINT: Value of pos is: { neighbors: <Object>, x: 42, y: 15, bitmap$0: true }
Oct 24, 2021 2:34:10 PM de.l7r7.lab.conway.GameOfLife.Board $anonfun$next$3
INFO: LOGPOINT: Value of pos is: { neighbors: <Object>, x: 36, y: 23, bitmap$0: true }
Oct 24, 2021 2:34:10 PM de.l7r7.lab.conway.GameOfLife.Board $anonfun$next$3
INFO: LOGPOINT: Value of pos is: { neighbors: <Object>, x: 7, y: 1, bitmap$0: true }
Oct 24, 2021 2:34:10 PM de.l7r7.lab.conway.GameOfLife.Board $anonfun$next$3
INFO: LOGPOINT: Value of pos is: { neighbors: <Object>, x: 38, y: 25, bitmap$0: true }
Oct 24, 2021 2:34:10 PM de.l7r7.lab.conway.GameOfLife.Board $anonfun$next$3
INFO: LOGPOINT: Value of pos is: { neighbors: <Object>, x: 1, y: 20, bitmap$0: true }
Oct 24, 2021 2:34:10 PM de.l7r7.lab.conway.GameOfLife.Board $anonfun$next$3
INFO: LOGPOINT: Value of pos is: { neighbors: <Object>, x: 7, y: 5, bitmap$0: true }
Oct 24, 2021 2:34:10 PM de.l7r7.lab.conway.GameOfLife.Board $anonfun$next$3
INFO: LOGPOINT: Value of pos is: { neighbors: <Object>, x: 32, y: 17, bitmap$0: true }
Oct 24, 2021 2:34:10 PM de.l7r7.lab.conway.GameOfLife.Board $anonfun$next$3
INFO: LOGPOINT: Value of pos is: { neighbors: <Object>, x: 7, y: 21, bitmap$0: true }
Notice that since the app runs quickly, you might need to re-run it and make sure you send the logs to the right instance of the app.
Once you’re done with the log, you can delete it from the right click menu or the tree on the right.
Step 4 – Add a Snapshot
Back in line 75, right-click, then select Lightrun and Snapshot. Notice that a snapshot doesn’t have a format entry:
The snapshot is like a debugging breakpoint, but it doesn’t stop the execution of the application. As such it can’t break your live application flow.
You can see the stack trace on the left which you can navigate through. You can see variable values on the right matching the current stack frame and you can use those to understand what went on in a particular phase.
Notice that all Lightrun actions (snapshots, logs and metrics) support conditional execution. That means you can use an expression to limit them to a specific case. E.g. userName.equalsIgnoreCase("Shai")
which can limit logs/snapshots only to the user Shai.
Notice that conditions, expressions etc. are all written using Java syntax and not Scala syntax.
There’s So Much More
I barely scratched the surface here. I didn’t touch metrics, tags or complex usages. Installation and setup are also pretty elaborate in some cases, so if you run into trouble don’t hesitate to contact our support team via the chat widget on the website.
Lightrun can give your application a level of observability that hasn’t been seen before.
See Also
See more posts in this series:
It’s Really not that Complicated.
You can actually understand what’s going on inside your live applications.