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