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;
039
040/**
041 * Built-in service that initializes and manages Logging via Log4j.
042 * <p>
043 * Oozie Lo4gj default configuration file is <code>oozie-log4j.properties</code>.
044 * <p>
045 * The file name can be changed by setting the Java System property <code>oozie.log4j.file</code>.
046 * <p>
047 * The Log4j configuration files must be a properties file.
048 * <p>
049 * The Log4j configuration file is first looked in the Oozie configuration directory see {@link ConfigurationService}.
050 * If the file is not found there, it is looked in the classpath.
051 * <p>
052 * If the Log4j configuration file is loaded from Oozie configuration directory, automatic reloading is enabled.
053 * <p>
054 * If the Log4j configuration file is loaded from the classpath, automatic reloading is disabled.
055 * <p>
056 * the automatic reloading interval is defined by the Java System property <code>oozie.log4j.reload</code>. The default
057 * value is 10 seconds.
058 * <p>
059 * <p>
060 * Unlike most of the other Services, XLogService isn't easily overridable because Services depends on XLogService being available
061 */
062public 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    private boolean errorLogEnabled = true;
096    private boolean auditLogEnabled = true;
097
098
099    private static final String STARTUP_MESSAGE = "{E}"
100            + " ******************************************************************************* {E}"
101            + "  STARTUP MSG: Oozie BUILD_VERSION [{0}] compiled by [{1}] on [{2}]{E}"
102            + "  STARTUP MSG:       revision [{3}]@[{4}]{E}"
103            + "*******************************************************************************";
104
105    private String oozieLogPath;
106    private String oozieLogName;
107    private String oozieErrorLogPath;
108    private String oozieErrorLogName;
109    private String oozieAuditLogPath;
110    private String oozieAuditLogName;
111    private int oozieLogRotation = -1;
112    private int oozieErrorLogRotation = -1;
113    private int oozieAuditLogRotation = -1;
114
115
116
117    public XLogService() {
118    }
119
120    public String getOozieLogPath() {
121        return oozieLogPath;
122    }
123
124    public String getOozieErrorLogPath() {
125        return oozieErrorLogPath;
126    }
127
128    public String getOozieLogName() {
129        return oozieLogName;
130    }
131
132    public String getOozieErrorLogName() {
133        return oozieErrorLogName;
134    }
135
136
137    /**
138     * Initialize the log service.
139     *
140     * @param services services instance.
141     * @throws ServiceException thrown if the log service could not be initialized.
142     */
143    public void init(Services services) throws ServiceException {
144        String oozieHome = Services.getOozieHome();
145        String oozieLogs = System.getProperty(OOZIE_LOG_DIR, oozieHome + "/logs");
146        System.setProperty(OOZIE_LOG_DIR, oozieLogs);
147
148        try {
149            LogManager.resetConfiguration();
150            log4jFileName = System.getProperty(LOG4J_FILE, DEFAULT_LOG4J_PROPERTIES);
151            if (log4jFileName.contains("/")) {
152                throw new ServiceException(ErrorCode.E0011, log4jFileName);
153            }
154            if (!log4jFileName.endsWith(".properties")) {
155                throw new ServiceException(ErrorCode.E0012, log4jFileName);
156            }
157            String configPath = ConfigurationService.getConfigurationDirectory();
158            File log4jFile = new File(configPath, log4jFileName);
159            if (log4jFile.exists()) {
160                fromClasspath = false;
161            }
162            else {
163                ClassLoader cl = Thread.currentThread().getContextClassLoader();
164                URL log4jUrl = cl.getResource(log4jFileName);
165                if (log4jUrl == null) {
166                    throw new ServiceException(ErrorCode.E0013, log4jFileName, configPath);
167                }
168                fromClasspath = true;
169            }
170
171            if (fromClasspath) {
172                ClassLoader cl = Thread.currentThread().getContextClassLoader();
173                URL log4jUrl = cl.getResource(log4jFileName);
174                PropertyConfigurator.configure(log4jUrl);
175            }
176            else {
177                interval = Long.parseLong(System.getProperty(LOG4J_RELOAD, DEFAULT_RELOAD_INTERVAL));
178                PropertyConfigurator.configureAndWatch(log4jFile.toString(), interval * 1000);
179            }
180
181            log = new XLog(LogFactory.getLog(getClass()));
182
183            log.info(XLog.OPS, STARTUP_MESSAGE, BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VERSION),
184                    BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_USER_NAME), BuildInfo.getBuildInfo()
185                            .getProperty(BuildInfo.BUILD_TIME), BuildInfo.getBuildInfo().getProperty(
186                            BuildInfo.BUILD_VC_REVISION), BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VC_URL));
187
188            String from = (fromClasspath) ? "CLASSPATH" : configPath;
189            String reload = (fromClasspath) ? "disabled" : Long.toString(interval) + " sec";
190            log.info("Log4j configuration file [{0}]", log4jFileName);
191            log.info("Log4j configuration file loaded from [{0}]", from);
192            log.info("Log4j reload interval [{0}]", reload);
193
194            XLog.Info.reset();
195            XLog.Info.defineParameter(USER);
196            XLog.Info.defineParameter(GROUP);
197            XLogFilter.reset();
198            XLogFilter.defineParameter(USER);
199            XLogFilter.defineParameter(GROUP);
200
201            // Getting configuration for oozie log via WS
202            ClassLoader cl = Thread.currentThread().getContextClassLoader();
203            InputStream is = (fromClasspath) ? cl.getResourceAsStream(log4jFileName) : new FileInputStream(log4jFile);
204            extractInfoForLogWebService(is);
205        }
206        catch (IOException ex) {
207            throw new ServiceException(ErrorCode.E0010, ex.getMessage(), ex);
208        }
209    }
210
211    private void extractInfoForLogWebService(InputStream is) throws IOException {
212        Properties props = new Properties();
213        props.load(is);
214
215        XConfiguration conf = new XConfiguration();
216        conf.setRestrictSystemProperties(false);
217        for (Map.Entry entry : props.entrySet()) {
218            conf.set((String) entry.getKey(), (String) entry.getValue());
219        }
220
221        XLogUtil logUtil = new XLogUtil(conf, "oozie");
222        logOverWS = logUtil.isLogOverEnable();
223        oozieLogRotation = logUtil.getLogRotation() == 0 ? oozieLogRotation : logUtil.getLogRotation();
224        oozieLogPath = logUtil.getLogPath() == null ? oozieLogPath : logUtil.getLogPath();
225        oozieLogName = logUtil.getLogFileName() == null ? oozieLogName : logUtil.getLogFileName();
226
227        logUtil = new XLogUtil(conf, "oozieError");
228        errorLogEnabled = logUtil.isLogOverEnable();
229        oozieErrorLogRotation = logUtil.getLogRotation() == 0 ? oozieErrorLogRotation : logUtil.getLogRotation();
230        oozieErrorLogPath = logUtil.getLogPath() == null ? oozieErrorLogPath : logUtil.getLogPath();
231        oozieErrorLogName = logUtil.getLogFileName() == null ? oozieErrorLogName : logUtil.getLogFileName();
232
233        logUtil = new XLogUtil(conf, "oozieaudit");
234        auditLogEnabled = logUtil.isLogOverEnable();
235        oozieAuditLogRotation = logUtil.getLogRotation() == 0 ? oozieAuditLogRotation : logUtil.getLogRotation();
236        oozieAuditLogPath = logUtil.getLogPath() == null ? oozieAuditLogPath : logUtil.getLogPath();
237        oozieAuditLogName = logUtil.getLogFileName() == null ? oozieAuditLogName : logUtil.getLogFileName();
238
239    }
240
241    /**
242     * Destroy the log service.
243     */
244    public void destroy() {
245        LogManager.shutdown();
246        XLog.Info.reset();
247        XLogFilter.reset();
248    }
249
250    /**
251     * Group log info constant.
252     */
253    public static final String USER = "USER";
254
255    /**
256     * Group log info constant.
257     */
258    public static final String GROUP = "GROUP";
259
260    /**
261     * Return the public interface for log service.
262     *
263     * @return {@link XLogService}.
264     */
265    public Class<? extends Service> getInterface() {
266        return XLogService.class;
267    }
268
269    /**
270     * Instruments the log service.
271     * <p>
272     * It sets instrumentation variables indicating the config file, reload interval and if loaded from the classpath.
273     *
274     * @param instr instrumentation to use.
275     */
276    public void instrument(Instrumentation instr) {
277
278        instr.addVariable(INSTRUMENTATION_GROUP, "config.file", new Instrumentation.Variable<String>() {
279            public String getValue() {
280                return log4jFileName;
281            }
282        });
283        instr.addVariable(INSTRUMENTATION_GROUP, "reload.interval", new Instrumentation.Variable<Long>() {
284            public Long getValue() {
285                return interval;
286            }
287        });
288        instr.addVariable(INSTRUMENTATION_GROUP, "from.classpath", new Instrumentation.Variable<Boolean>() {
289            public Boolean getValue() {
290                return fromClasspath;
291            }
292        });
293        instr.addVariable(INSTRUMENTATION_GROUP, "log.over.web-service", new Instrumentation.Variable<Boolean>() {
294            public Boolean getValue() {
295                return logOverWS;
296            }
297        });
298    }
299
300    boolean getLogOverWS() {
301        return logOverWS;
302    }
303
304    boolean isErrorLogEnabled(){
305        return errorLogEnabled;
306    }
307
308    int getOozieLogRotation() {
309        return oozieLogRotation;
310    }
311
312    int getOozieErrorLogRotation() {
313        return oozieErrorLogRotation;
314    }
315
316    int getOozieAuditLogRotation() {
317        return oozieAuditLogRotation;
318    }
319
320    public String getOozieAuditLogPath() {
321        return oozieAuditLogPath;
322    }
323
324    public String getOozieAuditLogName() {
325        return oozieAuditLogName;
326    }
327
328    boolean isAuditLogEnabled() {
329        return auditLogEnabled;
330    }
331
332    String getLog4jProperties() {
333        return log4jFileName;
334    }
335
336    boolean getFromClasspath() {
337        return fromClasspath;
338    }
339
340}