001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.classic;
015
016import static ch.qos.logback.core.CoreConstants.EVALUATOR_MAP;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.concurrent.ConcurrentHashMap;
025import java.util.concurrent.ScheduledFuture;
026
027import org.slf4j.ILoggerFactory;
028import org.slf4j.Marker;
029
030import ch.qos.logback.classic.spi.LoggerComparator;
031import ch.qos.logback.classic.spi.LoggerContextListener;
032import ch.qos.logback.classic.spi.LoggerContextVO;
033import ch.qos.logback.classic.spi.TurboFilterList;
034import ch.qos.logback.classic.turbo.TurboFilter;
035import ch.qos.logback.classic.util.LoggerNameUtil;
036import ch.qos.logback.core.ContextBase;
037import ch.qos.logback.core.boolex.EventEvaluator;
038import ch.qos.logback.core.spi.FilterReply;
039import ch.qos.logback.core.spi.LifeCycle;
040import ch.qos.logback.core.status.StatusListener;
041import ch.qos.logback.core.status.StatusManager;
042import ch.qos.logback.core.status.WarnStatus;
043
044/**
045 * LoggerContext glues many of the logback-classic components together. In
046 * principle, every logback-classic component instance is attached either
047 * directly or indirectly to a LoggerContext instance. Just as importantly
048 * LoggerContext implements the {@link ILoggerFactory} acting as the
049 * manufacturing source of {@link Logger} instances.
050 *
051 * @author Ceki Gulcu
052 */
053public class LoggerContext extends ContextBase implements ILoggerFactory, LifeCycle {
054
055    /** Default setting of packaging data in stack traces */
056    public static final boolean DEFAULT_PACKAGING_DATA = false;
057
058    final Logger root;
059    private int size;
060    private int noAppenderWarning = 0;
061    final private List<LoggerContextListener> loggerContextListenerList = new ArrayList<LoggerContextListener>();
062
063    private Map<String, Logger> loggerCache;
064
065    private LoggerContextVO loggerContextRemoteView;
066    private final TurboFilterList turboFilterList = new TurboFilterList();
067    private boolean packagingDataEnabled = DEFAULT_PACKAGING_DATA;
068
069    private int maxCallerDataDepth = ClassicConstants.DEFAULT_MAX_CALLEDER_DATA_DEPTH;
070
071    int resetCount = 0;
072    private List<String> frameworkPackages;
073
074    public LoggerContext() {
075        super();
076        this.loggerCache = new ConcurrentHashMap<String, Logger>();
077
078        this.loggerContextRemoteView = new LoggerContextVO(this);
079        this.root = new Logger(Logger.ROOT_LOGGER_NAME, null, this);
080        this.root.setLevel(Level.DEBUG);
081        loggerCache.put(Logger.ROOT_LOGGER_NAME, root);
082        initEvaluatorMap();
083        size = 1;
084        this.frameworkPackages = new ArrayList<String>();
085    }
086
087    void initEvaluatorMap() {
088        putObject(EVALUATOR_MAP, new HashMap<String, EventEvaluator<?>>());
089    }
090
091    /**
092     * A new instance of LoggerContextRemoteView needs to be created each time the
093     * name or propertyMap (including keys or values) changes.
094     */
095    private void updateLoggerContextVO() {
096        loggerContextRemoteView = new LoggerContextVO(this);
097    }
098
099    @Override
100    public void putProperty(String key, String val) {
101        super.putProperty(key, val);
102        updateLoggerContextVO();
103    }
104
105    @Override
106    public void setName(String name) {
107        super.setName(name);
108        updateLoggerContextVO();
109    }
110
111    public final Logger getLogger(final Class<?> clazz) {
112        return getLogger(clazz.getName());
113    }
114
115    @Override
116    public final Logger getLogger(final String name) {
117
118        if (name == null) {
119            throw new IllegalArgumentException("name argument cannot be null");
120        }
121
122        // if we are asking for the root logger, then let us return it without
123        // wasting time
124        if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
125            return root;
126        }
127
128        int i = 0;
129        Logger logger = root;
130
131        // check if the desired logger exists, if it does, return it
132        // without further ado.
133        Logger childLogger = (Logger) loggerCache.get(name);
134        // if we have the child, then let us return it without wasting time
135        if (childLogger != null) {
136            return childLogger;
137        }
138
139        // if the desired logger does not exist, them create all the loggers
140        // in between as well (if they don't already exist)
141        String childName;
142        while (true) {
143            int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
144            if (h == -1) {
145                childName = name;
146            } else {
147                childName = name.substring(0, h);
148            }
149            // move i left of the last point
150            i = h + 1;
151            synchronized (logger) {
152                childLogger = logger.getChildByName(childName);
153                if (childLogger == null) {
154                    childLogger = logger.createChildByName(childName);
155                    loggerCache.put(childName, childLogger);
156                    incSize();
157                }
158            }
159            logger = childLogger;
160            if (h == -1) {
161                return childLogger;
162            }
163        }
164    }
165
166    private void incSize() {
167        size++;
168    }
169
170    int size() {
171        return size;
172    }
173
174    /**
175     * Check if the named logger exists in the hierarchy. If so return its
176     * reference, otherwise returns <code>null</code>.
177     *
178     * @param name the name of the logger to search for.
179     */
180    public Logger exists(String name) {
181        return (Logger) loggerCache.get(name);
182    }
183
184    final void noAppenderDefinedWarning(final Logger logger) {
185        if (noAppenderWarning++ == 0) {
186            getStatusManager().add(new WarnStatus("No appenders present in context [" + getName() + "] for logger [" + logger.getName() + "].", logger));
187        }
188    }
189
190    public List<Logger> getLoggerList() {
191        Collection<Logger> collection = loggerCache.values();
192        List<Logger> loggerList = new ArrayList<Logger>(collection);
193        Collections.sort(loggerList, new LoggerComparator());
194        return loggerList;
195    }
196
197    public LoggerContextVO getLoggerContextRemoteView() {
198        return loggerContextRemoteView;
199    }
200
201    public void setPackagingDataEnabled(boolean packagingDataEnabled) {
202        this.packagingDataEnabled = packagingDataEnabled;
203    }
204
205    public boolean isPackagingDataEnabled() {
206        return packagingDataEnabled;
207    }
208
209    /**
210     * This method clears all internal properties, except internal status messages,
211     * closes all appenders, removes any turboFilters, fires an OnReset event,
212     * removes all status listeners, removes all context listeners
213     * (except those which are reset resistant).
214     * <p/>
215     * As mentioned above, internal status messages survive resets.
216     */
217    @Override
218    public void reset() {
219        resetCount++;
220        super.reset();
221        initEvaluatorMap();
222        initCollisionMaps();
223        root.recursiveReset();
224        resetTurboFilterList();
225        cancelScheduledTasks();
226        fireOnReset();
227        resetListenersExceptResetResistant();
228        resetStatusListeners();
229    }
230
231    private void cancelScheduledTasks() {
232        for(ScheduledFuture<?> sf: scheduledFutures) {
233            sf.cancel(false);
234        }
235        scheduledFutures.clear();
236    }
237
238    private void resetStatusListeners() {
239        StatusManager sm = getStatusManager();
240        for (StatusListener sl : sm.getCopyOfStatusListenerList()) {
241            sm.remove(sl);
242        }
243    }
244
245    public TurboFilterList getTurboFilterList() {
246        return turboFilterList;
247    }
248
249    public void addTurboFilter(TurboFilter newFilter) {
250        turboFilterList.add(newFilter);
251    }
252
253    /**
254     * First processPriorToRemoval all registered turbo filters and then clear the registration
255     * list.
256     */
257    public void resetTurboFilterList() {
258        for (TurboFilter tf : turboFilterList) {
259            tf.stop();
260        }
261        turboFilterList.clear();
262    }
263
264    final FilterReply getTurboFilterChainDecision_0_3OrMore(final Marker marker, final Logger logger, final Level level, final String format,
265                    final Object[] params, final Throwable t) {
266        if (turboFilterList.size() == 0) {
267            return FilterReply.NEUTRAL;
268        }
269        return turboFilterList.getTurboFilterChainDecision(marker, logger, level, format, params, t);
270    }
271
272    final FilterReply getTurboFilterChainDecision_1(final Marker marker, final Logger logger, final Level level, final String format, final Object param,
273                    final Throwable t) {
274        if (turboFilterList.size() == 0) {
275            return FilterReply.NEUTRAL;
276        }
277        return turboFilterList.getTurboFilterChainDecision(marker, logger, level, format, new Object[] { param }, t);
278    }
279
280    final FilterReply getTurboFilterChainDecision_2(final Marker marker, final Logger logger, final Level level, final String format, final Object param1,
281                    final Object param2, final Throwable t) {
282        if (turboFilterList.size() == 0) {
283            return FilterReply.NEUTRAL;
284        }
285        return turboFilterList.getTurboFilterChainDecision(marker, logger, level, format, new Object[] { param1, param2 }, t);
286    }
287
288    // === start listeners ==============================================
289    public void addListener(LoggerContextListener listener) {
290        loggerContextListenerList.add(listener);
291    }
292
293    public void removeListener(LoggerContextListener listener) {
294        loggerContextListenerList.remove(listener);
295    }
296
297    private void resetListenersExceptResetResistant() {
298        List<LoggerContextListener> toRetain = new ArrayList<LoggerContextListener>();
299
300        for (LoggerContextListener lcl : loggerContextListenerList) {
301            if (lcl.isResetResistant()) {
302                toRetain.add(lcl);
303            }
304        }
305        loggerContextListenerList.retainAll(toRetain);
306    }
307
308    private void resetAllListeners() {
309        loggerContextListenerList.clear();
310    }
311
312    public List<LoggerContextListener> getCopyOfListenerList() {
313        return new ArrayList<LoggerContextListener>(loggerContextListenerList);
314    }
315
316    void fireOnLevelChange(Logger logger, Level level) {
317        for (LoggerContextListener listener : loggerContextListenerList) {
318            listener.onLevelChange(logger, level);
319        }
320    }
321
322    private void fireOnReset() {
323        for (LoggerContextListener listener : loggerContextListenerList) {
324            listener.onReset(this);
325        }
326    }
327
328    private void fireOnStart() {
329        for (LoggerContextListener listener : loggerContextListenerList) {
330            listener.onStart(this);
331        }
332    }
333
334    private void fireOnStop() {
335        for (LoggerContextListener listener : loggerContextListenerList) {
336            listener.onStop(this);
337        }
338    }
339
340    // === end listeners ==============================================
341
342    public void start() {
343        super.start();
344        fireOnStart();
345    }
346
347    public void stop() {
348        reset();
349        fireOnStop();
350        resetAllListeners();
351        super.stop();
352    }
353
354    @Override
355    public String toString() {
356        return this.getClass().getName() + "[" + getName() + "]";
357    }
358
359    public int getMaxCallerDataDepth() {
360        return maxCallerDataDepth;
361    }
362
363    public void setMaxCallerDataDepth(int maxCallerDataDepth) {
364        this.maxCallerDataDepth = maxCallerDataDepth;
365    }
366
367    /**
368     * List of packages considered part of the logging framework such that they are never considered
369     * as callers of the logging framework. This list used to compute the caller for logging events.
370     * <p/>
371     * To designate package "com.foo" as well as all its subpackages as being part of the logging framework, simply add
372     * "com.foo" to this list.
373     *
374     * @return list of framework packages
375     */
376    public List<String> getFrameworkPackages() {
377        return frameworkPackages;
378    }
379}