1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.slf4j.instrumentation;
29
30 import static org.slf4j.helpers.MessageFormatter.format;
31
32 import java.io.ByteArrayInputStream;
33 import java.lang.instrument.ClassFileTransformer;
34 import java.security.ProtectionDomain;
35
36 import javassist.CannotCompileException;
37 import javassist.ClassPool;
38 import javassist.CtBehavior;
39 import javassist.CtClass;
40 import javassist.CtField;
41 import javassist.NotFoundException;
42
43 import org.slf4j.helpers.MessageFormatter;
44
45
46
47
48
49
50
51
52
53
54
55 public class LogTransformer implements ClassFileTransformer {
56
57
58
59
60
61
62
63
64 public static class Builder {
65
66
67
68
69
70
71
72 public LogTransformer build() {
73 if (verbose) {
74 System.err.println("Creating LogTransformer");
75 }
76 return new LogTransformer(this);
77 }
78
79 boolean addEntryExit;
80
81
82
83
84
85
86
87
88
89 public Builder addEntryExit(boolean b) {
90 addEntryExit = b;
91 return this;
92 }
93
94 boolean addVariableAssignment;
95
96
97
98
99
100
101
102 boolean verbose;
103
104
105
106
107
108
109
110
111 public Builder verbose(boolean b) {
112 verbose = b;
113 return this;
114 }
115
116 String[] ignore = { "org/slf4j/", "ch/qos/logback/", "org/apache/log4j/" };
117
118 public Builder ignore(String[] strings) {
119 this.ignore = strings;
120 return this;
121 }
122
123 private String level = "info";
124
125 public Builder level(String level) {
126 level = level.toLowerCase();
127 if (level.equals("info") || level.equals("debug") || level.equals("trace")) {
128 this.level = level;
129 } else {
130 if (verbose) {
131 System.err.println("level not info/debug/trace : " + level);
132 }
133 }
134 return this;
135 }
136 }
137
138 private final String level;
139 private final String levelEnabled;
140
141 private LogTransformer(Builder builder) {
142 String s = "WARNING: javassist not available on classpath for javaagent, log statements will not be added";
143 try {
144 if (Class.forName("javassist.ClassPool") == null) {
145 System.err.println(s);
146 }
147 } catch (ClassNotFoundException e) {
148 System.err.println(s);
149 }
150
151 this.addEntryExit = builder.addEntryExit;
152
153 this.verbose = builder.verbose;
154 this.ignore = builder.ignore;
155 this.level = builder.level;
156 this.levelEnabled = "is" + builder.level.substring(0, 1).toUpperCase() + builder.level.substring(1) + "Enabled";
157 }
158
159 private final boolean addEntryExit;
160
161 private final boolean verbose;
162 private final String[] ignore;
163
164 public byte[] transform(ClassLoader loader, String className, Class<?> clazz, ProtectionDomain domain, byte[] bytes) {
165
166 try {
167 return transform0(className, clazz, domain, bytes);
168 } catch (Exception e) {
169 System.err.println("Could not instrument " + className);
170 e.printStackTrace();
171 return bytes;
172 }
173 }
174
175
176
177
178
179
180
181
182
183
184
185
186
187 private byte[] transform0(String className, Class<?> clazz, ProtectionDomain domain, byte[] bytes) {
188
189 try {
190 for (String s : ignore) {
191 if (className.startsWith(s)) {
192 return bytes;
193 }
194 }
195 String slf4jName = "org.slf4j.LoggerFactory";
196 try {
197 if (domain != null && domain.getClassLoader() != null) {
198 domain.getClassLoader().loadClass(slf4jName);
199 } else {
200 if (verbose) {
201 System.err.println("Skipping " + className + " as it doesn't have a domain or a class loader.");
202 }
203 return bytes;
204 }
205 } catch (ClassNotFoundException e) {
206 if (verbose) {
207 System.err.println("Skipping " + className + " as slf4j is not available to it");
208 }
209 return bytes;
210 }
211 if (verbose) {
212 System.err.println("Processing " + className);
213 }
214 return doClass(className, clazz, bytes);
215 } catch (Throwable e) {
216 System.out.println("e = " + e);
217 return bytes;
218 }
219 }
220
221 private String loggerName;
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236 private byte[] doClass(String name, Class<?> clazz, byte[] b) {
237 ClassPool pool = ClassPool.getDefault();
238 CtClass cl = null;
239 try {
240 cl = pool.makeClass(new ByteArrayInputStream(b));
241 if (cl.isInterface() == false) {
242
243 loggerName = "_____log";
244
245
246
247 String pattern1 = "private static org.slf4j.Logger {};";
248 String loggerDefinition = format(pattern1, loggerName).getMessage();
249 CtField field = CtField.make(loggerDefinition, cl);
250
251
252
253 String pattern2 = "org.slf4j.LoggerFactory.getLogger({}.class);";
254 String replace = name.replace('/', '.');
255 String getLogger = format(pattern2, replace).getMessage();
256
257 cl.addField(field, getLogger);
258
259
260
261
262
263
264
265 CtBehavior[] methods = cl.getDeclaredBehaviors();
266 for (CtBehavior method : methods) {
267 if (method.isEmpty() == false) {
268 doMethod(method);
269 }
270 }
271 b = cl.toBytecode();
272 }
273 } catch (Exception e) {
274 System.err.println("Could not instrument " + name + ", " + e);
275 e.printStackTrace(System.err);
276 } finally {
277 if (cl != null) {
278 cl.detach();
279 }
280 }
281 return b;
282 }
283
284
285
286
287
288
289
290
291
292
293 private void doMethod(CtBehavior method) throws NotFoundException, CannotCompileException {
294
295 String signature = JavassistHelper.getSignature(method);
296 String returnValue = JavassistHelper.returnValue(method);
297
298 if (addEntryExit) {
299 String messagePattern = "if ({}.{}()) {}.{}(\">> {}\");";
300 Object[] arg1 = new Object[] { loggerName, levelEnabled, loggerName, level, signature };
301 String before = MessageFormatter.arrayFormat(messagePattern, arg1).getMessage();
302
303 method.insertBefore(before);
304
305 String messagePattern2 = "if ({}.{}()) {}.{}(\"<< {}{}\");";
306 Object[] arg2 = new Object[] { loggerName, levelEnabled, loggerName, level, signature, returnValue };
307 String after = MessageFormatter.arrayFormat(messagePattern2, arg2).getMessage();
308
309 method.insertAfter(after);
310 }
311 }
312 }