001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2016, 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.jmx;
015
016import java.io.File;
017import java.io.FileNotFoundException;
018import java.net.MalformedURLException;
019import java.net.URL;
020import java.util.ArrayList;
021import java.util.Iterator;
022import java.util.List;
023
024import javax.management.InstanceNotFoundException;
025import javax.management.MBeanRegistrationException;
026import javax.management.MBeanServer;
027import javax.management.ObjectName;
028
029import ch.qos.logback.classic.Level;
030import ch.qos.logback.classic.Logger;
031import ch.qos.logback.classic.LoggerContext;
032import ch.qos.logback.classic.joran.JoranConfigurator;
033import ch.qos.logback.classic.spi.LoggerContextListener;
034import ch.qos.logback.classic.util.ContextInitializer;
035import ch.qos.logback.core.joran.spi.JoranException;
036import ch.qos.logback.core.spi.ContextAwareBase;
037import ch.qos.logback.core.status.Status;
038import ch.qos.logback.core.status.StatusListener;
039import ch.qos.logback.core.status.StatusListenerAsList;
040import ch.qos.logback.core.status.StatusManager;
041import ch.qos.logback.core.util.StatusPrinter;
042
043/**
044 * A class that provides access to logback components via JMX.
045 * 
046 * <p>
047 * Since this class implements {@link JMXConfiguratorMBean} it has to be named
048 * as JMXConfigurator}.
049 * 
050 * @author Ceki G&uuml;lc&uuml;
051 * @author S&eacute;bastien Pennec
052 * 
053 *         Contributor: Sebastian Davids See
054 *         http://bugzilla.qos.ch/show_bug.cgi?id=35
055 */
056public class JMXConfigurator extends ContextAwareBase implements JMXConfiguratorMBean, LoggerContextListener {
057
058    private static String EMPTY = "";
059
060    LoggerContext loggerContext;
061    MBeanServer mbs;
062    ObjectName objectName;
063    String objectNameAsString;
064
065    // whether to output status events on the console when reloading the
066    // configuration
067    boolean debug = true;
068
069    boolean started;
070
071    public JMXConfigurator(LoggerContext loggerContext, MBeanServer mbs, ObjectName objectName) {
072        started = true;
073        this.context = loggerContext;
074        this.loggerContext = loggerContext;
075        this.mbs = mbs;
076        this.objectName = objectName;
077        this.objectNameAsString = objectName.toString();
078        if (previouslyRegisteredListenerWithSameObjectName()) {
079            addError("Previously registered JMXConfigurator named [" + objectNameAsString
080                    + "] in the logger context named [" + loggerContext.getName() + "]");
081        } else {
082            // register as a listener only if there are no homonyms
083            loggerContext.addListener(this);
084        }
085    }
086
087    private boolean previouslyRegisteredListenerWithSameObjectName() {
088        List<LoggerContextListener> lcll = loggerContext.getCopyOfListenerList();
089        for (LoggerContextListener lcl : lcll) {
090            if (lcl instanceof JMXConfigurator) {
091                JMXConfigurator jmxConfigurator = (JMXConfigurator) lcl;
092                if (objectName.equals(jmxConfigurator.objectName)) {
093                    return true;
094                }
095            }
096        }
097        return false;
098    }
099
100    public void reloadDefaultConfiguration() throws JoranException {
101        ContextInitializer ci = new ContextInitializer(loggerContext);
102        URL url = ci.findURLOfDefaultConfigurationFile(true);
103        reloadByURL(url);
104    }
105
106    public void reloadByFileName(String fileName) throws JoranException, FileNotFoundException {
107        File f = new File(fileName);
108        if (f.exists() && f.isFile()) {
109            URL url;
110            try {
111                url = f.toURI().toURL();
112                reloadByURL(url);
113            } catch (MalformedURLException e) {
114                throw new RuntimeException("Unexpected MalformedURLException occured. See nexted cause.", e);
115            }
116
117        } else {
118            String errMsg = "Could not find [" + fileName + "]";
119            addInfo(errMsg);
120            throw new FileNotFoundException(errMsg);
121        }
122    }
123
124    void addStatusListener(StatusListener statusListener) {
125        StatusManager sm = loggerContext.getStatusManager();
126        sm.add(statusListener);
127    }
128
129    void removeStatusListener(StatusListener statusListener) {
130        StatusManager sm = loggerContext.getStatusManager();
131        sm.remove(statusListener);
132    }
133
134    public void reloadByURL(URL url) throws JoranException {
135        StatusListenerAsList statusListenerAsList = new StatusListenerAsList();
136
137        addStatusListener(statusListenerAsList);
138        addInfo("Resetting context: " + loggerContext.getName());
139        loggerContext.reset();
140
141        // after a reset the statusListenerAsList gets removed as a listener
142        addStatusListener(statusListenerAsList);
143
144        try {
145            if (url != null) {
146                JoranConfigurator configurator = new JoranConfigurator();
147                configurator.setContext(loggerContext);
148                configurator.doConfigure(url);
149                addInfo("Context: " + loggerContext.getName() + " reloaded.");
150            }
151        } finally {
152            removeStatusListener(statusListenerAsList);
153            if (debug) {
154                StatusPrinter.print(statusListenerAsList.getStatusList());
155            }
156        }
157    }
158
159    public void setLoggerLevel(String loggerName, String levelStr) {
160        if (loggerName == null) {
161            return;
162        }
163        if (levelStr == null) {
164            return;
165        }
166        loggerName = loggerName.trim();
167        levelStr = levelStr.trim();
168
169        addInfo("Trying to set level " + levelStr + " to logger " + loggerName);
170        LoggerContext lc = (LoggerContext) context;
171
172        Logger logger = lc.getLogger(loggerName);
173        if ("null".equalsIgnoreCase(levelStr)) {
174            logger.setLevel(null);
175        } else {
176            Level level = Level.toLevel(levelStr, null);
177            if (level != null) {
178                logger.setLevel(level);
179            }
180        }
181    }
182
183    public String getLoggerLevel(String loggerName) {
184        if (loggerName == null) {
185            return EMPTY;
186        }
187
188        loggerName = loggerName.trim();
189
190        LoggerContext lc = (LoggerContext) context;
191        Logger logger = lc.exists(loggerName);
192        if (logger != null && logger.getLevel() != null) {
193            return logger.getLevel().toString();
194        } else {
195            return EMPTY;
196        }
197    }
198
199    public String getLoggerEffectiveLevel(String loggerName) {
200        if (loggerName == null) {
201            return EMPTY;
202        }
203
204        loggerName = loggerName.trim();
205
206        LoggerContext lc = (LoggerContext) context;
207        Logger logger = lc.exists(loggerName);
208        if (logger != null) {
209            return logger.getEffectiveLevel().toString();
210        } else {
211            return EMPTY;
212        }
213    }
214
215    public List<String> getLoggerList() {
216        LoggerContext lc = (LoggerContext) context;
217        List<String> strList = new ArrayList<String>();
218        Iterator<Logger> it = lc.getLoggerList().iterator();
219        while (it.hasNext()) {
220            Logger log = it.next();
221            strList.add(log.getName());
222        }
223        return strList;
224    }
225
226    public List<String> getStatuses() {
227        List<String> list = new ArrayList<String>();
228        Iterator<Status> it = context.getStatusManager().getCopyOfStatusList().iterator();
229        while (it.hasNext()) {
230            list.add(it.next().toString());
231        }
232        return list;
233    }
234
235    /**
236     * When the associated LoggerContext is stopped, this configurator must be
237     * unregistered
238     */
239    public void onStop(LoggerContext context) {
240        if (!started) {
241            addInfo("onStop() method called on a stopped JMXActivator [" + objectNameAsString + "]");
242            return;
243        }
244        if (mbs.isRegistered(objectName)) {
245            try {
246                addInfo("Unregistering mbean [" + objectNameAsString + "]");
247                mbs.unregisterMBean(objectName);
248            } catch (InstanceNotFoundException e) {
249                // this is theoretically impossible
250                addError("Unable to find a verifiably registered mbean [" + objectNameAsString + "]", e);
251            } catch (MBeanRegistrationException e) {
252                addError("Failed to unregister [" + objectNameAsString + "]", e);
253            }
254        } else {
255            addInfo("mbean [" + objectNameAsString + "] was not in the mbean registry. This is OK.");
256        }
257        stop();
258    }
259
260    public void onLevelChange(Logger logger, Level level) {
261        // nothing to do
262    }
263
264    public void onReset(LoggerContext context) {
265        addInfo("onReset() method called JMXActivator [" + objectNameAsString + "]");
266    }
267
268    /**
269     * JMXConfigurator should not be removed subsequent to a LoggerContext reset.
270     * 
271     * @return
272     */
273    public boolean isResetResistant() {
274        return true;
275    }
276
277    private void clearFields() {
278        mbs = null;
279        objectName = null;
280        loggerContext = null;
281    }
282
283    private void stop() {
284        started = false;
285        clearFields();
286    }
287
288    public void onStart(LoggerContext context) {
289        // nop
290    }
291
292    @Override
293    public String toString() {
294        return this.getClass().getName() + "(" + context.getName() + ")";
295    }
296}