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