001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.oozie.service; 020 021import org.apache.commons.logging.LogFactory; 022import org.apache.log4j.LogManager; 023import org.apache.log4j.PropertyConfigurator; 024import org.apache.oozie.util.XLogFilter; 025import org.apache.oozie.util.Instrumentable; 026import org.apache.oozie.util.Instrumentation; 027import org.apache.oozie.util.XLog; 028import org.apache.oozie.util.XConfiguration; 029import org.apache.oozie.BuildInfo; 030import org.apache.oozie.ErrorCode; 031import org.apache.hadoop.conf.Configuration; 032 033import java.io.File; 034import java.io.FileInputStream; 035import java.io.IOException; 036import java.io.InputStream; 037import java.net.URL; 038import java.util.Properties; 039import java.util.Map; 040import java.util.regex.Pattern; 041 042/** 043 * Built-in service that initializes and manages Logging via Log4j. 044 * <p/> 045 * Oozie Lo4gj default configuration file is <code>oozie-log4j.properties</code>. 046 * <p/> 047 * The file name can be changed by setting the Java System property <code>oozie.log4j.file</code>. 048 * <p/> 049 * The Log4j configuration files must be a properties file. 050 * <p/> 051 * The Log4j configuration file is first looked in the Oozie configuration directory see {@link ConfigurationService}. 052 * If the file is not found there, it is looked in the classpath. 053 * <p/> 054 * If the Log4j configuration file is loaded from Oozie configuration directory, automatic reloading is enabled. 055 * <p/> 056 * If the Log4j configuration file is loaded from the classpath, automatic reloading is disabled. 057 * <p/> 058 * the automatic reloading interval is defined by the Java System property <code>oozie.log4j.reload</code>. The default 059 * value is 10 seconds. 060 * <p> 061 * <p> 062 * Unlike most of the other Services, XLogService isn't easily overridable because Services depends on XLogService being available 063 */ 064public class XLogService implements Service, Instrumentable { 065 private static final String INSTRUMENTATION_GROUP = "logging"; 066 067 /** 068 * System property that indicates the logs directory. 069 */ 070 public static final String OOZIE_LOG_DIR = "oozie.log.dir"; 071 072 /** 073 * System property that indicates the log4j configuration file to load. 074 */ 075 public static final String LOG4J_FILE = "oozie.log4j.file"; 076 077 /** 078 * System property that indicates the reload interval of the configuration file. 079 */ 080 public static final String LOG4J_RELOAD = "oozie.log4j.reload"; 081 082 /** 083 * Default value for the log4j configuration file if {@link #LOG4J_FILE} is not set. 084 */ 085 public static final String DEFAULT_LOG4J_PROPERTIES = "oozie-log4j.properties"; 086 087 /** 088 * Default value for the reload interval if {@link #LOG4J_RELOAD} is not set. 089 */ 090 public static final String DEFAULT_RELOAD_INTERVAL = "10"; 091 092 private XLog log; 093 private long interval; 094 private boolean fromClasspath; 095 private String log4jFileName; 096 private boolean logOverWS = true; 097 098 private static final String STARTUP_MESSAGE = "{E}" 099 + " ******************************************************************************* {E}" 100 + " STARTUP MSG: Oozie BUILD_VERSION [{0}] compiled by [{1}] on [{2}]{E}" 101 + " STARTUP MSG: revision [{3}]@[{4}]{E}" 102 + "*******************************************************************************"; 103 104 private String oozieLogPath; 105 private String oozieLogName; 106 private int oozieLogRotation = -1; 107 108 public XLogService() { 109 } 110 111 public String getOozieLogPath() { 112 return oozieLogPath; 113 } 114 115 public String getOozieLogName() { 116 return oozieLogName; 117 } 118 119 /** 120 * Initialize the log service. 121 * 122 * @param services services instance. 123 * @throws ServiceException thrown if the log service could not be initialized. 124 */ 125 public void init(Services services) throws ServiceException { 126 String oozieHome = Services.getOozieHome(); 127 String oozieLogs = System.getProperty(OOZIE_LOG_DIR, oozieHome + "/logs"); 128 System.setProperty(OOZIE_LOG_DIR, oozieLogs); 129 130 try { 131 LogManager.resetConfiguration(); 132 log4jFileName = System.getProperty(LOG4J_FILE, DEFAULT_LOG4J_PROPERTIES); 133 if (log4jFileName.contains("/")) { 134 throw new ServiceException(ErrorCode.E0011, log4jFileName); 135 } 136 if (!log4jFileName.endsWith(".properties")) { 137 throw new ServiceException(ErrorCode.E0012, log4jFileName); 138 } 139 String configPath = ConfigurationService.getConfigurationDirectory(); 140 File log4jFile = new File(configPath, log4jFileName); 141 if (log4jFile.exists()) { 142 fromClasspath = false; 143 } 144 else { 145 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 146 URL log4jUrl = cl.getResource(log4jFileName); 147 if (log4jUrl == null) { 148 throw new ServiceException(ErrorCode.E0013, log4jFileName, configPath); 149 } 150 fromClasspath = true; 151 } 152 153 if (fromClasspath) { 154 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 155 URL log4jUrl = cl.getResource(log4jFileName); 156 PropertyConfigurator.configure(log4jUrl); 157 } 158 else { 159 interval = Long.parseLong(System.getProperty(LOG4J_RELOAD, DEFAULT_RELOAD_INTERVAL)); 160 PropertyConfigurator.configureAndWatch(log4jFile.toString(), interval * 1000); 161 } 162 163 log = new XLog(LogFactory.getLog(getClass())); 164 165 log.info(XLog.OPS, STARTUP_MESSAGE, BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VERSION), 166 BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_USER_NAME), BuildInfo.getBuildInfo() 167 .getProperty(BuildInfo.BUILD_TIME), BuildInfo.getBuildInfo().getProperty( 168 BuildInfo.BUILD_VC_REVISION), BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VC_URL)); 169 170 String from = (fromClasspath) ? "CLASSPATH" : configPath; 171 String reload = (fromClasspath) ? "disabled" : Long.toString(interval) + " sec"; 172 log.info("Log4j configuration file [{0}]", log4jFileName); 173 log.info("Log4j configuration file loaded from [{0}]", from); 174 log.info("Log4j reload interval [{0}]", reload); 175 176 XLog.Info.reset(); 177 XLog.Info.defineParameter(USER); 178 XLog.Info.defineParameter(GROUP); 179 XLogFilter.reset(); 180 XLogFilter.defineParameter(USER); 181 XLogFilter.defineParameter(GROUP); 182 183 // Getting configuration for oozie log via WS 184 ClassLoader cl = Thread.currentThread().getContextClassLoader(); 185 InputStream is = (fromClasspath) ? cl.getResourceAsStream(log4jFileName) : new FileInputStream(log4jFile); 186 extractInfoForLogWebService(is); 187 } 188 catch (IOException ex) { 189 throw new ServiceException(ErrorCode.E0010, ex.getMessage(), ex); 190 } 191 } 192 193 private void extractInfoForLogWebService(InputStream is) throws IOException { 194 logOverWS = true; 195 Properties props = new Properties(); 196 props.load(is); 197 198 XConfiguration conf = new XConfiguration(); 199 conf.setRestrictSystemProperties(false); 200 for (Map.Entry entry : props.entrySet()) { 201 conf.set((String) entry.getKey(), (String) entry.getValue()); 202 } 203 String logFile = conf.get("log4j.appender.oozie.File"); 204 if (logFile == null) { 205 log.warn("Oozie WS log will be disabled, missing property 'log4j.appender.oozie.File' for 'oozie' " 206 + "appender"); 207 logOverWS = false; 208 } 209 else { 210 logFile = logFile.trim(); 211 int i = logFile.lastIndexOf("/"); 212 if (i == -1) { 213 log.warn("Oozie WS log will be disabled, log file is not an absolute path [{0}] for 'oozie' appender", 214 logFile); 215 logOverWS = false; 216 } 217 else { 218 String appenderClass = conf.get("log4j.appender.oozie"); 219 if (appenderClass == null) { 220 log.warn("Oozie WS log will be disabled, missing property [log4j.appender.oozie]"); 221 logOverWS = false; 222 } 223 else if (appenderClass.equals("org.apache.log4j.DailyRollingFileAppender")) { 224 String pattern = conf.get("log4j.appender.oozie.DatePattern"); 225 if (pattern == null) { 226 log.warn("Oozie WS log will be disabled, missing property [log4j.appender.oozie.DatePattern]"); 227 logOverWS = false; 228 } 229 else { 230 pattern = pattern.trim(); 231 if (pattern.endsWith("HH")) { 232 oozieLogRotation = 60 * 60; 233 } 234 else if (pattern.endsWith("dd")) { 235 oozieLogRotation = 60 * 60 * 24; 236 } 237 else { 238 log.warn("Oozie WS log will be disabled, DatePattern [{0}] should end with 'HH' or 'dd'", 239 pattern); 240 logOverWS = false; 241 } 242 if (oozieLogRotation > 0) { 243 oozieLogPath = logFile.substring(0, i); 244 oozieLogName = logFile.substring(i + 1); 245 } 246 } 247 } 248 else if (appenderClass.equals("org.apache.log4j.rolling.RollingFileAppender")) { 249 String pattern = conf.get("log4j.appender.oozie.RollingPolicy.FileNamePattern"); 250 if (pattern == null) { 251 log.warn("Oozie WS log will be disabled, missing property " 252 + "[log4j.appender.oozie.RollingPolicy.FileNamePattern]"); 253 logOverWS = false; 254 } 255 else { 256 pattern = pattern.trim(); 257 if (pattern.matches(Pattern.quote(logFile) + ".*-%d\\{yyyy-MM-dd-HH\\}(\\.gz)?")) { 258 oozieLogRotation = 60 * 60; 259 } 260 else { 261 log.warn("Oozie WS log will be disabled, RollingPolicy.FileNamePattern [{0}] should end with " 262 + "'-%d{yyyy-MM-dd-HH}' or '-%d{yyyy-MM-dd-HH}.gz' and also start with the value of " 263 + "log4j.appender.oozie.File [{1}]", pattern, logFile); 264 logOverWS = false; 265 } 266 if (oozieLogRotation > 0) { 267 oozieLogPath = logFile.substring(0, i); 268 oozieLogName = logFile.substring(i + 1); 269 } 270 } 271 } 272 else { 273 log.warn("Oozie WS log will be disabled, log4j.appender.oozie [" + appenderClass + "] should be " 274 + "either org.apache.log4j.DailyRollingFileAppender or org.apache.log4j.rolling.RollingFileAppender " 275 + "to enable it"); 276 logOverWS = false; 277 } 278 } 279 } 280 } 281 282 /** 283 * Destroy the log service. 284 */ 285 public void destroy() { 286 LogManager.shutdown(); 287 XLog.Info.reset(); 288 XLogFilter.reset(); 289 } 290 291 /** 292 * Group log info constant. 293 */ 294 public static final String USER = "USER"; 295 296 /** 297 * Group log info constant. 298 */ 299 public static final String GROUP = "GROUP"; 300 301 /** 302 * Return the public interface for log service. 303 * 304 * @return {@link XLogService}. 305 */ 306 public Class<? extends Service> getInterface() { 307 return XLogService.class; 308 } 309 310 /** 311 * Instruments the log service. 312 * <p/> 313 * It sets instrumentation variables indicating the config file, reload interval and if loaded from the classpath. 314 * 315 * @param instr instrumentation to use. 316 */ 317 public void instrument(Instrumentation instr) { 318 instr.addVariable(INSTRUMENTATION_GROUP, "config.file", new Instrumentation.Variable<String>() { 319 public String getValue() { 320 return log4jFileName; 321 } 322 }); 323 instr.addVariable(INSTRUMENTATION_GROUP, "reload.interval", new Instrumentation.Variable<Long>() { 324 public Long getValue() { 325 return interval; 326 } 327 }); 328 instr.addVariable(INSTRUMENTATION_GROUP, "from.classpath", new Instrumentation.Variable<Boolean>() { 329 public Boolean getValue() { 330 return fromClasspath; 331 } 332 }); 333 instr.addVariable(INSTRUMENTATION_GROUP, "log.over.web-service", new Instrumentation.Variable<Boolean>() { 334 public Boolean getValue() { 335 return logOverWS; 336 } 337 }); 338 } 339 340 boolean getLogOverWS() { 341 return logOverWS; 342 } 343 344 int getOozieLogRotation() { 345 return oozieLogRotation; 346 } 347 348 String getLog4jProperties() { 349 return log4jFileName; 350 } 351 352 boolean getFromClasspath() { 353 return fromClasspath; 354 } 355 356}