Configure logging with <Logging> element in origin.xml.
[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                 ConsoleAppender rootAppender = new ConsoleAppender();
99                 rootAppender.setWriter(new PrintWriter(System.out));
100                 rootAppender.setName("stdout");
101                 Logger.getRootLogger().addAppender(rootAppender);
102
103                 // rootAppender.setLayout(new PatternLayout("%-5p %-41X{serviceId} %d{ISO8601} (%c:%L) - %m%n")); 
104                 // Logger.getRootLogger().setLevel((Level) Level.DEBUG);
105                 Logger.getRootLogger().setLevel((Level) Level.INFO);
106                 rootAppender.setLayout(new PatternLayout("%d{ISO8601} %-5p %-41X{serviceId} - %m%n"));
107
108                 Logger.getLogger("org.apache.xml.security").setLevel((Level) Level.OFF);
109                 Logger.getLogger("org.opensaml").setLevel((Level) Level.OFF);
110
111                 //Silliness to get around xmlsec doing its own configuration, ie: we might need to override it
112                 Init.init();
113
114                 try {
115                         Document originConfig = OriginConfig.getOriginConfig(sce.getServletContext());
116                         loadConfiguration(originConfig);
117                 } 
118                 catch (ShibbolethConfigurationException e) {
119                         sce.getServletContext().log("Problem setting up logging.", e);
120                         log.fatal("Problem setting up logging: " + e);
121                         throw new Error("Problem setting up logging: " + e);  // XXX
122                 }
123
124                 log.info("Logger initialized.");
125         }
126
127         public void contextDestroyed(ServletContextEvent sce)
128         {
129             log.info("Shutting down logging infrastructure.");
130             LogManager.shutdown();
131         }
132
133         protected void loadConfiguration(Document originConfig) throws ShibbolethConfigurationException
134         {
135                 NodeList itemElements = originConfig.getDocumentElement().getElementsByTagNameNS(ShibbolethOriginConfig.originConfigNamespace, "Logging");
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                         Node errorLogNode = null;
145
146                         for (int i = 0; i < loggingNode.getChildNodes().getLength(); i++)
147                         {
148                                 Node node = loggingNode.getChildNodes().item(i);
149
150                                 if ("Log4JConfig".equals(node.getNodeName()))
151                                 {
152                                         doLog4JConfig(node);
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                         if (errorLogNode != null)
167                         {
168                                 configureErrorLog(errorLogNode);
169                         }
170                 }
171         }
172
173         // location should be a "file:/" uri
174         private RollingFileAppender makeRollingFileAppender(String location, String pattern) throws ShibbolethConfigurationException
175         {
176                 try {
177                         String logPath = new ShibResource(location, LoggingContextListener.class).getFile().getCanonicalPath();
178                         RollingFileAppender appender = new RollingFileAppender(new PatternLayout(pattern), logPath);
179
180                         appender.setMaximumFileSize(1024*1024);        // 1 megabyte
181                         appender.setMaxBackupIndex(Integer.MAX_VALUE); // imho we should not delete any log files
182
183                         return appender;
184                 }
185                 catch (IOException e) {
186                         log.fatal("<TransactionLog location=\"" + location + "\">: error creating RollingFileAppender: " + e);
187                         throw new ShibbolethConfigurationException("<TransactionLog location=\"" + location + "\">: error creating RollingFileAppender: " + e);
188                 }
189         }
190
191         private void configureErrorLog(Node node) throws ShibbolethConfigurationException
192         {
193                 NamedNodeMap attributes = node.getAttributes();
194
195                 /* schema check should catch if location is missing, NullPointerException here if not */
196                 String location = attributes.getNamedItem("location").getNodeValue();
197                 RollingFileAppender appender = makeRollingFileAppender(location, "%d{ISO8601} %-5p %-41X{serviceId} - %m%n");
198
199                 appender.setName("error");
200                 appender.setMaxBackupIndex(Integer.MAX_VALUE); // imho we should not delete any log files
201
202                 Level level = (Level) Level.WARN;
203                 if (attributes.getNamedItem("level") != null)
204                 {
205                         log.info("Setting log level to " + attributes.getNamedItem("level").getNodeValue());
206                         level = Level.toLevel(attributes.getNamedItem("level").getNodeValue());
207                         Logger.getRootLogger().setLevel(level);
208                 }
209
210                 // log this before switching levels
211                 log.info("Switching logging to " + appender.getFile());
212                 Logger.getRootLogger().removeAllAppenders();
213                 Logger.getRootLogger().addAppender(appender);
214
215                 Logger.getRootLogger().setLevel(level);
216         }
217
218         private void configureTransactionLog(Node node) throws ShibbolethConfigurationException
219         {
220                 NamedNodeMap attributes = node.getAttributes();
221
222                 // schema check should catch if location is missing, NullPointerException here if not
223                 String location = attributes.getNamedItem("location").getNodeValue();
224                 RollingFileAppender appender = makeRollingFileAppender(location, "%d{ISO8601} %m%n");
225                 appender.setName("transaction");
226
227                 Logger log = Logger.getLogger("Shibboleth-TRANSACTION");
228                 log.setAdditivity(false);         // do not want parent's messages
229                 log.setLevel((Level) Level.INFO); // all messages to this log are INFO
230
231                 // log.removeAllAppenders(); // imho we want these messages to appear in the "error" log if level >= INFO
232                 log.addAppender(appender);
233         }
234
235         private void doLog4JConfig(Node node) throws ShibbolethConfigurationException
236         {
237                 NamedNodeMap attributes = node.getAttributes();
238
239                 // schema check should catch if location is missing, NullPointerException here if not
240                 String location = attributes.getNamedItem("location").getNodeValue();
241
242                 String type = null;
243                 if (attributes.getNamedItem("type") != null) 
244                 {
245                         type = attributes.getNamedItem("type").getNodeValue();
246                 }
247
248                 URL url;
249                 try {
250                         url = new URL(location);
251                 }
252                 catch (MalformedURLException e) {
253                         log.fatal("<Log4JConfig location=\"" + location + "\">: not a valid URL: " + e);
254                         throw new ShibbolethConfigurationException("<Log4JConfig location=\"" + location + "\">: not a valid URL: " + e);
255                 }
256
257                 if (type == null || "properties".equals(type))
258                 {
259                         log.info("Using Properties log4j configuration from " + url);
260                         PropertyConfigurator.configure(url);
261                 }
262                 else if ("xml".equals(type))
263                 {
264                         log.info("Using XML log4j configuration from " + url);
265                         DOMConfigurator.configure(url);
266                 }
267         }
268 }
269