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