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