) Class.forName(key);
101 | } catch (final ClassNotFoundException ex) {
102 | throw new DecorException(
103 | ex,
104 | "Decor '%s' not found and class can't be instantiated",
105 | key
106 | );
107 | }
108 | }
109 | return type;
110 | }
111 |
112 | /**
113 | * Get ctor of the type.
114 | * @param type The type
115 | * @return The ctor
116 | * @throws DecorException If some problem
117 | */
118 | private static Constructor> ctor(final Class extends Formattable> type)
119 | throws DecorException {
120 | final Constructor>[] ctors = type.getDeclaredConstructors();
121 | if (ctors.length != 1) {
122 | throw new DecorException(
123 | "%s should have just one one-arg ctor, but there are %d",
124 | type.getName(),
125 | ctors.length
126 | );
127 | }
128 | final Constructor> ctor = ctors[0];
129 | if (ctor.getParameterTypes().length != 1) {
130 | throw new DecorException(
131 | "%s public ctor should have just once parameter",
132 | type.getName()
133 | );
134 | }
135 | return ctor;
136 | }
137 |
138 | }
139 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/DomDecor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.io.StringWriter;
8 | import java.util.Formattable;
9 | import java.util.Formatter;
10 | import javax.xml.transform.OutputKeys;
11 | import javax.xml.transform.Transformer;
12 | import javax.xml.transform.TransformerConfigurationException;
13 | import javax.xml.transform.TransformerException;
14 | import javax.xml.transform.TransformerFactory;
15 | import javax.xml.transform.dom.DOMSource;
16 | import javax.xml.transform.stream.StreamResult;
17 | import org.w3c.dom.Node;
18 |
19 | /**
20 | * Decorates XML Document.
21 | *
22 | * @since 0.1
23 | */
24 | final class DomDecor implements Formattable {
25 |
26 | /**
27 | * DOM transformer factory, DOM.
28 | */
29 | private static final TransformerFactory FACTORY =
30 | TransformerFactory.newInstance();
31 |
32 | /**
33 | * The document.
34 | */
35 | private final transient Node node;
36 |
37 | /**
38 | * Public ctor.
39 | * @param doc The document
40 | * @throws DecorException If some problem with it
41 | */
42 | @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors")
43 | DomDecor(final Object doc) throws DecorException {
44 | if (doc != null && !(doc instanceof Node)) {
45 | throw new DecorException(
46 | String.format(
47 | "Instance of org.w3c.dom.Node required, while %s provided",
48 | doc.getClass().getName()
49 | )
50 | );
51 | }
52 | this.node = (Node) doc;
53 | }
54 |
55 | // @checkstyle ParameterNumber (4 lines)
56 | @Override
57 | public void formatTo(final Formatter formatter, final int flags,
58 | final int width, final int precision) {
59 | final StringWriter writer = new StringWriter();
60 | if (this.node == null) {
61 | writer.write("NULL");
62 | } else {
63 | try {
64 | final Transformer trans = DomDecor.FACTORY.newTransformer();
65 | trans.setOutputProperty(OutputKeys.INDENT, "yes");
66 | trans.setOutputProperty(OutputKeys.STANDALONE, "no");
67 | trans.transform(
68 | new DOMSource(this.node),
69 | new StreamResult(writer)
70 | );
71 | } catch (final TransformerException ex) {
72 | throw new IllegalStateException(ex);
73 | }
74 | }
75 | formatter.format("%s", writer);
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/DullyFormatted.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | /**
8 | * Formats a log event without using ANSI color codes.
9 | * @since 0.18
10 | */
11 | class DullyFormatted implements Formatted {
12 |
13 | /**
14 | * String to be formatted.
15 | */
16 | private final transient String basic;
17 |
18 | /**
19 | * Contructor.
20 | * @param bas String to be formatted
21 | */
22 | DullyFormatted(final String bas) {
23 | this.basic = bas;
24 | }
25 |
26 | @Override
27 | public String format() {
28 | return this.basic.replaceAll(
29 | new ControlSequenceIndicatorFormatted("%s([0-9]*|\\?)m").format(),
30 | ""
31 | );
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/ExceptionDecor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.io.PrintWriter;
8 | import java.io.StringWriter;
9 | import java.util.Formattable;
10 | import java.util.FormattableFlags;
11 | import java.util.Formatter;
12 |
13 | /**
14 | * Decorates an exception.
15 | *
16 | * For example:
17 | *
18 | *
19 | * try {
20 | * // ...
21 | * } catch (final IOException ex) {
22 | * Logger.error("failed to open file: %[exception]s", ex);
23 | * throw new IllegalArgumentException(ex);
24 | * }
25 | *
26 | *
27 | * @since 0.1
28 | */
29 | final class ExceptionDecor implements Formattable {
30 |
31 | /**
32 | * The exception.
33 | */
34 | private final transient Throwable throwable;
35 |
36 | /**
37 | * Public ctor.
38 | * @param thr The exception
39 | */
40 | ExceptionDecor(final Throwable thr) {
41 | this.throwable = thr;
42 | }
43 |
44 | // @checkstyle ParameterNumber (4 lines)
45 | @Override
46 | public void formatTo(final Formatter formatter, final int flags,
47 | final int width, final int precision) {
48 | final String text;
49 | if (this.throwable == null) {
50 | text = "NULL";
51 | } else if ((flags & FormattableFlags.ALTERNATE) == 0) {
52 | final StringWriter writer = new StringWriter();
53 | this.throwable.printStackTrace(new PrintWriter(writer));
54 | text = writer.toString();
55 | } else {
56 | text = this.throwable.getMessage();
57 | }
58 | formatter.format("%s", text);
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/FileDecor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.io.StringWriter;
8 | import java.nio.file.Path;
9 | import java.nio.file.Paths;
10 | import java.util.Formattable;
11 | import java.util.Formatter;
12 |
13 | /**
14 | * Decorates File.
15 | *
16 | * @since 0.1
17 | */
18 | final class FileDecor implements Formattable {
19 |
20 | /**
21 | * The path.
22 | */
23 | private final transient Object path;
24 |
25 | /**
26 | * Public ctor.
27 | * @param file The file
28 | */
29 | FileDecor(final Object file) {
30 | this.path = file;
31 | }
32 |
33 | // @checkstyle ParameterNumber (4 lines)
34 | @Override
35 | public void formatTo(final Formatter formatter, final int flags,
36 | final int width, final int precision) {
37 | final StringWriter writer = new StringWriter();
38 | if (this.path == null) {
39 | writer.write("NULL");
40 | } else {
41 | final Path self = Paths.get(this.path.toString()).toAbsolutePath();
42 | final Path root = Paths.get("").toAbsolutePath();
43 | Path rlt;
44 | try {
45 | rlt = root.relativize(self);
46 | } catch (final IllegalArgumentException ex) {
47 | rlt = self;
48 | }
49 | String rel = rlt.toString();
50 | if (rel.startsWith("..")) {
51 | rel = self.toString();
52 | }
53 | if (rel.isEmpty()) {
54 | rel = "./";
55 | }
56 | writer.write(rel);
57 | }
58 | formatter.format("%s", writer);
59 | }
60 |
61 | }
62 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/Formatted.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | /**
8 | * Contract for a class that know how to format something.
9 | * @since 0.18
10 | */
11 | interface Formatted {
12 |
13 | /**
14 | * Return something formatted.
15 | * @return Formatted version of something
16 | */
17 | String format();
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/ListDecor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Arrays;
8 | import java.util.Collection;
9 | import java.util.Formattable;
10 | import java.util.Formatter;
11 |
12 | /**
13 | * Format list.
14 | * @since 0.1
15 | */
16 | final class ListDecor implements Formattable {
17 |
18 | /**
19 | * The list.
20 | */
21 | private final transient Collection> list;
22 |
23 | /**
24 | * Public ctor.
25 | * @param obj The object to format
26 | * @throws DecorException If some problem with it
27 | */
28 | @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors")
29 | ListDecor(final Object obj) throws DecorException {
30 | if (obj == null || obj instanceof Collection) {
31 | this.list = Collection.class.cast(obj);
32 | } else if (obj instanceof Object[]) {
33 | this.list = Arrays.asList((Object[]) obj);
34 | } else {
35 | throw new DecorException(
36 | String.format(
37 | "Collection or array required, while %s provided",
38 | obj.getClass().getName()
39 | )
40 | );
41 | }
42 | }
43 |
44 | // @checkstyle ParameterNumber (4 lines)
45 | @Override
46 | public void formatTo(final Formatter formatter, final int flags,
47 | final int width, final int precision) {
48 | final StringBuilder builder = new StringBuilder(0);
49 | builder.append('[');
50 | if (this.list == null) {
51 | builder.append("NULL");
52 | } else {
53 | boolean first = true;
54 | for (final Object item : this.list) {
55 | if (!first) {
56 | builder.append(", ");
57 | }
58 | builder.append(String.format("\"%s\"", item));
59 | first = false;
60 | }
61 | }
62 | builder.append(']');
63 | formatter.format("%s", builder);
64 | }
65 |
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/MsDecor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Formattable;
8 | import java.util.FormattableFlags;
9 | import java.util.Formatter;
10 |
11 | /**
12 | * Decorate time interval in milliseconds.
13 | *
14 | * For example:
15 | *
16 | *
17 | * final long start = System.currentTimeMillis();
18 | * // some operations
19 | * Logger.debug("completed in %[ms]s", System.currentTimeMillis() - start);
20 | *
21 | *
22 | * @since 0.1
23 | */
24 | final class MsDecor implements Formattable {
25 |
26 | /**
27 | * The period to work with, in milliseconds.
28 | */
29 | private final transient Double millis;
30 |
31 | /**
32 | * Public ctor.
33 | * @param msec The interval in milliseconds
34 | */
35 | @SuppressWarnings(
36 | {
37 | "PMD.NullAssignment",
38 | "PMD.ConstructorOnlyInitializesOrCallOtherConstructors"
39 | }
40 | )
41 | MsDecor(final Long msec) {
42 | if (msec == null) {
43 | this.millis = null;
44 | } else {
45 | this.millis = Double.valueOf(msec);
46 | }
47 | }
48 |
49 | // @checkstyle ParameterNumber (4 lines)
50 | @Override
51 | public void formatTo(final Formatter formatter, final int flags,
52 | final int width, final int precision) {
53 | if (this.millis == null) {
54 | formatter.format("NULL");
55 | } else {
56 | final StringBuilder format = new StringBuilder(0);
57 | format.append('%');
58 | if ((flags & FormattableFlags.LEFT_JUSTIFY) == FormattableFlags
59 | .LEFT_JUSTIFY) {
60 | format.append('-');
61 | }
62 | if (width > 0) {
63 | format.append(Integer.toString(width));
64 | }
65 | if ((flags & FormattableFlags.UPPERCASE) == FormattableFlags
66 | .UPPERCASE) {
67 | format.append('S');
68 | } else {
69 | format.append('s');
70 | }
71 | formatter.format(format.toString(), this.toText(precision));
72 | }
73 | }
74 |
75 | /**
76 | * Create text.
77 | * @param precision The precision
78 | * @return The text
79 | */
80 | private String toText(final int precision) {
81 | final double number;
82 | final String title;
83 | if (this.millis < 1000.0) {
84 | number = this.millis;
85 | title = "ms";
86 | } else if (this.millis < (double) (1000L * 60L)) {
87 | number = this.millis / 1000.0;
88 | title = "s";
89 | } else if (this.millis < (double) (1000L * 60L * 60L)) {
90 | number = this.millis / (double) (1000L * 60L);
91 | title = "min";
92 | } else if (this.millis < (double) (1000L * 60L * 60L * 24L)) {
93 | number = this.millis / (double) (1000L * 60L * 60L);
94 | title = "hr";
95 | } else if (this.millis < (double) (1000L * 60L * 60L * 24L * 30L)) {
96 | number = this.millis / (double) (1000L * 60L * 60L * 24L);
97 | title = "days";
98 | } else {
99 | number = this.millis / (double) (1000L * 60L * 60L * 24L * 30L);
100 | title = "mon";
101 | }
102 | final String format;
103 | if (precision >= 0) {
104 | format = String.format("%%.%df%%s", precision);
105 | } else {
106 | format = "%.0f%s";
107 | }
108 | return String.format(format, number, title);
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/MulticolorLayout.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Map;
8 | import java.util.concurrent.ConcurrentHashMap;
9 | import java.util.concurrent.ConcurrentMap;
10 | import org.apache.log4j.EnhancedPatternLayout;
11 | import org.apache.log4j.Level;
12 | import org.apache.log4j.spi.LoggingEvent;
13 |
14 | /**
15 | * Multi-color layout for LOG4J.
16 | *
17 | * Use it in your LOG4J configuration:
18 | *
19 | *
log4j.rootLogger=INFO, CONSOLE
20 | * log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
21 | * log4j.appender.CONSOLE.layout=com.jcabi.log.MulticolorLayout
22 | * log4j.appender.CONSOLE.layout.ConversionPattern=[%color{%-5p}] %c: %m%n
23 | *
24 | * The part of the message wrapped with {@code %color{...}}
25 | * will change its color according to the logging level of the event. Without
26 | * this highlighting the behavior of the layout is identical to
27 | * {@link EnhancedPatternLayout}. You can use {@code %color-red{...}} if you
28 | * want to use specifically red color for the wrapped piece of text. Supported
29 | * colors are: {@code red}, {@code blue}, {@code yellow}, {@code cyan},
30 | * {@code black}, and {@code white}.
31 | *
32 | *
Besides that you can specify any ANSI color you like with
33 | * {@code %color-;;{...}}, where
34 | * {@code } is a binary mask of attributes,
35 | * {@code } is a background color, and
36 | * {@code } is a foreground color. Read more about
37 | * ANSI escape code .
38 | *
39 | * This class or its parents are not serializable.
40 | *
41 | *
Maven dependency for this class is
42 | * (see How
43 | * to use with Maven instructions):
44 | *
45 | *
<dependency>
46 | * <groupId>com.jcabi</groupId>
47 | * <artifactId>jcabi-log</artifactId>
48 | * </dependency>
49 | * @since 0.1.10
50 | * @see ANSI escape code
51 | * @see PatternLayout from LOG4J
52 | * @see How to use with Maven
53 | */
54 | @SuppressWarnings("PMD.NonStaticInitializer")
55 | public final class MulticolorLayout extends EnhancedPatternLayout {
56 |
57 | /**
58 | * Name of the property that is used to disable log coloring.
59 | */
60 | private static final String COLORING_PROPERY = "com.jcabi.log.coloring";
61 |
62 | /**
63 | * Colors of levels.
64 | */
65 | private final transient ConcurrentMap levels =
66 | MulticolorLayout.levelMap();
67 |
68 | /**
69 | * Store original conversation pattern to be able
70 | * to recalculate it, if new colors are provided.
71 | */
72 | private transient String base;
73 |
74 | /**
75 | * Color human readable data.
76 | */
77 | private final transient Colors colors = new Colors();
78 |
79 | @Override
80 | public void setConversionPattern(final String pattern) {
81 | this.base = pattern;
82 | super.setConversionPattern(
83 | new ConversionPattern(this.base, this.colors).generate()
84 | );
85 | }
86 |
87 | /**
88 | * Allow to overwrite or specify new ANSI color names
89 | * in a javascript map like format.
90 | *
91 | * @param cols JavaScript like map of color names
92 | * @since 0.9
93 | */
94 | @SuppressWarnings("PMD.UseConcurrentHashMap")
95 | public void setColors(final String cols) {
96 | final Map parsed = new ParseableInformation(
97 | cols
98 | ).information();
99 | for (final Map.Entry entry : parsed.entrySet()) {
100 | this.colors.addColor(entry.getKey(), entry.getValue());
101 | }
102 | if (this.base != null) {
103 | this.setConversionPattern(this.base);
104 | }
105 | }
106 |
107 | /**
108 | * Allow to overwrite the ANSI color values for the log levels
109 | * in a javascript map like format.
110 | * @param lev JavaScript like map of levels
111 | * @since 0.9
112 | */
113 | @SuppressWarnings("PMD.UseConcurrentHashMap")
114 | public void setLevels(final String lev) {
115 | final Map parsed = new ParseableLevelInformation(
116 | lev
117 | ).information();
118 | for (final Map.Entry entry : parsed.entrySet()) {
119 | this.levels.put(entry.getKey(), entry.getValue());
120 | }
121 | }
122 |
123 | @Override
124 | public String format(final LoggingEvent event) {
125 | final Formatted formatted;
126 | if (MulticolorLayout.isColoringEnabled()) {
127 | formatted = this.colorfulFormatting(event);
128 | } else {
129 | formatted = this.dullFormatting(event);
130 | }
131 | return formatted.format();
132 | }
133 |
134 | /**
135 | * Generate a dull {@code Formatted}.
136 | * @param event Event to be formatted
137 | * @return A {@link Formatted} to format the event
138 | * @checkstyle NonStaticMethodCheck (10 lines)
139 | */
140 | private Formatted dullFormatting(final LoggingEvent event) {
141 | return new DullyFormatted(super.format(event));
142 | }
143 |
144 | /**
145 | * Generate a colorful {@code Formatted}.
146 | * @param event Event to be formatted
147 | * @return Text of a log event, probably colored with ANSI color codes
148 | */
149 | private Formatted colorfulFormatting(final LoggingEvent event) {
150 | return new ColorfullyFormatted(
151 | super.format(event),
152 | this.levels.get(event.getLevel().toString())
153 | );
154 | }
155 |
156 | /**
157 | * Level map.
158 | * @return Map of levels
159 | */
160 | private static ConcurrentMap levelMap() {
161 | final ConcurrentMap map = new ConcurrentHashMap<>(0);
162 | map.put(Level.TRACE.toString(), "2;33");
163 | map.put(Level.DEBUG.toString(), "2;37");
164 | map.put(Level.INFO.toString(), "0;37");
165 | map.put(Level.WARN.toString(), "0;33");
166 | map.put(Level.ERROR.toString(), "0;31");
167 | map.put(Level.FATAL.toString(), "0;35");
168 | return map;
169 | }
170 |
171 | /**
172 | * Should the logged text be colored or not.
173 | * @return True if the coloring is enabled, or false otherwise.
174 | */
175 | private static boolean isColoringEnabled() {
176 | return !"false".equals(
177 | System.getProperty(MulticolorLayout.COLORING_PROPERY)
178 | );
179 | }
180 |
181 | }
182 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/NanoDecor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Formattable;
8 | import java.util.FormattableFlags;
9 | import java.util.Formatter;
10 |
11 | /**
12 | * Decorate time interval in nanoseconds.
13 | *
14 | * For example:
15 | *
16 | *
17 | * final long start = System.nanoTime();
18 | * // some operations
19 | * Logger.debug("completed in %[nano]s", System.nanoTime() - start);
20 | *
21 | *
22 | * @since 0.1
23 | */
24 | final class NanoDecor implements Formattable {
25 |
26 | /**
27 | * The period to work with, in nanoseconds.
28 | */
29 | private final transient Double nano;
30 |
31 | /**
32 | * Public ctor.
33 | * @param nan The interval in nanoseconds
34 | */
35 | @SuppressWarnings(
36 | {
37 | "PMD.NullAssignment",
38 | "PMD.ConstructorOnlyInitializesOrCallOtherConstructors"
39 | }
40 | )
41 | NanoDecor(final Long nan) {
42 | if (nan == null) {
43 | this.nano = null;
44 | } else {
45 | this.nano = Double.valueOf(nan);
46 | }
47 | }
48 |
49 | // @checkstyle ParameterNumber (4 lines)
50 | @Override
51 | public void formatTo(final Formatter formatter, final int flags,
52 | final int width, final int precision) {
53 | if (this.nano == null) {
54 | formatter.format("NULL");
55 | } else {
56 | final StringBuilder format = new StringBuilder(0);
57 | format.append('%');
58 | if ((flags & FormattableFlags.LEFT_JUSTIFY) == FormattableFlags
59 | .LEFT_JUSTIFY) {
60 | format.append('-');
61 | }
62 | if (width > 0) {
63 | format.append(width);
64 | }
65 | if ((flags & FormattableFlags.UPPERCASE) == FormattableFlags
66 | .UPPERCASE) {
67 | format.append('S');
68 | } else {
69 | format.append('s');
70 | }
71 | formatter.format(format.toString(), this.toText(precision));
72 | }
73 | }
74 |
75 | /**
76 | * Create text.
77 | * @param precision The precision
78 | * @return The text
79 | */
80 | private String toText(final int precision) {
81 | final double number;
82 | final String title;
83 | if (this.nano < 1000.0) {
84 | number = this.nano;
85 | title = "ns";
86 | } else if (this.nano < (double) (1000L * 1000L)) {
87 | number = this.nano / 1000.0;
88 | title = "µs";
89 | } else if (this.nano < (double) (1000L * 1000L * 1000L)) {
90 | number = this.nano / (double) (1000L * 1000L);
91 | title = "ms";
92 | } else if (this.nano < (double) (1000L * 1000L * 1000L * 60L)) {
93 | number = this.nano / (double) (1000L * 1000L * 1000L);
94 | title = "s";
95 | } else {
96 | number = this.nano / (double) (1000L * 1000L * 1000L * 60L);
97 | title = "min";
98 | }
99 | final String format;
100 | if (precision >= 0) {
101 | format = String.format("%%.%df%%s", precision);
102 | } else {
103 | format = "%.0f%s";
104 | }
105 | return String.format(format, number, title);
106 | }
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/ObjectDecor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.lang.reflect.Field;
8 | import java.security.PrivilegedAction;
9 | import java.util.Arrays;
10 | import java.util.Formattable;
11 | import java.util.Formatter;
12 |
13 | /**
14 | * Format internal structure of an object.
15 | * @since 0.1
16 | */
17 | final class ObjectDecor implements Formattable {
18 |
19 | /**
20 | * The object to work with.
21 | */
22 | private final transient Object object;
23 |
24 | /**
25 | * Public ctor.
26 | * @param obj The object to format
27 | */
28 | ObjectDecor(final Object obj) {
29 | this.object = obj;
30 | }
31 |
32 | // @checkstyle ParameterNumber (4 lines)
33 | @Override
34 | public void formatTo(final Formatter formatter, final int flags,
35 | final int width, final int precision) {
36 | if (this.object == null) {
37 | formatter.format("NULL");
38 | } else if (this.object.getClass().isArray()) {
39 | formatter.format(
40 | new ObjectDecor.ArrayFormatAction((Object[]) this.object).run()
41 | );
42 | } else {
43 | final String output =
44 | new ObjectDecor.ObjectContentsFormatAction(this.object).run();
45 | formatter.format(output);
46 | }
47 | }
48 |
49 | /**
50 | * {@link PrivilegedAction} for obtaining array contents.
51 | *
52 | * @since 0.1
53 | */
54 | private static final class ArrayFormatAction
55 | implements PrivilegedAction {
56 | /**
57 | * Array to format.
58 | */
59 | private final transient Object[] array;
60 |
61 | /**
62 | * Constructor.
63 | * @param arr Array to format
64 | */
65 | ArrayFormatAction(final Object... arr) {
66 | this.array = Arrays.copyOf(arr, arr.length);
67 | }
68 |
69 | @Override
70 | @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
71 | public String run() {
72 | final StringBuilder builder = new StringBuilder("[");
73 | final Formatter formatter = new Formatter(builder);
74 | for (final Object obj : this.array) {
75 | new ObjectDecor(obj).formatTo(formatter, 0, 0, 0);
76 | // @checkstyle MultipleStringLiteralsCheck (1 line)
77 | builder.append(", ");
78 | }
79 | builder.replace(builder.length() - 2, builder.length(), "]");
80 | return builder.toString();
81 | }
82 | }
83 |
84 | /**
85 | * {@link PrivilegedAction} for obtaining object contents.
86 | *
87 | * @since 0.1
88 | */
89 | private static final class ObjectContentsFormatAction
90 | implements PrivilegedAction {
91 | /**
92 | * Object to format.
93 | */
94 | private final transient Object object;
95 |
96 | /**
97 | * Constructor.
98 | * @param obj Object to format
99 | */
100 | ObjectContentsFormatAction(final Object obj) {
101 | this.object = obj;
102 | }
103 |
104 | @Override
105 | public String run() {
106 | final StringBuilder builder = new StringBuilder("{");
107 | for (final Field field
108 | : this.object.getClass().getDeclaredFields()) {
109 | field.setAccessible(true);
110 | try {
111 | builder.append(
112 | String.format(
113 | "%s: \"%s\"",
114 | field.getName(),
115 | field.get(this.object)
116 | )
117 | );
118 | } catch (final IllegalAccessException ex) {
119 | throw new IllegalStateException(ex);
120 | }
121 | // @checkstyle MultipleStringLiteralsCheck (1 line)
122 | builder.append(", ");
123 | }
124 | builder.replace(builder.length() - 2, builder.length(), "}");
125 | return builder.toString();
126 | }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/ParseableInformation.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.HashMap;
8 | import java.util.Map;
9 |
10 | /**
11 | * Converts items inside a string like K1:V1,K2:V2 - where K is for key and V
12 | * is for value - to a {@code Map} of string key and string value.
13 | * @since 0.18
14 | */
15 | class ParseableInformation {
16 |
17 | /**
18 | * Information content to be parsed.
19 | */
20 | private final transient String content;
21 |
22 | /**
23 | * Construtor.
24 | * @param cont Content to be parsed
25 | */
26 | ParseableInformation(final String cont) {
27 | super();
28 | this.content = cont;
29 | }
30 |
31 | /**
32 | * Parse the information.
33 | * @return A {@link Map} with a key,value pair os strings
34 | */
35 | @SuppressWarnings("PMD.UseConcurrentHashMap")
36 | public final Map information() {
37 | final Map parsed = new HashMap<>(0);
38 | try {
39 | for (final String item : this.items()) {
40 | final String[] values = item.split(":");
41 | parsed.put(values[0], values[1]);
42 | }
43 | } catch (final ArrayIndexOutOfBoundsException ex) {
44 | throw new IllegalStateException(
45 | String.format(new StringBuilder(0)
46 | .append("Information is not using the pattern ")
47 | .append("KEY1:VALUE,KEY2:VALUE %s")
48 | .toString(),
49 | this.content
50 | ), ex
51 | );
52 | }
53 | return parsed;
54 | }
55 |
56 | /**
57 | * Split the information using {@link ParseableInformation#SPLIT_ITEMS}
58 | * pattern.
59 | * @return An array of items
60 | */
61 | private String[] items() {
62 | return this.content.split(",");
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/ParseableLevelInformation.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.HashMap;
8 | import java.util.Locale;
9 | import java.util.Map;
10 | import org.apache.log4j.Level;
11 |
12 | /**
13 | * Parse information like {@code ParseInformation} does, but increments with
14 | * some extra checks for {@code Level}s.
15 | * @since 0.18
16 | */
17 | class ParseableLevelInformation {
18 |
19 | /**
20 | * Information content to be parsed.
21 | */
22 | private final transient String content;
23 |
24 | /**
25 | * Construtor.
26 | * @param cont Content to be parsed
27 | */
28 | ParseableLevelInformation(final String cont) {
29 | this.content = cont;
30 | }
31 |
32 | /**
33 | * Parse the level information.
34 | * @return A {@link Map} with key,value pair of strings
35 | */
36 | @SuppressWarnings("PMD.UseConcurrentHashMap")
37 | public final Map information() {
38 | final Map parsed = new ParseableInformation(
39 | this.content
40 | ).information();
41 | final Map converted = new HashMap<>(0);
42 | for (final Map.Entry entry : parsed.entrySet()) {
43 | final String level = entry.getKey().toUpperCase(Locale.ENGLISH);
44 | if (Level.toLevel(level, null) == null) {
45 | throw new IllegalStateException(
46 | String.format(Locale.ENGLISH, "Unknown level '%s'", level)
47 | );
48 | }
49 | converted.put(level, entry.getValue());
50 | }
51 | return converted;
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/PreFormatter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Arrays;
8 | import java.util.List;
9 | import java.util.concurrent.CopyOnWriteArrayList;
10 | import java.util.regex.Matcher;
11 | import java.util.regex.Pattern;
12 |
13 | /**
14 | * Processor of formatting string and arguments, before sending it to
15 | * {@link String#format(String,Object[])}.
16 | *
17 | * @since 0.1
18 | */
19 | final class PreFormatter {
20 |
21 | /**
22 | * Pattern used for matching format string arguments.
23 | */
24 | private static final Pattern PATTERN = Pattern.compile(
25 | "%(\\d+\\$)?(\\[([A-Za-z\\-.0-9]+)])?[+\\-]?(?:\\d*(?:\\.\\d+)?)?[a-zA-Z%]"
26 | );
27 |
28 | /**
29 | * List of no argument specifier.
30 | */
31 | private static final List NO_ARG_SPECIFIERS =
32 | Arrays.asList("%n", "%%");
33 |
34 | /**
35 | * The formatting string.
36 | */
37 | private transient String format;
38 |
39 | /**
40 | * List of arguments.
41 | */
42 | private transient List arguments;
43 |
44 | /**
45 | * Public ctor.
46 | * @param fmt The formatting string
47 | * @param args The list of arguments
48 | */
49 | @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors")
50 | PreFormatter(final String fmt, final Object... args) {
51 | this.process(fmt, args);
52 | }
53 |
54 | /**
55 | * Get new formatting string.
56 | * @return The formatting text
57 | */
58 | public String getFormat() {
59 | return this.format;
60 | }
61 |
62 | /**
63 | * Get new list of arguments.
64 | * @return The list of arguments
65 | */
66 | public Object[] getArguments() {
67 | return this.arguments.toArray(new Object[this.arguments.size()]);
68 | }
69 |
70 | /**
71 | * Process the data provided.
72 | * @param fmt The formatting string
73 | * @param args The list of arguments
74 | * @checkstyle ExecutableStatementCountCheck (100 lines)
75 | */
76 | @SuppressWarnings("PMD.ConfusingTernary")
77 | private void process(final CharSequence fmt, final Object... args) {
78 | this.arguments = new CopyOnWriteArrayList<>();
79 | final StringBuffer buf = new StringBuffer(fmt.length());
80 | final Matcher matcher = PreFormatter.PATTERN.matcher(fmt);
81 | int pos = 0;
82 | while (matcher.find()) {
83 | final String group = matcher.group();
84 | if (PreFormatter.NO_ARG_SPECIFIERS.contains(group)) {
85 | matcher.appendReplacement(
86 | buf, Matcher.quoteReplacement(group)
87 | );
88 | } else {
89 | final String decor = matcher.group(3);
90 | if (matcher.group(1) != null) {
91 | matcher.appendReplacement(
92 | buf, Matcher.quoteReplacement(group)
93 | );
94 | --pos;
95 | } else if (decor == null) {
96 | matcher.appendReplacement(
97 | buf, Matcher.quoteReplacement(group)
98 | );
99 | this.arguments.add(args[pos]);
100 | } else {
101 | matcher.appendReplacement(
102 | buf,
103 | Matcher.quoteReplacement(
104 | group.replace(matcher.group(2), "")
105 | )
106 | );
107 | try {
108 | this.arguments.add(
109 | DecorsManager.decor(decor, args[pos])
110 | );
111 | } catch (final DecorException ex) {
112 | this.arguments.add(
113 | String.format("[%s]", ex.getMessage())
114 | );
115 | }
116 | }
117 | ++pos;
118 | }
119 | }
120 | if (pos < args.length) {
121 | throw new IllegalArgumentException(
122 | String.format(
123 | "There are %d parameter(s) but only %d format argument(s) were provided.",
124 | args.length,
125 | pos
126 | )
127 | );
128 | }
129 | matcher.appendTail(buf);
130 | this.format = buf.toString();
131 | }
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/SecretDecor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Formattable;
8 | import java.util.FormattableFlags;
9 | import java.util.Formatter;
10 |
11 | /**
12 | * Decorator of a secret text.
13 | * @since 0.1
14 | */
15 | final class SecretDecor implements Formattable {
16 |
17 | /**
18 | * The secret to work with.
19 | */
20 | private final transient String secret;
21 |
22 | /**
23 | * Public ctor.
24 | * @param scrt The secret
25 | */
26 | @SuppressWarnings(
27 | {
28 | "PMD.NullAssignment",
29 | "PMD.ConstructorOnlyInitializesOrCallOtherConstructors"
30 | }
31 | )
32 | SecretDecor(final Object scrt) {
33 | if (scrt == null) {
34 | this.secret = null;
35 | } else {
36 | this.secret = scrt.toString();
37 | }
38 | }
39 |
40 | // @checkstyle ParameterNumber (4 lines)
41 | @Override
42 | public void formatTo(final Formatter formatter, final int flags,
43 | final int width, final int precision) {
44 | if (this.secret == null) {
45 | formatter.format("NULL");
46 | } else {
47 | final StringBuilder fmt = new StringBuilder(10);
48 | fmt.append('%');
49 | if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0) {
50 | fmt.append('-');
51 | }
52 | if (width != 0) {
53 | fmt.append(width);
54 | }
55 | if ((flags & FormattableFlags.UPPERCASE) == 0) {
56 | fmt.append('s');
57 | } else {
58 | fmt.append('S');
59 | }
60 | formatter.format(
61 | fmt.toString(),
62 | SecretDecor.scramble(this.secret)
63 | );
64 | }
65 | }
66 |
67 | /**
68 | * Scramble it and make unreadable.
69 | * @param text The text to scramble
70 | * @return The result
71 | */
72 | private static String scramble(final String text) {
73 | final StringBuilder out = new StringBuilder(10);
74 | if (text.isEmpty()) {
75 | out.append('?');
76 | } else {
77 | out.append(text.charAt(0));
78 | }
79 | out.append("***");
80 | if (text.isEmpty()) {
81 | out.append('?');
82 | } else {
83 | out.append(text.charAt(text.length() - 1));
84 | }
85 | return out.toString();
86 | }
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/SizeDecor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Formattable;
8 | import java.util.FormattableFlags;
9 | import java.util.Formatter;
10 | import java.util.concurrent.ConcurrentHashMap;
11 | import java.util.concurrent.ConcurrentMap;
12 |
13 | /**
14 | * Size decorator.
15 | * @since 0.1
16 | */
17 | final class SizeDecor implements Formattable {
18 |
19 | /**
20 | * Highest power supported by this SizeDecor.
21 | */
22 | private static final int MAX_POWER = 6;
23 |
24 | /**
25 | * Map of prefixes for powers of 1024.
26 | */
27 | private static final ConcurrentMap SUFFIXES =
28 | new ConcurrentHashMap<>(0);
29 |
30 | /**
31 | * The size to work with.
32 | */
33 | private final transient Long size;
34 |
35 | static {
36 | SizeDecor.SUFFIXES.put(0, "b");
37 | SizeDecor.SUFFIXES.put(1, "Kb");
38 | SizeDecor.SUFFIXES.put(2, "Mb");
39 | SizeDecor.SUFFIXES.put(3, "Gb");
40 | SizeDecor.SUFFIXES.put(4, "Tb");
41 | SizeDecor.SUFFIXES.put(5, "Pb");
42 | SizeDecor.SUFFIXES.put(6, "Eb");
43 | }
44 |
45 | /**
46 | * Public ctor.
47 | * @param sze The size
48 | */
49 | SizeDecor(final Long sze) {
50 | this.size = sze;
51 | }
52 |
53 | // @checkstyle ParameterNumber (4 lines)
54 | @Override
55 | public void formatTo(final Formatter formatter, final int flags,
56 | final int width, final int precision) {
57 | if (this.size == null) {
58 | formatter.format("NULL");
59 | } else {
60 | final StringBuilder format = new StringBuilder().append('%');
61 | if ((flags & FormattableFlags.LEFT_JUSTIFY) == FormattableFlags
62 | .LEFT_JUSTIFY) {
63 | format.append('-');
64 | }
65 | if (width > 0) {
66 | format.append(width);
67 | }
68 | if ((flags & FormattableFlags.UPPERCASE) == FormattableFlags
69 | .UPPERCASE) {
70 | format.append('S');
71 | } else {
72 | format.append('s');
73 | }
74 | formatter.format(
75 | format.toString(), this.formatSizeWithSuffix(precision)
76 | );
77 | }
78 | }
79 |
80 | /**
81 | * Format the size, with suffix.
82 | * @param precision The precision to use
83 | * @return The formatted size
84 | */
85 | private String formatSizeWithSuffix(final int precision) {
86 | int power = 0;
87 | double number = this.size;
88 | while (number / 1024.0 >= 1.0 && power < SizeDecor.MAX_POWER) {
89 | number /= 1024.0;
90 | power += 1;
91 | }
92 | final String suffix = SizeDecor.SUFFIXES.get(power);
93 | final String format;
94 | if (precision >= 0) {
95 | format = String.format("%%.%df%%s", precision);
96 | } else {
97 | format = "%.0f%s";
98 | }
99 | return String.format(format, number, suffix);
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/Supplier.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | /**
8 | * Functional interface used as assignment target for Java8 lambda expressions
9 | * or method references. Can be used for method referencing when the method
10 | * signature respects the following: returns something and takes no arguments.
11 | *
12 | * @param The type of results supplied by this supplier
13 | * @since 0.18
14 | */
15 | public interface Supplier {
16 |
17 | /**
18 | * Gets a result.
19 | * @return A result
20 | */
21 | T get();
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/SupplierLogger.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | /**
8 | * Logging methods which take {@link Supplier} arguments.
9 | * Used with Java 8 method referencing.
10 | * @since 0.18
11 | * @checkstyle HideUtilityClassConstructorCheck (500 lines)
12 | */
13 | @SuppressWarnings({ "PMD.ProhibitPublicStaticMethods", "PMD.UseUtilityClass" })
14 | final class SupplierLogger {
15 |
16 | /**
17 | * Log one message, with {@code TRACE} priority level.
18 | * @param source The source of the logging operation
19 | * @param msg The text message to be logged, with meta-tags
20 | * @param args List of {@link Supplier} arguments. Objects are going
21 | * to be extracted from them and used for log message interpolation
22 | */
23 | public static void trace(
24 | final Object source, final String msg, final Supplier>... args) {
25 | if (Logger.isTraceEnabled(source)) {
26 | Logger.traceForced(source, msg, SupplierLogger.supplied(args));
27 | }
28 | }
29 |
30 | /**
31 | * Log one message, with {@code DEBUG} priority level.
32 | * @param source The source of the logging operation
33 | * @param msg The text message to be logged, with meta-tags
34 | * @param args List of {@link Supplier} arguments. Objects are going
35 | * to be extracted from them and used for log message interpolation
36 | */
37 | public static void debug(
38 | final Object source, final String msg, final Supplier>... args) {
39 | if (Logger.isDebugEnabled(source)) {
40 | Logger.debugForced(source, msg, SupplierLogger.supplied(args));
41 | }
42 | }
43 |
44 | /**
45 | * Log one message, with {@code INFO} priority level.
46 | * @param source The source of the logging operation
47 | * @param msg The text message to be logged, with meta-tags
48 | * @param args List of {@link Supplier} arguments. Objects are going
49 | * to be extracted from them and used for log message interpolation
50 | */
51 | public static void info(
52 | final Object source, final String msg, final Supplier>... args) {
53 | if (Logger.isInfoEnabled(source)) {
54 | Logger.infoForced(source, msg, SupplierLogger.supplied(args));
55 | }
56 | }
57 |
58 | /**
59 | * Log one message, with {@code WARN} priority level.
60 | * @param source The source of the logging operation
61 | * @param msg The text message to be logged, with meta-tags
62 | * @param args List of {@link Supplier} arguments. Objects are going
63 | * to be extracted from them and used for log message interpolation
64 | */
65 | public static void warn(
66 | final Object source, final String msg, final Supplier>... args) {
67 | if (Logger.isWarnEnabled(source)) {
68 | Logger.warnForced(source, msg, SupplierLogger.supplied(args));
69 | }
70 | }
71 |
72 | /**
73 | * Return the results of the given suppliers.
74 | * @param args Suppliers
75 | * @return Object array
76 | */
77 | private static Object[] supplied(final Supplier>... args) {
78 | final Object[] supplied = new Object[args.length];
79 | for (int idx = 0; idx < supplied.length; ++idx) {
80 | supplied[idx] = args[idx].get();
81 | }
82 | return supplied;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/TextDecor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Formattable;
8 | import java.util.Formatter;
9 |
10 | /**
11 | * Decorator of a text.
12 | *
13 | * For example:
14 | *
15 | *
public void func(Object input) {
16 | * Logger.debug("Long input '%[text]s' provided", input);
17 | * }
18 | *
19 | * @since 0.1.5
20 | */
21 | final class TextDecor implements Formattable {
22 |
23 | /**
24 | * Maximum length to show.
25 | */
26 | public static final int MAX = 100;
27 |
28 | /**
29 | * The object.
30 | */
31 | private final transient Object object;
32 |
33 | /**
34 | * Public ctor.
35 | * @param obj The object
36 | */
37 | TextDecor(final Object obj) {
38 | this.object = obj;
39 | }
40 |
41 | // @checkstyle ParameterNumber (4 lines)
42 | @Override
43 | public void formatTo(final Formatter formatter, final int flags,
44 | final int width, final int precision) {
45 | if (this.object == null) {
46 | formatter.format("NULL");
47 | } else {
48 | formatter.format("%s", TextDecor.pretty(this.object.toString()));
49 | }
50 | }
51 |
52 | /**
53 | * Make it look pretty.
54 | * @param text The text to prettify
55 | * @return The result
56 | */
57 | @SuppressWarnings("PMD.ConsecutiveAppendsShouldReuse")
58 | private static String pretty(final String text) {
59 | final String result;
60 | if (text.length() < TextDecor.MAX) {
61 | result = text;
62 | } else {
63 | final int skip = text.length() - TextDecor.MAX;
64 | final StringBuilder output = new StringBuilder(text.length());
65 | output.append(text.substring(0, (text.length() - skip) / 2));
66 | output.append("..").append(skip).append("..");
67 | output.append(
68 | text.substring(text.length() - TextDecor.MAX + output.length())
69 | );
70 | result = output.toString();
71 | }
72 | return result.replace("\n", "\\n");
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/TypeDecor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Formattable;
8 | import java.util.Formatter;
9 |
10 | /**
11 | * Decorator of a type.
12 | *
13 | * For example:
14 | *
15 | *
16 | * public void func(Object input) {
17 | * Logger.debug("Input of type %[type]s provided", input);
18 | * }
19 | *
20 | *
21 | * @since 0.1
22 | */
23 | final class TypeDecor implements Formattable {
24 |
25 | /**
26 | * The object.
27 | */
28 | private final transient Object object;
29 |
30 | /**
31 | * Public ctor.
32 | * @param obj The object
33 | */
34 | TypeDecor(final Object obj) {
35 | this.object = obj;
36 | }
37 |
38 | // @checkstyle ParameterNumber (4 lines)
39 | @Override
40 | public void formatTo(final Formatter formatter, final int flags,
41 | final int width, final int precision) {
42 | if (this.object == null) {
43 | formatter.format("NULL");
44 | } else {
45 | formatter.format("%s", this.object.getClass().getName());
46 | }
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/VerboseCallable.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.concurrent.Callable;
8 | import java.util.concurrent.TimeUnit;
9 |
10 | /**
11 | * Wrapper of {@link Callable}, that logs all uncaught runtime exceptions.
12 | *
13 | * You can use it with scheduled executor, for example:
14 | *
15 | *
Executors.newFixedThreadPool(1).submit(
16 | * new VerboseCallable(callable, true)
17 | * );
18 | *
19 | * Now, every runtime exception that is not caught inside your
20 | * {@link Callable} will be reported to log (using {@link Logger}).
21 | * Two-arguments constructor can be used when you need to instruct the class
22 | * about what to do with the exception: either swallow it or escalate.
23 | * Sometimes it's very important to swallow exceptions. Otherwise an entire
24 | * thread may get stuck (like in the example above).
25 | *
26 | *
This class is thread-safe.
27 | *
28 | * @since 0.16
29 | * @param Type of result
30 | * @see VerboseThreads
31 | * @link Java theory and practice: Dealing with InterruptedException
32 | */
33 | @SuppressWarnings("PMD.DoNotUseThreads")
34 | public final class VerboseCallable implements Callable {
35 |
36 | /**
37 | * Original runnable.
38 | */
39 | private final transient Callable origin;
40 |
41 | /**
42 | * Rethrow exceptions (TRUE) or swallow them?
43 | */
44 | private final transient boolean rethrow;
45 |
46 | /**
47 | * Shall we report a full stacktrace?
48 | */
49 | private final transient boolean verbose;
50 |
51 | /**
52 | * Default constructor, doesn't swallow exceptions.
53 | * @param callable Callable to wrap
54 | */
55 | public VerboseCallable(final Callable callable) {
56 | this(callable, false);
57 | }
58 |
59 | /**
60 | * Default constructor, doesn't swallow exceptions.
61 | * @param callable Callable to wrap
62 | * @param swallow Shall we swallow exceptions
63 | * ({@code TRUE}) or re-throw
64 | * ({@code FALSE})? Exception swallowing means that {@link #call()}
65 | * will never throw any exceptions (in any case all exceptions are logged
66 | * using {@link Logger}.
67 | */
68 | public VerboseCallable(final Callable callable, final boolean swallow) {
69 | this(callable, swallow, true);
70 | }
71 |
72 | /**
73 | * Default constructor.
74 | * @param runnable Runnable to wrap
75 | * @param swallow Shall we swallow exceptions
76 | * ({@code TRUE}) or re-throw
77 | * ({@code FALSE})? Exception swallowing means that {@link #call()}
78 | * will never throw any exceptions (in any case all exceptions are logged
79 | * using {@link Logger}.
80 | * @param vrbs Shall we report the entire
81 | * stacktrace of the exception
82 | * ({@code TRUE}) or just its message in one line ({@code FALSE})
83 | */
84 | @SuppressWarnings("PMD.AvoidCatchingGenericException")
85 | public VerboseCallable(final Runnable runnable,
86 | final boolean swallow, final boolean vrbs) {
87 | this(
88 | new Callable() {
89 | @Override
90 | public T call() {
91 | runnable.run();
92 | return null;
93 | }
94 |
95 | @Override
96 | public String toString() {
97 | return runnable.toString();
98 | }
99 | },
100 | swallow,
101 | vrbs
102 | );
103 | }
104 |
105 | /**
106 | * Default constructor, with configurable behavior for exceptions.
107 | * @param runnable Runnable to wrap
108 | * @param swallow Shall we swallow exceptions
109 | * ({@code TRUE}) or re-throw
110 | * ({@code FALSE})? Exception swallowing means that {@link #call()}
111 | * will never throw any exceptions (in any case all exceptions are logged
112 | * using {@link Logger}.
113 | */
114 | public VerboseCallable(final Runnable runnable, final boolean swallow) {
115 | this(runnable, swallow, true);
116 | }
117 |
118 | /**
119 | * Default constructor, with fully configurable behavior.
120 | * @param callable Runnable to wrap
121 | * @param swallow Shall we swallow exceptions
122 | * ({@code TRUE}) or re-throw
123 | * ({@code FALSE})? Exception swallowing means that {@link #call()}
124 | * will never throw any exceptions (in any case all exceptions are logged
125 | * using {@link Logger}.
126 | * @param vrbs Shall we report the entire
127 | * stacktrace of the exception
128 | * ({@code TRUE}) or just its message in one line ({@code FALSE})
129 | */
130 | @SuppressWarnings("PMD.BooleanInversion")
131 | public VerboseCallable(final Callable callable,
132 | final boolean swallow, final boolean vrbs) {
133 | this.origin = callable;
134 | this.rethrow = !swallow;
135 | this.verbose = vrbs;
136 | }
137 |
138 | @Override
139 | public String toString() {
140 | return this.origin.toString();
141 | }
142 |
143 | @Override
144 | @SuppressWarnings("PMD.AvoidCatchingGenericException")
145 | public T call() throws Exception {
146 | T result = null;
147 | try {
148 | result = this.origin.call();
149 | // @checkstyle IllegalCatch (1 line)
150 | } catch (final RuntimeException ex) {
151 | if (this.rethrow) {
152 | Logger.warn(
153 | this, "Escalated runtime exception: %s", this.tail(ex)
154 | );
155 | throw ex;
156 | }
157 | Logger.warn(this, "Swallowed runtime exception: %s", this.tail(ex));
158 | // @checkstyle IllegalCatch (1 line)
159 | } catch (final Exception ex) {
160 | if (this.rethrow) {
161 | Logger.warn(this, "Escalated exception: %s", this.tail(ex));
162 | throw ex;
163 | }
164 | Logger.warn(this, "Swallowed exception: %s", this.tail(ex));
165 | // @checkstyle IllegalCatch (1 line)
166 | } catch (final Error error) {
167 | if (this.rethrow) {
168 | Logger.error(this, "Escalated error: %s", this.tail(error));
169 | throw error;
170 | }
171 | Logger.error(this, "Swallowed error: %s", this.tail(error));
172 | }
173 | try {
174 | TimeUnit.MICROSECONDS.sleep(1L);
175 | } catch (final InterruptedException ex) {
176 | Thread.currentThread().interrupt();
177 | throw new IllegalStateException(ex);
178 | }
179 | return result;
180 | }
181 |
182 | /**
183 | * Make a tail of the error/warning message, using the exception thrown.
184 | * @param throwable The exception/error caught
185 | * @return The message to show in logs
186 | */
187 | private String tail(final Throwable throwable) {
188 | final String tail;
189 | if (this.verbose) {
190 | tail = Logger.format("%[exception]s", throwable);
191 | } else {
192 | tail = Logger.format(
193 | "%[type]s('%s')",
194 | throwable,
195 | throwable.getMessage()
196 | );
197 | }
198 | return tail;
199 | }
200 |
201 | }
202 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/VerboseRunnable.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.concurrent.Callable;
8 |
9 | /**
10 | * Wrapper of {@link Runnable}, that logs all uncaught runtime exceptions.
11 | *
12 | * You can use it with scheduled executor, for example:
13 | *
14 | *
Executors.newScheduledThreadPool(2).scheduleAtFixedRate(
15 | * new VerboseRunnable(runnable, true), 1L, 1L, TimeUnit.SECONDS
16 | * );
17 | *
18 | * Now, every runtime exception that is not caught inside your
19 | * {@link Runnable} will be reported to log (using {@link Logger}).
20 | * Two-arguments constructor can be used when you need to instruct the class
21 | * about what to do with the exception: either swallow it or escalate.
22 | * Sometimes it's very important to swallow exceptions. Otherwise an entire
23 | * thread may get stuck (like in the example above).
24 | *
25 | *
This class is thread-safe.
26 | *
27 | * @since 0.1.3
28 | * @see VerboseThreads
29 | * @link Java theory and practice: Dealing with InterruptedException
30 | */
31 | @SuppressWarnings("PMD.DoNotUseThreads")
32 | public final class VerboseRunnable implements Runnable {
33 |
34 | /**
35 | * Original runnable.
36 | */
37 | private final transient Runnable origin;
38 |
39 | /**
40 | * Rethrow exceptions (TRUE) or swallow them?
41 | */
42 | private final transient boolean rethrow;
43 |
44 | /**
45 | * Shall we report a full stacktrace?
46 | */
47 | private final transient boolean verbose;
48 |
49 | /**
50 | * Default constructor, doesn't swallow exceptions.
51 | * @param runnable Runnable to wrap
52 | */
53 | public VerboseRunnable(final Runnable runnable) {
54 | this(runnable, false);
55 | }
56 |
57 | /**
58 | * Default constructor, doesn't swallow exceptions.
59 | * @param callable Callable to wrap
60 | * @since 0.7.17
61 | */
62 | public VerboseRunnable(final Callable> callable) {
63 | this(callable, false);
64 | }
65 |
66 | /**
67 | * Default constructor, doesn't swallow exceptions.
68 | * @param callable Callable to wrap
69 | * @param swallow Shall we swallow exceptions
70 | * ({@code TRUE}) or re-throw
71 | * ({@code FALSE})? Exception swallowing means that {@link #run()}
72 | * will never throw any exceptions (in any case all exceptions are logged
73 | * using {@link Logger}.
74 | * @since 0.1.10
75 | */
76 | public VerboseRunnable(final Callable> callable, final boolean swallow) {
77 | this(callable, swallow, true);
78 | }
79 |
80 | /**
81 | * Default constructor.
82 | * @param callable Callable to wrap
83 | * @param swallow Shall we swallow exceptions
84 | * ({@code TRUE}) or re-throw
85 | * ({@code FALSE})? Exception swallowing means that {@link #run()}
86 | * will never throw any exceptions (in any case all exceptions are logged
87 | * using {@link Logger}.
88 | * @param vrbs Shall we report the entire
89 | * stacktrace of the exception
90 | * ({@code TRUE}) or just its message in one line ({@code FALSE})
91 | * @since 0.7.17
92 | */
93 | @SuppressWarnings("PMD.AvoidCatchingGenericException")
94 | public VerboseRunnable(final Callable> callable,
95 | final boolean swallow, final boolean vrbs) {
96 | this(
97 | new Runnable() {
98 | @Override
99 | public void run() {
100 | try {
101 | callable.call();
102 | } catch (final InterruptedException ex) {
103 | Thread.currentThread().interrupt();
104 | throw new IllegalStateException(ex);
105 | // @checkstyle IllegalCatch (1 line)
106 | } catch (final Exception ex) {
107 | throw new IllegalStateException(ex);
108 | }
109 | }
110 |
111 | @Override
112 | public String toString() {
113 | return callable.toString();
114 | }
115 | },
116 | swallow,
117 | vrbs
118 | );
119 | }
120 |
121 | /**
122 | * Default constructor, with configurable behavior for exceptions.
123 | * @param runnable Runnable to wrap
124 | * @param swallow Shall we swallow exceptions
125 | * ({@code TRUE}) or re-throw
126 | * ({@code FALSE})? Exception swallowing means that {@link #run()}
127 | * will never throw any exceptions (in any case all exceptions are logged
128 | * using {@link Logger}.
129 | * @since 0.1.4
130 | */
131 | public VerboseRunnable(final Runnable runnable, final boolean swallow) {
132 | this(runnable, swallow, true);
133 | }
134 |
135 | /**
136 | * Default constructor, with fully configurable behavior.
137 | * @param runnable Runnable to wrap
138 | * @param swallow Shall we swallow exceptions
139 | * ({@code TRUE}) or re-throw
140 | * ({@code FALSE})? Exception swallowing means that {@link #run()}
141 | * will never throw any exceptions (in any case all exceptions are logged
142 | * using {@link Logger}.
143 | * @param vrbs Shall we report the entire
144 | * stacktrace of the exception
145 | * ({@code TRUE}) or just its message in one line ({@code FALSE})
146 | * @since 0.7.17
147 | */
148 | @SuppressWarnings("PMD.BooleanInversion")
149 | public VerboseRunnable(final Runnable runnable,
150 | final boolean swallow, final boolean vrbs) {
151 | this.origin = runnable;
152 | this.rethrow = !swallow;
153 | this.verbose = vrbs;
154 | }
155 |
156 | @Override
157 | public String toString() {
158 | return this.origin.toString();
159 | }
160 |
161 | @Override
162 | @SuppressWarnings("PMD.AvoidCatchingGenericException")
163 | public void run() {
164 | try {
165 | this.origin.run();
166 | // @checkstyle IllegalCatch (1 line)
167 | } catch (final RuntimeException ex) {
168 | if (this.rethrow) {
169 | Logger.warn(this, "Escalated exception: %s", this.tail(ex));
170 | throw ex;
171 | }
172 | Logger.warn(this, "Swallowed exception: %s", this.tail(ex));
173 | // @checkstyle IllegalCatch (1 line)
174 | } catch (final Error error) {
175 | if (this.rethrow) {
176 | Logger.error(this, "Escalated error: %s", this.tail(error));
177 | throw error;
178 | }
179 | Logger.error(this, "Swallowed error: %s", this.tail(error));
180 | }
181 | if (Thread.currentThread().isInterrupted()) {
182 | Thread.currentThread().interrupt();
183 | throw new IllegalStateException(
184 | "The thread has been interrupted"
185 | );
186 | }
187 | }
188 |
189 | /**
190 | * Make a tail of the error/warning message, using the exception thrown.
191 | * @param throwable The exception/error caught
192 | * @return The message to show in logs
193 | */
194 | private String tail(final Throwable throwable) {
195 | final String tail;
196 | if (this.verbose) {
197 | tail = Logger.format("%[exception]s", throwable);
198 | } else {
199 | tail = Logger.format(
200 | "%[type]s('%s')",
201 | throwable,
202 | throwable.getMessage()
203 | );
204 | }
205 | return tail;
206 | }
207 |
208 | }
209 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/VerboseThreads.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.concurrent.ThreadFactory;
8 | import java.util.concurrent.atomic.AtomicInteger;
9 |
10 | /**
11 | * Convenient {@link ThreadFactory}, that logs all uncaught exceptions.
12 | *
13 | *
The factory should be used together
14 | * with executor services from {@code java.util.concurrent} package. Without
15 | * these "verbose" threads your runnable tasks will not report anything to
16 | * console once they die because of a runtime exception, for example:
17 | *
18 | *
Executors.newScheduledThreadPool(2).scheduleAtFixedRate(
19 | * new Runnable() {
20 | * @Override
21 | * public void run() {
22 | * // some sensitive operation that may throw
23 | * // a runtime exception
24 | * },
25 | * 1L, 1L, TimeUnit.SECONDS
26 | * }
27 | * );
28 | *
29 | * The exception in this example will never be caught by nobody. It will
30 | * just terminate current execution of the {@link Runnable} task. Moreover,
31 | * it won't reach any {@link Thread.UncaughtExceptionHandler},
32 | * because this
33 | * is how {@link java.util.concurrent.ScheduledExecutorService}
34 | * is behaving. This is how we solve
35 | * the problem with {@link VerboseThreads}:
36 | *
37 | *
ThreadFactory factory = new VerboseThreads();
38 | * Executors.newScheduledThreadPool(2, factory).scheduleAtFixedRate(
39 | * new Runnable() {
40 | * @Override
41 | * public void run() {
42 | * // the same sensitive operation that may throw
43 | * // a runtime exception
44 | * },
45 | * 1L, 1L, TimeUnit.SECONDS
46 | * }
47 | * );
48 | *
49 | * Now, every runtime exception that is not caught inside your
50 | * {@link Runnable} will be reported to log (using {@link Logger}).
51 | *
52 | *
This class is thread-safe.
53 | *
54 | * @since 0.1.2
55 | * @see VerboseRunnable
56 | */
57 | @SuppressWarnings("PMD.DoNotUseThreads")
58 | public final class VerboseThreads implements ThreadFactory {
59 |
60 | /**
61 | * Thread group.
62 | */
63 | private final transient ThreadGroup group;
64 |
65 | /**
66 | * Prefix to use.
67 | */
68 | private final transient String prefix;
69 |
70 | /**
71 | * Number of the next thread to create.
72 | */
73 | private final transient AtomicInteger number;
74 |
75 | /**
76 | * Create threads as daemons?
77 | */
78 | private final transient boolean daemon;
79 |
80 | /**
81 | * Default thread priority.
82 | */
83 | private final transient int priority;
84 |
85 | /**
86 | * Default constructor ({@code "verbose"} as a prefix, threads are daemons,
87 | * default thread priority is {@code 1}).
88 | */
89 | public VerboseThreads() {
90 | this("verbose", true, 1);
91 | }
92 |
93 | /**
94 | * Detailed constructor, with a prefix of thread names (threads are daemons,
95 | * default thread priority is {@code 1}).
96 | * @param pfx Prefix for thread names
97 | */
98 | public VerboseThreads(final String pfx) {
99 | this(pfx, true, 1);
100 | }
101 |
102 | /**
103 | * Detailed constructor, with a prefix of thread names (threads are daemons,
104 | * default thread priority is {@code 1}).
105 | * @param type Prefix will be build from this type name
106 | */
107 | public VerboseThreads(final Object type) {
108 | this(type.getClass().getSimpleName(), true, 1);
109 | }
110 |
111 | /**
112 | * Detailed constructor, with a prefix of thread names (threads are daemons,
113 | * default thread priority is {@code 1}).
114 | * @param type Prefix will be build from this type name
115 | */
116 | public VerboseThreads(final Class> type) {
117 | this(type.getSimpleName(), true, 1);
118 | }
119 |
120 | /**
121 | * Detailed constructor.
122 | * @param pfx Prefix for thread names
123 | * @param dmn Threads should be daemons?
124 | * @param prt Default priority for all threads
125 | */
126 | public VerboseThreads(final String pfx, final boolean dmn,
127 | final int prt) {
128 | this.prefix = pfx;
129 | this.daemon = dmn;
130 | this.priority = prt;
131 | this.group = new VerboseThreads.Group(pfx);
132 | this.number = new AtomicInteger(1);
133 | }
134 |
135 | @Override
136 | public Thread newThread(final Runnable runnable) {
137 | final Thread thread = new Thread(
138 | this.group,
139 | new VerboseThreads.Wrap(runnable)
140 | );
141 | thread.setName(
142 | String.format(
143 | "%s-%d",
144 | this.prefix,
145 | this.number.getAndIncrement()
146 | )
147 | );
148 | thread.setDaemon(this.daemon);
149 | thread.setPriority(this.priority);
150 | return thread;
151 | }
152 |
153 | /**
154 | * Group to use.
155 | *
156 | * @since 0.1
157 | */
158 | private static final class Group extends ThreadGroup {
159 | /**
160 | * Ctor.
161 | * @param name Name of it
162 | */
163 | Group(final String name) {
164 | super(name);
165 | }
166 |
167 | @Override
168 | public void uncaughtException(final Thread thread,
169 | final Throwable throwable) {
170 | Logger.warn(this, "%[exception]s", throwable);
171 | }
172 | }
173 |
174 | /**
175 | * Runnable decorator.
176 | *
177 | * @since 0.1
178 | */
179 | private static final class Wrap implements Runnable {
180 | /**
181 | * Origin runnable.
182 | */
183 | private final transient Runnable origin;
184 |
185 | /**
186 | * Ctor.
187 | * @param runnable Origin runnable
188 | */
189 | Wrap(final Runnable runnable) {
190 | this.origin = runnable;
191 | }
192 |
193 | @Override
194 | @SuppressWarnings("PMD.AvoidCatchingGenericException")
195 | public void run() {
196 | try {
197 | this.origin.run();
198 | // @checkstyle IllegalCatch (1 line)
199 | } catch (final RuntimeException ex) {
200 | Logger.warn(
201 | this,
202 | "%s: %[exception]s",
203 | Thread.currentThread().getName(),
204 | ex
205 | );
206 | throw ex;
207 | // @checkstyle IllegalCatch (1 line)
208 | } catch (final Error error) {
209 | Logger.error(
210 | this,
211 | "%s (error): %[exception]s",
212 | Thread.currentThread().getName(),
213 | error
214 | );
215 | throw error;
216 | }
217 | }
218 | }
219 |
220 | }
221 |
--------------------------------------------------------------------------------
/src/main/java/com/jcabi/log/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 |
6 | /**
7 | * Convenient logging utils.
8 | *
9 | *
The only dependency you need is (check our latest version available
10 | * at www.jcabi.com ):
11 | *
12 | *
<depedency>
13 | * <groupId>com.jcabi</groupId>
14 | * <artifactId>jcabi-log</artifactId>
15 | * </dependency>
16 | *
17 | * @see project website
18 | */
19 | package com.jcabi.log;
20 |
--------------------------------------------------------------------------------
/src/site/apt/decors.apt.vm:
--------------------------------------------------------------------------------
1 | ------
2 | Logging decors
3 | ------
4 | Yegor Bugayenko
5 | ------
6 | 2012-04-29
7 | ------
8 |
9 | ~~
10 | ~~ Copyright (c) 2012-2025 Yegor Bugayenko
11 | ~~ All rights reserved.
12 | ~~
13 | ~~ Redistribution and use in source and binary forms, with or without
14 | ~~ modification, are permitted provided that the following conditions
15 | ~~ are met: 1) Redistributions of source code must retain the above
16 | ~~ copyright notice, this list of conditions and the following
17 | ~~ disclaimer. 2) Redistributions in binary form must reproduce the above
18 | ~~ copyright notice, this list of conditions and the following
19 | ~~ disclaimer in the documentation and/or other materials provided
20 | ~~ with the distribution. 3) Neither the name of the jcabi.com nor
21 | ~~ the names of its contributors may be used to endorse or promote
22 | ~~ products derived from this software without specific prior written
23 | ~~ permission.
24 | ~~
25 | ~~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 | ~~ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
27 | ~~ NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
28 | ~~ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
29 | ~~ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
30 | ~~ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 | ~~ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 | ~~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 | ~~ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 | ~~ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 | ~~ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36 | ~~ OF THE POSSIBILITY OF SUCH DAMAGE.
37 | ~~
38 |
39 | Decors
40 |
41 | Log an event like this:
42 |
43 | +--
44 | import com.jcabi.log.Logger;
45 | class Foo {
46 | void bar(int value) {
47 | final long start = System.nanoTime();
48 | // some operations
49 | Logger.debug(
50 | this,
51 | "bar(%d): done in %[nano]d",
52 | value,
53 | System.nanoTime() - start
54 | );
55 | }
56 | }
57 | +--
58 |
59 | You will see something like this in log:
60 |
61 | ---
62 | [DEBUG] Foo: #bar(1234): done in 3.45ms
63 | ---
64 |
65 | Nano-seconds will be formatted as time through a built-in "decor".
66 |
67 | There are a few other pre-defined decors:
68 |
69 | * <<>> -- converts <<>> to text.
70 |
71 | * <<>> -- exception message and full stacktrace;
72 |
73 | * <<>> -- array or list of elements into text;
74 |
75 | * <<>> -- milliseconds into their text presentation;
76 |
77 | * <<>> -- nanoseconds into their text presentation;
78 |
79 | * <<>> -- makes text readable;
80 |
81 | * <<>> -- type name of the object.
82 |
83 | In order to use your own decor just implement
84 | {{{http://docs.oracle.com/javase/7/docs/api/java/util/Formattable.html}<<>>}}:
85 |
86 | +--
87 | import com.jcabi.log.Decor;
88 | import java.util.Formattable;
89 | import java.util.Formatter;
90 | public final class DataDecor implements Formattable {
91 | private final transient Data data;
92 | public DataDecor(final Data dat) {
93 | this.data = dat;
94 | }
95 | @Override
96 | public void formatTo(final Formatter formatter, final int flags,
97 | final int width, final int precision) {
98 | formatter.format("%s", this.data.getSomeValueOutOfIt());
99 | }
100 | }
101 | +--
102 |
103 | Then, provide its class name in log formatting string, for example:
104 |
105 | +--
106 | import com.jcabi.log.Logger;
107 | public class Main {
108 | public static void main(String[] args) {
109 | Logger.debug(
110 | this,
111 | "bar(%d): show some data: %[com.example.DataDecor]s",
112 | value,
113 | data
114 | );
115 | }
116 | }
117 | +--
118 |
--------------------------------------------------------------------------------
/src/site/apt/index.apt.vm:
--------------------------------------------------------------------------------
1 | ------
2 | Static Wrapper of SLF4J
3 | ------
4 | Yegor Bugayenko
5 | ------
6 | 2012-04-29
7 | ------
8 |
9 | ~~
10 | ~~ Copyright (c) 2012-2025 Yegor Bugayenko
11 | ~~ All rights reserved.
12 | ~~
13 | ~~ Redistribution and use in source and binary forms, with or without
14 | ~~ modification, are permitted provided that the following conditions
15 | ~~ are met: 1) Redistributions of source code must retain the above
16 | ~~ copyright notice, this list of conditions and the following
17 | ~~ disclaimer. 2) Redistributions in binary form must reproduce the above
18 | ~~ copyright notice, this list of conditions and the following
19 | ~~ disclaimer in the documentation and/or other materials provided
20 | ~~ with the distribution. 3) Neither the name of the jcabi.com nor
21 | ~~ the names of its contributors may be used to endorse or promote
22 | ~~ products derived from this software without specific prior written
23 | ~~ permission.
24 | ~~
25 | ~~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 | ~~ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
27 | ~~ NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
28 | ~~ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
29 | ~~ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
30 | ~~ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 | ~~ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 | ~~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 | ~~ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 | ~~ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 | ~~ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36 | ~~ OF THE POSSIBILITY OF SUCH DAMAGE.
37 | ~~
38 |
39 | Static Wrapper of SLF4J
40 |
41 | {{{./apidocs-${project.version}/com/jcabi/log/Logger.html}<<>>}}
42 | is a convenient static wrapper of {{{http://www.slf4j.org/}slf4j}}
43 | (don't forget to include one of
44 | {{{http://www.slf4j.org/manual.html#binding}SLF4J Bindings}}
45 | into the project):
46 |
47 | +--
48 | import com.jcabi.log.Logger;
49 | class Foo {
50 | void bar(int value) {
51 | Logger.debug(this, "method #bar(d) was called", value);
52 | }
53 | }
54 | +--
55 |
56 | Read also about log {{{./decors.html}decors}} and a convenient
57 | AOP annotation
58 | {{{http://aspects.jcabi.com/annotation-loggable.html}<<<@Loggable>>>}} from
59 | {{{http://aspects.jcabi.com/index.html}jcabi-aspects}}.
60 |
61 | There are a few other convenient classes in this package:
62 |
63 | * {{{./apidocs-${project.version}/com/jcabi/log/VerboseRunnable.html}<<>>}}:
64 | wrapper around <<>> that swallows all runtime
65 | exceptions and logs them to SLF4J.
66 |
67 | * {{{./apidocs-${project.version}/com/jcabi/log/VerboseProcess.html}<<>>}}:
68 | wrapper around <<>> that monitors process executions,
69 | collects its output into a <<>> and logs everything through SLF4J.
70 |
71 | * {{{./apidocs-${project.version}/com/jcabi/log/VerboseThreads.html}<<>>}}:
72 | an implementation of <<>> that
73 | instantiates threads that log all runtime exceptions through SLF4J.
74 |
75 | The only dependency you need is
76 | (you can also download
77 | {{{http://repo1.maven.org/maven2/com/jcabi/jcabi-log/${project.version}/jcabi-log-${project.version}.jar}<<>>}}
78 | and add it to the classpath):
79 |
80 | +--
81 |
82 | com.jcabi
83 | jcabi-log
84 | ${project.version}
85 |
86 | +--
87 |
88 | * Cutting Edge Version
89 |
90 | If you want to use current version of the product, you can do it with
91 | this configuration in your <<>>:
92 |
93 | +--
94 |
95 |
96 | oss.sonatype.org
97 | https://oss.sonatype.org/content/repositories/snapshots/
98 |
99 |
100 |
101 |
102 | com.jcabi
103 | jcabi-log
104 | 1.0-SNAPSHOT
105 |
106 |
107 | +--
108 |
--------------------------------------------------------------------------------
/src/site/apt/multicolor.apt.vm:
--------------------------------------------------------------------------------
1 | ------
2 | Multicolor Layout for LOG4J
3 | ------
4 | Yegor Bugayenko
5 | ------
6 | 2012-08-30
7 | ------
8 |
9 | ~~
10 | ~~ Copyright (c) 2012-2025 Yegor Bugayenko
11 | ~~ All rights reserved.
12 | ~~
13 | ~~ Redistribution and use in source and binary forms, with or without
14 | ~~ modification, are permitted provided that the following conditions
15 | ~~ are met: 1) Redistributions of source code must retain the above
16 | ~~ copyright notice, this list of conditions and the following
17 | ~~ disclaimer. 2) Redistributions in binary form must reproduce the above
18 | ~~ copyright notice, this list of conditions and the following
19 | ~~ disclaimer in the documentation and/or other materials provided
20 | ~~ with the distribution. 3) Neither the name of the jcabi.com nor
21 | ~~ the names of its contributors may be used to endorse or promote
22 | ~~ products derived from this software without specific prior written
23 | ~~ permission.
24 | ~~
25 | ~~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 | ~~ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
27 | ~~ NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
28 | ~~ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
29 | ~~ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
30 | ~~ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 | ~~ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 | ~~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 | ~~ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 | ~~ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 | ~~ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36 | ~~ OF THE POSSIBILITY OF SUCH DAMAGE.
37 | ~~
38 |
39 | Multicolor Layout for LOG4J
40 |
41 | Configure it in your <<>>:
42 |
43 | +--
44 | log4j.rootLogger=INFO, CONSOLE
45 | log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
46 | log4j.appender.CONSOLE.layout=com.jcabi.log.MulticolorLayout
47 | log4j.appender.CONSOLE.layout.ConversionPattern=[%color{%-5p}] %c: %m%n
48 | +--
49 |
50 | Or in XML config:
51 |
52 | +--
53 |
54 |
55 |
56 |
57 |
58 | +--
59 |
60 | In version 0.9 there is an ability to re-defined standard colors for
61 | logging levels. Besides that, it's possible to re-define numeric values
62 | of standard colors, for example:
63 |
64 | +--
65 | log4j.appender.CONSOLE.layout.Levels=INFO:2;10,WARN:2;32
66 | log4j.appender.CONSOLE.layout.Colors=white:10
67 | +--
68 |
69 | Or:
70 |
71 | +--
72 |
73 |
74 | +--
75 |
76 | Read JavaDoc of
77 | {{{./apidocs-${project.version}/com/jcabi/log/MulticolorLayout.html}<<>>}}.
78 |
79 | The only dependency you need is
80 | (you can also download
81 | {{{http://repo1.maven.org/maven2/com/jcabi/jcabi-log/${project.version}/jcabi-log-${project.version}.jar}<<>>}}
82 | and add it to the classpath):
83 |
84 | +--
85 |
86 | com.jcabi
87 | jcabi-log
88 | ${project.version}
89 |
90 | +--
91 |
--------------------------------------------------------------------------------
/src/site/apt/threads-VerboseProcess.apt.vm:
--------------------------------------------------------------------------------
1 | ------
2 | Process That Logs And Consumes Output
3 | ------
4 | Yegor Bugayenko
5 | ------
6 | 2012-12-16
7 | ------
8 |
9 | ~~
10 | ~~ Copyright (c) 2012-2025 Yegor Bugayenko
11 | ~~ All rights reserved.
12 | ~~
13 | ~~ Redistribution and use in source and binary forms, with or without
14 | ~~ modification, are permitted provided that the following conditions
15 | ~~ are met: 1) Redistributions of source code must retain the above
16 | ~~ copyright notice, this list of conditions and the following
17 | ~~ disclaimer. 2) Redistributions in binary form must reproduce the above
18 | ~~ copyright notice, this list of conditions and the following
19 | ~~ disclaimer in the documentation and/or other materials provided
20 | ~~ with the distribution. 3) Neither the name of the jcabi.com nor
21 | ~~ the names of its contributors may be used to endorse or promote
22 | ~~ products derived from this software without specific prior written
23 | ~~ permission.
24 | ~~
25 | ~~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 | ~~ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
27 | ~~ NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
28 | ~~ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
29 | ~~ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
30 | ~~ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 | ~~ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 | ~~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 | ~~ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 | ~~ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 | ~~ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36 | ~~ OF THE POSSIBILITY OF SUCH DAMAGE.
37 | ~~
38 |
39 | Process That Logs And Consumes Output
40 |
41 | {{{./apidocs-${project.version}/com/jcabi/log/VerboseProcess.html}<<>>}}
42 | logs output of a java
43 | {{{http://docs.oracle.com/javase/7/docs/api/java/lang/Process.html}<<>>}}
44 | and consumes its output at the same time, for example:
45 |
46 | +--
47 | public class Main {
48 | public static void main(String[] args) {
49 | String name = new VerboseProcess(
50 | new ProcessBuilder("who", "am", "i")
51 | ).stdout();
52 | System.out.println("I am: " + name);
53 | }
54 | }
55 | +--
56 |
57 | {{{./apidocs-${project.version}/com/jcabi/log/VerboseProcess.html}<<>>}}
58 | throws an exception if the process returns a non-zero exit code.
59 |
60 | The only dependency you need is
61 | (you can also download
62 | {{{http://repo1.maven.org/maven2/com/jcabi/jcabi-log/${project.version}/jcabi-log-${project.version}.jar}<<>>}}
63 | and add it to the classpath):
64 |
65 | +--
66 |
67 | com.jcabi
68 | jcabi-log
69 | ${project.version}
70 |
71 | +--
72 |
--------------------------------------------------------------------------------
/src/site/apt/threads-VerboseRunnable.apt.vm:
--------------------------------------------------------------------------------
1 | ------
2 | Runnable That Logs Runtime Exceptions
3 | ------
4 | Yegor Bugayenko
5 | ------
6 | 2012-12-16
7 | ------
8 |
9 | ~~
10 | ~~ Copyright (c) 2012-2025 Yegor Bugayenko
11 | ~~ All rights reserved.
12 | ~~
13 | ~~ Redistribution and use in source and binary forms, with or without
14 | ~~ modification, are permitted provided that the following conditions
15 | ~~ are met: 1) Redistributions of source code must retain the above
16 | ~~ copyright notice, this list of conditions and the following
17 | ~~ disclaimer. 2) Redistributions in binary form must reproduce the above
18 | ~~ copyright notice, this list of conditions and the following
19 | ~~ disclaimer in the documentation and/or other materials provided
20 | ~~ with the distribution. 3) Neither the name of the jcabi.com nor
21 | ~~ the names of its contributors may be used to endorse or promote
22 | ~~ products derived from this software without specific prior written
23 | ~~ permission.
24 | ~~
25 | ~~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 | ~~ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
27 | ~~ NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
28 | ~~ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
29 | ~~ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
30 | ~~ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 | ~~ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 | ~~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 | ~~ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 | ~~ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 | ~~ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36 | ~~ OF THE POSSIBILITY OF SUCH DAMAGE.
37 | ~~
38 |
39 | Runnable That Logs Runtime Exceptions
40 |
41 | You can use
42 | {{{./apidocs-${project.version}/com/jcabi/log/VerboseRunnable.html}<<>>}}:
43 | with scheduled executor, for example:
44 |
45 | +--
46 | public class Main {
47 | public static void main(String[] args) {
48 | Executors.newScheduledThreadPool(2).scheduleAtFixedRate(
49 | new VerboseRunnable(
50 | new Runnable() {
51 | @Override
52 | public void run() {
53 | // some operation that may lead to a runtime
54 | // exception, which we want to log through SLF4J
55 | }
56 | }
57 | ),
58 | 1L, 1L, TimeUnit.SECONDS
59 | );
60 | }
61 | }
62 | +--
63 |
64 | Now, every runtime exception that is not caught inside your
65 | {{{http://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html}<<>>}}
66 | will be reported to log (using
67 | {{{./apidocs-${project.version}/com/jcabi/log/Logger.html}<<>>}}).
68 |
69 | Two-arguments constructor can be used when you need to instruct the class
70 | about what to do with the exception: either swallow it or escalate.
71 | Sometimes it's very important to swallow exceptions. Otherwise an entire
72 | thread may get stuck (like in the example above).
73 |
74 | The only dependency you need is
75 | (you can also download
76 | {{{http://repo1.maven.org/maven2/com/jcabi/jcabi-log/${project.version}/jcabi-log-${project.version}.jar}<<>>}}
77 | and add it to the classpath):
78 |
79 | +--
80 |
81 | com.jcabi
82 | jcabi-log
83 | ${project.version}
84 |
85 | +--
86 |
--------------------------------------------------------------------------------
/src/site/apt/threads-VerboseThreads.apt.vm:
--------------------------------------------------------------------------------
1 | ------
2 | Thread Factory that Logs Exceptions
3 | ------
4 | Yegor Bugayenko
5 | ------
6 | 2012-12-16
7 | ------
8 |
9 | ~~
10 | ~~ Copyright (c) 2012-2025 Yegor Bugayenko
11 | ~~ All rights reserved.
12 | ~~
13 | ~~ Redistribution and use in source and binary forms, with or without
14 | ~~ modification, are permitted provided that the following conditions
15 | ~~ are met: 1) Redistributions of source code must retain the above
16 | ~~ copyright notice, this list of conditions and the following
17 | ~~ disclaimer. 2) Redistributions in binary form must reproduce the above
18 | ~~ copyright notice, this list of conditions and the following
19 | ~~ disclaimer in the documentation and/or other materials provided
20 | ~~ with the distribution. 3) Neither the name of the jcabi.com nor
21 | ~~ the names of its contributors may be used to endorse or promote
22 | ~~ products derived from this software without specific prior written
23 | ~~ permission.
24 | ~~
25 | ~~ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 | ~~ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
27 | ~~ NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
28 | ~~ FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
29 | ~~ THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
30 | ~~ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
31 | ~~ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32 | ~~ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 | ~~ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34 | ~~ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
35 | ~~ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36 | ~~ OF THE POSSIBILITY OF SUCH DAMAGE.
37 | ~~
38 |
39 | Thread Factory that Logs Exceptions
40 |
41 | {{{./apidocs-${project.version}/com/jcabi/log/VerboseThreads.html}<<>>}}
42 | is an implementation of
43 | {{{http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ThreadFactory.html}<<>>}}
44 | that instantiates threads that log all runtime exceptions through SLF4J.
45 |
46 | {{{./apidocs-${project.version}/com/jcabi/log/VerboseThreads.html}<<>>}}
47 | factory should be used together
48 | with executor services from <<>> package. Without
49 | this "verbose" thread factory your runnable tasks will not report anything to
50 | console once they die because of a runtime exception, for example:
51 |
52 | +--
53 | public class Main {
54 | public static void main(String[] args) {
55 | Executors.newScheduledThreadPool(2).scheduleAtFixedRate(
56 | new Runnable() {
57 | @Override
58 | public void run() {
59 | // some sensitive operation that may throw
60 | // a runtime exception
61 | }
62 | }, 1L, 1L, TimeUnit.SECONDS
63 | );
64 | }
65 | }
66 | +--
67 |
68 | The exception in this example will never be caught by nobody. It will
69 | just terminate current execution of the <<>> task. Moreover,
70 | it won't reach any
71 | {{{http://docs.oracle.com/javase/7/docs/api/java/lang/Thread.UncaughtExceptionHandler.html}<<>>}},
72 | because this
73 | is how
74 | {{{http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html}<<>>}}
75 | is behaving by default. This is how we solve
76 | the problem with
77 | {{{./apidocs-${project.version}/com/jcabi/log/VerboseThreads.html}<<>>}}:
78 |
79 | +--
80 | public class Main {
81 | public static void main(String[] args) {
82 | ThreadFactory factory = new VerboseThreads();
83 | Executors.newScheduledThreadPool(2, factory).scheduleAtFixedRate(
84 | new Runnable() {
85 | @Override
86 | public void run() {
87 | // the same sensitive operation that may throw
88 | // a runtime exception
89 | }
90 | }, 1L, 1L, TimeUnit.SECONDS
91 | );
92 | }
93 | }
94 | +--
95 |
96 | Now, every runtime exception that is not caught inside your
97 | {{{http://docs.oracle.com/javase/7/docs/api/java/lang/Runnable.html}<<>>}}
98 | will be reported to log (using
99 | {{{./apidocs-${project.version}/com/jcabi/log/Logger.html}<<>>}}).
100 |
101 | The only dependency you need is
102 | (you can also download
103 | {{{http://repo1.maven.org/maven2/com/jcabi/jcabi-log/${project.version}/jcabi-log-${project.version}.jar}<<>>}}
104 | and add it to the classpath):
105 |
106 | +--
107 |
108 | com.jcabi
109 | jcabi-log
110 | ${project.version}
111 |
112 | +--
113 |
--------------------------------------------------------------------------------
/src/site/resources/CNAME:
--------------------------------------------------------------------------------
1 | log.jcabi.com
2 |
--------------------------------------------------------------------------------
/src/site/site.xml:
--------------------------------------------------------------------------------
1 |
2 |
31 |
32 |
33 | com.jcabi
34 | jcabi-maven-skin
35 | 1.7.1
36 |
37 |
38 | jcabi
39 | https://www.jcabi.com/logo-square.svg
40 | https://www.jcabi.com/
41 | 64
42 | 64
43 |
44 | UA-1963507-23
45 |
46 |
47 | <link href="https://www.jcabi.com/logo-square.svg" rel="shortcut icon"/>
48 | <link href="https://plus.google.com/u/0/114792568016408327418?rel=author" rel="author"/>
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/ConversionPatternTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import org.hamcrest.MatcherAssert;
8 | import org.hamcrest.Matchers;
9 | import org.junit.jupiter.api.Test;
10 |
11 | /**
12 | * Test case for {@link ConversionPattern}.
13 | * @since 0.19
14 | */
15 | @SuppressWarnings("PMD.AvoidDuplicateLiterals")
16 | final class ConversionPatternTest {
17 | /**
18 | * Control Sequence Indicator.
19 | */
20 | private static final String CSI = "\u001b\\[";
21 |
22 | /**
23 | * Default color map for testing.
24 | */
25 | private static final Colors COLORS = new Colors();
26 |
27 | @Test
28 | void testGenerateNoReplacement() {
29 | MatcherAssert.assertThat(
30 | convert(""),
31 | Matchers.equalTo("")
32 | );
33 | MatcherAssert.assertThat(
34 | convert("foo"),
35 | Matchers.equalTo("foo")
36 | );
37 | MatcherAssert.assertThat(
38 | convert("%color"),
39 | Matchers.equalTo("%color")
40 | );
41 | MatcherAssert.assertThat(
42 | convert("%color-"),
43 | Matchers.equalTo("%color-")
44 | );
45 | MatcherAssert.assertThat(
46 | convert("%color{%c{1}foo"),
47 | Matchers.equalTo("%color{%c{1}foo")
48 | );
49 | }
50 |
51 | @Test
52 | void testGenerateEmpty() {
53 | MatcherAssert.assertThat(
54 | convert("%color{}"),
55 | Matchers.equalTo(colorWrap(""))
56 | );
57 | }
58 |
59 | @Test
60 | void testGenerateSimple() {
61 | MatcherAssert.assertThat(
62 | convert("%color{Hello World}"),
63 | Matchers.equalTo(colorWrap("Hello World"))
64 | );
65 | MatcherAssert.assertThat(
66 | convert("%color{Hello World}foo"),
67 | Matchers.equalTo(String.format("%sfoo", colorWrap("Hello World")))
68 | );
69 | MatcherAssert.assertThat(
70 | convert("%color{Hello}%color{World}"),
71 | Matchers.equalTo(
72 | String.format("%s%s", colorWrap("Hello"), colorWrap("World"))
73 | )
74 | );
75 | }
76 |
77 | @Test
78 | void testGenerateCurlyBraces() {
79 | MatcherAssert.assertThat(
80 | ConversionPatternTest.convert("%color{%c{1}}"),
81 | Matchers.equalTo(ConversionPatternTest.colorWrap("%c{1}"))
82 | );
83 | MatcherAssert.assertThat(
84 | ConversionPatternTest.convert("%color{%c{1}}foo"),
85 | Matchers.equalTo(
86 | String.format("%sfoo", ConversionPatternTest.colorWrap("%c{1}"))
87 | )
88 | );
89 | MatcherAssert.assertThat(
90 | ConversionPatternTest.convert("%color{%c1}}foo"),
91 | Matchers.equalTo(
92 | String.format("%s}foo", ConversionPatternTest.colorWrap("%c1"))
93 | )
94 | );
95 | MatcherAssert.assertThat(
96 | ConversionPatternTest.convert("%color{%c{{{1}{2}}}}foo"),
97 | Matchers.equalTo(
98 | String.format(
99 | "%sfoo",
100 | ConversionPatternTest.colorWrap("%c{{{1}{2}}}")
101 | )
102 | )
103 | );
104 | }
105 |
106 | /**
107 | * Convenience method to generate conversion pattern for the tests.
108 | * @param pat Pattern to be used
109 | * @return Conversion pattern
110 | */
111 | private static String convert(final String pat) {
112 | return new ConversionPattern(
113 | pat,
114 | ConversionPatternTest.COLORS
115 | ).generate();
116 | }
117 |
118 | /**
119 | * Wraps the given string in the expected ANSI color sequence.
120 | * @param str Input string to wrap.
121 | * @return Wrapped string.
122 | */
123 | private static String colorWrap(final String str) {
124 | return String.format(
125 | "%s?m%s%sm",
126 | ConversionPatternTest.CSI,
127 | str,
128 | ConversionPatternTest.CSI
129 | );
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/DecorMocker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Formattable;
8 | import java.util.Formatter;
9 |
10 | /**
11 | * Primitive decor, for testing only.
12 | * @since 0.1
13 | */
14 | public final class DecorMocker implements Formattable {
15 |
16 | /**
17 | * The text.
18 | */
19 | private final transient String text;
20 |
21 | /**
22 | * Public ctor.
23 | * @param txt The text to output
24 | */
25 | public DecorMocker(final Object txt) {
26 | this.text = txt.toString();
27 | }
28 |
29 | // @checkstyle ParameterNumber (4 lines)
30 | @Override
31 | public void formatTo(final Formatter formatter, final int flags,
32 | final int width, final int precision) {
33 | formatter.format(
34 | String.format(
35 | "%s [f=%d, w=%d, p=%d]",
36 | this.text,
37 | flags,
38 | width,
39 | precision
40 | )
41 | );
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/DecorsManagerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import org.hamcrest.MatcherAssert;
8 | import org.hamcrest.Matchers;
9 | import org.junit.jupiter.api.Assertions;
10 | import org.junit.jupiter.api.Test;
11 |
12 | /**
13 | * Test case for {@link DecorsManager}.
14 | * @since 0.1
15 | */
16 | final class DecorsManagerTest {
17 |
18 | @Test
19 | void hasBuiltInDecors() throws Exception {
20 | MatcherAssert.assertThat(
21 | DecorsManager.decor("nano", 1L),
22 | Matchers.instanceOf(NanoDecor.class)
23 | );
24 | }
25 |
26 | @Test
27 | void throwsExceptionForAbsentDecor() {
28 | Assertions.assertThrows(
29 | DecorException.class,
30 | () -> DecorsManager.decor("non-existing-formatter", null)
31 | );
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/DomDecorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Formattable;
8 | import java.util.Formatter;
9 | import javax.xml.parsers.DocumentBuilderFactory;
10 | import org.hamcrest.Matchers;
11 | import org.junit.jupiter.api.Test;
12 | import org.mockito.Mockito;
13 | import org.mockito.hamcrest.MockitoHamcrest;
14 | import org.w3c.dom.Document;
15 |
16 | /**
17 | * Test case for {@link DomDecor}.
18 | *
19 | * @since 0.1
20 | */
21 | final class DomDecorTest {
22 |
23 | @Test
24 | void convertsDocumentToText() throws Exception {
25 | final Document doc = DocumentBuilderFactory.newInstance()
26 | .newDocumentBuilder().newDocument();
27 | doc.appendChild(doc.createElement("root"));
28 | final Formattable decor = new DomDecor(doc);
29 | final Appendable dest = Mockito.mock(Appendable.class);
30 | final Formatter fmt = new Formatter(dest);
31 | decor.formatTo(fmt, 0, 0, 0);
32 | Mockito.verify(dest).append(
33 | MockitoHamcrest.argThat(Matchers.containsString(" "))
34 | );
35 | }
36 |
37 | @Test
38 | void convertsNullToText() throws Exception {
39 | final Formattable decor = new DomDecor(null);
40 | final Appendable dest = Mockito.mock(Appendable.class);
41 | final Formatter fmt = new Formatter(dest);
42 | decor.formatTo(fmt, 0, 0, 0);
43 | Mockito.verify(dest).append("NULL");
44 | }
45 |
46 | }
47 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/ExceptionDecorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.io.IOException;
8 | import java.util.Formattable;
9 | import java.util.Formatter;
10 | import org.hamcrest.Matchers;
11 | import org.junit.jupiter.api.Test;
12 | import org.mockito.Mockito;
13 | import org.mockito.hamcrest.MockitoHamcrest;
14 |
15 | /**
16 | * Test case for {@link ExceptionDecor}.
17 | * @since 0.1
18 | */
19 | final class ExceptionDecorTest {
20 |
21 | @Test
22 | void convertsExceptionToText() throws Exception {
23 | final Formattable decor = new ExceptionDecor(new IOException("ouch!"));
24 | final Appendable dest = Mockito.mock(Appendable.class);
25 | final Formatter fmt = new Formatter(dest);
26 | decor.formatTo(fmt, 0, 0, 0);
27 | Mockito.verify(dest).append(
28 | MockitoHamcrest.argThat(
29 | Matchers.allOf(
30 | Matchers.containsString(
31 | "java.io.IOException: ouch!"
32 | ),
33 | Matchers.containsString(
34 | "at com.jcabi.log.ExceptionDecorTest."
35 | )
36 | )
37 | )
38 | );
39 | }
40 |
41 | @Test
42 | void convertsNullToText() throws Exception {
43 | final Formattable decor = new ExceptionDecor(null);
44 | final Appendable dest = Mockito.mock(Appendable.class);
45 | final Formatter fmt = new Formatter(dest);
46 | decor.formatTo(fmt, 0, 0, 0);
47 | Mockito.verify(dest).append("NULL");
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/FileDecorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.io.File;
8 | import java.nio.file.Paths;
9 | import java.util.Arrays;
10 | import java.util.Collection;
11 | import java.util.Locale;
12 | import org.hamcrest.MatcherAssert;
13 | import org.hamcrest.Matchers;
14 | import org.junit.jupiter.api.Test;
15 | import org.junit.jupiter.api.condition.DisabledOnOs;
16 | import org.junit.jupiter.api.condition.EnabledOnOs;
17 | import org.junit.jupiter.api.condition.OS;
18 | import org.junit.jupiter.params.ParameterizedTest;
19 | import org.junit.jupiter.params.provider.MethodSource;
20 |
21 | /**
22 | * Test case for {@link FileDecor}.
23 | *
24 | * @since 0.1
25 | * @checkstyle ParameterNumberCheck (500 lines)
26 | */
27 | final class FileDecorTest {
28 |
29 | @Test
30 | @DisabledOnOs(OS.WINDOWS)
31 | void simplyWorksOnUnix() {
32 | MatcherAssert.assertThat(
33 | new Printed(new FileDecor("/tmp/test-me.txt"), 0, 0, 0).toString(),
34 | Matchers.endsWith("test-me.txt")
35 | );
36 | }
37 |
38 | @Test
39 | @EnabledOnOs(OS.WINDOWS)
40 | void simplyWorksOnWindows() {
41 | MatcherAssert.assertThat(
42 | new Printed(new FileDecor("F:\\hahaha\\b\\foo.txt"), 0, 0, 0).toString(),
43 | Matchers.endsWith("foo.txt")
44 | );
45 | }
46 |
47 | @DisabledOnOs(OS.WINDOWS)
48 | @ParameterizedTest
49 | @MethodSource("params")
50 | void testPrintsRight(final Object path, final String text,
51 | final int flags, final int width, final int precision) {
52 | Locale.setDefault(Locale.US);
53 | MatcherAssert.assertThat(
54 | new Printed(new FileDecor(path), flags, width, precision),
55 | Matchers.hasToString(text)
56 | );
57 | }
58 |
59 | @DisabledOnOs(OS.WINDOWS)
60 | @ParameterizedTest
61 | @MethodSource("params")
62 | void testLogsRight(final Object path, final String text,
63 | final int flags, final int width, final int precision) {
64 | Locale.setDefault(Locale.US);
65 | MatcherAssert.assertThat(
66 | new Logged(new FileDecor(path), flags, width, precision),
67 | Matchers.hasToString(text)
68 | );
69 | }
70 |
71 | /**
72 | * Params for this parametrized test.
73 | * @return Array of arrays of params for ctor
74 | */
75 | @SuppressWarnings("PMD.UnusedPrivateMethod")
76 | private static Collection params() {
77 | return Arrays.asList(
78 | new Object[][] {
79 | {null, "NULL", 0, 0, 0},
80 | {"foo.txt", "foo.txt", 0, 0, 0},
81 | {".", "./", 0, 0, 0},
82 | {"/tmp", "/tmp", 0, 0, 0},
83 | {new File("/tmp/x.txt"), "/tmp/x.txt", 0, 0, 0},
84 | {Paths.get("/a/b/c.txt"), "/a/b/c.txt", 0, 0, 0},
85 | }
86 | );
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/LineNumberTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.io.StringWriter;
8 | import java.util.concurrent.TimeUnit;
9 | import org.apache.log4j.Appender;
10 | import org.apache.log4j.Level;
11 | import org.apache.log4j.LogManager;
12 | import org.apache.log4j.PatternLayout;
13 | import org.apache.log4j.WriterAppender;
14 | import org.hamcrest.MatcherAssert;
15 | import org.hamcrest.Matchers;
16 | import org.junit.jupiter.api.Test;
17 |
18 | /**
19 | * Test case for %L pattern.
20 | * If you change this class, you have to care about line number
21 | * in "com.jcabi.log.LineNumberTest:72"
22 | * @since 1.18
23 | */
24 | final class LineNumberTest {
25 |
26 | /**
27 | * Conversation pattern for test case.
28 | */
29 | private static final String CONV_PATTERN = "%c:%L";
30 |
31 | @Test
32 | void testLineNumber() throws Exception {
33 | final PatternLayout layout = new PatternLayout();
34 | layout.setConversionPattern(LineNumberTest.CONV_PATTERN);
35 | final org.apache.log4j.Logger root = LogManager.getRootLogger();
36 | final Level level = root.getLevel();
37 | root.setLevel(Level.INFO);
38 | final StringWriter writer = new StringWriter();
39 | final Appender appender = new WriterAppender(layout, writer);
40 | root.addAppender(appender);
41 | try {
42 | Logger.info(this, "Test");
43 | TimeUnit.MILLISECONDS.sleep(1L);
44 | MatcherAssert.assertThat(
45 | writer.toString(),
46 | Matchers.containsString(
47 | "com.jcabi.log.LineNumberTest:241"
48 | )
49 | );
50 | } finally {
51 | root.removeAppender(appender);
52 | root.setLevel(level);
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/ListDecorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.ArrayList;
8 | import java.util.Arrays;
9 | import java.util.Collection;
10 | import java.util.Locale;
11 | import org.hamcrest.MatcherAssert;
12 | import org.hamcrest.Matchers;
13 | import org.junit.jupiter.params.ParameterizedTest;
14 | import org.junit.jupiter.params.provider.MethodSource;
15 |
16 | /**
17 | * Test case for {@link ListDecor}.
18 | *
19 | * @since 0.1
20 | * @checkstyle ParameterNumberCheck (500 lines)
21 | */
22 | final class ListDecorTest {
23 |
24 | @ParameterizedTest
25 | @MethodSource("params")
26 | void testPrintsRight(final Object list, final String text,
27 | final int flags, final int width, final int precision) throws DecorException {
28 | Locale.setDefault(Locale.US);
29 | MatcherAssert.assertThat(
30 | new Printed(new ListDecor(list), flags, width, precision),
31 | Matchers.hasToString(text)
32 | );
33 | }
34 |
35 | @ParameterizedTest
36 | @MethodSource("params")
37 | void testLogsRight(final Object list, final String text,
38 | final int flags, final int width, final int precision) throws DecorException {
39 | Locale.setDefault(Locale.US);
40 | MatcherAssert.assertThat(
41 | new Logged(new ListDecor(list), flags, width, precision),
42 | Matchers.hasToString(text)
43 | );
44 | }
45 |
46 | /**
47 | * Params for this parametrized test.
48 | * @return Array of arrays of params for ctor
49 | */
50 | @SuppressWarnings("PMD.UnusedPrivateMethod")
51 | private static Collection params() {
52 | return Arrays.asList(
53 | new Object[][] {
54 | // @checkstyle MultipleStringLiterals (8 lines)
55 | {null, "[NULL]", 0, 0, 0},
56 | {new String[] {}, "[]", 0, 0, 0},
57 | {new String[] {"a"}, "[\"a\"]", 0, 0, 0},
58 | {new Long[] {2L, 1L}, "[\"2\", \"1\"]", 0, 0, 0},
59 | {new Object[] {"b", "c"}, "[\"b\", \"c\"]", 0, 0, 0},
60 | {new Object[] {"foo", 2L}, "[\"foo\", \"2\"]", 0, 0, 0},
61 | {new ArrayList(0), "[]", 0, 0, 0},
62 | {Arrays.asList(new String[] {"x"}), "[\"x\"]", 0, 0, 0},
63 | {Arrays.asList(new Long[] {1L, 2L}), "[\"1\", \"2\"]", 0, 0, 0},
64 | }
65 | );
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/Logged.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Formattable;
8 | import java.util.FormattableFlags;
9 |
10 | /**
11 | * Logs decor.
12 | *
13 | * @since 0.1
14 | */
15 | public final class Logged {
16 |
17 | /**
18 | * The decor.
19 | */
20 | private final transient Formattable decor;
21 |
22 | /**
23 | * Formatting flags.
24 | */
25 | private final transient int flags;
26 |
27 | /**
28 | * Formatting width.
29 | */
30 | private final transient int width;
31 |
32 | /**
33 | * Formatting precision.
34 | */
35 | private final transient int precision;
36 |
37 | /**
38 | * Public ctor.
39 | * @param dcr Decor
40 | * @param flgs Flags
41 | * @param wdt Width
42 | * @param prcs Precision
43 | * @checkstyle ParameterNumber (3 lines)
44 | */
45 | public Logged(final Formattable dcr,
46 | final int flgs, final int wdt, final int prcs) {
47 | this.decor = dcr;
48 | this.flags = flgs;
49 | this.width = wdt;
50 | this.precision = prcs;
51 | }
52 |
53 | @Override
54 | public String toString() {
55 | final StringBuilder format = new StringBuilder(0);
56 | format.append('%');
57 | if ((this.flags & FormattableFlags.LEFT_JUSTIFY) == FormattableFlags
58 | .LEFT_JUSTIFY) {
59 | format.append('-');
60 | }
61 | if (this.width > 0) {
62 | format.append(this.width);
63 | }
64 | if (this.precision > 0) {
65 | format.append('.').append(this.precision);
66 | }
67 | if ((this.flags & FormattableFlags.UPPERCASE) == FormattableFlags
68 | .UPPERCASE) {
69 | format.append('S');
70 | } else {
71 | format.append('s');
72 | }
73 | return Logger.format(format.toString(), this.decor);
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/LoggerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.io.OutputStream;
8 | import java.io.OutputStreamWriter;
9 | import java.io.PrintWriter;
10 | import java.util.concurrent.TimeUnit;
11 | import java.util.logging.Level;
12 | import org.apache.log4j.LogManager;
13 | import org.hamcrest.MatcherAssert;
14 | import org.hamcrest.Matchers;
15 | import org.junit.jupiter.api.Assertions;
16 | import org.junit.jupiter.api.Test;
17 |
18 | /**
19 | * Test case for {@link Logger}.
20 | * @since 0.1
21 | */
22 | final class LoggerTest {
23 |
24 | @Test
25 | void detectsLoggerNameCorrectly() {
26 | // not implemented yet
27 | }
28 |
29 | @Test
30 | void detectsNameOfStaticSource() {
31 | // not implemented yet
32 | }
33 |
34 | @Test
35 | void setsLoggingLevel() {
36 | // not implemented yet
37 | }
38 |
39 | @Test
40 | void doesntFormatArraysSinceTheyAreVarArgs() {
41 | Assertions.assertThrows(
42 | IllegalArgumentException.class,
43 | () -> Logger.format("array: %[list]s", new Object[] {"hi", 1})
44 | );
45 | }
46 |
47 | @Test
48 | void interpretsArraysAsVarArgs() {
49 | MatcherAssert.assertThat(
50 | Logger.format("array: %s : %d", new Object[] {"hello", 2}),
51 | Matchers.is("array: hello : 2")
52 | );
53 | }
54 |
55 | @Test
56 | void providesOutputStream() throws Exception {
57 | final OutputStream stream = Logger.stream(Level.INFO, this);
58 | final PrintWriter writer = new PrintWriter(
59 | new OutputStreamWriter(stream, "UTF-8")
60 | );
61 | writer.print("hello, \u20ac, how're\u040a?\nI'm fine, \u0000\u0007!\n");
62 | writer.flush();
63 | writer.close();
64 | }
65 |
66 | @Test
67 | void throwsWhenParamsLessThanFormatArgs() {
68 | Assertions.assertThrows(
69 | ArrayIndexOutOfBoundsException.class,
70 | () -> Logger.format("String %s Char %c Number %d", "howdy", 'x')
71 | );
72 | }
73 |
74 | @Test
75 | void throwsWhenParamsMoreThanFormatArgs() {
76 | Assertions.assertThrows(
77 | IllegalArgumentException.class,
78 | () -> Logger.format("String %s Number %d Char %c", "hey", 1, 'x', 2)
79 | );
80 | }
81 |
82 | @Test
83 | void checksLogLevel() throws Exception {
84 | LogManager.getRootLogger().setLevel(org.apache.log4j.Level.INFO);
85 | TimeUnit.MILLISECONDS.sleep(1L);
86 | MatcherAssert.assertThat(
87 | Logger.isEnabled(Level.INFO, LogManager.getRootLogger()),
88 | Matchers.is(true)
89 | );
90 | MatcherAssert.assertThat(
91 | Logger.isEnabled(Level.FINEST, LogManager.getRootLogger()),
92 | Matchers.is(false)
93 | );
94 | }
95 |
96 | @Test
97 | void usesStringAsLoggerName() {
98 | Logger.info("com.jcabi.log...why.not", "hello, %s!", "world!");
99 | }
100 |
101 | @Test
102 | void findsArgsByPositions() {
103 | final String first = "xyz";
104 | final String second = "ddd";
105 | MatcherAssert.assertThat(
106 | Logger.format("first: %s, first again: %1$s %s", first, second),
107 | Matchers.endsWith(
108 | String.format(": %s, first again: %1$s %s", first, second)
109 | )
110 | );
111 | }
112 |
113 | }
114 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/MsDecorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Arrays;
8 | import java.util.Collection;
9 | import java.util.FormattableFlags;
10 | import java.util.Locale;
11 | import org.hamcrest.MatcherAssert;
12 | import org.hamcrest.Matchers;
13 | import org.junit.jupiter.api.Test;
14 | import org.junit.jupiter.params.ParameterizedTest;
15 | import org.junit.jupiter.params.provider.MethodSource;
16 |
17 | /**
18 | * Test case for {@link MsDecor}.
19 | *
20 | * @since 0.1
21 | * @checkstyle ParameterNumberCheck (500 lines)
22 | */
23 | final class MsDecorTest {
24 |
25 | @ParameterizedTest
26 | @MethodSource("params")
27 | void testPrintsRight(final long value, final String text,
28 | final int flags, final int width, final int precision) {
29 | Locale.setDefault(Locale.US);
30 | MatcherAssert.assertThat(
31 | new Printed(new MsDecor(value), flags, width, precision),
32 | Matchers.hasToString(text)
33 | );
34 | }
35 |
36 | @ParameterizedTest
37 | @MethodSource("params")
38 | void testLogsRight(final long value, final String text,
39 | final int flags, final int width, final int precision) {
40 | Locale.setDefault(Locale.US);
41 | MatcherAssert.assertThat(
42 | new Logged(new MsDecor(value), flags, width, precision),
43 | Matchers.hasToString(text)
44 | );
45 | }
46 |
47 | @Test
48 | void testPrintsNullRight() {
49 | MatcherAssert.assertThat(
50 | new Logged(new MsDecor(null), 0, 0, 0),
51 | Matchers.hasToString("NULL")
52 | );
53 | }
54 |
55 | /**
56 | * Params for this parametrized test.
57 | * @return Array of arrays of params for ctor
58 | */
59 | @SuppressWarnings("PMD.UnusedPrivateMethod")
60 | private static Collection params() {
61 | return Arrays.asList(
62 | new Object[][] {
63 | {13L, "13ms", 0, 0, -1},
64 | {13L, "13.0ms", 0, 0, 1},
65 | {1024L, "1s", 0, 0, 0},
66 | {6001L, "6.0010s", 0, 0, 4},
67 | {122_001L, " 2MIN", FormattableFlags.UPPERCASE, 6, 0},
68 | {3_789_003L, "1hr", 0, 0, 0},
69 | {86_400_000L, "1days", 0, 0, 0},
70 | {864_000_000L, "10days", 0, 0, 0},
71 | }
72 | );
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/MulticolorLayoutTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import org.apache.commons.text.StringEscapeUtils;
8 | import org.apache.log4j.Level;
9 | import org.apache.log4j.spi.LoggingEvent;
10 | import org.hamcrest.MatcherAssert;
11 | import org.hamcrest.Matchers;
12 | import org.junit.jupiter.api.Assertions;
13 | import org.junit.jupiter.api.Test;
14 | import org.mockito.Mockito;
15 |
16 | /**
17 | * Test case for {@link MulticolorLayout}.
18 | *
19 | * @since 0.1
20 | */
21 | final class MulticolorLayoutTest {
22 |
23 | /**
24 | * Conversation pattern for test case.
25 | */
26 | private static final String CONV_PATTERN = "[%color{%p}] %color{%m}";
27 |
28 | @Test
29 | void transformsLoggingEventToText() {
30 | final MulticolorLayout layout = new MulticolorLayout();
31 | layout.setConversionPattern(MulticolorLayoutTest.CONV_PATTERN);
32 | final LoggingEvent event = Mockito.mock(LoggingEvent.class);
33 | Mockito.doReturn(Level.DEBUG).when(event).getLevel();
34 | Mockito.doReturn("hello").when(event).getRenderedMessage();
35 | MatcherAssert.assertThat(
36 | StringEscapeUtils.escapeJava(layout.format(event)),
37 | Matchers.equalTo(
38 | "[\\u001B[2;37mDEBUG\\u001B[m] \\u001B[2;37mhello\\u001B[m"
39 | )
40 | );
41 | }
42 |
43 | @Test
44 | void overwriteDefaultColor() {
45 | final MulticolorLayout layout = new MulticolorLayout();
46 | layout.setConversionPattern(MulticolorLayoutTest.CONV_PATTERN);
47 | layout.setLevels("INFO:2;10");
48 | final LoggingEvent event = Mockito.mock(LoggingEvent.class);
49 | Mockito.doReturn(Level.INFO).when(event).getLevel();
50 | Mockito.doReturn("change").when(event).getRenderedMessage();
51 | MatcherAssert.assertThat(
52 | StringEscapeUtils.escapeJava(layout.format(event)),
53 | Matchers.equalTo(
54 | "[\\u001B[2;10mINFO\\u001B[m] \\u001B[2;10mchange\\u001B[m"
55 | )
56 | );
57 | }
58 |
59 | @Test
60 | void rendersCustomConstantColor() {
61 | final MulticolorLayout layout = new MulticolorLayout();
62 | layout.setConversionPattern("%color-red{%p} %m");
63 | final LoggingEvent event = Mockito.mock(LoggingEvent.class);
64 | Mockito.doReturn(Level.DEBUG).when(event).getLevel();
65 | Mockito.doReturn("foo").when(event).getRenderedMessage();
66 | MatcherAssert.assertThat(
67 | StringEscapeUtils.escapeJava(layout.format(event)),
68 | Matchers.equalTo("\\u001B[31mDEBUG\\u001B[m foo")
69 | );
70 | }
71 |
72 | @Test
73 | void overwriteCustomConstantColor() {
74 | final MulticolorLayout layout = new MulticolorLayout();
75 | layout.setConversionPattern("%color-white{%p} %m");
76 | layout.setColors("white:10");
77 | final LoggingEvent event = Mockito.mock(LoggingEvent.class);
78 | Mockito.doReturn(Level.DEBUG).when(event).getLevel();
79 | Mockito.doReturn("const").when(event).getRenderedMessage();
80 | MatcherAssert.assertThat(
81 | StringEscapeUtils.escapeJava(layout.format(event)),
82 | Matchers.equalTo("\\u001B[10mDEBUG\\u001B[m const")
83 | );
84 | }
85 |
86 | @Test
87 | void rendersAnsiConstantColor() {
88 | final MulticolorLayout layout = new MulticolorLayout();
89 | layout.setConversionPattern("%color-0;0;31{%p} %m");
90 | final LoggingEvent event = Mockito.mock(LoggingEvent.class);
91 | Mockito.doReturn(Level.DEBUG).when(event).getLevel();
92 | Mockito.doReturn("bar").when(event).getRenderedMessage();
93 | MatcherAssert.assertThat(
94 | StringEscapeUtils.escapeJava(layout.format(event)),
95 | Matchers.equalTo("\\u001B[0;0;31mDEBUG\\u001B[m bar")
96 | );
97 | }
98 |
99 | @Test
100 | void throwsOnIllegalColorName() {
101 | Assertions.assertThrows(
102 | IllegalArgumentException.class,
103 | () -> {
104 | final MulticolorLayout layout = new MulticolorLayout();
105 | layout.setConversionPattern("%color-oops{%p} %m");
106 | final LoggingEvent event = Mockito.mock(LoggingEvent.class);
107 | Mockito.doReturn(Level.DEBUG).when(event).getLevel();
108 | Mockito.doReturn("text").when(event).getRenderedMessage();
109 | layout.format(event);
110 | }
111 | );
112 | }
113 |
114 | }
115 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/NanoDecorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Arrays;
8 | import java.util.Collection;
9 | import java.util.FormattableFlags;
10 | import java.util.Locale;
11 | import org.hamcrest.MatcherAssert;
12 | import org.hamcrest.Matchers;
13 | import org.junit.jupiter.api.Test;
14 | import org.junit.jupiter.params.ParameterizedTest;
15 | import org.junit.jupiter.params.provider.MethodSource;
16 |
17 | /**
18 | * Test case for {@link NanoDecor}.
19 |
20 | * @since 0.1
21 | * @checkstyle ParameterNumberCheck (500 lines)
22 | */
23 | final class NanoDecorTest {
24 |
25 | @ParameterizedTest
26 | @MethodSource("params")
27 | void testPrintsRight(final long nano, final String text,
28 | final int flags, final int width, final int precision) {
29 | Locale.setDefault(Locale.US);
30 | MatcherAssert.assertThat(
31 | new Printed(new NanoDecor(nano), flags, width, precision),
32 | Matchers.hasToString(text)
33 | );
34 | }
35 |
36 | @ParameterizedTest
37 | @MethodSource("params")
38 | void testLogsRight(final long nano, final String text,
39 | final int flags, final int width, final int precision) {
40 | Locale.setDefault(Locale.US);
41 | MatcherAssert.assertThat(
42 | new Logged(new NanoDecor(nano), flags, width, precision),
43 | Matchers.hasToString(text)
44 | );
45 | }
46 |
47 | @Test
48 | void testPrintsNullRight() {
49 | MatcherAssert.assertThat(
50 | new Logged(new NanoDecor(null), 0, 0, 0),
51 | Matchers.hasToString("NULL")
52 | );
53 | }
54 |
55 | /**
56 | * Params for this parametrized test.
57 | * @return Array of arrays of params for ctor
58 | */
59 | @SuppressWarnings("PMD.UnusedPrivateMethod")
60 | private static Collection params() {
61 | return Arrays.asList(
62 | new Object[][] {
63 | {13L, "13ns", 0, 0, -1},
64 | {13L, "13.0ns", 0, 0, 1},
65 | {25L, "25.00ns", 0, 0, 2},
66 | {234L, "234.0ns", 0, 0, 1},
67 | {1024L, "1µs", 0, 0, 0},
68 | {1056L, "1.056µs", 0, 0, 3},
69 | {9022L, "9.02µs", 0, 0, 2},
70 | {53_111L, "53.11µs ", FormattableFlags.LEFT_JUSTIFY, 10, 2},
71 | {53_156L, " 53µs", 0, 7, 0},
72 | {87_090_432L, " 87ms", 0, 6, 0},
73 | {87_090_543L, "87.09ms", 0, 0, 2},
74 | {87_090_548L, "87.0905ms", 0, 0, 4},
75 | {6_001_001_001L, "6.0010s", 0, 0, 4},
76 | {122_001_001_001L, " 2MIN", FormattableFlags.UPPERCASE, 6, 0},
77 | {3_789_001_001_001L, "63.15002min", 0, 0, 5},
78 | {3_789_002_002_002L, "63.2min", 0, 0, 1},
79 | {3_789_003_003_003L, "63min", 0, 0, 0},
80 | {342_000_004_004_004L, "5700min", 0, 0, 0},
81 | }
82 | );
83 | }
84 |
85 | }
86 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/ObjectDecorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Arrays;
8 | import java.util.Collection;
9 | import java.util.Locale;
10 | import org.hamcrest.MatcherAssert;
11 | import org.hamcrest.Matchers;
12 | import org.junit.jupiter.params.ParameterizedTest;
13 | import org.junit.jupiter.params.provider.MethodSource;
14 |
15 | /**
16 | * Test case for {@link ObjectDecor}.
17 | *
18 | * @since 0.1
19 | * @checkstyle ParameterNumberCheck (500 lines)
20 | */
21 | final class ObjectDecorTest {
22 |
23 | @ParameterizedTest
24 | @MethodSource("params")
25 | void testPrintsRight(final Object obj, final String text,
26 | final int flags, final int width, final int precision) {
27 | Locale.setDefault(Locale.US);
28 | MatcherAssert.assertThat(
29 | new Printed(new ObjectDecor(obj), flags, width, precision),
30 | Matchers.hasToString(Matchers.containsString(text))
31 | );
32 | }
33 |
34 | @ParameterizedTest
35 | @MethodSource("params")
36 | void testLogsRight(final Object obj, final String text,
37 | final int flags, final int width, final int precision) {
38 | Locale.setDefault(Locale.US);
39 | MatcherAssert.assertThat(
40 | new Logged(new ObjectDecor(obj), flags, width, precision),
41 | Matchers.hasToString(Matchers.containsString(text))
42 | );
43 | }
44 |
45 | /**
46 | * Params for this parametrized test.
47 | * @return Array of arrays of params for ctor
48 | */
49 | @SuppressWarnings("PMD.UnusedPrivateMethod")
50 | private static Collection params() {
51 | return Arrays.asList(
52 | new Object[][] {
53 | {null, "NULL", 0, 0, 0},
54 | {new SecretDecor("x"), "{secret: \"x\"", 0, 0, 0},
55 | {new ObjectDecorTest.Foo(1, "one"), "{num: \"1\", name: \"one\"", 0, 0, 0},
56 | {
57 | new Object[]{
58 | new ObjectDecorTest.Foo(0, "zero"),
59 | new ObjectDecorTest.Foo(2, "two"),
60 | },
61 | "[{num: \"0\", name: \"zero\"",
62 | 0, 0, 0,
63 | },
64 | {
65 | new Object[]{
66 | new ObjectDecorTest.Foo(0, "abc"),
67 | new ObjectDecorTest.Foo(2, "cde"),
68 | },
69 | ", {num: \"2\", name: \"cde\"",
70 | 0, 0, 0,
71 | },
72 | {
73 | new Object[] {new Object[] {null}, }, "[[NULL", 0, 0, 0,
74 | },
75 | }
76 | );
77 | }
78 |
79 | /**
80 | * Test class for displaying object contents.
81 | *
82 | * @since 0.1
83 | */
84 | private static final class Foo {
85 | /**
86 | * The number.
87 | */
88 | @SuppressWarnings("unused")
89 | private final transient int num;
90 |
91 | /**
92 | * The name.
93 | */
94 | @SuppressWarnings("unused")
95 | private final transient String name;
96 |
97 | /**
98 | * Ctor.
99 | * @param number The number
100 | * @param nme The name
101 | */
102 | Foo(final int number, final String nme) {
103 | this.num = number;
104 | this.name = nme;
105 | }
106 | }
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/ParseableInformationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Map;
8 | import org.apache.commons.lang3.StringUtils;
9 | import org.hamcrest.MatcherAssert;
10 | import org.hamcrest.Matchers;
11 | import org.junit.jupiter.api.Assertions;
12 | import org.junit.jupiter.api.Test;
13 |
14 | /**
15 | * ParseableInformation test case.
16 | * @since 0.18
17 | */
18 | class ParseableInformationTest {
19 |
20 | /**
21 | * ParseableInformation can parse if the information correctly if is using
22 | * the right pattern.
23 | */
24 | @Test
25 | @SuppressWarnings("PMD.UseConcurrentHashMap")
26 | final void parsesTheInformationCorrectly() {
27 | final Map parsed = new ParseableInformation(
28 | "red:10,black:20"
29 | ).information();
30 | MatcherAssert.assertThat(parsed, Matchers.hasEntry("red", "10"));
31 | MatcherAssert.assertThat(parsed, Matchers.hasEntry("black", "20"));
32 | }
33 |
34 | /**
35 | * ParseableInformation can throw an an exception when parsing wrong info.
36 | */
37 | @Test
38 | final void throwsAnExceptionWhenParsingSomethingWrong() {
39 | final String white = "white";
40 | try {
41 | new ParseableInformation(white).information();
42 | Assertions.fail("Should never enter this assert!");
43 | } catch (final IllegalStateException ex) {
44 | MatcherAssert.assertThat(
45 | ex.getMessage(), Matchers.equalTo(
46 | String.format(
47 | StringUtils.join(
48 | "Information is not using the pattern ",
49 | "KEY1:VALUE,KEY2:VALUE %s"
50 | ), white
51 | )
52 | )
53 | );
54 | }
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/ParseableLevelInformationTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Map;
8 | import org.apache.commons.lang3.StringUtils;
9 | import org.hamcrest.MatcherAssert;
10 | import org.hamcrest.Matchers;
11 | import org.junit.jupiter.api.Assertions;
12 | import org.junit.jupiter.api.Test;
13 |
14 | /**
15 | * ParseableLevelInformation test case.
16 | * @since 0.18
17 | */
18 | class ParseableLevelInformationTest {
19 |
20 | /**
21 | * ParseableLevelInformation can parse the information correctly when it's
22 | * with the right pattern.
23 | */
24 | @Test
25 | @SuppressWarnings("PMD.UseConcurrentHashMap")
26 | final void parsesCorrectlyTheInformation() {
27 | final Map parsed = new ParseableLevelInformation(
28 | "INFO:2;10,WARN:2;32"
29 | ).information();
30 | MatcherAssert.assertThat(parsed, Matchers.hasEntry("INFO", "2;10"));
31 | MatcherAssert.assertThat(parsed, Matchers.hasEntry("WARN", "2;32"));
32 | }
33 |
34 | /**
35 | * ParseableLevelInformation can throw an exception when information is
36 | * not with the right pattern.
37 | */
38 | @Test
39 | final void throwsAnExceptionWhenParsingIncorrectInformation() {
40 | final String wrong = "INFO;10,WARN;32";
41 | try {
42 | new ParseableLevelInformation(wrong).information();
43 | Assertions.fail("Something was wrong");
44 | } catch (final IllegalStateException ex) {
45 | MatcherAssert.assertThat(
46 | ex.getMessage(), Matchers.equalTo(
47 | String.format(
48 | StringUtils.join(
49 | "Information is not using the pattern ",
50 | "KEY1:VALUE,KEY2:VALUE %s"
51 | ), wrong
52 | )
53 | )
54 | );
55 | }
56 | }
57 |
58 | /**
59 | * ParseableLevelInformation can throw an exception when passing information
60 | * with a wrong type of level.
61 | */
62 | @Test
63 | final void throwsAnExceptionWhenParsingWrongLevelType() {
64 | try {
65 | new ParseableLevelInformation(
66 | "INFO:2;10,EXTREME:2;32"
67 | ).information();
68 | Assertions.fail("");
69 | } catch (final IllegalStateException ex) {
70 | MatcherAssert.assertThat(
71 | ex.getMessage(),
72 | Matchers.equalTo("Unknown level 'EXTREME'")
73 | );
74 | }
75 | }
76 |
77 | }
78 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/PreFormatterTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import org.hamcrest.MatcherAssert;
8 | import org.hamcrest.Matchers;
9 | import org.junit.jupiter.api.Test;
10 |
11 | /**
12 | * Test case for {@link PreFormatter}.
13 | * @since 0.1
14 | */
15 | final class PreFormatterTest {
16 |
17 | /**
18 | * PreFormatter can format simple texts.
19 | */
20 | @Test
21 | void decoratesArguments() {
22 | final PreFormatter pre = new PreFormatter(
23 | "%[com.jcabi.log.DecorMocker]-5.2f and %1$+.6f",
24 | 1.0d
25 | );
26 | MatcherAssert.assertThat(
27 | pre.getFormat(),
28 | Matchers.equalTo("%-5.2f and %1$+.6f")
29 | );
30 | MatcherAssert.assertThat(
31 | pre.getArguments()[0],
32 | Matchers.instanceOf(DecorMocker.class)
33 | );
34 | }
35 |
36 | /**
37 | * PreFormatter can handle missed decors.
38 | */
39 | @Test
40 | void formatsEvenWithMissedDecors() {
41 | final PreFormatter pre =
42 | new PreFormatter("ouch: %[missed]s", "test");
43 | MatcherAssert.assertThat(
44 | pre.getFormat(),
45 | Matchers.equalTo("ouch: %s")
46 | );
47 | MatcherAssert.assertThat(
48 | pre.getArguments()[0],
49 | Matchers.instanceOf(String.class)
50 | );
51 | }
52 |
53 | /**
54 | * PreFormatter can handle directly provided decors.
55 | */
56 | @Test
57 | void formatsWithDirectlyProvidedDecors() {
58 | final DecorMocker decor = new DecorMocker("a");
59 | final PreFormatter pre = new PreFormatter("test: %s", decor);
60 | MatcherAssert.assertThat(
61 | pre.getArguments()[0],
62 | Matchers.equalTo(decor)
63 | );
64 | }
65 |
66 | /**
67 | * PreFormatter can handle new line specifier.
68 | */
69 | @Test
70 | void handleNewLineSpecifier() {
71 | final String fmt = "%s%n%s";
72 | final Object[] args = {"new", "line"};
73 | final PreFormatter pre = new PreFormatter(fmt, args);
74 | MatcherAssert.assertThat(
75 | pre.getFormat(),
76 | Matchers.is(fmt)
77 | );
78 | MatcherAssert.assertThat(
79 | pre.getArguments(),
80 | Matchers.is(args)
81 | );
82 | }
83 |
84 | /**
85 | * PreFormatter can handle percent specifier.
86 | */
87 | @Test
88 | void handlePercentSpecifier() {
89 | final String fmt = "%s%%";
90 | final Object[] args = {"percent: "};
91 | final PreFormatter pre = new PreFormatter(fmt, args);
92 | MatcherAssert.assertThat(
93 | pre.getFormat(),
94 | Matchers.is(fmt)
95 | );
96 | MatcherAssert.assertThat(
97 | pre.getArguments(),
98 | Matchers.is(args)
99 | );
100 | }
101 |
102 | }
103 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/Printed.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.io.ByteArrayOutputStream;
8 | import java.util.Formattable;
9 | import java.util.Formatter;
10 |
11 | /**
12 | * Prints decor.
13 | *
14 | * @since 0.1
15 | */
16 | public final class Printed {
17 |
18 | /**
19 | * The decor.
20 | */
21 | private final transient Formattable decor;
22 |
23 | /**
24 | * Formatting flags.
25 | */
26 | private final transient int flags;
27 |
28 | /**
29 | * Formatting width.
30 | */
31 | private final transient int width;
32 |
33 | /**
34 | * Formatting precision.
35 | */
36 | private final transient int precision;
37 |
38 | /**
39 | * Public ctor.
40 | * @param dcr Decor
41 | * @param flgs Flags
42 | * @param wdt Width
43 | * @param prcs Precision
44 | * @checkstyle ParameterNumber (3 lines)
45 | */
46 | public Printed(final Formattable dcr,
47 | final int flgs, final int wdt, final int prcs) {
48 | this.decor = dcr;
49 | this.flags = flgs;
50 | this.width = wdt;
51 | this.precision = prcs;
52 | }
53 |
54 | @Override
55 | public String toString() {
56 | final ByteArrayOutputStream baos = new ByteArrayOutputStream();
57 | final Formatter fmt = new Formatter(baos);
58 | this.decor.formatTo(fmt, this.flags, this.width, this.precision);
59 | fmt.flush();
60 | return baos.toString();
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/SecretDecorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Arrays;
8 | import java.util.Collection;
9 | import java.util.FormattableFlags;
10 | import java.util.Locale;
11 | import org.hamcrest.MatcherAssert;
12 | import org.hamcrest.Matchers;
13 | import org.junit.jupiter.params.ParameterizedTest;
14 | import org.junit.jupiter.params.provider.MethodSource;
15 |
16 | /**
17 | * Test case for {@link SecretDecor}.
18 | *
19 | * @since 0.1
20 | * @checkstyle ParameterNumberCheck (500 lines)
21 | */
22 | final class SecretDecorTest {
23 |
24 | @ParameterizedTest
25 | @MethodSource("params")
26 | void testPrintsRight(final Object list, final String text,
27 | final int flags, final int width, final int precision) {
28 | Locale.setDefault(Locale.US);
29 | MatcherAssert.assertThat(
30 | new Printed(new SecretDecor(list), flags, width, precision),
31 | Matchers.hasToString(text)
32 | );
33 | }
34 |
35 | @ParameterizedTest
36 | @MethodSource("params")
37 | void testLogsRight(final Object list, final String text,
38 | final int flags, final int width, final int precision) {
39 | Locale.setDefault(Locale.US);
40 | MatcherAssert.assertThat(
41 | new Logged(new SecretDecor(list), flags, width, precision),
42 | Matchers.hasToString(text)
43 | );
44 | }
45 |
46 | /**
47 | * Params for this parametrized test.
48 | * @return Array of arrays of params for ctor
49 | */
50 | @SuppressWarnings("PMD.UnusedPrivateMethod")
51 | private static Collection params() {
52 | return Arrays.asList(
53 | new Object[][] {
54 | {"testing", "t***g", 0, 0, 0},
55 | {"ouch", "o***h ", FormattableFlags.LEFT_JUSTIFY, 7, 5},
56 | {"x", " X***X", FormattableFlags.UPPERCASE, 6, 0},
57 | {null, "NULL", FormattableFlags.UPPERCASE, 6, 0},
58 | }
59 | );
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/SizeDecorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Arrays;
8 | import java.util.Collection;
9 | import java.util.FormattableFlags;
10 | import java.util.Locale;
11 | import org.hamcrest.MatcherAssert;
12 | import org.hamcrest.Matchers;
13 | import org.junit.jupiter.api.Test;
14 | import org.junit.jupiter.params.ParameterizedTest;
15 | import org.junit.jupiter.params.provider.MethodSource;
16 |
17 | /**
18 | * Test case for {@link SizeDecor}.
19 | *
20 | * @since 0.1
21 | * @checkstyle ParameterNumberCheck (500 lines)
22 | */
23 | final class SizeDecorTest {
24 |
25 | @ParameterizedTest
26 | @MethodSource("params")
27 | void testPrintsRight(final long size, final String text,
28 | final int flags, final int width, final int precision) {
29 | Locale.setDefault(Locale.US);
30 | MatcherAssert.assertThat(
31 | new Printed(new SizeDecor(size), flags, width, precision),
32 | Matchers.hasToString(text)
33 | );
34 | }
35 |
36 | @ParameterizedTest
37 | @MethodSource("params")
38 | void testLogsRight(final long size, final String text,
39 | final int flags, final int width, final int precision) {
40 | Locale.setDefault(Locale.US);
41 | MatcherAssert.assertThat(
42 | new Logged(new SizeDecor(size), flags, width, precision),
43 | Matchers.hasToString(text)
44 | );
45 | }
46 |
47 | @Test
48 | void testPrintsNullRight() {
49 | MatcherAssert.assertThat(
50 | new Logged(new SizeDecor(null), 0, 0, 0),
51 | Matchers.hasToString("NULL")
52 | );
53 | }
54 |
55 | /**
56 | * Params for this parametrized test.
57 | * @return Array of arrays of params for ctor
58 | */
59 | @SuppressWarnings("PMD.UnusedPrivateMethod")
60 | private static Collection params() {
61 | return Arrays.asList(
62 | new Object[][] {
63 | {1L, "1b", 0, 0, 0},
64 | {123L, " 123b", 0, 6, 0},
65 | {1024L, "1.000Kb", 0, 0, 3},
66 | {5120L, "5Kb", 0, 0, 0},
67 | {12_345L, "12.056Kb", 0, 0, 3},
68 | {12_345L, "12.1Kb ", FormattableFlags.LEFT_JUSTIFY, 8, 1},
69 | {98_765_432L, "94.190MB", FormattableFlags.UPPERCASE, 0, 3},
70 | {98_765_432L, "94.190Mb", 0, 0, 3},
71 | {90L * 1024L * 1024L * 1024L, "90Gb", 0, 0, 0},
72 | {13L * 1024L * 1024L * 1024L * 1024L, "13Tb", 0, 0, 0},
73 | {33L * 1024L * 1024L * 1024L * 1024L * 1024L, "33Pb", 0, 0, 0},
74 | {3L * 1024L * 1024L * 1024L * 1024L * 1024L * 1024L, "3Eb", 0, 0, 0},
75 | }
76 | );
77 | }
78 |
79 | }
80 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/SupplierLoggerTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import org.apache.log4j.Level;
8 | import org.hamcrest.MatcherAssert;
9 | import org.hamcrest.Matchers;
10 | import org.junit.jupiter.api.Disabled;
11 | import org.junit.jupiter.api.Test;
12 |
13 | /**
14 | * Test case for {@link SupplierLogger}.
15 | * @since 0.18
16 | * @todo #100:30min Some tests here are ignored since they conflict
17 | * in multi-threading run. I don't know exactly how to fix them,
18 | * but we need to fix and remove the "Ignore" annotations.
19 | */
20 | @SuppressWarnings("PMD.MoreThanOneLogger")
21 | final class SupplierLoggerTest {
22 |
23 | @Test
24 | void debugIsDisabled() {
25 | final String name = "nodebug";
26 | final String appender = "nodebugapp";
27 | final org.apache.log4j.Logger logger = this.loggerForTest(
28 | name, appender, Level.ERROR
29 | );
30 | SupplierLogger.debug(
31 | name, "Debug disabled: %s",
32 | (Supplier) () -> "test1"
33 | );
34 | MatcherAssert.assertThat(
35 | ((UnitTestAppender) logger.getAppender(appender)).output(),
36 | Matchers.emptyString()
37 | );
38 | }
39 |
40 | @Test
41 | @Disabled
42 | void debugIsEnabled() {
43 | final String name = "debugen";
44 | final String appender = "debugapp";
45 | final org.apache.log4j.Logger logger = this.loggerForTest(
46 | name, appender, Level.DEBUG
47 | );
48 | final String text = "test2";
49 | SupplierLogger.debug(
50 | name, "Debug enabled: %s",
51 | (Supplier) () -> text
52 | );
53 | MatcherAssert.assertThat(
54 | ((UnitTestAppender) logger.getAppender(appender)).output(),
55 | Matchers.containsString(text)
56 | );
57 | }
58 |
59 | @Test
60 | void traceIsDisabled() {
61 | final String name = "notrace";
62 | final String appender = "notraceapp";
63 | final org.apache.log4j.Logger logger = this.loggerForTest(
64 | name, appender, Level.ERROR
65 | );
66 | SupplierLogger.trace(
67 | name, "Trace disabled: %s",
68 | (Supplier) () -> "test3"
69 | );
70 | MatcherAssert.assertThat(
71 | ((UnitTestAppender) logger.getAppender(appender)).output(),
72 | Matchers.emptyString()
73 | );
74 | }
75 |
76 | @Test
77 | void traceIsEnabled() {
78 | final String name = "enabledtrace";
79 | final String appender = "traceapp";
80 | final org.apache.log4j.Logger logger = this.loggerForTest(
81 | name, appender, Level.TRACE
82 | );
83 | final String text = "text4";
84 | SupplierLogger.trace(
85 | name, "Trace enabled: %s",
86 | (Supplier) () -> text
87 | );
88 | MatcherAssert.assertThat(
89 | ((UnitTestAppender) logger.getAppender(appender)).output(),
90 | Matchers.containsString(text)
91 | );
92 | }
93 |
94 | @Test
95 | void warnIsDisabled() {
96 | final String name = "nowarn";
97 | final String appender = "nowarnapp";
98 | final org.apache.log4j.Logger logger = this.loggerForTest(
99 | name, appender, Level.ERROR
100 | );
101 | SupplierLogger.warn(
102 | name, "Warn disabled: %s",
103 | (Supplier) () -> "test5"
104 | );
105 | MatcherAssert.assertThat(
106 | ((UnitTestAppender) logger.getAppender(appender)).output(),
107 | Matchers.emptyString()
108 | );
109 | }
110 |
111 | @Test
112 | @Disabled
113 | void warnIsEnabled() {
114 | final String name = "enwarn";
115 | final String appender = "warnapp";
116 | final org.apache.log4j.Logger logger = this.loggerForTest(
117 | name, appender, Level.WARN
118 | );
119 | final String text = "test6";
120 | SupplierLogger.warn(
121 | name, "Warn enabled: %s",
122 | (Supplier) () -> text
123 | );
124 | MatcherAssert.assertThat(
125 | ((UnitTestAppender) logger.getAppender(appender)).output(),
126 | Matchers.containsString(text)
127 | );
128 | }
129 |
130 | @Test
131 | void infoIsDisabled() {
132 | final String name = "noinfo";
133 | final String appender = "noinfoapp";
134 | final org.apache.log4j.Logger logger = this.loggerForTest(
135 | name, appender, Level.WARN
136 | );
137 | SupplierLogger.info(
138 | name, "Info disabled: %s",
139 | (Supplier) () -> "test7"
140 | );
141 | MatcherAssert.assertThat(
142 | ((UnitTestAppender) logger.getAppender(appender)).output(),
143 | Matchers.emptyString()
144 | );
145 | }
146 |
147 | @Test
148 | @Disabled
149 | void infoIsEnabled() {
150 | final String name = "withinfo";
151 | final String appender = "infoapp";
152 | final org.apache.log4j.Logger logger = this.loggerForTest(
153 | name, appender, Level.INFO
154 | );
155 | final String text = "text8";
156 | SupplierLogger.info(
157 | name, "Info enabled: %s",
158 | (Supplier) () -> text
159 | );
160 | MatcherAssert.assertThat(
161 | ((UnitTestAppender) logger.getAppender(appender)).output(),
162 | Matchers.containsString(text)
163 | );
164 | }
165 |
166 | /**
167 | * Builds a logger for each test method.
168 | * @param name Logger's name
169 | * @param appender Appender's name
170 | * @param level Logging level
171 | * @return Logger for test
172 | */
173 | private org.apache.log4j.Logger loggerForTest(
174 | final String name, final String appender, final Level level) {
175 | final org.apache.log4j.Logger logger = org.apache.log4j.Logger
176 | .getLogger(name);
177 | final UnitTestAppender app = new UnitTestAppender(appender);
178 | app.activateOptions();
179 | logger.addAppender(app);
180 | logger.setLevel(level);
181 | return logger;
182 | }
183 |
184 | }
185 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/TextDecorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.nio.charset.Charset;
8 | import java.util.Arrays;
9 | import java.util.Collection;
10 | import java.util.Formattable;
11 | import java.util.Formatter;
12 | import java.util.Locale;
13 | import org.apache.commons.lang3.StringUtils;
14 | import org.hamcrest.MatcherAssert;
15 | import org.hamcrest.Matchers;
16 | import org.junit.jupiter.api.Assumptions;
17 | import org.junit.jupiter.api.Test;
18 | import org.junit.jupiter.params.ParameterizedTest;
19 | import org.junit.jupiter.params.provider.MethodSource;
20 |
21 | /**
22 | * Test case for {@link TextDecor}.
23 | *
24 | * @since 0.1
25 | * @checkstyle ParameterNumberCheck (500 lines)
26 | */
27 | final class TextDecorTest {
28 |
29 | @ParameterizedTest
30 | @MethodSource("params")
31 | void testPrintsRight(final String obj, final String text,
32 | final int flags, final int width, final int precision) {
33 | Assumptions.assumeTrue("UTF-8".equals(Charset.defaultCharset().name()));
34 | Locale.setDefault(Locale.US);
35 | MatcherAssert.assertThat(
36 | new Printed(new TextDecor(obj), flags, width, precision),
37 | Matchers.hasToString(text)
38 | );
39 | }
40 |
41 | @ParameterizedTest
42 | @MethodSource("params")
43 | void testLogsRight(final String obj, final String text,
44 | final int flags, final int width, final int precision) {
45 | Assumptions.assumeTrue("UTF-8".equals(Charset.defaultCharset().name()));
46 | Locale.setDefault(Locale.US);
47 | MatcherAssert.assertThat(
48 | new Logged(new TextDecor(obj), flags, width, precision),
49 | Matchers.hasToString(text)
50 | );
51 | }
52 |
53 | /**
54 | * Test for a long text.
55 | */
56 | @Test
57 | void compressesLongText() {
58 | final int len = 1000;
59 | final String text = StringUtils.repeat('x', len);
60 | final Formattable fmt = new TextDecor(text);
61 | final StringBuilder output = new StringBuilder(100);
62 | fmt.formatTo(new Formatter(output), 0, 0, 0);
63 | MatcherAssert.assertThat(
64 | output.length(),
65 | Matchers.describedAs(
66 | output.toString(),
67 | Matchers.equalTo(TextDecor.MAX)
68 | )
69 | );
70 | }
71 |
72 | /**
73 | * Params for this parametrized test.
74 | * @return Array of arrays of params for ctor
75 | */
76 | @SuppressWarnings(
77 | {
78 | "PMD.AvoidDuplicateLiterals",
79 | "PMD.UnusedPrivateMethod"
80 | }
81 | )
82 | private static Collection params() {
83 | return Arrays.asList(
84 | new Object[][] {
85 | // @checkstyle MultipleStringLiterals (1 line)
86 | {"simple text", "simple text", 0, 0, 0},
87 | {null, "NULL", 0, 0, 0},
88 | // @checkstyle MultipleStringLiteralsCheck (1 line)
89 | {"\u0433!", "\u0433!", 0, 0, 0},
90 | }
91 | );
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/TypeDecorTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.Arrays;
8 | import java.util.Collection;
9 | import java.util.Locale;
10 | import org.hamcrest.MatcherAssert;
11 | import org.hamcrest.Matchers;
12 | import org.junit.jupiter.params.ParameterizedTest;
13 | import org.junit.jupiter.params.provider.MethodSource;
14 |
15 | /**
16 | * Test case for {@link TypeDecor}.
17 | *
18 | * @since 0.1
19 | * @checkstyle ParameterNumberCheck (500 lines)
20 | */
21 | final class TypeDecorTest {
22 |
23 | @ParameterizedTest
24 | @MethodSource("params")
25 | void testPrintsRight(final Object list, final String text,
26 | final int flags, final int width, final int precision) {
27 | Locale.setDefault(Locale.US);
28 | MatcherAssert.assertThat(
29 | new Printed(new TypeDecor(list), flags, width, precision),
30 | Matchers.hasToString(text)
31 | );
32 | }
33 |
34 | @ParameterizedTest
35 | @MethodSource("params")
36 | void testLogsRight(final Object list, final String text,
37 | final int flags, final int width, final int precision) {
38 | Locale.setDefault(Locale.US);
39 | MatcherAssert.assertThat(
40 | new Logged(new TypeDecor(list), flags, width, precision),
41 | Matchers.hasToString(text)
42 | );
43 | }
44 |
45 | /**
46 | * Params for this parametrized test.
47 | * @return Array of arrays of params for ctor
48 | */
49 | @SuppressWarnings("PMD.UnusedPrivateMethod")
50 | private static Collection params() {
51 | return Arrays.asList(
52 | new Object[][] {
53 | {"testing", "java.lang.String", 0, 0, 0},
54 | {null, "NULL", 0, 0, 0},
55 | {1.0d, "java.lang.Double", 0, 0, 0},
56 | }
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/UnitTestAppender.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 |
6 | package com.jcabi.log;
7 |
8 | import java.io.ByteArrayOutputStream;
9 | import java.nio.charset.StandardCharsets;
10 | import org.apache.log4j.PatternLayout;
11 | import org.apache.log4j.WriterAppender;
12 |
13 | /**
14 | * Log4j appender for unit tests. Normally, we could use
15 | * slf4j-test , but we
16 | * have log4j in the classpath anyway, for {@link MulticolorLayout}.
17 | * @since 0.18
18 | */
19 | final class UnitTestAppender extends WriterAppender {
20 |
21 | /**
22 | * OutputStream where this Appender writes.
23 | */
24 | private final transient ByteArrayOutputStream logs;
25 |
26 | /**
27 | * Ctor.
28 | * @param name The appender's name
29 | */
30 | @SuppressWarnings("PMD.ConstructorOnlyInitializesOrCallOtherConstructors")
31 | UnitTestAppender(final String name) {
32 | super();
33 | this.setName(name);
34 | this.logs = new ByteArrayOutputStream();
35 | }
36 |
37 | /**
38 | * Prepares the appender for use.
39 | */
40 | public void activateOptions() {
41 | this.setWriter(this.createWriter(this.logs));
42 | this.setLayout(new PatternLayout("%d %c{1} - %m%n"));
43 | super.activateOptions();
44 | }
45 |
46 | /**
47 | * Return the logged messages.
48 | * @return String logs
49 | */
50 | public String output() {
51 | return new String(this.logs.toByteArray(), StandardCharsets.UTF_8);
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/VerboseCallableTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import org.junit.jupiter.api.Assertions;
8 | import org.junit.jupiter.api.Test;
9 |
10 | /**
11 | * Test case for {@link VerboseCallable}.
12 | * @since 0.16
13 | */
14 | @SuppressWarnings({ "PMD.DoNotUseThreads", "PMD.TooManyMethods" })
15 | final class VerboseCallableTest {
16 |
17 | /**
18 | * VerboseCallable can log exceptions inside Callable.
19 | */
20 | @Test
21 | void logsExceptionsInCallable() {
22 | Assertions.assertThrows(
23 | IllegalArgumentException.class,
24 | () -> new VerboseCallable(
25 | () -> {
26 | throw new IllegalArgumentException("oops");
27 | }
28 | ).call()
29 | );
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/VerboseRunnableTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.concurrent.Callable;
8 | import java.util.concurrent.Executors;
9 | import java.util.concurrent.ScheduledExecutorService;
10 | import java.util.concurrent.TimeUnit;
11 | import java.util.concurrent.atomic.AtomicInteger;
12 | import java.util.concurrent.atomic.AtomicReference;
13 | import org.hamcrest.MatcherAssert;
14 | import org.hamcrest.Matchers;
15 | import org.junit.jupiter.api.Assertions;
16 | import org.junit.jupiter.api.Test;
17 |
18 | /**
19 | * Test case for {@link VerboseRunnable}.
20 | * @since 0.1
21 | */
22 | @SuppressWarnings({ "PMD.DoNotUseThreads", "PMD.TooManyMethods" })
23 | final class VerboseRunnableTest {
24 |
25 | @Test
26 | void logsExceptionsInRunnable() {
27 | Assertions.assertThrows(
28 | IllegalArgumentException.class,
29 | () -> new VerboseRunnable(
30 | (Runnable) () -> {
31 | throw new IllegalArgumentException("oops");
32 | }
33 | ).run()
34 | );
35 | }
36 |
37 | @Test
38 | void swallowsExceptionsInRunnable() {
39 | new VerboseRunnable(
40 | (Runnable) () -> {
41 | throw new IllegalArgumentException("boom");
42 | },
43 | true
44 | ).run();
45 | }
46 |
47 | @Test
48 | void swallowsExceptionsInCallable() {
49 | new VerboseRunnable(
50 | () -> {
51 | throw new IllegalArgumentException("boom-2");
52 | },
53 | true
54 | ).run();
55 | }
56 |
57 | @Test
58 | void translatesToStringFromUnderlyingRunnable() {
59 | final String text = "some text abc";
60 | final Runnable verbose = new VerboseRunnable(
61 | new Runnable() {
62 | @Override
63 | public void run() {
64 | assert true;
65 | }
66 |
67 | @Override
68 | public String toString() {
69 | return text;
70 | }
71 | }
72 | );
73 | MatcherAssert.assertThat(
74 | verbose,
75 | Matchers.hasToString(Matchers.containsString(text))
76 | );
77 | }
78 |
79 | @Test
80 | void translatesToStringFromUnderlyingCallable() {
81 | final String text = "some text abc-2";
82 | final Runnable verbose = new VerboseRunnable(
83 | new Callable() {
84 | @Override
85 | public Void call() {
86 | return null;
87 | }
88 |
89 | @Override
90 | public String toString() {
91 | return text;
92 | }
93 | },
94 | true
95 | );
96 | MatcherAssert.assertThat(
97 | verbose,
98 | Matchers.hasToString(Matchers.containsString(text))
99 | );
100 | }
101 |
102 | @Test
103 | void preservesInterruptedStatus() throws Exception {
104 | final ScheduledExecutorService svc =
105 | Executors.newSingleThreadScheduledExecutor();
106 | final AtomicReference thread = new AtomicReference<>();
107 | final AtomicInteger runs = new AtomicInteger();
108 | svc.scheduleWithFixedDelay(
109 | new VerboseRunnable(
110 | () -> {
111 | runs.addAndGet(1);
112 | thread.set(Thread.currentThread());
113 | TimeUnit.HOURS.sleep(1L);
114 | return null;
115 | },
116 | true,
117 | false
118 | ),
119 | 1L, 1L,
120 | TimeUnit.MICROSECONDS
121 | );
122 | while (thread.get() == null) {
123 | TimeUnit.MILLISECONDS.sleep(1L);
124 | }
125 | thread.get().interrupt();
126 | TimeUnit.SECONDS.sleep(1L);
127 | svc.shutdown();
128 | MatcherAssert.assertThat(runs.get(), Matchers.is(1));
129 | MatcherAssert.assertThat(
130 | svc.awaitTermination(1L, TimeUnit.SECONDS),
131 | Matchers.is(true)
132 | );
133 | }
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/VerboseThreadsTest.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 | package com.jcabi.log;
6 |
7 | import java.util.concurrent.ExecutorService;
8 | import java.util.concurrent.Executors;
9 | import java.util.concurrent.Future;
10 | import java.util.concurrent.TimeUnit;
11 | import org.junit.jupiter.api.Test;
12 |
13 | /**
14 | * Test case for {@link VerboseThreads}.
15 | * @since 0.1
16 | */
17 | @SuppressWarnings("PMD.DoNotUseThreads")
18 | final class VerboseThreadsTest {
19 |
20 | @Test
21 | void instantiatesThreadsOnDemand() throws Exception {
22 | final ExecutorService service = Executors
23 | .newSingleThreadExecutor(new VerboseThreads("foo"));
24 | service.execute(
25 | () -> {
26 | throw new IllegalArgumentException("oops");
27 | }
28 | );
29 | TimeUnit.SECONDS.sleep(1L);
30 | service.shutdown();
31 | }
32 |
33 | @Test
34 | void logsWhenThreadsAreNotDying() throws Exception {
35 | final ExecutorService service = Executors
36 | .newSingleThreadExecutor(new VerboseThreads(this));
37 | final Future> future = service.submit(
38 | (Runnable) () -> {
39 | throw new IllegalArgumentException("boom");
40 | }
41 | );
42 | while (!future.isDone()) {
43 | TimeUnit.SECONDS.sleep(1L);
44 | }
45 | service.shutdown();
46 | }
47 |
48 | }
49 |
--------------------------------------------------------------------------------
/src/test/java/com/jcabi/log/package-info.java:
--------------------------------------------------------------------------------
1 | /*
2 | * SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
3 | * SPDX-License-Identifier: MIT
4 | */
5 |
6 | /**
7 | * Convenient utils, tests.
8 | */
9 | package com.jcabi.log;
10 |
--------------------------------------------------------------------------------
/src/test/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | # SPDX-FileCopyrightText: Copyright (c) 2012-2025 Yegor Bugayenko
2 | # SPDX-License-Identifier: MIT
3 |
4 | # Set root logger level to DEBUG and its only appender to CONSOLE
5 | log4j.rootLogger=WARN, CONSOLE
6 |
7 | # "Console" is set to be a ConsoleAppender.
8 | log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
9 | log4j.appender.CONSOLE.layout=com.jcabi.log.MulticolorLayout
10 | log4j.appender.CONSOLE.layout.ConversionPattern=[%color{%p}] %t %c: %m%n
11 |
12 | # Application-specific logging
13 | log4j.logger.com.jcabi=DEBUG
14 |
--------------------------------------------------------------------------------