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