Skip to content

Access logs

Write one access-log line per exchange, tunnel or flow, in Envoy's format strings or JSON, filtered, to a file, standard output or the System.Logger. Use this page to turn access logs on; the operators, filters and presets are in the access-log format reference, and the flags in response flags.

Access logs (Envoy-compatible)

ActivityLogger writes one access-log line per finished exchange or flow. Its format strings are compatible with Envoy's access-log command operators, so %START_TIME%, %REQ(:PATH)%, %RESPONSE_FLAGS% and the rest mean what they mean in Envoy, and tools that read Envoy's logs read these. Formats are parsed once, into an array of pieces that each render one literal or one operator: rendering a line uses no regular expressions and allocates a few small strings.

MicroProxy.bootstrap()
        .plusActivityTracker(ActivityLogger.builder()
                .format(LogFormat.envoy("[%START_TIME%] %REQ(:METHOD)% %REQ(:AUTHORITY)%%REQ(:PATH)%"
                        + " %RESPONSE_CODE% %RESPONSE_FLAGS% %DURATION%ms %UPSTREAM_HOST%"))
                .filter(AccessLogFilter.parse("status>=500 || response_flag"))
                .sink(AccessLogWriter.open(Path.of("access.log")))   // default: the System.Logger
                .build())
        .start();

From the command line, --access-log <path|-> writes to a file (appended, a line flushed at a time) or to standard output, in Envoy's default format unless --access-log-format (a format string, or a preset's name) or --access-log-json (a JSON object of keys and format strings) says otherwise; --access-log-filter (repeatable, all must pass) leaves lines out. Without --access-log, the lines go to the System.Logger named org.microproxy.extras.ActivityLogger. The properties file takes access_log, access_log_format, access_log_json and access_log_filter.

java -jar microproxy.jar --port 8080 --access-log - \
    --access-log-format '[%START_TIME%] "%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%" %RESPONSE_CODE% %RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS%'
# [2026-10-08T12:34:56.789Z] "GET /api HTTP/1.1" 502 UF proxy_failure:connect_failed

java -jar microproxy.jar --port 8080 --access-log access.jsonl \
    --access-log-json '{"start":"%START_TIME%","status":"%RESPONSE_CODE%","flags":"%RESPONSE_FLAGS%","ms":"%DURATION%","flow":"%MP_FLOW%"}' \
    --access-log-filter 'not_health_check && (status>=400 || duration>=1s)'
# {"start":"2026-10-08T12:34:56.789Z","status":504,"flags":"UT","ms":10003,"flow":"h2"}

When lines are written

Once per HTTP exchange, when its response has been written or it was abandoned (then with the flag that says why); once per CONNECT tunnel (over HTTP/1, HTTP/2 or HTTP/3), SOCKS client, WebSocket session, TCP-hook tunnel, CONNECT-UDP flow, QUIC flow intercepted stream by stream and relayed transparent QUIC flow, when it closes, with its bytes, duration and flags. A CONNECT the proxy intercepts is a line of its own, which ends when the session inside starts; the requests inside are lines of their own. Trackers get the same records from ActivityTracker.flowEnded(FlowRecord).

Choosing a record of traffic

Three ways to keep a record of traffic:

  • ActivityLogger: one line per exchange or flow, in an access-log format. Cheap enough to leave on.
  • HttpLogger: whole messages, with their headers, what the proxy and filters changed, and optionally bodies, as readable blocks or JSON lines. For debugging and audits (see Request/response logging).
  • WarcRecorder: an archive of the exchanges with servers as binary WARC records, complete and unredacted, for replay tools (see WARC recording).