1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18 package org.apache.log4j;
19
20 import org.apache.log4j.helpers.LogLog;
21 import org.apache.log4j.or.ObjectRenderer;
22 import org.apache.log4j.or.RendererMap;
23 import org.apache.log4j.plugins.Plugin;
24 import org.apache.log4j.plugins.PluginRegistry;
25 import org.apache.log4j.scheduler.Scheduler;
26 import org.apache.log4j.spi.*;
27 import org.apache.log4j.xml.DOMConfigurator;
28 import org.apache.log4j.xml.UnrecognizedElementHandler;
29 import org.w3c.dom.Element;
30
31 import java.util.*;
32
33
34 /**
35 * This class implements LoggerRepositoryEx by
36 * wrapping an existing LoggerRepository implementation
37 * and implementing the newly added capabilities.
38 */
39 public final class LoggerRepositoryExImpl
40 implements LoggerRepositoryEx,
41 RendererSupport,
42 UnrecognizedElementHandler {
43
44 /**
45 * Wrapped logger repository.
46 */
47 private final LoggerRepository repo;
48
49 /**
50 * Logger factory. Does not affect class of logger
51 * created by underlying repository.
52 */
53 private LoggerFactory loggerFactory;
54
55 /**
56 * Renderer support.
57 */
58 private final RendererSupport rendererSupport;
59
60 /**
61 * List of repository event listeners.
62 */
63 private final ArrayList<LoggerRepositoryEventListener> repositoryEventListeners = new ArrayList<>();
64 /**
65 * Map of HierarchyEventListener keyed by LoggingEventListener.
66 */
67 private final Map<LoggerEventListener, HierarchyEventListenerProxy> loggerEventListeners = new HashMap<>();
68 /**
69 * Name of hierarchy.
70 */
71 private String name;
72 /**
73 * Plug in registry.
74 */
75 private PluginRegistry pluginRegistry;
76 /**
77 * Properties.
78 */
79 private final Map<String, String> properties = new Hashtable<>();
80 /**
81 * Scheduler.
82 */
83 private Scheduler scheduler;
84
85 /**
86 * The repository can also be used as an object store
87 * for various objects used by log4j components.
88 */
89 private Map<String, Object> objectMap = new HashMap<>();
90
91
92 /**
93 * Error list.
94 */
95 private List<ErrorItem> errorList = new Vector<>();
96
97 /**
98 * True if hierarchy has not been modified.
99 */
100 private boolean pristine = true;
101
102 /**
103 * Constructs a new logger hierarchy.
104 *
105 * @param repository Base implementation of repository.
106 */
107 public LoggerRepositoryExImpl(final LoggerRepository repository) {
108 super();
109 if (repository == null) {
110 throw new NullPointerException("repository");
111 }
112 repo = repository;
113 if (repository instanceof RendererSupport) {
114 rendererSupport = (RendererSupport) repository;
115 } else {
116 rendererSupport = new RendererSupportImpl();
117 }
118 }
119
120
121 /**
122 * Add a {@link LoggerRepositoryEventListener} to the repository. The
123 * listener will be called when repository events occur.
124 *
125 * @param listener listener
126 */
127 public void addLoggerRepositoryEventListener(
128 final LoggerRepositoryEventListener listener) {
129 synchronized (repositoryEventListeners) {
130 if (repositoryEventListeners.contains(listener)) {
131 LogLog.warn(
132 "Ignoring attempt to add a previously "
133 + "registered LoggerRepositoryEventListener.");
134 } else {
135 repositoryEventListeners.add(listener);
136 }
137 }
138 }
139
140
141 /**
142 * Remove a {@link LoggerRepositoryEventListener} from the repository.
143 *
144 * @param listener listener
145 */
146 public void removeLoggerRepositoryEventListener(
147 final LoggerRepositoryEventListener listener) {
148 synchronized (repositoryEventListeners) {
149 if (!repositoryEventListeners.contains(listener)) {
150 LogLog.warn(
151 "Ignoring attempt to remove a "
152 + "non-registered LoggerRepositoryEventListener.");
153 } else {
154 repositoryEventListeners.remove(listener);
155 }
156 }
157 }
158
159 /**
160 * Add a {@link LoggerEventListener} to the repository. The listener
161 * will be called when repository events occur.
162 *
163 * @param listener listener
164 */
165 public void addLoggerEventListener(final LoggerEventListener listener) {
166 synchronized (loggerEventListeners) {
167 if (loggerEventListeners.get(listener) != null) {
168 LogLog.warn(
169 "Ignoring attempt to add a previously registerd LoggerEventListener.");
170 } else {
171 HierarchyEventListenerProxy proxy =
172 new HierarchyEventListenerProxy(listener);
173 loggerEventListeners.put(listener, proxy);
174 repo.addHierarchyEventListener(proxy);
175 }
176 }
177 }
178
179 /**
180 * Add a {@link org.apache.log4j.spi.HierarchyEventListener}
181 * event to the repository.
182 *
183 * @param listener listener
184 * @deprecated Superceded by addLoggerEventListener
185 */
186 public void addHierarchyEventListener(final HierarchyEventListener listener) {
187 repo.addHierarchyEventListener(listener);
188 }
189
190
191 /**
192 * Remove a {@link LoggerEventListener} from the repository.
193 *
194 * @param listener listener to be removed
195 */
196 public void removeLoggerEventListener(final LoggerEventListener listener) {
197 synchronized (loggerEventListeners) {
198 HierarchyEventListenerProxy proxy =
199 loggerEventListeners.get(listener);
200 if (proxy == null) {
201 LogLog.warn(
202 "Ignoring attempt to remove a non-registered LoggerEventListener.");
203 } else {
204 loggerEventListeners.remove(listener);
205 proxy.disable();
206 }
207 }
208 }
209
210 /**
211 * Issue warning that there are no appenders in hierarchy.
212 *
213 * @param cat logger, not currently used.
214 */
215 public void emitNoAppenderWarning(final Category cat) {
216 repo.emitNoAppenderWarning(cat);
217 }
218
219 /**
220 * Check if the named logger exists in the hierarchy. If so return
221 * its reference, otherwise returns <code>null</code>.
222 *
223 * @param loggerName The name of the logger to search for.
224 * @return true if logger exists.
225 */
226 public Logger exists(final String loggerName) {
227 return repo.exists(loggerName);
228 }
229
230 /**
231 * Return the name of this hierarchy.
232 *
233 * @return name of hierarchy
234 */
235 public String getName() {
236 return name;
237 }
238
239 /**
240 * Set the name of this repository.
241 * <p>
242 * Note that once named, a repository cannot be rerenamed.
243 *
244 * @param repoName name of hierarchy
245 */
246 public void setName(final String repoName) {
247 if (name == null) {
248 name = repoName;
249 } else if (!name.equals(repoName)) {
250 throw new IllegalStateException(
251 "Repository [" + name + "] cannot be renamed as [" + repoName + "].");
252 }
253 }
254
255 /**
256 * {@inheritDoc}
257 */
258 public Map<String, String> getProperties() {
259 return properties;
260 }
261
262 /**
263 * {@inheritDoc}
264 */
265 public String getProperty(final String key) {
266 return properties.get(key);
267 }
268
269 /**
270 * Set a property by key and value. The property will be shared by all
271 * events in this repository.
272 *
273 * @param key property name
274 * @param value property value
275 */
276 public void setProperty(final String key,
277 final String value) {
278 properties.put(key, value);
279 }
280
281 /**
282 * The string form of {@link #setThreshold(Level)}.
283 *
284 * @param levelStr symbolic name for level
285 */
286 public void setThreshold(final String levelStr) {
287 repo.setThreshold(levelStr);
288 }
289
290 /**
291 * Enable logging for logging requests with level <code>l</code> or
292 * higher. By default all levels are enabled.
293 *
294 * @param l The minimum level for which logging requests are sent to
295 * their appenders.
296 */
297 public void setThreshold(final Level l) {
298 repo.setThreshold(l);
299 }
300
301 /**
302 * {@inheritDoc}
303 */
304 public PluginRegistry getPluginRegistry() {
305 if (pluginRegistry == null) {
306 pluginRegistry = new PluginRegistry(this);
307 }
308 return pluginRegistry;
309 }
310
311
312 /**
313 * Requests that a appender added event be sent to any registered
314 * {@link LoggerEventListener}.
315 *
316 * @param logger The logger to which the appender was added.
317 * @param appender The appender added to the logger.
318 */
319 public void fireAddAppenderEvent(final Category logger,
320 final Appender appender) {
321 repo.fireAddAppenderEvent(logger, appender);
322 }
323
324
325 /**
326 * Requests that a appender removed event be sent to any registered
327 * {@link LoggerEventListener}.
328 *
329 * @param logger The logger from which the appender was removed.
330 * @param appender The appender removed from the logger.
331 */
332 public void fireRemoveAppenderEvent(final Category logger,
333 final Appender appender) {
334 if (repo instanceof Hierarchy) {
335 ((Hierarchy) repo).fireRemoveAppenderEvent(logger, appender);
336 }
337 }
338
339
340 /**
341 * Requests that a level changed event be sent to any registered
342 * {@link LoggerEventListener}.
343 *
344 * @param logger The logger which changed levels.
345 */
346 public void fireLevelChangedEvent(final Logger logger) {
347 }
348
349 /**
350 * Requests that a configuration changed event be sent to any registered
351 * {@link LoggerRepositoryEventListener}.
352 */
353 public void fireConfigurationChangedEvent() {
354 }
355
356
357 /**
358 * Returns the current threshold.
359 *
360 * @return current threshold level
361 * @since 1.2
362 */
363 public Level getThreshold() {
364 return repo.getThreshold();
365 }
366
367
368 /**
369 * Return a new logger instance named as the first parameter using
370 * the default factory.
371 * <p>
372 * <p>If a logger of that name already exists, then it will be
373 * returned. Otherwise, a new logger will be instantiated and
374 * then linked with its existing ancestors as well as children.
375 *
376 * @param loggerName The name of the logger to retrieve.
377 * @return logger
378 */
379 public Logger getLogger(final String loggerName) {
380 return repo.getLogger(loggerName);
381 }
382
383 /**
384 * Return a new logger instance named as the first parameter using
385 * <code>factory</code>.
386 * <p>
387 * <p>If a logger of that name already exists, then it will be
388 * returned. Otherwise, a new logger will be instantiated by the
389 * <code>factory</code> parameter and linked with its existing
390 * ancestors as well as children.
391 *
392 * @param loggerName The name of the logger to retrieve.
393 * @param factory The factory that will make the new logger instance.
394 * @return logger
395 */
396 public Logger getLogger(final String loggerName,
397 final LoggerFactory factory) {
398 return repo.getLogger(loggerName, factory);
399 }
400
401 /**
402 * Returns all the currently defined categories in this hierarchy as
403 * an {@link java.util.Enumeration Enumeration}.
404 * <p>
405 * <p>The root logger is <em>not</em> included in the returned
406 * {@link Enumeration}.
407 *
408 * @return enumerator of current loggers
409 */
410 public Enumeration getCurrentLoggers() {
411 return repo.getCurrentLoggers();
412 }
413
414 /**
415 * Return the the list of previously encoutered {@link ErrorItem error items}.
416 *
417 * @return list of errors
418 */
419 public List<ErrorItem> getErrorList() {
420 return errorList;
421 }
422
423 /**
424 * Add an error item to the list of previously encountered errors.
425 *
426 * @param errorItem error to add to list of errors.
427 */
428 public void addErrorItem(final ErrorItem errorItem) {
429 getErrorList().add(errorItem);
430 }
431
432 /**
433 * Get enumerator over current loggers.
434 *
435 * @return enumerator over current loggers
436 * @deprecated Please use {@link #getCurrentLoggers} instead.
437 */
438 public Enumeration getCurrentCategories() {
439 return repo.getCurrentCategories();
440 }
441
442 /**
443 * Get the renderer map for this hierarchy.
444 *
445 * @return renderer map
446 */
447 public RendererMap getRendererMap() {
448 return rendererSupport.getRendererMap();
449 }
450
451 /**
452 * Get the root of this hierarchy.
453 *
454 * @return root of hierarchy
455 * @since 0.9.0
456 */
457 public Logger getRootLogger() {
458 return repo.getRootLogger();
459 }
460
461 /**
462 * This method will return <code>true</code> if this repository is
463 * disabled for <code>level</code> value passed as parameter and
464 * <code>false</code> otherwise. See also the {@link
465 * #setThreshold(Level) threshold} method.
466 *
467 * @param level numeric value for level.
468 * @return true if disabled for specified level
469 */
470 public boolean isDisabled(final int level) {
471 return repo.isDisabled(level);
472 }
473
474 /**
475 * Reset all values contained in this hierarchy instance to their
476 * default. This removes all appenders from all categories, sets
477 * the level of all non-root categories to <code>null</code>,
478 * sets their additivity flag to <code>true</code> and sets the level
479 * of the root logger to DEBUG. Moreover,
480 * message disabling is set its default "off" value.
481 * <p>
482 * <p>Existing categories are not removed. They are just reset.
483 * <p>
484 * <p>This method should be used sparingly and with care as it will
485 * block all logging until it is completed.</p>
486 *
487 * @since 0.8.5
488 */
489 public void resetConfiguration() {
490 repo.resetConfiguration();
491 }
492
493 /**
494 * Used by subclasses to add a renderer to the hierarchy passed as parameter.
495 *
496 * @param renderedClass class
497 * @param renderer object used to render class.
498 */
499 public void setRenderer(final Class renderedClass,
500 final ObjectRenderer renderer) {
501 rendererSupport.setRenderer(renderedClass, renderer);
502 }
503
504 /**
505 * {@inheritDoc}
506 */
507 public boolean isPristine() {
508 return pristine;
509 }
510
511 /**
512 * {@inheritDoc}
513 */
514 public void setPristine(final boolean state) {
515 pristine = state;
516 }
517
518 /**
519 * Shutting down a hierarchy will <em>safely</em> close and remove
520 * all appenders in all categories including the root logger.
521 * <p>
522 * <p>Some appenders such as org.apache.log4j.net.SocketAppender
523 * and AsyncAppender need to be closed before the
524 * application exists. Otherwise, pending logging events might be
525 * lost.
526 * <p>
527 * <p>The <code>shutdown</code> method is careful to close nested
528 * appenders before closing regular appenders. This is allows
529 * configurations where a regular appender is attached to a logger
530 * and again to a nested appender.
531 *
532 * @since 1.0
533 */
534 public void shutdown() {
535 repo.shutdown();
536 }
537
538
539 /**
540 * Return this repository's own scheduler.
541 * The scheduler is lazily instantiated.
542 *
543 * @return this repository's own scheduler.
544 */
545 public Scheduler getScheduler() {
546 if (scheduler == null) {
547 scheduler = new Scheduler();
548 scheduler.setDaemon(true);
549 scheduler.start();
550 }
551 return scheduler;
552 }
553
554 /**
555 * Puts object by key.
556 *
557 * @param key key, may not be null.
558 * @param value object to associate with key.
559 */
560 public void putObject(final String key,
561 final Object value) {
562 objectMap.put(key, value);
563 }
564
565 /**
566 * Get object by key.
567 *
568 * @param key key, may not be null.
569 * @return object associated with key or null.
570 */
571 public Object getObject(final String key) {
572 return objectMap.get(key);
573 }
574
575 /**
576 * Set logger factory.
577 *
578 * @param factory logger factory.
579 */
580 public void setLoggerFactory(final LoggerFactory factory) {
581 if (factory == null) {
582 throw new NullPointerException();
583 }
584 this.loggerFactory = factory;
585 }
586
587 /**
588 * Get logger factory.
589 *
590 * @return logger factory.
591 */
592 public LoggerFactory getLoggerFactory() {
593 return loggerFactory;
594 }
595
596 /**
597 * {@inheritDoc}
598 */
599 public boolean parseUnrecognizedElement(
600 final Element element,
601 final Properties props) throws Exception {
602 if ("plugin".equals(element.getNodeName())) {
603 Object instance =
604 DOMConfigurator.parseElement(element, props, Plugin.class);
605 if (instance instanceof Plugin) {
606 Plugin plugin = (Plugin) instance;
607 String pluginName = DOMConfigurator.subst(element.getAttribute("name"), props);
608 if (pluginName.length() > 0) {
609 plugin.setName(pluginName);
610 }
611 getPluginRegistry().addPlugin(plugin);
612 plugin.setLoggerRepository(this);
613
614 LogLog.debug("Pushing plugin on to the object stack.");
615 plugin.activateOptions();
616 return true;
617 }
618 }
619 return false;
620 }
621
622
623 /**
624 * Implementation of RendererSupportImpl if not
625 * provided by LoggerRepository.
626 */
627 private static final class RendererSupportImpl implements RendererSupport {
628 /**
629 * Renderer map.
630 */
631 private final RendererMap renderers = new RendererMap();
632
633 /**
634 * Create new instance.
635 */
636 public RendererSupportImpl() {
637 super();
638 }
639
640 /**
641 * {@inheritDoc}
642 */
643 public RendererMap getRendererMap() {
644 return renderers;
645 }
646
647 /**
648 * {@inheritDoc}
649 */
650 public void setRenderer(final Class renderedClass,
651 final ObjectRenderer renderer) {
652 renderers.put(renderedClass, renderer);
653 }
654 }
655
656 /**
657 * Proxy that implements HierarchyEventListener
658 * and delegates to LoggerEventListener.
659 */
660 private static final class HierarchyEventListenerProxy
661 implements HierarchyEventListener {
662 /**
663 * Wrapper listener.
664 */
665 private LoggerEventListener listener;
666
667 /**
668 * Creates new instance.
669 *
670 * @param l listener
671 */
672 public HierarchyEventListenerProxy(final LoggerEventListener l) {
673 super();
674 if (l == null) {
675 throw new NullPointerException("l");
676 }
677 listener = l;
678 }
679
680 /**
681 * {@inheritDoc}
682 */
683 public void addAppenderEvent(final Category cat,
684 final Appender appender) {
685 if (isEnabled() && cat instanceof Logger) {
686 listener.appenderAddedEvent((Logger) cat, appender);
687 }
688 }
689
690 /**
691 * {@inheritDoc}
692 */
693 public void removeAppenderEvent(final Category cat,
694 final Appender appender) {
695 if (isEnabled() && cat instanceof Logger) {
696 listener.appenderRemovedEvent((Logger) cat, appender);
697 }
698 }
699
700 /**
701 * Disable forwarding of notifications to
702 * simulate removal of listener.
703 */
704 public synchronized void disable() {
705 listener = null;
706 }
707
708 /**
709 * Gets whether proxy is enabled.
710 *
711 * @return true if proxy is enabled.
712 */
713 private synchronized boolean isEnabled() {
714 return listener != null;
715 }
716 }
717
718 }