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