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