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