Java Agent
The Levo Java Agent captures TLS-encrypted traffic directly from the JVM by instrumenting the SSL/TLS layer at the bytecode level. It works alongside the Levo eBPF Sensor, which reconstructs HTTP requests and responses from the captured bytes.
Prerequisites
- Java 8 or later
- Levo eBPF Sensor installed and running — see Install eBPF Sensor
libnative_ioctl.soaccessible on the host (included in the agent download)
Installation
1. Download and extract
Download the agent package from the Downloads repo and extract it on the application server:
levo-agent-0.9.0-all.jar # main agent JAR
levo-agent-bootstrap-0.9.0.jar # bootstrap JAR
libnative_ioctl.so # native JNI library (amd64)
2. Set the native library path
export LIB_NATIVE_IOCTL_FILE_PATH=/path/to/libnative_ioctl.so
3. Start the application with the agent
java -javaagent:/path/to/levo-agent-0.9.0-all.jar \
-Dbootstrap.jar=/path/to/levo-agent-bootstrap-0.9.0.jar \
-jar your-application.jar
For JRuby applications:
JRUBY_OPTS="-J-javaagent:/path/to/levo-agent-0.9.0-all.jar -J-Dbootstrap.jar=/path/to/levo-agent-bootstrap-0.9.0.jar" ruby app.rb
Quick Start Examples
Standard server-side capture (most applications):
java -javaagent:/path/to/levo-agent-0.9.0-all.jar=LevoSatelliteUrl=localhost:4317,LevoSamplingPercent=100 \
-Dbootstrap.jar=/path/to/levo-agent-bootstrap-0.9.0.jar \
-jar your-application.jar
Full capture with debug logging:
java -javaagent:/path/to/levo-agent-0.9.0-all.jar=LevoSatelliteUrl=localhost:4317,LevoSamplingPercent=100,LevoDebug=true,LevoLogFile=/tmp/levo-agent.log \
-Dbootstrap.jar=/path/to/levo-agent-bootstrap-0.9.0.jar \
-jar your-application.jar
Client-side only (outbound third-party API discovery):
java -javaagent:/path/to/levo-agent-0.9.0-all.jar=TraceClientTrafficOnly=true \
-Dbootstrap.jar=/path/to/levo-agent-bootstrap-0.9.0.jar \
-jar your-application.jar
Configuration Reference
Agent arguments are passed as key=value pairs on the -javaagent: flag, separated by commas. Most settings also accept a JVM system property (-Dlevo.xxx=value) as an alternative.
Precedence order (highest to lowest):
- Agent argument on
-javaagent:flag - JVM system property (
-D...) - Environment variable (logging settings only)
- Built-in default
Instrumentation Toggles
Controls which SSL/TLS interception paths are active. Defaults are tuned for modern Java stacks.
| Agent Arg | Default | Covers | When to Change |
|---|---|---|---|
SunSecuritySslInstrumentation | true | Blocking JDK SSLSocket (sun.security.ssl.AppInputStream/AppOutputStream). Covers Kafka clients, HttpsURLConnection, Apache HttpClient, JDBC SSL, JRuby OpenSSL bridge. | Rarely needed to disable. |
JavaxSSLEngineInstrumentation | true | Non-blocking JDK SSLEngine (sun.security.ssl.SSLEngineImpl, io.netty.handler.ssl.JdkSslEngine). Covers Vert.x, Quarkus, Netty with SslProvider.JDK, gRPC JDK provider. | Disable only when isolating instrumentation paths. |
NettyOpenSslInstrumentation | true | Netty BoringSSL/OpenSSL (ReferenceCountedOpenSslEngine). Covers Netty/Vert.x with SslProvider.OPENSSL via netty-tcnative. | Safe to leave on — no-op if netty-tcnative is not on the classpath. |
JavaxSslSocketStreamInstrumentation | false | SSLSocketImpl.getInputStream()/getOutputStream() wrapping. | Enable for specific raw-SSLSocket use cases. |
JRubyOpensslInstrumentation | false | JRuby OpenSSL bridge (org.jruby.ext.openssl.SSLSocket). | Enable for JRuby applications. |
NettySslHandlerProbeInstrumentation | true | Logs the active SSLEngine implementation once at startup. | Disable to suppress the startup INFO line. |
NettySslHandlerChannelBindInstrumentation | true | 5-tuple recovery for Netty/Vert.x/Spring WebFlux/Reactor-Netty. | Disable only for diagnostic purposes. |
TomcatSecureChannelBindInstrumentation | true | 5-tuple recovery for Tomcat NIO/NIO2. Covers Tomcat 8.5/9/10/11. | Disable only for diagnostic purposes. |
JettySslConnectionBindInstrumentation | true | 5-tuple recovery for Jetty. Covers Jetty 9.4/10/11/12. | Disable only for diagnostic purposes. |
SSLContextCreateEngineInstrumentation | true | 5-tuple recovery for JCE stacks. Covers JDK HttpsServer, BouncyCastle server, Conscrypt server. | Disable only for diagnostic purposes. |
Capture Filters
| Agent Arg | System Property | Default | Description |
|---|---|---|---|
TraceClientTrafficOnly | levo.trace.client.traffic.only | false | Capture only outbound (client-side) traffic. Use for third-party API discovery. |
LevoCapturePorts | levo.capture.ports | unset | Comma-separated port allowlist (e.g. 8443,9000). Unset = all ports. |
LevoExcludePorts | levo.exclude.ports | unset | Comma-separated port denylist. Wins over the allowlist. |
LevoCaptureHosts | levo.capture.hosts | unset | Host allowlist with wildcard support (*.foo.com, api.*). |
LevoExcludeHosts | levo.exclude.hosts | unset | Host denylist. Use to exclude health-check and metrics endpoints. |
LevoSuppressHttpProbes | levo.suppress.http.probes | on | Suppress HEAD / keepalive probes from being captured. |
LevoHttpProbePaths | levo.suppress.http.probe.paths | / | Comma-separated paths suppressed when LevoSuppressHttpProbes=on. |
Filter precedence (evaluated in order):
Port allowlist → Port denylist → Host allowlist → Host denylist
→ Connection sampling → Rate cap → Dispatcher
Denylist always wins over allowlist.
Sampling and Queue
| Agent Arg | System Property | Default | Description |
|---|---|---|---|
LevoSamplingPercent | levo.sampling.percent | 100 | Per-connection sticky sampling (0–100). Decision is made once per TCP connection and held for its lifetime — guarantees fully reconstructable HTTP messages. |
LevoSamplingRatePerSec | levo.sampling.rate.per.sec | 0 | Token-bucket cap on captured events/sec. 0 disables. |
LevoMaxQueueSize | levo.dispatch.queue.size | 8192 | Bounded queue capacity (rounds up to next power of two). |
LevoDispatcherThreads | levo.dispatch.threads | 1 | Consumer threads draining the queue. Clamped to 1–8. |
LevoMaxPayloadBytes | levo.max.payload.bytes | 32768 | Truncate captured payloads above this size (bytes). 0 disables truncation. |
LevoDropPolicy | levo.drop.policy | newest | Queue overflow behavior: newest drops incoming events; oldest evicts the queue head. |
LevoMetricsLogIntervalSec | levo.metrics.log.interval.sec | 60 | Interval for the periodic dispatch summary log line (seconds). 0 disables. |
LevoSamplingPercent applies once per connection, not per chunk. A sampled-in connection is fully captured for its lifetime, guaranteeing fully reconstructable HTTP messages.
Debug and Logging
| Agent Arg | System Property | Env Var | Default | Description |
|---|---|---|---|---|
LevoDebug | levo.debug | LEVO_DEBUG | false | Enable debug mode. Forces LevoSamplingPercent=100 and LevoSamplingRatePerSec=0 unless explicitly overridden. |
LevoLogLevel | levo.log.level | LEVO_LOG_LEVEL | DEBUG | Log level: DEBUG, INFO, WARN, ERROR, or OFF. |
LevoLogFile | levo.log.file | LEVO_LOG_FILE | /tmp/levo-agent-debug.log | Path for agent log output. |
LevoLogMaxSizeMb | levo.log.max.size.mb | LEVO_LOG_MAX_SIZE_MB | 10 | Max log file size (MB) before rotation. |
LevoLogMaxFiles | levo.log.max.files | LEVO_LOG_MAX_FILES | 3 | Number of rotated log files to retain. |
LevoSatelliteUrl | levo.satellite.url | — | — | Host and port of the Levo collector (e.g. localhost:4317). |
Native Library
| Setting | Type | Default | Description |
|---|---|---|---|
LIB_NATIVE_IOCTL_FILE_PATH | Environment variable | /usr/lib/libnative_ioctl.so | Absolute path to the shared JNI library. The agent logs the resolved path at startup. |
Understanding the Startup Banner
At startup the agent logs a full configuration banner showing every effective value and its source (agentArg, default, sysProp):
LEVO JAVA AGENT STARTUP
Agent version : 0.9.0
LevoMaxPayloadBytes : 32768 (default)
LevoSamplingPercent : 100 (set: agentArg)
SunSecuritySslInstrumentation: true (default)
JavaxSSLEngineInstrumentation: true (default)
...
Each instrumentation reports its transform result at install time:
INFO JavaxSSLEngineInstrumentation: at install — transformed 1 class(es), 0 error(s)
A deferred health check fires 60 seconds after startup and logs a summary of active instrumentation.
Understanding the Dispatch Summary
Every LevoMetricsLogIntervalSec seconds (default 60s), the agent logs a dispatch summary:
Dispatch: events[r/w/a/c]=842/388/8/4 bytes[r/w]=1.2MB/0.6MB
drops[sampled/full/oversize/empty]=0/0/0/0
dropReasons={http_probe_suppressed=12}
tuple[netty/tomcat/jetty/peer/reflect/fallback]=388/0/0/0/0/0
ioctl[nonzero/err/ackAgoSec]=1230/0/never
topHosts={localhost:8443=842}
| Field | What it means |
|---|---|
events[r/w/a/c] | READ / WRITE / ACCEPT / CLOSE events captured |
drops[sampled/full/oversize/empty] | Events filtered by sampling / queue capacity / payload size / empty buffer |
dropReasons | Named drop reasons including filter hits and probe suppression |
tuple[netty/tomcat/jetty/peer/reflect/fallback] | 5-tuple resolution breakdown — shows which framework paths are active |
ioctl[nonzero/err/ackAgoSec] | ioctl delivery counters |
topHosts | Most active captured hosts |
JMX Metrics
The agent exposes live metrics at ai.levo:type=Metrics:
| Attribute | Description |
|---|---|
EventsOffered | Total events submitted to the dispatcher |
EventsAccepted | Events that passed sampling and entered the queue |
EventsDroppedQueueFull | Events dropped due to queue capacity |
EventsDroppedOversize | Events dropped for exceeding LevoMaxPayloadBytes |
EventsProcessed | Events successfully delivered to the sensor |
BytesProcessed | Total payload bytes delivered to the sensor |
QueueDepth | Current queue depth (live) |
IoctlNonZeroReturns | ioctl delivery counter |
Coexistence with Other Java Agents
The Levo agent is compatible with premain-attach APM agents including OpenTelemetry, DataDog, and New Relic. When running alongside dynamic-attach APM agents (Riverbed AppInternals, AppDynamics, Dynatrace OneAgent), configure both agents to instrument non-overlapping class sets.
Contact support@levo.ai for the full compatibility matrix and configuration guidance.
Support
For assistance, contact Levo support at support@levo.ai or via the support portal.
When filing a support request, include:
- Agent version (
grep "Agent version" /tmp/levo-agent-debug.log | head -1) - The full startup banner from the agent log
- Three consecutive
Dispatch:summary lines from the agent log - Output of
bpftool prog show | grep -iE "levo|kprobe"from the sensor host