d6948c01417983054ab7b85347d2cc4672a3e0fc
[java-idp.git] / src / edu / internet2 / middleware / shibboleth / log / LoggingInitializer.java
1 /*
2  * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package edu.internet2.middleware.shibboleth.log;
18
19 import java.io.IOException;
20
21 import org.apache.log4j.ConsoleAppender;
22 import org.apache.log4j.FileAppender;
23 import org.apache.log4j.Level;
24 import org.apache.log4j.Logger;
25 import org.apache.log4j.PatternLayout;
26 import org.apache.log4j.PropertyConfigurator;
27 import org.apache.log4j.xml.DOMConfigurator;
28 import org.w3c.dom.Element;
29 import org.w3c.dom.NamedNodeMap;
30 import org.w3c.dom.Node;
31 import org.w3c.dom.NodeList;
32
33 import edu.internet2.middleware.shibboleth.common.ShibResource;
34 import edu.internet2.middleware.shibboleth.common.ShibbolethConfigurationException;
35 import edu.internet2.middleware.shibboleth.idp.IdPConfig;
36
37 /**
38  * A helper class for configuring the the IdP transaction log, general system log, and any logs specified in a Log4J
39  * configuration file.
40  * <p>
41  * The IdP transaction log with the name <code>Shibboleth-TRANSACTION</code> and should be used to track an
42  * individuals path through the IdP. It's default logging level is <code>INFO</code>.
43  * <p>
44  * The general system log logs messages from any class in either the <code>edu.internet2.middleware.shibboleth</code>
45  * package or the <code>org.opensaml</code> package. It's default logging level is <code>WARN</code>
46  * <p>
47  * All logs are configured through information found in the IdP XML configuration file.
48  * 
49  * @author Chad La Joie
50  */
51 public class LoggingInitializer {
52
53     /**
54      * The log file extension
55      */
56     private static String logFileExtension = ".log";
57     
58         /**
59          * Log message layout pattern for the transaction log
60          */
61         private static String txLogLayoutPattern = "%d{ISO8601} %m%n";
62
63         /**
64          * Date pattern used at the end of the transaction log filename
65          */
66         private static String txLogAppenderDatePattern = "'.'yyyy-MM-dd";
67
68         /**
69          * Log message layout pattern for the general system log
70          */
71         private static String sysLogLayoutPattern = "%d{ISO8601} %-5p %-41X{serviceId} - %m%n";
72
73         /**
74          * Date pattern used at the end of the general system log filename
75          */
76         private static String sysLogAppenderDatePattern = "'.'yyyy-MM-dd";
77
78         /**
79          * Initializes the Log4J logging framework.
80          * 
81          * @param configuration
82          *            logging configuration element from the IdP XML configuration file
83          * @throws ShibbolethConfigurationException
84          *             thrown if there is a problem configuring the logs
85          */
86         public static void initializeLogging(Element configuration) throws ShibbolethConfigurationException {
87
88                 NodeList txLogElems = configuration.getElementsByTagNameNS(IdPConfig.configNameSpace, "TransactionLog");
89                 if (txLogElems.getLength() > 0) {
90                         if (txLogElems.getLength() > 1) {
91                                 System.err.println("WARNING: More than one TransactionLog element detected in IdP logging "
92                                                 + "configuration, only the first one will be used.");
93                         }
94                         Element txLogConfig = (Element) txLogElems.item(0);
95                         configureTransactionLog(txLogConfig);
96                 } else {
97                         configureTransactionLog();
98                 }
99
100                 NodeList sysLogElems = configuration.getElementsByTagNameNS(IdPConfig.configNameSpace, "ErrorLog");
101                 if (sysLogElems.getLength() > 0) {
102                         if (sysLogElems.getLength() > 1) {
103                                 System.err.println("WARNING: More than one ErrorLog element detected in IdP logging configuration, "
104                                                 + "only the first one will be used.");
105                         }
106                         Element sysLogConfig = (Element) sysLogElems.item(0);
107                         configureSystemLog(sysLogConfig);
108                 } else {
109                         configureSystemLog();
110                 }
111
112                 NodeList log4jElems = configuration.getElementsByTagNameNS(IdPConfig.configNameSpace, "Log4JConfig");
113                 if (log4jElems.getLength() > 0) {
114                         if (log4jElems.getLength() > 1) {
115                                 System.err.println("WARNING: More than one Log4JConfig element detected in IdP logging configuration, "
116                                                 + "only the first one will be used.");
117                         }
118                         Element log4jConfig = (Element) log4jElems.item(0);
119                         configureLog4J(log4jConfig);
120                 }
121         }
122
123         /**
124          * Initialize the logs for the Shibboleth-TRANSACTION log, edu.internet2.middleware.shibboleth, and org.opensaml
125          * logs. Output is directed to the standard out with the the transaction log at INFO level and the remainder at
126          * warn.
127          */
128         public static void initializeLogging() {
129
130                 configureTransactionLog();
131                 configureSystemLog();
132         }
133
134         /**
135          * Configured the transaction log to log to the console at INFO level.
136          */
137         private static void configureTransactionLog() {
138
139                 ConsoleAppender appender = new ConsoleAppender(new PatternLayout(txLogLayoutPattern),
140                                 ConsoleAppender.SYSTEM_OUT);
141                 Logger log = Logger.getLogger("Shibboleth-TRANSACTION");
142                 log.setAdditivity(false); // do not want parent's messages
143                 log.setLevel(Level.INFO);
144                 log.addAppender(appender);
145         }
146
147         /**
148          * Configures the transaction log.
149          * 
150          * @param configuration
151          *            the TransactionLog element from the IdP XML logging configuration
152          * @throws ShibbolethConfigurationException
153          *             thrown if there is a problem configuring the logs
154          */
155         private static void configureTransactionLog(Element configuration) throws ShibbolethConfigurationException {
156
157                 NamedNodeMap attributes = configuration.getAttributes();
158
159                 String location = attributes.getNamedItem("location").getNodeValue();
160                 if (location == null) { throw new ShibbolethConfigurationException(
161                                 "No log file location attribute specified in TransactionLog element"); }
162
163                 FileAppender appender = null;
164                 try {
165                         String logPath = new ShibResource(location, LoggingInitializer.class).getFile().getCanonicalPath();
166                         appender = createRollingFileAppender(txLogLayoutPattern, logPath, txLogAppenderDatePattern);
167                         appender.setName("shibboleth-transaction");
168                 } catch (Exception e) {
169                         throw new ShibbolethConfigurationException("<TransactionLog location=\"" + location
170                                         + "\">: error creating DailyRollingFileAppender: " + e);
171                 }
172
173                 Level level = Level.INFO;
174                 if (attributes.getNamedItem("level") != null) {
175                         level = Level.toLevel(attributes.getNamedItem("level").getNodeValue());
176                 }
177
178                 Logger log = Logger.getLogger("Shibboleth-TRANSACTION");
179                 log.setAdditivity(false); // do not want parent's messages
180                 log.setLevel(level);
181                 log.addAppender(appender);
182         }
183
184         /**
185          * Configures the standard system log to log messages from edu.internet2.middleware.shibboleth and org.opensaml to
186          * the console at WARN level.
187          */
188         private static void configureSystemLog() {
189
190                 ConsoleAppender appender = new ConsoleAppender(new PatternLayout(sysLogLayoutPattern),
191                                 ConsoleAppender.SYSTEM_OUT);
192                 Logger shibLog = Logger.getLogger("edu.internet2.middleware.shibboleth");
193                 shibLog.setLevel(Level.WARN);
194                 shibLog.addAppender(appender);
195
196                 Logger openSAMLLog = Logger.getLogger("org.opensaml");
197                 openSAMLLog.setLevel(Level.WARN);
198                 openSAMLLog.addAppender(appender);
199         }
200
201         /**
202          * Configures the system-wide IdP log.
203          * 
204          * @param configuration
205          *            the ErrorLog element from the IdP XML logging configuration
206          * @throws ShibbolethConfigurationException
207          *             thrown if there is a problem configuring the logs
208          */
209         private static void configureSystemLog(Element configuration) throws ShibbolethConfigurationException {
210
211                 NamedNodeMap attributes = configuration.getAttributes();
212
213                 String location = attributes.getNamedItem("location").getNodeValue();
214                 if (location == null) { throw new ShibbolethConfigurationException(
215                                 "No log file location attribute specified in ErrorLog element"); }
216
217                 FileAppender appender = null;
218                 try {
219                         String logPath = new ShibResource(location, LoggingInitializer.class).getFile().getCanonicalPath();
220                         appender = createRollingFileAppender(sysLogLayoutPattern, logPath, sysLogAppenderDatePattern);
221                         appender.setName("shibboleth-error");
222                 } catch (Exception e) { // catch any exception
223                         throw new ShibbolethConfigurationException("<ErrorLog location=\"" + location
224                                         + "\">: error creating DailyRollingFileAppender: " + e);
225                 }
226
227                 Level level = Level.WARN;
228                 if (attributes.getNamedItem("level") != null) {
229                         level = Level.toLevel(attributes.getNamedItem("level").getNodeValue());
230                 }
231
232                 Logger shibLog = Logger.getLogger("edu.internet2.middleware.shibboleth");
233                 shibLog.setLevel(level);
234                 shibLog.addAppender(appender);
235
236                 Logger openSAMLLog = Logger.getLogger("org.opensaml");
237                 openSAMLLog.setLevel(level);
238                 openSAMLLog.addAppender(appender);
239         }
240     
241     /**
242      * Creates a rolling file appender.  If the given log file ends with .* the characters after the . 
243      * will be treated as the logs extension.  If there is no . in the log file path a default extension 
244      * of "log" will be used.  When the log file is rolled the resulting file name is "logfile"."date"."extension".
245      * 
246      * @param messagePattern patterns for the log messages
247      * @param logFile the log file
248      * @param datePattern the date pattern to roll the file one
249      * 
250      * @return a rolling file appender
251      * 
252      * @throws IOException thrown if the appender can not create the initial log file
253      */
254     private static FileAppender createRollingFileAppender(String messagePattern, String logFile, String datePattern) throws IOException {
255         PatternLayout messageLayout = new PatternLayout(messagePattern);
256         
257         int fileExtDelimIndex = logFile.lastIndexOf(".");
258         if(fileExtDelimIndex <= 0) {
259             return new RollingFileAppender(messageLayout, logFile, datePattern, ".log");
260         }else {
261             String filePath = logFile.substring(0, fileExtDelimIndex);
262             String fileExtension = logFile.substring(fileExtDelimIndex);
263             
264             return new RollingFileAppender(messageLayout, filePath, datePattern, fileExtension);
265         }
266     }
267
268         /**
269          * Configures Log4J by way of a Log4J specific configuration file.
270          * 
271          * @param configuration
272          *            the Log4JConfig element from the IdP XML logging configuration
273          * @throws ShibbolethConfigurationException
274          *             thrown if there is a problem configuring the logs
275          */
276         private static void configureLog4J(Element configuration) throws ShibbolethConfigurationException {
277
278                 NamedNodeMap attributes = configuration.getAttributes();
279
280                 String location = attributes.getNamedItem("location").getNodeValue();
281                 if (location == null) { throw new ShibbolethConfigurationException(
282                                 "No configuration file location attribute specified in Log4JConfig element"); }
283
284                 String type = null;
285                 Node typeNode = attributes.getNamedItem("type");
286                 if (typeNode != null) {
287                         type = typeNode.getNodeValue();
288                 }
289
290                 ShibResource log4jConfig;
291                 try {
292                         log4jConfig = new ShibResource(location);
293                         if (type == null || "properties".equals(type)) {
294                                 PropertyConfigurator.configure(log4jConfig.getURL());
295                         } else if ("xml".equals(type)) {
296                                 DOMConfigurator.configure(log4jConfig.getURL());
297                         } else {
298                                 throw new ShibbolethConfigurationException(
299                                                 "<Log4JConfig (type) attribute must be one of \"xml\" or \"properties\".");
300                         }
301                 } catch (IOException e) {
302                         throw new ShibbolethConfigurationException("<Log4JConfig location=\"" + location + "\">: not a valid URL: "
303                                         + e);
304                 }
305
306         }
307 }