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.client; 018 019import static org.apache.juneau.commons.utils.Shorts.*; 020 021import java.util.*; 022 023/** 024 * Per-tier formatter for JUL-level-driven REST client debug logging. 025 * 026 * <p> 027 * The client debug pipeline invokes these methods cumulatively based on the resolved logger level: 028 * <ul> 029 * <li>{@link #formatBasic(RestRequest, RestResponse)} at {@code INFO}. 030 * <li>{@link #formatHeaders(RestRequest, RestResponse)} added at {@code FINE}. 031 * <li>{@link #formatBody(RestRequest, RestResponse)} added at {@code FINEST}. 032 * </ul> 033 * 034 * @since 10.0.0 035 */ 036public interface RestClientDebugFormatter { 037 038 /** 039 * Renders the basic ({@code INFO}-tier) portion of a debug record. 040 * 041 * @param req The request. 042 * @param res The response. 043 * @return The formatted message chunk. 044 */ 045 String formatBasic(RestRequest req, RestResponse res); 046 047 /** 048 * Renders the headers ({@code FINE}-tier) portion of a debug record. 049 * 050 * @param req The request. 051 * @param res The response. 052 * @return The formatted message chunk. 053 */ 054 default String formatHeaders(RestRequest req, RestResponse res) { 055 return ""; 056 } 057 058 /** 059 * Renders the body ({@code FINEST}-tier) portion of a debug record. 060 * 061 * @param req The request. 062 * @param res The response. 063 * @return The formatted message chunk. 064 */ 065 default String formatBody(RestRequest req, RestResponse res) { 066 return ""; 067 } 068 069 /** 070 * Maximum number of request/response body bytes to capture for {@code FINEST} rendering. 071 * 072 * @return The capture cap in bytes. 073 */ 074 default int bodyCap() { 075 return 8 * 1024; 076 } 077 078 /** 079 * Returns <jk>true</jk> if a body with the given content type is worth rendering as text (as opposed to being 080 * binary/unknown). 081 * 082 * <p> 083 * This is a text-vs-binary renderability predicate, <b>not</b> a redaction allowlist. It parses only the media type 084 * (type/subtype, ignoring parameters such as {@code ; charset=utf-8}), case-folds, and treats the following as 085 * renderable: {@code text/*}, {@code application/json}, {@code application/xml}, any {@code +json}/{@code +xml} 086 * suffix, and {@code application/x-www-form-urlencoded}. {@code multipart/form-data} is explicitly non-renderable. 087 * An absent or blank content type is conservatively treated as non-renderable. 088 * 089 * <p> 090 * {@code Content-Encoding} is not visible to this one-argument predicate; a caller that dumps bodies must separately 091 * treat any non-identity encoding (e.g. {@code gzip}) as non-renderable. 092 * 093 * <p> 094 * Keep byte-identical to {@code org.apache.juneau.rest.server.logging.RestDebugFormatter}'s counterpart. 095 * 096 * @param contentType The body's content type. Can be <jk>null</jk> (returns <jk>false</jk>). 097 * @return <jk>true</jk> if the content type is renderable as text. 098 */ 099 default boolean isBodyRenderable(String contentType) { 100 if (contentType == null) 101 return false; 102 var ct = contentType.trim(); 103 var semi = ct.indexOf(';'); 104 if (semi >= 0) 105 ct = ct.substring(0, semi).trim(); 106 ct = ct.toLowerCase(Locale.ROOT); 107 if (ct.isEmpty()) 108 return false; 109 if (ct.startsWith("text/")) 110 return true; 111 if (eq(ct, "multipart/form-data")) 112 return false; 113 if (eqa(ct, "application/json", "application/xml", "application/x-www-form-urlencoded")) 114 return true; 115 return ct.endsWith("+json") || ct.endsWith("+xml"); 116 } 117}