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