Skip to content

Package: CollectorFormatter

CollectorFormatter

nameinstructionbranchcomplexitylinemethod
CollectorFormatter()
M: 31 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 9 C: 0
0%
M: 1 C: 0
0%
CollectorFormatter(String)
M: 35 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 9 C: 0
0%
M: 1 C: 0
0%
CollectorFormatter(String, Formatter, Comparator)
M: 31 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 9 C: 0
0%
M: 1 C: 0
0%
accept(LogRecord, LogRecord)
M: 48 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 11 C: 0
0%
M: 1 C: 0
0%
acceptAndUpdate(LogRecord, LogRecord)
M: 12 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
apply(LogRecord, LogRecord)
M: 23 C: 0
0%
M: 8 C: 0
0%
M: 5 C: 0
0%
M: 5 C: 0
0%
M: 1 C: 0
0%
finish(String)
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
format(LogRecord)
M: 39 C: 0
0%
M: 8 C: 0
0%
M: 5 C: 0
0%
M: 10 C: 0
0%
M: 1 C: 0
0%
formatRecord(Handler, boolean)
M: 195 C: 0
0%
M: 16 C: 0
0%
M: 9 C: 0
0%
M: 33 C: 0
0%
M: 1 C: 0
0%
getTail(Handler)
M: 9 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 2 C: 0
0%
M: 1 C: 0
0%
initComparator(String)
M: 70 C: 0
0%
M: 14 C: 0
0%
M: 8 C: 0
0%
M: 20 C: 0
0%
M: 1 C: 0
0%
initFormat(String)
M: 14 C: 0
0%
M: 4 C: 0
0%
M: 3 C: 0
0%
M: 4 C: 0
0%
M: 1 C: 0
0%
initFormatter(String)
M: 39 C: 0
0%
M: 6 C: 0
0%
M: 4 C: 0
0%
M: 12 C: 0
0%
M: 1 C: 0
0%
peek()
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
reset(long)
M: 25 C: 0
0%
M: 2 C: 0
0%
M: 2 C: 0
0%
M: 8 C: 0
0%
M: 1 C: 0
0%
static {...}
M: 3 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 1 C: 0
0%
M: 1 C: 0
0%
toString()
M: 13 C: 0
0%
M: 0 C: 0
100%
M: 1 C: 0
0%
M: 5 C: 0
0%
M: 1 C: 0
0%

Coverage

1: /*
2: * Copyright (c) 2013, 2023 Oracle and/or its affiliates. All rights reserved.
3: * Copyright (c) 2013, 2023 Jason Mehrens. All rights reserved.
4: *
5: * This program and the accompanying materials are made available under the
6: * terms of the Eclipse Public License v. 2.0, which is available at
7: * http://www.eclipse.org/legal/epl-2.0.
8: *
9: * This Source Code may also be made available under the following Secondary
10: * Licenses when the conditions for such availability set forth in the
11: * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
12: * version 2 with the GNU Classpath Exception, which is available at
13: * https://www.gnu.org/software/classpath/license.html.
14: *
15: * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
16: */
17:
18: package org.eclipse.angus.mail.util.logging;
19:
20: import static org.eclipse.angus.mail.util.logging.LogManagerProperties.fromLogManager;
21: import java.lang.reflect.UndeclaredThrowableException;
22: import java.text.MessageFormat;
23: import java.util.Comparator;
24: import java.util.Locale;
25: import java.util.ResourceBundle;
26: import java.util.logging.Formatter;
27: import java.util.logging.Handler;
28: import java.util.logging.LogRecord;
29:
30: /**
31: * A LogRecord formatter that takes a sequence of LogRecords and combines them
32: * into a single summary result. Formating of the head, LogRecord, and tail are
33: * delegated to the wrapped formatter.
34: *
35: * <p>
36: * By default each <code>CollectorFormatter</code> is initialized using the
37: * following LogManager configuration properties where
38: * <code><formatter-name></code> refers to the fully qualified class name
39: * or the fully qualified derived class name of the formatter. If properties
40: * are not defined, or contain invalid values, then the specified default values
41: * are used.
42: * <ul>
43: * <li><formatter-name>.comparator name of a
44: * {@linkplain java.util.Comparator} class used to choose the collected
45: * <code>LogRecord</code>. If a comparator is specified then the max
46: * <code>LogRecord</code> is chosen. If comparator is set to the string literal
47: * null, then the last record is chosen. (defaults to
48: * {@linkplain SeverityComparator})
49: *
50: * <li><formatter-name>.comparator.reverse a boolean
51: * <code>true</code> to collect the min <code>LogRecord</code> or
52: * <code>false</code> to collect the max <code>LogRecord</code>.
53: * (defaults to <code>false</code>)
54: *
55: * <li><formatter-name>.format the
56: * {@linkplain java.text.MessageFormat MessageFormat} string used to format the
57: * collected summary statistics. The arguments are explained in detail in the
58: * {@linkplain #getTail(java.util.logging.Handler) getTail} documentation.
59: * (defaults to <code>{0}{1}{2}{4,choice,-1#|0#|0<... {4,number,integer}
60: * more}\n</code>)
61: *
62: * <li><formatter-name>.formatter name of a <code>Formatter</code> class
63: * used to format the collected LogRecord.
64: * (defaults to {@linkplain CompactFormatter})
65: *
66: * </ul>
67: *
68: * @author Jason Mehrens
69: * @since JavaMail 1.5.2
70: */
71: public class CollectorFormatter extends Formatter {
72: /**
73: * Avoid depending on JMX runtime bean to get the start time.
74: */
75: private static final long INIT_TIME = System.currentTimeMillis();
76: /**
77: * The message format string used as the formatted output.
78: */
79: private final String fmt;
80: /**
81: * The formatter used to format the chosen log record.
82: */
83: private final Formatter formatter;
84: /**
85: * The comparator used to pick the log record to format.
86: */
87: private final Comparator<? super LogRecord> comparator;
88: /**
89: * The last accepted record. Synchronized access is preferred over volatile
90: * for this class.
91: */
92: private LogRecord last;
93: /**
94: * The number of log records that have been formatted.
95: */
96: private long count;
97: /**
98: * The number of log produced containing at least one log record.
99: * Only incremented when this formatter is reset.
100: */
101: private long generation = 1L;
102: /**
103: * The number of log records that have been formatted with a thrown object.
104: */
105: private long thrown;
106: /**
107: * The eldest log record time or eldest time possible for this instance.
108: */
109: private long minMillis = INIT_TIME;
110: /**
111: * The newest log record time.
112: */
113: private long maxMillis = Long.MIN_VALUE;
114:
115: /**
116: * Creates the formatter using the LogManager defaults.
117: *
118: * @throws SecurityException if a security manager exists and the caller
119: * does not have <code>LoggingPermission("control")</code>.
120: * @throws UndeclaredThrowableException if there are problems loading from
121: * the LogManager.
122: */
123: public CollectorFormatter() {
124: final String p = getClass().getName();
125: this.fmt = initFormat(p);
126: this.formatter = initFormatter(p);
127: this.comparator = initComparator(p);
128: }
129:
130: /**
131: * Creates the formatter using the given format.
132: *
133: * @param format the message format or null to use the LogManager default.
134: * @throws SecurityException if a security manager exists and the caller
135: * does not have <code>LoggingPermission("control")</code>.
136: * @throws UndeclaredThrowableException if there are problems loading from
137: * the LogManager.
138: */
139: public CollectorFormatter(String format) {
140: final String p = getClass().getName();
141:• this.fmt = format == null ? initFormat(p) : format;
142: this.formatter = initFormatter(p);
143: this.comparator = initComparator(p);
144: }
145:
146: /**
147: * Creates the formatter using the given values.
148: *
149: * @param format the format string or null to use the LogManager default.
150: * @param f the formatter used on the collected log record or null to
151: * specify no formatter.
152: * @param c the comparator used to determine which log record to format or
153: * null to specify no comparator.
154: * @throws SecurityException if a security manager exists and the caller
155: * does not have <code>LoggingPermission("control")</code>.
156: * @throws UndeclaredThrowableException if there are problems loading from
157: * the LogManager.
158: */
159: public CollectorFormatter(String format, Formatter f,
160: Comparator<? super LogRecord> c) {
161: final String p = getClass().getName();
162:• this.fmt = format == null ? initFormat(p) : format;
163: this.formatter = f;
164: this.comparator = c;
165: }
166:
167: /**
168: * Accumulates log records which will be used to produce the final output.
169: * The output is generated using the {@link #getTail} method which also
170: * resets this formatter back to its original state.
171: *
172: * @param record the record to store.
173: * @return an empty string.
174: * @throws NullPointerException if the given record is null.
175: */
176: @Override
177: public String format(final LogRecord record) {
178:• if (record == null) {
179: throw new NullPointerException();
180: }
181:
182: boolean accepted;
183: do {
184: final LogRecord peek = peek();
185: //The self compare of the first record acts like a type check.
186:• LogRecord update = apply(peek != null ? peek : record, record);
187:• if (peek != update) { //Not identical.
188: update.getSourceMethodName(); //Infer caller, null check.
189: accepted = acceptAndUpdate(peek, update);
190: } else {
191: accepted = accept(peek, record);
192: }
193:• } while (!accepted);
194: return "";
195: }
196:
197: /**
198: * Formats the collected LogRecord and summary statistics. The collected
199: * results are reset after calling this method. The
200: * {@linkplain java.text.MessageFormat java.text} argument indexes are
201: * assigned to the following properties:
202: *
203: * <ol start='0'>
204: * <li>{@code head} the
205: * {@linkplain Formatter#getHead(java.util.logging.Handler) head} string
206: * returned from the target formatter and
207: * {@linkplain #finish(java.lang.String) finished} by this formatter.
208: * <li>{@code formatted} the current log record
209: * {@linkplain Formatter#format(java.util.logging.LogRecord) formatted} by
210: * the target formatter and {@linkplain #finish(java.lang.String) finished}
211: * by this formatter. If the formatter is null then record is formatted by
212: * this {@linkplain #formatMessage(java.util.logging.LogRecord) formatter}.
213: * <li>{@code tail} the
214: * {@linkplain Formatter#getTail(java.util.logging.Handler) tail} string
215: * returned from the target formatter and
216: * {@linkplain #finish(java.lang.String) finished} by this formatter.
217: * <li>{@code count} the total number of log records
218: * {@linkplain #format consumed} by this formatter.
219: * <li>{@code remaining} the count minus one.
220: * <li>{@code thrown} the total number of log records
221: * {@linkplain #format consumed} by this formatter with an assigned
222: * {@linkplain java.util.logging.LogRecord#getThrown throwable}.
223: * <li>{@code normal messages} the count minus the thrown.
224: * <li>{@code minMillis} the eldest log record
225: * {@linkplain java.util.logging.LogRecord#getMillis event time}
226: * {@linkplain #format consumed} by this formatter. If the count is zero
227: * then this is set to the previous max or approximate start time if there
228: * was no previous max. By default this parameter is defined as a number.
229: * The format type and format style rules from the
230: * {@linkplain java.text.MessageFormat} should be used to convert this from
231: * milliseconds to a date or time.
232: * <li>{@code maxMillis} the most recent log record
233: * {@linkplain java.util.logging.LogRecord#getMillis event time}
234: * {@linkplain #format consumed} by this formatter. If the count is zero
235: * then this is set to the {@linkplain System#currentTimeMillis() current time}.
236: * By default this parameter is defined as a number. The format type and
237: * format style rules from the {@linkplain java.text.MessageFormat} should
238: * be used to convert this from milliseconds to a date or time.
239: * <li>{@code elapsed} the elapsed time in milliseconds between the
240: * {@code maxMillis} and {@code minMillis}.
241: * <li>{@code startTime} the approximate start time in milliseconds. By
242: * default this parameter is defined as a number. The format type and format
243: * style rules from the {@linkplain java.text.MessageFormat} should be used
244: * to convert this from milliseconds to a date or time.
245: * <li>{@code currentTime} the
246: * {@linkplain System#currentTimeMillis() current time} in milliseconds. By
247: * default this parameter is defined as a number. The format type and format
248: * style rules from the {@linkplain java.text.MessageFormat} should be used
249: * to convert this from milliseconds to a date or time.
250: * <li>{@code uptime} the elapsed time in milliseconds between the
251: * {@code currentTime} and {@code startTime}.
252: * <li>{@code generation} the number times this method produced output with
253: * at least one {@linkplain #format consumed} log record. This can be used
254: * to track the number of complete reports this formatter has produced.
255: * </ol>
256: *
257: * <p>
258: * Some example formats:<br>
259: * <ul>
260: * <li>{@code org.eclipse.angus.mail.util.logging.CollectorFormatter.format={0}{1}{2}{4,choice,-1#|0#|0<... {4,number,integer} more}\n}
261: * <p>
262: * This prints the head ({@code {0}}), format ({@code {1}}), and tail
263: * ({@code {2}}) from the target formatter followed by the number of
264: * remaining ({@code {4}}) log records consumed by this formatter if there
265: * are any remaining records.
266: * <pre>
267: * Encoding failed.|NullPointerException: null String.getBytes(:913)... 3 more
268: * </pre>
269: * <li>{@code org.eclipse.angus.mail.util.logging.CollectorFormatter.format=These {3} messages occurred between\n{7,date,EEE, MMM dd HH:mm:ss:S ZZZ yyyy} and {8,time,EEE, MMM dd HH:mm:ss:S ZZZ yyyy}\n}
270: * <p>
271: * This prints the count ({@code {3}}) followed by the date and time of the
272: * eldest log record ({@code {7}}) and the date and time of the most recent
273: * log record ({@code {8}}).
274: * <pre>
275: * These 292 messages occurred between
276: * Tue, Jul 21 14:11:42:449 -0500 2009 and Fri, Nov 20 07:29:24:0 -0600 2009
277: * </pre>
278: * <li>{@code org.eclipse.angus.mail.util.logging.CollectorFormatter.format=These {3} messages occurred between {9,choice,86400000#{7,date} {7,time} and {8,time}|86400000<{7,date} and {8,date}}\n}
279: * <p>
280: * This prints the count ({@code {3}}) and then chooses the format based on
281: * the elapsed time ({@code {9}}). If the elapsed time is less than one day
282: * then the eldest log record ({@code {7}}) date and time is formatted
283: * followed by just the time of the most recent log record ({@code {8}}.
284: * Otherwise, the just the date of the eldest log record ({@code {7}}) and
285: * just the date of most recent log record ({@code {8}} is formatted.
286: * <pre>
287: * These 73 messages occurred between Jul 21, 2009 2:11:42 PM and 2:13:32 PM
288: *
289: * These 116 messages occurred between Jul 21, 2009 and Aug 20, 2009
290: * </pre>
291: * <li>{@code org.eclipse.angus.mail.util.logging.CollectorFormatter.format={13} alert reports since {10,date}.\n}
292: * <p>
293: * This prints the generation ({@code {13}}) followed by the start time
294: * ({@code {10}}) formatted as a date.
295: * <pre>
296: * 4,320 alert reports since Jul 21, 2012.
297: * </pre>
298: * </ul>
299: *
300: * @param h the handler or null.
301: * @return the output string.
302: */
303: @Override
304: public String getTail(final Handler h) {
305: super.getTail(h); //Be forward compatible with super.getHead.
306: return formatRecord(h, true);
307: }
308:
309: /**
310: * Formats the collected LogRecord and summary statistics. The LogRecord and
311: * summary statistics are not changed by calling this method.
312: *
313: * @return the current record formatted or the default toString.
314: * @see #getTail(java.util.logging.Handler)
315: */
316: @Override
317: public String toString() {
318: String result;
319: try {
320: result = formatRecord((Handler) null, false);
321: } catch (final RuntimeException ignore) {
322: result = super.toString();
323: }
324: return result;
325: }
326:
327: /**
328: * Used to choose the collected LogRecord. This implementation returns the
329: * greater of two LogRecords.
330: *
331: * @param t the current record.
332: * @param u the record that could replace the current.
333: * @return the greater of the given log records.
334: * @throws NullPointerException may occur if either record is null.
335: */
336: protected LogRecord apply(final LogRecord t, final LogRecord u) {
337:• if (t == null || u == null) {
338: throw new NullPointerException();
339: }
340:
341:• if (comparator != null) {
342:• return comparator.compare(t, u) >= 0 ? t : u;
343: } else {
344: return u;
345: }
346: }
347:
348: /**
349: * Updates the summary statistics only if the expected record matches the
350: * last record. The update record is not stored.
351: *
352: * @param e the LogRecord that is expected.
353: * @param u the LogRecord used to collect statistics.
354: * @return true if the last record was the expected record.
355: * @throws NullPointerException if the update record is null.
356: */
357: private synchronized boolean accept(final LogRecord e, final LogRecord u) {
358: /**
359: * LogRecord methods must be called before the check of the last stored
360: * record to guard against subclasses of LogRecord that might attempt to
361: * reset the state by triggering a call to getTail.
362: */
363: final long millis = u.getMillis(); //Null check.
364: final Throwable ex = u.getThrown();
365:• if (last == e) { //Only if the exact same reference.
366:• if (++count != 1L) {
367: minMillis = Math.min(minMillis, millis);
368: } else { //Show single records as instant and not a time period.
369: minMillis = millis;
370: }
371: maxMillis = Math.max(maxMillis, millis);
372:
373:• if (ex != null) {
374: ++thrown;
375: }
376: return true;
377: } else {
378: return false;
379: }
380: }
381:
382: /**
383: * Resets all of the collected summary statistics including the LogRecord.
384: * @param min the current min milliseconds.
385: */
386: private synchronized void reset(final long min) {
387:• if (last != null) {
388: last = null;
389: ++generation;
390: }
391:
392: count = 0L;
393: thrown = 0L;
394: minMillis = min;
395: maxMillis = Long.MIN_VALUE;
396: }
397:
398: /**
399: * Formats the given record with the head and tail.
400: *
401: * @param h the Handler or null.
402: * @param reset true if the summary statistics and LogRecord should be reset
403: * back to initial values.
404: * @return the formatted string.
405: * @see #getTail(java.util.logging.Handler)
406: */
407: private String formatRecord(final Handler h, final boolean reset) {
408: final LogRecord record;
409: final long c;
410: final long t;
411: final long g;
412: long msl;
413: long msh;
414: long now;
415: synchronized (this) {
416: record = last;
417: c = count;
418: g = generation;
419: t = thrown;
420: msl = minMillis;
421: msh = maxMillis;
422: now = System.currentTimeMillis();
423:• if (c == 0L) {
424: msh = now;
425: }
426:
427:• if (reset) { //BUG ID 6351685
428: reset(msh);
429: }
430: }
431:
432: final String head;
433: final String msg;
434: final String tail;
435: final Formatter f = this.formatter;
436:• if (f != null) {
437: synchronized (f) {
438: head = f.getHead(h);
439:• msg = record != null ? f.format(record) : "";
440: tail = f.getTail(h);
441: }
442: } else {
443: head = "";
444:• msg = record != null ? formatMessage(record) : "";
445: tail = "";
446: }
447:
448: Locale l = null;
449:• if (record != null) {
450: ResourceBundle rb = record.getResourceBundle();
451:• l = rb == null ? null : rb.getLocale();
452: }
453:
454: final MessageFormat mf;
455:• if (l == null) { //BUG ID 8039165
456: mf = new MessageFormat(fmt);
457: } else {
458: mf = new MessageFormat(fmt, l);
459: }
460:
461: /**
462: * These arguments are described in the getTail documentation.
463: */
464: return mf.format(new Object[]{finish(head), finish(msg), finish(tail),
465: c, (c - 1L), t, (c - t), msl, msh, (msh - msl), INIT_TIME, now,
466: (now - INIT_TIME), g});
467: }
468:
469: /**
470: * Applied to the head, format, and tail returned by the target formatter.
471: * This implementation trims all input strings.
472: *
473: * @param s the string to transform.
474: * @return the transformed string.
475: * @throws NullPointerException if the given string is null.
476: */
477: protected String finish(String s) {
478: return s.trim();
479: }
480:
481: /**
482: * Peek at the current log record.
483: *
484: * @return null or the current log record.
485: */
486: private synchronized LogRecord peek() {
487: return this.last;
488: }
489:
490: /**
491: * Updates the summary statistics and stores given LogRecord if the expected
492: * record matches the current record.
493: *
494: * @param e the expected record.
495: * @param u the update record.
496: * @return true if the update was performed.
497: * @throws NullPointerException if the update record is null.
498: */
499: private synchronized boolean acceptAndUpdate(LogRecord e, LogRecord u) {
500:• if (accept(e, u)) {
501: this.last = u;
502: return true;
503: } else {
504: return false;
505: }
506: }
507:
508: /**
509: * Gets the message format string from the LogManager or creates the default
510: * message format string.
511: *
512: * @param p the class name prefix.
513: * @return the format string.
514: * @throws NullPointerException if the given argument is null.
515: */
516: private String initFormat(final String p) {
517: String v = fromLogManager(p.concat(".format"));
518:• if (v == null || v.length() == 0) {
519: v = "{0}{1}{2}{4,choice,-1#|0#|0<... {4,number,integer} more}\n";
520: }
521: return v;
522: }
523:
524: /**
525: * Gets and creates the formatter from the LogManager or creates the default
526: * formatter.
527: *
528: * @param p the class name prefix.
529: * @return the formatter.
530: * @throws NullPointerException if the given argument is null.
531: * @throws UndeclaredThrowableException if the formatter can not be created.
532: */
533: private Formatter initFormatter(final String p) {
534: Formatter f;
535: String v = fromLogManager(p.concat(".formatter"));
536:• if (v != null && v.length() != 0) {
537:• if (!"null".equalsIgnoreCase(v)) {
538: try {
539: f = LogManagerProperties.newFormatter(v);
540: } catch (final RuntimeException re) {
541: throw re;
542: } catch (final Exception e) {
543: throw new UndeclaredThrowableException(e);
544: }
545: } else {
546: f = null;
547: }
548: } else {
549: //Don't force the byte code verifier to load the formatter.
550: f = Formatter.class.cast(new CompactFormatter());
551: }
552: return f;
553: }
554:
555: /**
556: * Gets and creates the comparator from the LogManager or returns the
557: * default comparator.
558: *
559: * @param p the class name prefix.
560: * @return the comparator or null.
561: * @throws IllegalArgumentException if it was specified that the comparator
562: * should be reversed but no initial comparator was specified.
563: * @throws NullPointerException if the given argument is null.
564: * @throws UndeclaredThrowableException if the comparator can not be
565: * created.
566: */
567: @SuppressWarnings("unchecked")
568: private Comparator<? super LogRecord> initComparator(final String p) {
569: Comparator<? super LogRecord> c;
570: final String name = fromLogManager(p.concat(".comparator"));
571: final String reverse = fromLogManager(p.concat(".comparator.reverse"));
572: try {
573:• if (name != null && name.length() != 0) {
574:• if (!"null".equalsIgnoreCase(name)) {
575: c = LogManagerProperties.newComparator(name);
576:• if (Boolean.parseBoolean(reverse)) {
577:• assert c != null;
578: c = LogManagerProperties.reverseOrder(c);
579: }
580: } else {
581:• if (reverse != null) {
582: throw new IllegalArgumentException(
583: "No comparator to reverse.");
584: } else {
585: c = null; //No ordering.
586: }
587: }
588: } else {
589:• if (reverse != null) {
590: throw new IllegalArgumentException(
591: "No comparator to reverse.");
592: } else {
593: //Don't force the byte code verifier to load the comparator.
594: c = Comparator.class.cast(SeverityComparator.getInstance());
595: }
596: }
597: } catch (final RuntimeException re) {
598: throw re; //Avoid catch all.
599: } catch (final Exception e) {
600: throw new UndeclaredThrowableException(e);
601: }
602: return c;
603: }
604: }