Keep up to 50 old logs, 2 megs each.
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / log / LoggingContextListener.java
1 /*
2  * The Shibboleth License, Version 1. Copyright (c) 2002 University Corporation for Advanced Internet Development, Inc.
3  * All rights reserved Redistribution and use in source and binary forms, with or without modification, are permitted
4  * provided that the following conditions are met: Redistributions of source code must retain the above copyright
5  * notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the
6  * above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other
7  * materials provided with the distribution, if any, must include the following acknowledgment: "This product includes
8  * software developed by the University Corporation for Advanced Internet Development <http://www.ucaid.edu>Internet2
9  * Project. Alternately, this acknowledegement may appear in the software itself, if and wherever such third-party
10  * acknowledgments normally appear. Neither the name of Shibboleth nor the names of its contributors, nor Internet2,
11  * nor the University Corporation for Advanced Internet Development, Inc., nor UCAID may be used to endorse or promote
12  * products derived from this software without specific prior written permission. For written permission, please
13  * contact shibboleth@shibboleth.org Products derived from this software may not be called Shibboleth, Internet2,
14  * UCAID, or the University Corporation for Advanced Internet Development, nor may Shibboleth appear in their name,
15  * without prior written permission of the University Corporation for Advanced Internet Development. THIS SOFTWARE IS
16  * PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND WITH ALL FAULTS. ANY EXPRESS OR IMPLIED WARRANTIES,
17  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND
18  * NON-INFRINGEMENT ARE DISCLAIMED AND THE ENTIRE RISK OF SATISFACTORY QUALITY, PERFORMANCE, ACCURACY, AND EFFORT IS
19  * WITH LICENSEE. IN NO EVENT SHALL THE COPYRIGHT OWNER, CONTRIBUTORS OR THE UNIVERSITY CORPORATION FOR ADVANCED
20  * INTERNET DEVELOPMENT, INC. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22  * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
23  * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24  * POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 package edu.internet2.middleware.shibboleth.log;
28
29 import java.io.IOException;
30 import java.io.PrintWriter;
31 import java.net.MalformedURLException;
32 import java.net.URL;
33
34 import javax.servlet.ServletContextEvent;
35 import javax.servlet.ServletContextListener;
36
37 import org.apache.log4j.ConsoleAppender;
38 import org.apache.log4j.Level;
39 import org.apache.log4j.LogManager;
40 import org.apache.log4j.Logger;
41 import org.apache.log4j.PatternLayout;
42 import org.apache.log4j.PropertyConfigurator;
43 import org.apache.log4j.RollingFileAppender;
44 import org.apache.log4j.xml.DOMConfigurator;
45 import org.apache.xml.security.Init;
46 import org.w3c.dom.Document;
47 import org.w3c.dom.NamedNodeMap;
48 import org.w3c.dom.Node;
49 import org.w3c.dom.NodeList;
50
51 import edu.internet2.middleware.shibboleth.common.OriginConfig;
52 import edu.internet2.middleware.shibboleth.common.ShibResource;
53 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
54 import edu.internet2.middleware.shibboleth.common.ShibbolethOriginConfig;
55
56 /**
57  * {@link ServletContextListener}used to configure logging for other components.
58  * 
59  * @author Walter Hoehn
60  * @author Noah Levitt
61  */
62 public class LoggingContextListener implements ServletContextListener {
63
64         private static Logger   log     = Logger.getLogger(LoggingContextListener.class.getName());
65
66         // tomcat calls this before the servlet init()s, but is that guaranteed?
67         public void contextInitialized(ServletContextEvent sce) {
68                 //Silliness to get around xmlsec doing its own configuration, ie: we might need to override it
69                 Init.init();
70
71                 ConsoleAppender rootAppender = new ConsoleAppender();
72                 rootAppender.setWriter(new PrintWriter(System.out));
73                 rootAppender.setName("stdout");
74                 Logger.getRootLogger().addAppender(rootAppender);
75
76                 // rootAppender.setLayout(new PatternLayout("%-5p %-41X{serviceId} %d{ISO8601} (%c:%L) - %m%n"));
77                 // Logger.getRootLogger().setLevel((Level) Level.DEBUG);
78                 Logger.getRootLogger().setLevel((Level) Level.INFO);
79                 rootAppender.setLayout(new PatternLayout("%d{ISO8601} %-5p %-41X{serviceId} - %m%n"));
80
81                 try {
82                         Document originConfig = OriginConfig.getOriginConfig(sce.getServletContext());
83                         loadConfiguration(originConfig);
84                 } catch (ShibbolethConfigurationException e) {
85                         sce.getServletContext().log("Problem setting up logging.", e);
86                         log.fatal("Problem setting up logging: " + e);
87                         throw new Error("Problem setting up logging: " + e); // XXX
88                 }
89
90                 log.info("Logger initialized.");
91         }
92
93         public void contextDestroyed(ServletContextEvent sce) {
94                 log.info("Shutting down logging infrastructure.");
95                 LogManager.shutdown();
96         }
97
98         protected void loadConfiguration(Document originConfig) throws ShibbolethConfigurationException {
99                 NodeList itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(
100                                 ShibbolethOriginConfig.originConfigNamespace, "Logging");
101                 Node errorLogNode = null;
102                 boolean encounteredLog4JConfig = false;
103
104                 if (itemElements.getLength() > 1) {
105                         log.warn("Encountered multiple <Logging> configuration elements. Using first one.");
106                 }
107
108                 if (itemElements.getLength() >= 1) {
109                         Node loggingNode = itemElements.item(0);
110
111                         for (int i = 0; i < loggingNode.getChildNodes().getLength(); i++) {
112                                 Node node = loggingNode.getChildNodes().item(i);
113
114                                 if ("Log4JConfig".equals(node.getNodeName())) {
115                                         doLog4JConfig(node);
116                                         encounteredLog4JConfig = true;
117                                 } else if ("TransactionLog".equals(node.getNodeName())) {
118                                         configureTransactionLog(node);
119                                 } else if ("ErrorLog".equals(node.getNodeName())) {
120                                         // make sure we do ErrorLog after TransactionLog so that the transaction log
121                                         // initialization info always gets logged in the same place
122                                         errorLogNode = node;
123                                 }
124                         }
125                 }
126
127                 if (errorLogNode != null) {
128                         configureErrorLog(errorLogNode);
129                 } else {
130                         // started out at INFO for logging config messages
131                         Logger.getRootLogger().setLevel((Level) Level.WARN);
132                 }
133
134                 // turn these off by default
135                 if (!encounteredLog4JConfig) {
136                         Logger.getLogger("org.apache.xml.security").setLevel((Level) Level.OFF);
137                         Logger.getLogger("org.opensaml").setLevel((Level) Level.OFF);
138                 }
139         }
140
141         // location should be a "file:/" uri
142         private RollingFileAppender makeRollingFileAppender(String location, String pattern)
143                         throws ShibbolethConfigurationException {
144                 try {
145                         String logPath = new ShibResource(location, LoggingContextListener.class).getFile().getCanonicalPath();
146                         RollingFileAppender appender = new RollingFileAppender(new PatternLayout(pattern), logPath);
147
148                         appender.setMaximumFileSize(2 * 1024 * 1024); // 2 megs
149                         appender.setMaxBackupIndex(50);
150
151                         return appender;
152                 } catch (IOException e) {
153                         log.fatal("<TransactionLog location=\"" + location + "\">: error creating RollingFileAppender: " + e);
154                         throw new ShibbolethConfigurationException("<TransactionLog location=\"" + location
155                                         + "\">: error creating RollingFileAppender: " + e);
156                 }
157         }
158
159         private void configureErrorLog(Node node) throws ShibbolethConfigurationException {
160                 NamedNodeMap attributes = node.getAttributes();
161
162                 /* schema check should catch if location is missing, NullPointerException here if not */
163                 String location = attributes.getNamedItem("location").getNodeValue();
164                 RollingFileAppender appender = makeRollingFileAppender(location, "%d{ISO8601} %-5p %-41X{serviceId} - %m%n");
165                 appender.setName("error");
166
167                 Level level = (Level) Level.WARN;
168                 if (attributes.getNamedItem("level") != null) {
169                         log.info("Setting log level to " + attributes.getNamedItem("level").getNodeValue());
170                         level = Level.toLevel(attributes.getNamedItem("level").getNodeValue());
171                         Logger.getRootLogger().setLevel(level);
172                 }
173
174                 // log this before switching levels
175                 log.info("Switching logging to " + appender.getFile());
176                 Logger.getRootLogger().removeAllAppenders();
177                 Logger.getRootLogger().addAppender(appender);
178
179                 Logger.getRootLogger().setLevel(level);
180         }
181
182         private void configureTransactionLog(Node node) throws ShibbolethConfigurationException {
183                 NamedNodeMap attributes = node.getAttributes();
184
185                 // schema check should catch if location is missing, NullPointerException here if not
186                 String location = attributes.getNamedItem("location").getNodeValue();
187                 RollingFileAppender appender = makeRollingFileAppender(location, "%d{ISO8601} %m%n");
188                 appender.setName("transaction");
189
190                 Logger log = Logger.getLogger("Shibboleth-TRANSACTION");
191                 log.setAdditivity(false); // do not want parent's messages
192                 log.setLevel((Level) Level.INFO); // all messages to this log are INFO
193
194                 // log.removeAllAppenders(); // imho we want these messages to appear in the "error" log if level >= INFO
195                 log.addAppender(appender);
196         }
197
198         private void doLog4JConfig(Node node) throws ShibbolethConfigurationException {
199                 NamedNodeMap attributes = node.getAttributes();
200
201                 // schema check should catch if location is missing, NullPointerException here if not
202                 String location = attributes.getNamedItem("location").getNodeValue();
203
204                 String type = null;
205                 if (attributes.getNamedItem("type") != null) {
206                         type = attributes.getNamedItem("type").getNodeValue();
207                 }
208
209                 URL url;
210                 try {
211                         url = new URL(location);
212                 } catch (MalformedURLException e) {
213                         log.fatal("<Log4JConfig location=\"" + location + "\">: not a valid URL: " + e);
214                         throw new ShibbolethConfigurationException("<Log4JConfig location=\"" + location + "\">: not a valid URL: "
215                                         + e);
216                 }
217
218                 if (type == null || "properties".equals(type)) {
219                         log.info("Using Properties log4j configuration from " + url);
220                         PropertyConfigurator.configure(url);
221                 } else if ("xml".equals(type)) {
222                         log.info("Using XML log4j configuration from " + url);
223                         DOMConfigurator.configure(url);
224                 }
225         }
226 }