Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
VerboseThreads |
|
| 1.4;1.4 | ||||
VerboseThreads$Group |
|
| 1.4;1.4 | ||||
VerboseThreads$Wrap |
|
| 1.4;1.4 |
1 | /** | |
2 | * Copyright (c) 2012-2015, jcabi.com | |
3 | * All rights reserved. | |
4 | * | |
5 | * Redistribution and use in source and binary forms, with or without | |
6 | * modification, are permitted provided that the following conditions | |
7 | * are met: 1) Redistributions of source code must retain the above | |
8 | * copyright notice, this list of conditions and the following | |
9 | * disclaimer. 2) Redistributions in binary form must reproduce the above | |
10 | * copyright notice, this list of conditions and the following | |
11 | * disclaimer in the documentation and/or other materials provided | |
12 | * with the distribution. 3) Neither the name of the jcabi.com nor | |
13 | * the names of its contributors may be used to endorse or promote | |
14 | * products derived from this software without specific prior written | |
15 | * permission. | |
16 | * | |
17 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | |
18 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT | |
19 | * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND | |
20 | * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL | |
21 | * THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, | |
22 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | |
23 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
24 | * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
25 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, | |
26 | * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) | |
27 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED | |
28 | * OF THE POSSIBILITY OF SUCH DAMAGE. | |
29 | */ | |
30 | package com.jcabi.log; | |
31 | ||
32 | import java.util.concurrent.ThreadFactory; | |
33 | import java.util.concurrent.atomic.AtomicInteger; | |
34 | import lombok.EqualsAndHashCode; | |
35 | import lombok.ToString; | |
36 | ||
37 | /** | |
38 | * Convenient {@link ThreadFactory}, that logs all uncaught exceptions. | |
39 | * | |
40 | * <p>The factory should be used together | |
41 | * with executor services from {@code java.util.concurrent} package. Without | |
42 | * these "verbose" threads your runnable tasks will not report anything to | |
43 | * console once they die because of a runtime exception, for example: | |
44 | * | |
45 | * <pre> Executors.newScheduledThreadPool(2).scheduleAtFixedRate( | |
46 | * new Runnable() { | |
47 | * @Override | |
48 | * public void run() { | |
49 | * // some sensitive operation that may throw | |
50 | * // a runtime exception | |
51 | * }, | |
52 | * 1L, 1L, TimeUnit.SECONDS | |
53 | * } | |
54 | * );</pre> | |
55 | * | |
56 | * <p>The exception in this example will never be caught by nobody. It will | |
57 | * just terminate current execution of the {@link Runnable} task. Moreover, | |
58 | * it won't reach any {@link Thread.UncaughtExceptionHandler}, | |
59 | * because this | |
60 | * is how {@link java.util.concurrent.ScheduledExecutorService} | |
61 | * is behaving. This is how we solve | |
62 | * the problem with {@link VerboseThreads}: | |
63 | * | |
64 | * <pre> ThreadFactory factory = new VerboseThreads(); | |
65 | * Executors.newScheduledThreadPool(2, factory).scheduleAtFixedRate( | |
66 | * new Runnable() { | |
67 | * @Override | |
68 | * public void run() { | |
69 | * // the same sensitive operation that may throw | |
70 | * // a runtime exception | |
71 | * }, | |
72 | * 1L, 1L, TimeUnit.SECONDS | |
73 | * } | |
74 | * );</pre> | |
75 | * | |
76 | * <p>Now, every runtime exception that is not caught inside your | |
77 | * {@link Runnable} will be reported to log (using {@link Logger}). | |
78 | * | |
79 | * <p>This class is thread-safe. | |
80 | * | |
81 | * @author Yegor Bugayenko (yegor@teamed.io) | |
82 | * @version $Id: ab2ee168b7585550ffd05251b381a8999de83786 $ | |
83 | * @since 0.1.2 | |
84 | * @see VerboseRunnable | |
85 | */ | |
86 | 0 | @ToString |
87 | 0 | @EqualsAndHashCode(of = { "group", "prefix", "number", "daemon", "priority" }) |
88 | @SuppressWarnings("PMD.DoNotUseThreads") | |
89 | public final class VerboseThreads implements ThreadFactory { | |
90 | ||
91 | /** | |
92 | * Thread group. | |
93 | */ | |
94 | private final transient ThreadGroup group; | |
95 | ||
96 | /** | |
97 | * Prefix to use. | |
98 | */ | |
99 | private final transient String prefix; | |
100 | ||
101 | /** | |
102 | * Number of the next thread to create. | |
103 | */ | |
104 | 0 | private final transient AtomicInteger number = new AtomicInteger(1); |
105 | ||
106 | /** | |
107 | * Create threads as daemons? | |
108 | */ | |
109 | private final transient boolean daemon; | |
110 | ||
111 | /** | |
112 | * Default thread priority. | |
113 | */ | |
114 | private final transient int priority; | |
115 | ||
116 | /** | |
117 | * Default constructor ({@code "verbose"} as a prefix, threads are daemons, | |
118 | * default thread priority is {@code 1}). | |
119 | */ | |
120 | public VerboseThreads() { | |
121 | 0 | this("verbose", true, 1); |
122 | 0 | } |
123 | ||
124 | /** | |
125 | * Detailed constructor, with a prefix of thread names (threads are daemons, | |
126 | * default thread priority is {@code 1}). | |
127 | * @param pfx Prefix for thread names | |
128 | */ | |
129 | public VerboseThreads(final String pfx) { | |
130 | 0 | this(pfx, true, 1); |
131 | 0 | } |
132 | ||
133 | /** | |
134 | * Detailed constructor, with a prefix of thread names (threads are daemons, | |
135 | * default thread priority is {@code 1}). | |
136 | * @param type Prefix will be build from this type name | |
137 | */ | |
138 | public VerboseThreads(final Object type) { | |
139 | 0 | this(type.getClass().getSimpleName(), true, 1); |
140 | 0 | } |
141 | ||
142 | /** | |
143 | * Detailed constructor, with a prefix of thread names (threads are daemons, | |
144 | * default thread priority is {@code 1}). | |
145 | * @param type Prefix will be build from this type name | |
146 | */ | |
147 | public VerboseThreads(final Class<?> type) { | |
148 | 0 | this(type.getSimpleName(), true, 1); |
149 | 0 | } |
150 | ||
151 | /** | |
152 | * Detailed constructor. | |
153 | * @param pfx Prefix for thread names | |
154 | * @param dmn Threads should be daemons? | |
155 | * @param prt Default priority for all threads | |
156 | */ | |
157 | public VerboseThreads(final String pfx, final boolean dmn, | |
158 | 0 | final int prt) { |
159 | 0 | this.prefix = pfx; |
160 | 0 | this.daemon = dmn; |
161 | 0 | this.priority = prt; |
162 | 0 | this.group = new VerboseThreads.Group(pfx); |
163 | 0 | } |
164 | ||
165 | @Override | |
166 | public Thread newThread(final Runnable runnable) { | |
167 | 0 | final Thread thread = new Thread( |
168 | this.group, | |
169 | new VerboseThreads.Wrap(runnable) | |
170 | ); | |
171 | 0 | thread.setName( |
172 | String.format( | |
173 | "%s-%d", | |
174 | this.prefix, | |
175 | this.number.getAndIncrement() | |
176 | ) | |
177 | ); | |
178 | 0 | thread.setDaemon(this.daemon); |
179 | 0 | thread.setPriority(this.priority); |
180 | 0 | return thread; |
181 | } | |
182 | ||
183 | /** | |
184 | * Group to use. | |
185 | */ | |
186 | private static final class Group extends ThreadGroup { | |
187 | /** | |
188 | * Ctor. | |
189 | * @param name Name of it | |
190 | */ | |
191 | Group(final String name) { | |
192 | 0 | super(name); |
193 | 0 | } |
194 | @Override | |
195 | public void uncaughtException(final Thread thread, | |
196 | final Throwable throwable) { | |
197 | 0 | Logger.warn(this, "%[exception]s", throwable); |
198 | 0 | } |
199 | } | |
200 | ||
201 | /** | |
202 | * Runnable decorator. | |
203 | */ | |
204 | private static final class Wrap implements Runnable { | |
205 | /** | |
206 | * Origin runnable. | |
207 | */ | |
208 | private final transient Runnable origin; | |
209 | /** | |
210 | * Ctor. | |
211 | * @param runnable Origin runnable | |
212 | */ | |
213 | 0 | Wrap(final Runnable runnable) { |
214 | 0 | this.origin = runnable; |
215 | 0 | } |
216 | @Override | |
217 | @SuppressWarnings("PMD.AvoidCatchingGenericException") | |
218 | public void run() { | |
219 | try { | |
220 | 0 | this.origin.run(); |
221 | // @checkstyle IllegalCatch (1 line) | |
222 | 0 | } catch (final RuntimeException ex) { |
223 | 0 | Logger.warn( |
224 | this, | |
225 | "%s: %[exception]s", | |
226 | Thread.currentThread().getName(), | |
227 | ex | |
228 | ); | |
229 | 0 | throw ex; |
230 | // @checkstyle IllegalCatch (1 line) | |
231 | 0 | } catch (final Error error) { |
232 | 0 | Logger.error( |
233 | this, | |
234 | "%s (error): %[exception]s", | |
235 | Thread.currentThread().getName(), | |
236 | error | |
237 | ); | |
238 | 0 | throw error; |
239 | 0 | } |
240 | 0 | } |
241 | } | |
242 | ||
243 | } |