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