1
17 package org.apache.juli;
18
19 import java.io.PrintWriter;
20 import java.io.StringWriter;
21 import java.io.Writer;
22 import java.lang.management.ManagementFactory;
23 import java.lang.management.ThreadInfo;
24 import java.lang.management.ThreadMXBean;
25 import java.util.LinkedHashMap;
26 import java.util.Map;
27 import java.util.Map.Entry;
28 import java.util.logging.Formatter;
29 import java.util.logging.LogManager;
30 import java.util.logging.LogRecord;
31
32
37
40 public class OneLineFormatter extends Formatter {
41
42 private static final String UNKNOWN_THREAD_NAME = "Unknown thread with ID ";
43 private static final Object threadMxBeanLock = new Object();
44 private static volatile ThreadMXBean threadMxBean = null;
45 private static final int THREAD_NAME_CACHE_SIZE = 10000;
46 private static ThreadLocal<ThreadNameCache> threadNameCache = new ThreadLocal<ThreadNameCache>() {
47 @Override
48 protected ThreadNameCache initialValue() {
49 return new ThreadNameCache(THREAD_NAME_CACHE_SIZE);
50 }
51 };
52
53
54 private static final String DEFAULT_TIME_FORMAT = "dd-MMM-yyyy HH:mm:ss.SSS";
55
56
59 private static final int globalCacheSize = 30;
60
61
64 private static final int localCacheSize = 5;
65
66
69 private ThreadLocal<DateFormatCache> localDateCache;
70
71 private volatile MillisHandling millisHandling = MillisHandling.APPEND;
72
73
74 public OneLineFormatter() {
75 String timeFormat = LogManager.getLogManager().getProperty(
76 OneLineFormatter.class.getName() + ".timeFormat");
77 if (timeFormat == null) {
78 timeFormat = DEFAULT_TIME_FORMAT;
79 }
80 setTimeFormat(timeFormat);
81 }
82
83
84
90 public void setTimeFormat(final String timeFormat) {
91 final String cachedTimeFormat;
92
93 if (timeFormat.endsWith(".SSS")) {
94 cachedTimeFormat = timeFormat.substring(0, timeFormat.length() - 4);
95 millisHandling = MillisHandling.APPEND;
96 } else if (timeFormat.contains("SSS")) {
97 millisHandling = MillisHandling.REPLACE_SSS;
98 cachedTimeFormat = timeFormat;
99 } else if (timeFormat.contains("SS")) {
100 millisHandling = MillisHandling.REPLACE_SS;
101 cachedTimeFormat = timeFormat;
102 } else if (timeFormat.contains("S")) {
103 millisHandling = MillisHandling.REPLACE_S;
104 cachedTimeFormat = timeFormat;
105 } else {
106 millisHandling = MillisHandling.NONE;
107 cachedTimeFormat = timeFormat;
108 }
109
110 final DateFormatCache globalDateCache =
111 new DateFormatCache(globalCacheSize, cachedTimeFormat, null);
112 localDateCache = new ThreadLocal<DateFormatCache>() {
113 @Override
114 protected DateFormatCache initialValue() {
115 return new DateFormatCache(localCacheSize, cachedTimeFormat, globalDateCache);
116 }
117 };
118 }
119
120
121
126 public String getTimeFormat() {
127 return localDateCache.get().getTimeFormat();
128 }
129
130
131 @Override
132 public String format(LogRecord record) {
133 StringBuilder sb = new StringBuilder();
134
135
136 addTimestamp(sb, record.getMillis());
137
138
139 sb.append(' ');
140 sb.append(record.getLevel().getLocalizedName());
141
142
143 sb.append(' ');
144 sb.append('[');
145 if (Thread.currentThread() instanceof AsyncFileHandler.LoggerThread) {
146
147
148 sb.append(getThreadName(record.getThreadID()));
149 } else {
150 sb.append(Thread.currentThread().getName());
151 }
152 sb.append(']');
153
154
155 sb.append(' ');
156 sb.append(record.getSourceClassName());
157 sb.append('.');
158 sb.append(record.getSourceMethodName());
159
160
161 sb.append(' ');
162 sb.append(formatMessage(record));
163
164
165 sb.append(System.lineSeparator());
166
167
168 if (record.getThrown() != null) {
169 StringWriter sw = new StringWriter();
170 PrintWriter pw = new IndentingPrintWriter(sw);
171 record.getThrown().printStackTrace(pw);
172 pw.close();
173 sb.append(sw.getBuffer());
174 }
175
176 return sb.toString();
177 }
178
179 protected void addTimestamp(StringBuilder buf, long timestamp) {
180 String cachedTimeStamp = localDateCache.get().getFormat(timestamp);
181 if (millisHandling == MillisHandling.NONE) {
182 buf.append(cachedTimeStamp);
183 } else if (millisHandling == MillisHandling.APPEND) {
184 buf.append(cachedTimeStamp);
185 long frac = timestamp % 1000;
186 buf.append('.');
187 if (frac < 100) {
188 if (frac < 10) {
189 buf.append('0');
190 buf.append('0');
191 } else {
192 buf.append('0');
193 }
194 }
195 buf.append(frac);
196 } else {
197
198 long frac = timestamp % 1000;
199
200 int insertStart = cachedTimeStamp.indexOf(DateFormatCache.MSEC_PATTERN);
201 buf.append(cachedTimeStamp.subSequence(0, insertStart));
202 if (frac < 100 && millisHandling == MillisHandling.REPLACE_SSS) {
203 buf.append('0');
204 if (frac < 10) {
205 buf.append('0');
206 }
207 } else if (frac < 10 && millisHandling == MillisHandling.REPLACE_SS) {
208 buf.append('0');
209 }
210 buf.append(frac);
211 if (millisHandling == MillisHandling.REPLACE_SSS) {
212 buf.append(cachedTimeStamp.substring(insertStart + 3));
213 } else if (millisHandling == MillisHandling.REPLACE_SS) {
214 buf.append(cachedTimeStamp.substring(insertStart + 2));
215 } else {
216 buf.append(cachedTimeStamp.substring(insertStart + 1));
217 }
218 }
219 }
220
221
222
231 private static String getThreadName(int logRecordThreadId) {
232 Map<Integer,String> cache = threadNameCache.get();
233 String result = null;
234
235 if (logRecordThreadId > (Integer.MAX_VALUE / 2)) {
236 result = cache.get(Integer.valueOf(logRecordThreadId));
237 }
238
239 if (result != null) {
240 return result;
241 }
242
243 if (logRecordThreadId > Integer.MAX_VALUE / 2) {
244 result = UNKNOWN_THREAD_NAME + logRecordThreadId;
245 } else {
246
247 if (threadMxBean == null) {
248 synchronized (threadMxBeanLock) {
249 if (threadMxBean == null) {
250 threadMxBean = ManagementFactory.getThreadMXBean();
251 }
252 }
253 }
254 ThreadInfo threadInfo =
255 threadMxBean.getThreadInfo(logRecordThreadId);
256 if (threadInfo == null) {
257 return Long.toString(logRecordThreadId);
258 }
259 result = threadInfo.getThreadName();
260 }
261
262 cache.put(Integer.valueOf(logRecordThreadId), result);
263
264 return result;
265 }
266
267
268 private static class ThreadNameCache extends LinkedHashMap<Integer,String> {
269
270 private static final long serialVersionUID = 1L;
271
272 private final int cacheSize;
273
274 public ThreadNameCache(int cacheSize) {
275 this.cacheSize = cacheSize;
276 }
277
278 @Override
279 protected boolean removeEldestEntry(Entry<Integer, String> eldest) {
280 return (size() > cacheSize);
281 }
282 }
283
284
285
289 private static class IndentingPrintWriter extends PrintWriter {
290
291 public IndentingPrintWriter(Writer out) {
292 super(out);
293 }
294
295 @Override
296 public void println(Object x) {
297 super.print('\t');
298 super.println(x);
299 }
300 }
301
302
303 private static enum MillisHandling {
304 NONE,
305 APPEND,
306 REPLACE_S,
307 REPLACE_SS,
308 REPLACE_SSS,
309 }
310 }
311