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