
Debugging
Let's start by admitting that the debugging facilities for dynamic languages on the JVM are lacking: breakpoints are not supported and there are no language-specific runtime inspection tools. However, there are enough other options for debugging in Prudence that you should be able to find all your bugs. Squashing them is up to you!
Live Execution
This immensely powerful (and potentially dangerous) tool allows you to execute arbitrary code in live running applications. There are, of course, endless ways in which you can use this feature for debugging. Below are a few common examples.
Dumping Values
Anything you print to standard output will be returned to you:
curl --data '<% println(application.globals.get("db.connection")) %>' http://localhost:8080/myapp/execute/
You can also dump values to the log:
curl --data '<% application.logger.info(application.globals.get("db.connection")) %>' http://localhost:8080/myapp/execute/
Scheduling Background Tasks
Here's an example of dumping a value to the log every 5 seconds using a background task. First let's create "program.js":
<% document.require('/prudence/tasks/') Prudence.Tasks.task({ fn: function() { application.logger.info(application.globals.get('db.connection')) }, repeatEvery: '5s' }) %>
Then execute it:
curl --data-binary @program.js http://localhost:8080/myapp/execute/
Debug Page
The debug page contains a lot of useful debugging information. It's easy to get it just by causing an exception on purpose:
curl --data '<% throw null %>' http://localhost:8080/myapp/execute/
Logging
Logging is by far the most useful weapon in your debugging arsenal. Learn how to use it and use it well.
Some programming languages—Python and Ruby—have their own standard logging mechanisms, which you are welcome to use if you prefer. However, consider using Prudence's (actually Sincerity's) logging system instead: it is especially powerful and flexible, and moreover is shared by all programming languages running in Prudence.
The default logging configuration is production-ready: it will use the following rolling log files under your container's "/logs/" directory:
- "common.log": The rolling log file shared by all applications and libraries. If they don't specify their own, it will default to this (it's configured as attached to the root logger.)
- "application-[name].log": A rolling log file per Prudence application, where "[name]" is the application's subdirectory name.
- "web.log": A rolling log file of all incoming HTTP requests in NCSA-style.
- "service-prudence.log": This will be available if you're running Prudence as a service. Note that it is configured separately, and does not in fact use JVM logging.
Introduction
It's important to understand that there's a clear separation of concerns between what to log and how to log it. Programming should be concerned only with the what: the use of the API during runtime and the content of messages. Administration should be concerned only with the how: configuration of the logging system, which occurs during bootstrap.
From a programming perspective, you only care about "loggers," the entities through which you send log messages. The programmer doesn't actually know where the log messages are going. (While it is possible to change the logging configuration via API during runtime, it is not a common practice.)
"Appenders" are the mechanisms that actually write/store/send the messages somewhere, whether it's to a file, to a database, or over the network. The API is entirely ignorant as to what appenders are attached to which loggers: appenders are purely a matter of configuration.
API
Loggers are identified by a unique name in your container, and are configured globally.
The logger names are hierarchical, whereby the hierarchy is specified using ".". For example, "myApp.system.mail" is a child logger of "myApp.system". A child logger inherits its parent's configuration, which it can then override. Parents inherit from their parents, etc., up to what is called the "root logger," which is the logger with an empty string name.
Every log message you send has a "log level." The lower the level, the more specific the message. Loggers are configured with a minimum level: they will log only messages equal to or above it. So, the lower the minimum log level configured for the logger, the more chatter you should get. The following levels are supported:
- severe (1000): These are very important messages, usually about errors or other problems. You'd likely want all loggers to include this level. You should use this level sparingly.
- warning (900): These are important, but not crucial. Usually used for temporary errors or for problems that do not cause failure.
- config (800): These express change of state (configuration), and as such probably don't happen that often. They most likely appear during the initial bootstrapping, but can happen while the application runs if something causes a subsystem to restart or reconfigure itself.
- info (700): These informational messages are used for keeping track of the application's runtime health: they don't specify a problem or a change, only report normal activity. This is the most commonly used message.
- fine (500): This and the following two levels should be reserved for debugging: you normally won't go beyond "info" unless you're specifically trying to debug a problem, as this would cause a lot of chatter in your logs.
- finer (400): More minute debugging.
- finest (300): Most minute debugging.
application.logger gives you access to a unique Logger for the application, with the "prudence." prefix. The name of the logger can be configured in settings.js, and defaults to the name of the application's subdirectory.
The easiest way to use this API is via the methods named after the log levels:
application.logger.info('An info-level message') application.logger.warning('Something bad happened!')
application.getSubLogger can be used to easily access child loggers of the application.logger:
application.getSubLogger('email').info('An info-level message')
If your application logger name is "cms", then the above logger name would be "prudence.cms.email".
Instead of using application.logger and application.getSubLogger, you can access the logging API directly to retrieve a logger with any name:
importClass(java.util.logging.Logger) var logger = Logger.getLogger('myApp.system.mail') logger.info('My logger!')
If you're using JavaScript, it's recommend that you use this friendly wrapper, which adds many convenient additions over the raw logging API.
Here are examples of simple logging, where you can refer to the log levels using simple strings, or use dedicated methods named after the log levels:
document.require('/prudence/logging/') var logger = new Prudence.Logging.Logger() logger.log('info', 'An informational message') logger.fine('A fine message') logger.warning('A warning') new Prudence.Logging.Logger('email').info('Hello from the Email subsystem')
String interpolation is supported for all methods, using /sincerity/templates/:
logger.severe('Hi, {0} and {1}!', 'mom', 'dad') logger.info('Hi, {name}!', {name: 'mom'}) logger.finest('Hi, {name}!', function(original, key) { if (key == 'name') { return 'mom' } return null })
Quick JSON dump, in short form (single line, condensed) or long form (multi-line, indented):
var x = {test: 'hello', more: [1, 2, 3]} logger.dumpShort(x, 'Our object in short form') logger.dumpLong(x, 'Our object in long form') logger.dump(x, 'This is an alias for dumpShort')
Log exceptions using full or partial stack trace (supports both JavaScript and JVM exceptions):
try { nothing() } catch (e) { logger.exception(e) }
Also very useful is automatic timing of sections of code, which will emit a message when the function starts, when it ends with the execution duration (either due to successful completion or due to a uncaught exception):
logger.time('The loop', function() { var x = 1 for (var i = 0; i < 100000; i++) { x /= 2 } })
Log messages will not be written if they are configured to be filtered out. However, your code is still constructing the message and sending it to the logging system, which may involve a performance hit.
The easiest way to optimize is to check for the log level before constructing the message:
if (logger.isLoggable(java.util.logging.Level.FINE)) { var data = heavyFetchFromDatabase() return 'This function was called because the "fine" log level is permitted' }
If you're using the /prudence/logging/ library, as a shortcut you can send an anonymous function:
logger.fine(function() { var data = heavyFetchFromDatabase() return 'This function was called because the "fine" log level is permitted' })
Obviously, this is cumbersome to always follow this format, so it's best used only if constructing the message indeed involves heavy lifting.
Configuration
You configure all your loggers and appenders under your container's "/configuration/logging/" directory, which is handled by Sincerity's logging plugin, so it's recommended you read the documentation there. The plugin has useful add-ons for centralizing logging, for example to a dedicated Log4j server, or directly to MongoDB.
The best way to learn how to configure logging is to go through the default logging configuration. As usual, Prudence and Sincerity use "configuration by script," so in fact you'll be using the /sincerity/log4j/ library.
In order to maintain conformity, the API used throughout Prudence is the standard JVM logging API (often called JULI: "java.util.logging Interface"). However, the logging system used by default in Prudence is Sincerity's logging plugin funnels several different logging APIs into Apache Log4j.
Unfortunately, until the JVM finally included a logging standard, there were already other popular logging APIs. Also unfortunately, the introduced standard is simply not as powerful as Log4j, and we did not want to compromise on power. Fortunately, the SLF4J library allows Sincerity to mimic all these various APIs while implementing them in Log4j, so you get the best of all worlds as well as a uniform system.
The outcome of this is slightly annoying: the log level names used in the configuration files (Log4j) are different from those used in the API (JULI), and indeed the configuration is coarser than the API. Here's how they match up:
Configuration Log Level | API Log Level |
fatal | |
error | severe |
warn | warning |
config | |
info | info |
debug | fine |
finer | |
trace | finest |
So, configuring a logger's level to "info" will mean that it will log the following API levels: "info," "config," "info," "warning" and "severe."
To learn how to configure the message format for this special logger, see the log service.
Various subsystems within Prudence use their own loggers. The default configuration files set these to reasonable defaults, which you can modify:
- Restlet logs its internal application events to "org.restlet.Application.[name]". You can access this logger in your code using the application.application.logger API. By default, it is attached to the application's appender and set to level "warn".
- Jetty and Hazelcast both feature robust event logging.
Quick-and-Dirty Logging to the Console
Sometimes, in the midst of intensive debugging, setting up proper logging is just too time consuming. It would also be wasteful, because you know you will quickly delete the logging message once you find the bug.
In such situations the most straightforward solution is to output your message to the console. Since often Prudence captures standard output (for example, in template resources), you may need to override any captures and go straight to the console:
java.lang.System.out.println('Desperate times call for desperate measures!')
Note that there is no console if you're running Prudence as a service/daemon (it is captured to the wrapper log). In those cases you either want to switch to running Prudence from the console, or use the logging API instead.
JVM Logging
If you prefer, you can use the JVM's built-in logging instead of the Sincerity plugin. It is less flexible and much less scalable, but may be preferable in some cases. To configure it, create a configuration file, for example "logging.properties", and define the properties of the handlers and loggers:
handlers = java.util.logging.FileHandler, java.util.logging.ConsoleHandler java.util.logging.FileHandler.pattern = log.%u.%g.txt java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter java.util.logging.SimpleFormatter.format=%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp %2$s %4$s: %5$s%n java.util.logging.ConsoleHandler.level = ALL com.threecrickets.prudence.cache.level = ALL .level = INFO
Then, start Prudence using a switch to point at that configuration:
JVM_SWITCHES=-Djava.util.logging.config.file=logging.properties sincerity use mycontainer : start prudence
For more information, see the JVM documentation.
Best Practices
There are many different styles of logging, but here are some good rules of thumb:
- Remember that people read the logs! This can mean other developers in your team, but it can also be you in the future, at a time when you may forget why you added a particular log message. Because of this, you always want every log message to be clear in and of itself. For example, if you're dumping the contents of a variable, prefix it with the name of the variable, as well as the name of the function or file from which it is logged. Otherwise, it would be impossible for others to find out where the dump is coming from and how to disable it.
- Use log levels wisely: if you're adding a logging message purely for debugging, don't set it to "info", otherwise it will remain as distracting chatter during normal operations. Use "fine" instead.
- Some log messages are very specific, and added during debugging, but would be distracting once the bug is solved. In that case make sure to disable the message when you're dong debugging by either switching it to the "finest" level, commenting out the code, or just deleting it. Make sure not to commit code that outputs distracting log messages.
- A good, clear log is an invaluable debugging tool. A confusing, messy log is worthless!
Debug Page
Uncaught exceptions will cause Prudence to generate a 500 "internal error" HTTP status. When debug modeis enabled, this will come with a generated HTML page full of useful debugging information:
- Error stack trace, with links to the source code
- HTTP request warnings
-
HTTP request information, including all headers parsed and organized:
- URIs
- Query params
- Media preferences for content negotiation
- Conditions for content negotiation
- Caching directives
- Client identification
- Cookies
- Payload
- conversations.locals
- HTTP response attributes
- Application information:
-
Scripturian:
- Executable attributes
- Execution context attributes
- Low-level JVM stack trace
Obviously, this information may include private data, so you'll want to turn this debug page off for production deployments.
Monitoring
One of the advantages of having Prudence running on the JVM is its powerful built-in monitoring capabilities based on JMX technology. Using the VisualVM tool, you can get a comprehensive view of the JVM's memory use over time, examine garbage collection, threading behavior and performance, and perform basic profiling. It's an invaluable tool for debugging memory leaks and performance bottlenecks.
Threads
Here are a few common thread groups you will see for the Prudence's subsystems:
- qtp: Jetty server's selectors and acceptors
- HttpClient: Jetty client
- cron4j: cron4j
- hz: Hazelcast
- Wrapper: The Sincerity service plugin wrapper
- JXM and RMI: These are used by JMX itself
The "thread dump" button is very useful for getting a picture of what all threads are doing at a particular moment.
Management Beans
If you install the MBeans plugin, you will gain access to useful information and functionality exposed by Prudence's subsystems:
- java.lang: General JVM tools, such as operating system information as well as class loader, memory, compilation and threading statistics; use the "gc" operation in the "Memory" type to cause immediate garbage collection
- java.nio: Some useful I/O statistics
- java.util.logging: Allows you to query the JULI API
- org.apache.logging.log4j2: Available when you are using the Sincerity logging plugin; access to the logging implementation; note that you can also install a dedicated GUI using the JConsole plugin: see the Log4j management documentation for more information
- org.tanukisoftware.wrapper: Available when you are using the Sincerity service plugin; operations allows you to stop/restart the service
Remote JMX
Connecting to a running Prudence JVM over TCP/IP is not so trivial. See the "extras" section in the Sincerity service plugin documentation for a guide.
Memory Leaks
These are sometimes difficult to debug: by the time you get an OutOfMemoryError, it might be impossible to connect over JMX.
You should familiarize yourself with these two powerful heap analysis tools.
Use jmap to get a live dump from a running JVM:
jmap -dump:live,format=b,file=/full/path/to/dumps/prudence.bin <pid>
If you're getting connection errors, try the following:
-
In Linux, you might need to enable inter-process access for ptrace:
echo 0 | sudo tee /proc/sys/kernel/yama/ptrace_scope
- Try using root access to run jmap
- Try adding the "-F" switch (though it will likely only create a partial dump)
When opening the dump using jhat, you might get an OutOfMememoryError (again!) if the file is very large. Increase available memory like so:
jhat -J-mx2000m prudence.bin
Point your web browser to http://localhost:7000/ to see jhat's analysis.
You can enforce a heap dump to file upon OutOfMemoryError using the following JVM command line switches:
JVM_SWITCHES=\ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/full/path/to/logs/ \ sincerity start prudence
Likewise, it's a good idea to turn on the garbage collector log:
JVM_SWITCHES=\ -Xloggc:/full/path/to/logs/gc.log \ -XX:+PrintGCDetails \ -XX:+PrintTenuringDistribution \ sincerity start prudence
If you're using the Sincerity service plugin, you can enable both of these features by creating a "/configuration/service/jvm/memory-monitoring.conf" file:
# Out-of-memory heap dumps -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath={sincerity.container.root}/logs/ # Garbage collection log -Xloggc:{sincerity.container.root}/logs/gc.log -XX:+PrintGCDetails -XX:+PrintTenuringDistribution
The Prudence Manual is provided for you under the terms of the Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. The complete manual is available for download as a PDF.