001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.juneau.rest.server.logging; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.util.*; 022 023import org.apache.juneau.http.*; 024import org.apache.juneau.rest.server.*; 025 026import jakarta.servlet.http.*; 027 028/** 029 * Per-tier formatter for JUL-level-driven REST debug logging. 030 * 031 * <p> 032 * This is the single public extension point for customizing how request/response debug records are rendered. 033 * The internal two-phase pipeline invokes the tier methods <b>cumulatively</b> based on the resolved logger level: 034 * <ul> 035 * <li>{@link #formatBasic(RestRequest,RestResponse) formatBasic} — always (tier {@code INFO}). 036 * <li>{@link #formatHeaders(RestRequest,RestResponse) formatHeaders} — added at tier {@code FINE}. 037 * <li>{@link #formatBody(RestRequest,RestResponse) formatBody} — added at tier {@code FINEST}. 038 * </ul> 039 * 040 * <p> 041 * The two additive tiers default to the empty string so that a bare implementation supplying only 042 * {@link #formatBasic(RestRequest,RestResponse) formatBasic} is additive-safe. Most implementations instead extend 043 * {@link BasicRestDebugFormatter} and override only the tier(s) they wish to change. 044 * 045 * <h5 class='section'>Secure by default</h5> 046 * <p> 047 * {@link BasicRestDebugFormatter} is secure by default: request/response <b>bodies are never dumped</b> unless the 048 * operator has deliberately set the {@code JUNEAU_REST_DEBUG_ALLOW_DUMP_BODIES} environment variable (and even then only 049 * at {@code FINEST} on renderable content). Credential-bearing header values are masked, every client-controlled string 050 * is escaped against log forging, and all output is length-bounded. See 051 * {@link BasicRestDebugFormatter} for the full contract. 052 * 053 * <p> 054 * The cached request/response bytes, the thrown exception, and the request execution time are reachable through the 055 * {@link RestRequest}/{@link RestResponse} accessors ({@link RestRequest#getCachedContent()}, 056 * {@link RestRequest#getCachedContentLength()}, {@link RestRequest#getException()}, {@link RestRequest#getExecTime()}, 057 * {@link RestResponse#getCachedContent()}, {@link RestResponse#getCachedContentLength()}). 058 * 059 * <h5 class='section'>See Also:</h5><ul> 060 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerLoggingAndDebugging">Logging / Debugging</a> 061 * </ul> 062 * 063 * @since 10.0.0 064 */ 065public interface RestDebugFormatter { 066 067 /** Default cap on the rendered URI length in the status line. */ 068 int DEFAULT_MAX_URI_LENGTH = 2048; 069 070 /** 071 * Renders the basic ({@code INFO}-tier) portion of the debug record. 072 * 073 * <p> 074 * Always invoked. Typically a single status line such as {@code [200] HTTP GET /foo}. 075 * 076 * @param req The current REST request. Never <jk>null</jk>. 077 * @param res The current REST response. Never <jk>null</jk>. 078 * @return The rendered basic portion. Never <jk>null</jk>. 079 */ 080 String formatBasic(RestRequest req, RestResponse res); 081 082 /** 083 * Renders the headers ({@code FINE}-tier) portion of the debug record. 084 * 085 * <p> 086 * Appended to {@link #formatBasic(RestRequest,RestResponse)} when the resolved logger is at {@code FINE}-or-finer. 087 * The default returns the empty string. 088 * 089 * @param req The current REST request. Never <jk>null</jk>. 090 * @param res The current REST response. Never <jk>null</jk>. 091 * @return The rendered headers portion. Never <jk>null</jk>. 092 */ 093 default String formatHeaders(RestRequest req, RestResponse res) { 094 return ""; 095 } 096 097 /** 098 * Renders the body ({@code FINEST}-tier) portion of the debug record. 099 * 100 * <p> 101 * Appended when the resolved logger is at {@code FINEST}. Reads the cached request/response bytes. The default 102 * returns the empty string. 103 * 104 * @param req The current REST request. Never <jk>null</jk>. 105 * @param res The current REST response. Never <jk>null</jk>. 106 * @return The rendered body portion. Never <jk>null</jk>. 107 */ 108 default String formatBody(RestRequest req, RestResponse res) { 109 return ""; 110 } 111 112 /** 113 * Renders the basic status line ({@code [status] HTTP method uri}) directly from servlet objects. 114 * 115 * <p> 116 * Used both by {@link #formatBasic(RestRequest,RestResponse)} and by the pipeline's no-operation (404/405) path, 117 * which has no {@link RestRequest}/{@link RestResponse} to render through. The method and URI are always 118 * {@link DebugTextSanitizer#sanitize(String,int) sanitized} (CR/LF and control characters escaped) so a 119 * client-controlled URI cannot forge a log line, and the rendered URI is length-capped. This default applies 120 * {@link #DEFAULT_MAX_URI_LENGTH}; {@link BasicRestDebugFormatter} overrides it to apply its configurable 121 * {@code maxUriLength}. 122 * 123 * <p> 124 * The query string is intentionally excluded (matching {@link HttpServletRequest#getRequestURI()}), so plaintext 125 * {@code ?token=…} disclosure is never re-introduced through this path. 126 * 127 * @param req The servlet request. Never <jk>null</jk>. 128 * @param res The servlet response. Never <jk>null</jk>. 129 * @return The rendered, sanitized status line. 130 */ 131 default String statusLine(HttpServletRequest req, HttpServletResponse res) { 132 var method = DebugTextSanitizer.sanitize(req.getMethod(), DEFAULT_MAX_URI_LENGTH); 133 var uri = DebugTextSanitizer.sanitize(req.getRequestURI(), DEFAULT_MAX_URI_LENGTH); 134 return new StringBuilder().append('[').append(res.getStatus()).append("] HTTP ").append(method).append(' ').append(uri).toString(); 135 } 136 137 /** 138 * Returns <jk>true</jk> if a body with the given content type is worth rendering as text (as opposed to being 139 * binary/unknown). 140 * 141 * <p> 142 * This is a text-vs-binary renderability predicate, <b>not</b> a redaction allowlist. It parses only the media type 143 * (type/subtype, ignoring parameters such as {@code ; charset=utf-8}), case-folds, and treats the following as 144 * renderable: {@code text/*}, {@code application/json}, {@code application/xml}, any {@code +json}/{@code +xml} 145 * suffix, and {@code application/x-www-form-urlencoded}. {@code multipart/form-data} is explicitly non-renderable. 146 * An absent or blank content type is conservatively treated as non-renderable. 147 * 148 * <p> 149 * {@code Content-Encoding} is not visible to this one-argument predicate; a caller that dumps bodies must separately 150 * treat any non-identity encoding (e.g. {@code gzip}) as non-renderable. 151 * 152 * @param contentType The body's content type. Can be <jk>null</jk> (returns <jk>false</jk>). 153 * @return <jk>true</jk> if the content type is renderable as text. 154 */ 155 default boolean isBodyRenderable(String contentType) { 156 if (contentType == null) 157 return false; 158 var ct = contentType.trim(); 159 var semi = ct.indexOf(';'); 160 if (semi >= 0) 161 ct = ct.substring(0, semi).trim(); 162 ct = ct.toLowerCase(Locale.ROOT); 163 if (ct.isEmpty()) 164 return false; 165 if (ct.startsWith("text/")) 166 return true; 167 if (eq(ct, "multipart/form-data")) 168 return false; 169 if (eqa(ct, "application/json", "application/xml", "application/x-www-form-urlencoded")) 170 return true; 171 return ct.endsWith("+json") || ct.endsWith("+xml"); 172 } 173 174 /** 175 * Returns the maximum number of request/response body bytes to capture for the {@code FINEST}-tier body rendering. 176 * 177 * <p> 178 * This cap is enforced at <i>capture</i> time by the two-phase pipeline, so memory stays bounded even for 179 * large uploads/downloads. Defaults to 8 KB. 180 * 181 * @return The body capture cap in bytes. 182 */ 183 default int bodyCap() { 184 return 8 * 1024; 185 } 186}