001/* 002 * Copyright (c) 2016 Chris K Wensel <chris@wensel.net>. All Rights Reserved. 003 * Copyright (c) 2007-2017 Xplenty, Inc. All Rights Reserved. 004 * 005 * Project and contact information: http://www.cascading.org/ 006 * 007 * This file is part of the Cascading project. 008 * 009 * Licensed under the Apache License, Version 2.0 (the "License"); 010 * you may not use this file except in compliance with the License. 011 * You may obtain a copy of the License at 012 * 013 * http://www.apache.org/licenses/LICENSE-2.0 014 * 015 * Unless required by applicable law or agreed to in writing, software 016 * distributed under the License is distributed on an "AS IS" BASIS, 017 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 018 * See the License for the specific language governing permissions and 019 * limitations under the License. 020 */ 021 022package cascading.util; 023 024import java.io.IOException; 025import java.io.LineNumberReader; 026import java.io.PrintWriter; 027import java.io.StringReader; 028import java.io.StringWriter; 029import java.io.Writer; 030import java.util.ArrayList; 031import java.util.Collection; 032import java.util.List; 033import java.util.Map; 034import java.util.concurrent.ConcurrentHashMap; 035import java.util.regex.Pattern; 036 037import cascading.flow.FlowElement; 038import cascading.operation.BaseOperation; 039import cascading.operation.Operation; 040import cascading.pipe.Pipe; 041import cascading.scheme.Scheme; 042import cascading.tap.Tap; 043 044/** 045 * 046 */ 047public class TraceUtil 048 { 049 /** 050 * The set of regex patterns specifying fully qualified class or package names that serve as boundaries 051 * for collecting traces and apiCalls in captureDebugTraceAndApiCall. 052 */ 053 private static final Map<String, Pattern> registeredApiBoundaries = new ConcurrentHashMap<String, Pattern>(); 054 055 private static interface TraceFormatter 056 { 057 String format( String trace ); 058 } 059 060 /** 061 * Add a regex that serves as a boundary for tracing. That is to say any trace 062 * captured by {@link #captureDebugTrace(Object)} will be from a caller that comes higher in the 063 * stack than any apiBoundary package or class. 064 * 065 * @param apiBoundary 066 */ 067 public static void registerApiBoundary( String apiBoundary ) 068 { 069 registeredApiBoundaries.put( apiBoundary, Pattern.compile( apiBoundary ) ); 070 } 071 072 /** 073 * Remove a regex as a boundary for tracing. See registerApiBoundary. 074 * 075 * @param apiBoundary 076 */ 077 public static void unregisterApiBoundary( String apiBoundary ) 078 { 079 registeredApiBoundaries.remove( apiBoundary ); 080 } 081 082 /** 083 * Allows for custom trace fields on Pipe, Tap, and Scheme types 084 * 085 * @param object 086 * @param trace 087 */ 088 public static void setTrace( Object object, String trace ) 089 { 090 Util.setInstanceFieldIfExists( object, "trace", trace ); 091 } 092 093 private static String formatTrace( Traceable traceable, String message, TraceFormatter formatter ) 094 { 095 if( traceable == null ) 096 return message; 097 098 String trace = traceable.getTrace(); 099 100 if( trace == null ) 101 return message; 102 103 return formatter.format( trace ) + " " + message; 104 } 105 106 public static String formatTraces( Collection<FlowElement> flowElements, String delim ) 107 { 108 List<String> messages = new ArrayList<>( flowElements.size() ); 109 110 for( FlowElement flowElement : flowElements ) 111 messages.add( formatTrace( (Traceable) flowElement, flowElement.toString(), new TraceFormatter() 112 { 113 @Override 114 public String format( String trace ) 115 { 116 return "[" + trace + "] ->"; 117 } 118 } ) ); 119 120 return Util.join( messages, delim ); 121 } 122 123 public static String formatTrace( final Scheme scheme, String message ) 124 { 125 return formatTrace( scheme, message, new TraceFormatter() 126 { 127 @Override 128 public String format( String trace ) 129 { 130 return "[" + Util.truncate( scheme.toString(), 25 ) + "][" + trace + "]"; 131 } 132 } ); 133 } 134 135 public static String formatTrace( FlowElement flowElement, String message ) 136 { 137 if( flowElement == null ) 138 return message; 139 140 if( flowElement instanceof Pipe ) 141 return formatTrace( (Pipe) flowElement, message ); 142 143 if( flowElement instanceof Tap ) 144 return formatTrace( (Tap) flowElement, message ); 145 146 throw new UnsupportedOperationException( "cannot format type: " + flowElement.getClass().getName() ); 147 } 148 149 /** 150 * Method formatRawTrace does not include the pipe name 151 * 152 * @param pipe of type Pipe 153 * @param message of type String 154 * @return String 155 */ 156 public static String formatRawTrace( Pipe pipe, String message ) 157 { 158 return formatTrace( pipe, message, new TraceFormatter() 159 { 160 @Override 161 public String format( String trace ) 162 { 163 return "[" + trace + "]"; 164 } 165 } ); 166 } 167 168 public static String formatTrace( final Pipe pipe, String message ) 169 { 170 return formatTrace( pipe, message, new TraceFormatter() 171 { 172 @Override 173 public String format( String trace ) 174 { 175 return "[" + Util.truncate( pipe.getName(), 25 ) + "][" + trace + "]"; 176 } 177 } ); 178 } 179 180 public static String formatTrace( final Tap tap, String message ) 181 { 182 return formatTrace( tap, message, new TraceFormatter() 183 { 184 @Override 185 public String format( String trace ) 186 { 187 return "[" + Util.truncate( tap.toString(), 25 ) + "][" + trace + "]"; 188 } 189 } ); 190 } 191 192 public static String formatTrace( Operation operation, String message ) 193 { 194 if( !( operation instanceof BaseOperation ) ) 195 return message; 196 197 String trace = ( (BaseOperation) operation ).getTrace(); 198 199 if( trace == null ) 200 return message; 201 202 return "[" + trace + "] " + message; 203 } 204 205 public static String captureDebugTrace( Object target ) 206 { 207 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); 208 209 StackTraceElement candidateUserCodeElement = null; 210 StackTraceElement apiCallElement = null; 211 Class<?> tracingBoundary = target.getClass(); 212 String boundaryClassName = tracingBoundary.getName(); 213 214 // walk from the bottom of the stack(which is at the end of the array) upwards towards any boundary. 215 // The apiCall is the element at the boundary and the previous stack element is the user code 216 for( int i = stackTrace.length - 1; i >= 0; i-- ) 217 { 218 StackTraceElement stackTraceElement = stackTrace[ i ]; 219 String stackClassName = stackTraceElement.getClassName(); 220 221 boolean atApiBoundary = atApiBoundary( stackTraceElement.toString() ); 222 223 if( ( stackClassName != null && ( stackClassName.startsWith( boundaryClassName ) ) || atApiBoundary ) ) 224 { 225 // only record the apiCallElement if we're at an apiBoundary. That means 226 // Trace will only have "apiMethod() @ call_location" when a registered 227 // api boundary is found. The default for Cascading will just have 228 // call_location. 229 if( atApiBoundary ) 230 apiCallElement = stackTraceElement; 231 232 break; 233 } 234 235 candidateUserCodeElement = stackTraceElement; 236 } 237 238 String userCode = candidateUserCodeElement == null ? "" : candidateUserCodeElement.toString(); 239 String apiCall = ""; 240 241 if( apiCallElement != null ) 242 { 243 String method = apiCallElement.getMethodName(); 244 245 if( method.equals( "<init>" ) ) 246 apiCall = String.format( "new %s()", getSimpleClassName( apiCallElement.getClassName() ) ); 247 else 248 apiCall = String.format( "%s()", method ); 249 } 250 251 return userCode.isEmpty() ? apiCall : apiCall.isEmpty() ? userCode : String.format( "%s @ %s", apiCall, userCode ); 252 } 253 254 private static Object getSimpleClassName( String className ) 255 { 256 if( className == null || className.isEmpty() ) 257 return ""; 258 259 String parts[] = className.split( "\\." ); 260 261 if( parts.length == 0 ) 262 return ""; 263 264 return parts[ parts.length - 1 ]; 265 } 266 267 private static boolean atApiBoundary( String stackTraceElement ) 268 { 269 for( Pattern boundary : registeredApiBoundaries.values() ) 270 { 271 if( boundary.matcher( stackTraceElement ).matches() ) 272 return true; 273 } 274 275 return false; 276 } 277 278 public static String stringifyStackTrace( Throwable throwable, String lineSeparator, boolean trimLines, int lineLimit ) 279 { 280 if( lineLimit == 0 ) 281 return null; 282 283 Writer traceWriter = new StringWriter(); 284 PrintWriter printWriter = new PrintWriter( traceWriter ); 285 286 throwable.printStackTrace( printWriter ); 287 288 String trace = traceWriter.toString(); 289 290 if( lineSeparator.equals( System.getProperty( "line.separator" ) ) && !trimLines && lineLimit == -1 ) 291 return trace; 292 293 lineLimit = lineLimit == -1 ? Integer.MAX_VALUE : lineLimit; 294 295 StringBuilder buffer = new StringBuilder(); 296 LineNumberReader reader = new LineNumberReader( new StringReader( trace ) ); 297 298 try 299 { 300 String line = reader.readLine(); 301 302 while( line != null && reader.getLineNumber() - 1 < lineLimit ) 303 { 304 if( reader.getLineNumber() > 1 ) 305 buffer.append( lineSeparator ); 306 307 if( trimLines ) 308 line = line.trim(); 309 310 buffer.append( line ); 311 312 line = reader.readLine(); 313 } 314 } 315 catch( IOException exception ) 316 { 317 // ignore - reading a string 318 } 319 320 return buffer.toString(); 321 } 322 }