001/** 002 * Copyright (c) 2015-2022, Michael Yang 杨福海 (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package io.jboot.web.handler; 017 018import com.jfinal.aop.Interceptor; 019import com.jfinal.aop.Invocation; 020import com.jfinal.core.Action; 021import com.jfinal.core.ActionReporter; 022import com.jfinal.core.Controller; 023import com.jfinal.core.JFinal; 024import com.jfinal.kit.JsonKit; 025import com.jfinal.render.*; 026import io.jboot.Jboot; 027import io.jboot.JbootConsts; 028import io.jboot.support.jwt.JwtInterceptor; 029import io.jboot.utils.ClassUtil; 030import io.jboot.utils.ReflectUtil; 031import io.jboot.utils.RequestUtil; 032import io.jboot.utils.StrUtil; 033import io.jboot.web.controller.JbootController; 034import io.jboot.web.render.JbootReturnValueRender; 035import javassist.*; 036 037import javax.servlet.http.HttpServletRequest; 038import java.io.File; 039import java.io.IOException; 040import java.io.Writer; 041import java.text.SimpleDateFormat; 042import java.util.Date; 043import java.util.Enumeration; 044import java.util.List; 045 046 047/** 048 * JbootActionReporter 参考 ActionReporter 049 */ 050public class JbootActionReporter { 051 052 private static final String title = "\nJboot-" + JbootConsts.VERSION + " action report -------- "; 053 private static final String interceptMethodDesc = "(Lcom/jfinal/aop/Invocation;)V"; 054 private static int maxOutputLengthOfParaValue = 512; 055 private static Writer writer = new SystemOutWriter(); 056 private static ActionReporter actionReporter = JFinal.me().getConstants().getActionReporter(); 057 private static boolean reportEnable = Jboot.isDevMode(); 058 private static boolean colorRenderEnable = true; 059 private static boolean reportAllText = false; 060 061 private static final ThreadLocal<SimpleDateFormat> sdf = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); 062 063 064 public static void setMaxOutputLengthOfParaValue(int maxOutputLengthOfParaValue) { 065 if (maxOutputLengthOfParaValue < 16) { 066 throw new IllegalArgumentException("maxOutputLengthOfParaValue must more than 16"); 067 } 068 JbootActionReporter.maxOutputLengthOfParaValue = maxOutputLengthOfParaValue; 069 } 070 071 public static void setWriter(Writer writer) { 072 if (writer == null) { 073 throw new IllegalArgumentException("writer can not be null"); 074 } 075 JbootActionReporter.writer = writer; 076 } 077 078 public static Writer getWriter() { 079 return writer; 080 } 081 082 public static boolean isReportEnable() { 083 return reportEnable; 084 } 085 086 public static void setReportEnable(boolean reportEnable) { 087 JbootActionReporter.reportEnable = reportEnable; 088 } 089 090 public static boolean isReportAllText() { 091 return reportAllText; 092 } 093 094 public static void setReportAllText(boolean reportAllText) { 095 JbootActionReporter.reportAllText = reportAllText; 096 } 097 098 public static boolean isColorRenderEnable() { 099 return colorRenderEnable; 100 } 101 102 public static void setColorRenderEnable(boolean colorRenderEnable) { 103 JbootActionReporter.colorRenderEnable = colorRenderEnable; 104 } 105 106 /** 107 * Report the action 108 */ 109 public static void report(String target, Controller controller, Action action, Invocation invocation, long time) { 110 try { 111 doReport(target, controller, action, invocation, time); 112 } 113 // 在 tomcat 或者自定义 classloader 的情况下, 114 // 可能会出现 NotFoundException 错误 115 catch (NotFoundException e) { 116 ClassPool.getDefault().insertClassPath(new ClassClassPath(controller.getClass())); 117 try { 118 doReport(target, controller, action, invocation, time); 119 } catch (Exception exception) { 120 actionReporter.report(target, controller, action); 121 } 122 } catch (Exception ex) { 123 actionReporter.report(target, controller, action); 124 } finally { 125 JbootActionReporterInvocation.clear(); 126 } 127 } 128 129 130 private static void doReport(String target, Controller controller, Action action, Invocation invocation, long time) throws Exception { 131 CtClass ctClass = ClassPool.getDefault().get(ClassUtil.getUsefulClass(action.getControllerClass()).getName()); 132 ClassPool.getDefault().get(ClassUtil.getUsefulClass(action.getControllerClass()).getName()); 133 String desc = JbootActionReporterUtil.getMethodDescWithoutName(action.getMethod()); 134 CtMethod ctMethod = ctClass.getMethod(action.getMethodName(), desc); 135 int lineNumber = ctMethod.getMethodInfo().getLineNumber(0); 136 137 StringBuilder sb = new StringBuilder(title).append(sdf.get().format(new Date(time))).append(" -------------------------\n"); 138 sb.append("Request : ").append(controller.getRequest().getMethod()).append(" ").append(target).append("\n"); 139 Class<?> cc = action.getMethod().getDeclaringClass(); 140 sb.append("Controller : ").append(cc.getName()).append(".(").append(getClassFileName(cc)).append(".java:" + lineNumber + ")"); 141 if (JbootActionReporterInvocation.isControllerInvoked()) { 142 sb.append((colorRenderEnable ? ConsoleColor.GREEN_BRIGHT : "") + " ---> invoked √" + (colorRenderEnable ? ConsoleColor.RESET : "")); 143 } else { 144 sb.append((colorRenderEnable ? ConsoleColor.RED_BRIGHT : "") + " ---> skipped ×" + (colorRenderEnable ? ConsoleColor.RESET : "")); 145 } 146 sb.append("\nMethod : ").append(JbootActionReporterUtil.getMethodString(action.getMethod())).append("\n"); 147 148 149 String urlParas = controller.getPara(); 150 if (urlParas != null) { 151 sb.append("UrlPara : ").append(urlParas).append("\n"); 152 } 153 154 Interceptor[] inters = invocation instanceof JbootActionReporterInvocation ? ((JbootActionReporterInvocation) invocation).getInters() : action.getInterceptors(); 155 List<Interceptor> invokedInterceptors = JbootActionReporterInvocation.getInvokedInterceptor(); 156 157 boolean printJwt = false; 158 159 if (inters.length > 0) { 160 sb.append("Interceptor : "); 161 for (int i = 0; i < inters.length; i++) { 162 if (i > 0) { 163 sb.append("\n "); 164 } 165 Interceptor inter = inters[i]; 166 Class<?> interClass = ClassUtil.getUsefulClass(inter.getClass()); 167 168 if (interClass == JwtInterceptor.class) { 169 printJwt = true; 170 } 171 172 CtClass icClass = ClassPool.getDefault().get(interClass.getName()); 173 CtMethod icMethod = icClass.getMethod("intercept", interceptMethodDesc); 174 int icLineNumber = icMethod.getMethodInfo().getLineNumber(0); 175 sb.append(icMethod.getDeclaringClass().getName()).append(".(").append(getClassFileName(interClass)).append(".java:" + icLineNumber + ")"); 176 177 if (invokedInterceptors.contains(inter)) { 178 sb.append((colorRenderEnable ? ConsoleColor.GREEN_BRIGHT : "") + " ---> invoked √" + (colorRenderEnable ? ConsoleColor.RESET : "")); 179 } else { 180 sb.append((colorRenderEnable ? ConsoleColor.RED_BRIGHT : "") + " ---> skipped ×" + (colorRenderEnable ? ConsoleColor.RESET : "")); 181 } 182 } 183 sb.append("\n"); 184 } 185 186 // print all parameters 187 HttpServletRequest request = controller.getRequest(); 188 Enumeration<String> e = request.getParameterNames(); 189 if (e.hasMoreElements()) { 190 sb.append("Parameter : "); 191 while (e.hasMoreElements()) { 192 String name = e.nextElement(); 193 String[] values = request.getParameterValues(name); 194 if (values.length == 1) { 195 sb.append(name).append("="); 196 if (values[0] != null && values[0].length() > maxOutputLengthOfParaValue) { 197 sb.append(values[0], 0, maxOutputLengthOfParaValue).append("..."); 198 } else { 199 sb.append(values[0]); 200 } 201 } else { 202 sb.append(name).append("[]={"); 203 for (int i = 0; i < values.length; i++) { 204 if (i > 0) { 205 sb.append(","); 206 } 207 sb.append(values[i]); 208 } 209 sb.append("}"); 210 } 211 sb.append(" "); 212 } 213 sb.append("\n"); 214 } 215 216 217 if (!"GET".equalsIgnoreCase(controller.getRequest().getMethod()) 218 && !RequestUtil.isMultipartRequest(controller.getRequest()) 219 && StrUtil.isNotBlank(controller.getRawData())) { 220 sb.append("RawData : ").append(controller.getRawData()); 221 sb.append("\n"); 222 } 223 224 if (printJwt && controller instanceof JbootController) { 225 String jwtString = JsonKit.toJson(((JbootController) controller).getJwtParas()); 226 if (StrUtil.isNotBlank(jwtString)) { 227 sb.append("Jwt : ").append(jwtString.replace("\n", "")); 228 sb.append("\n"); 229 } 230 } 231 232 appendRenderMessage(controller.getRender(), sb); 233 234 sb.append("----------------------------------- took " + (System.currentTimeMillis() - time) + " ms --------------------------------\n\n\n"); 235 236 writer.write(sb.toString()); 237 } 238 239 private static void appendRenderMessage(Render render, StringBuilder sb) { 240 if (render == null) { 241 return; 242 } 243 String view = render.getView(); 244 if (StrUtil.isNotBlank(view)) { 245 sb.append("Render : ").append(view); 246 } else if (render instanceof JsonRender) { 247 String jsontext = ((JsonRender) render).getJsonText(); 248 if (jsontext == null) { 249 jsontext = ""; 250 } 251 jsontext = jsontext.replace("\n", ""); 252 sb.append("Render : ").append(getRenderText(jsontext)); 253 } else if (render instanceof TextRender) { 254 String text = ((TextRender) render).getText(); 255 if (text == null) { 256 text = ""; 257 } 258 text = text.replace("\n", ""); 259 sb.append("Render : ").append(getRenderText(text)); 260 } else if (render instanceof FileRender) { 261 File file = ReflectUtil.getFieldValue(render, "file"); 262 sb.append("Render : ").append(file); 263 } else if (render instanceof RedirectRender) { 264 String url = ReflectUtil.getFieldValue(render, "url"); 265 sb.append("Redirect : ").append(url); 266 } else if (render instanceof NullRender) { 267 sb.append("Render : null"); 268 } else if (render instanceof JbootReturnValueRender) { 269 appendRenderMessage(((JbootReturnValueRender) render).getRealRender(), sb); 270 } else { 271 sb.append("Render : ").append(ClassUtil.getUsefulClass(render.getClass()).getName()); 272 } 273 sb.append("\n"); 274 } 275 276 277 private static String getRenderText(String orignalText) { 278 if (StrUtil.isBlank(orignalText)) { 279 return ""; 280 } 281 282 if (!reportAllText && orignalText.length() > 100) { 283 return orignalText.substring(0, 100) + "..."; 284 } 285 286 return orignalText; 287 } 288 289 290 private static String getClassFileName(Class<?> clazz) { 291 String classFileName = clazz.getName(); 292 if (classFileName.contains("$")) { 293 int indexOf = classFileName.contains(".") ? classFileName.lastIndexOf(".") + 1 : 0; 294 return classFileName.substring(indexOf, classFileName.indexOf("$")); 295 } else { 296 return clazz.getSimpleName(); 297 } 298 } 299 300 301 private static class SystemOutWriter extends Writer { 302 @Override 303 public void write(String str) throws IOException { 304 System.out.print(str); 305 } 306 307 @Override 308 public void write(char[] cbuf, int off, int len) throws IOException { 309 } 310 311 @Override 312 public void flush() throws IOException { 313 } 314 315 @Override 316 public void close() throws IOException { 317 } 318 } 319} 320 321