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        try {
148            LogManager.resetConfiguration();
149            log4jFileName = System.getProperty(LOG4J_FILE, DEFAULT_LOG4J_PROPERTIES);
150            if (log4jFileName.contains("/")) {
151                throw new ServiceException(ErrorCode.E0011, log4jFileName);
152            }
153            if (!log4jFileName.endsWith(".properties")) {
154                throw new ServiceException(ErrorCode.E0012, log4jFileName);
155            }
156            String configPath = ConfigurationService.getConfigurationDirectory();
157            File log4jFile = new File(configPath, log4jFileName);
158            if (log4jFile.exists()) {
159                fromClasspath = false;
160            }
161            else {
162                ClassLoader cl = Thread.currentThread().getContextClassLoader();
163                URL log4jUrl = cl.getResource(log4jFileName);
164                if (log4jUrl == null) {
165                    throw new ServiceException(ErrorCode.E0013, log4jFileName, configPath);
166                }
167                fromClasspath = true;
168            }
169
170            if (fromClasspath) {
171                ClassLoader cl = Thread.currentThread().getContextClassLoader();
172                URL log4jUrl = cl.getResource(log4jFileName);
173                PropertyConfigurator.configure(log4jUrl);
174            }
175            else {
176                interval = Long.parseLong(System.getProperty(LOG4J_RELOAD, DEFAULT_RELOAD_INTERVAL));
177                PropertyConfigurator.configureAndWatch(log4jFile.toString(), interval * 1000);
178            }
179
180            log = new XLog(LogFactory.getLog(getClass()));
181
182            log.info(XLog.OPS, STARTUP_MESSAGE, BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VERSION),
183                    BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_USER_NAME), BuildInfo.getBuildInfo()
184                            .getProperty(BuildInfo.BUILD_TIME), BuildInfo.getBuildInfo().getProperty(
185                            BuildInfo.BUILD_VC_REVISION), BuildInfo.getBuildInfo().getProperty(BuildInfo.BUILD_VC_URL));
186
187            String from = (fromClasspath) ? "CLASSPATH" : configPath;
188            String reload = (fromClasspath) ? "disabled" : Long.toString(interval) + " sec";
189            log.info("Log4j configuration file [{0}]", log4jFileName);
190            log.info("Log4j configuration file loaded from [{0}]", from);
191            log.info("Log4j reload interval [{0}]", reload);
192
193            XLog.Info.reset();
194            XLog.Info.defineParameter(USER);
195            XLog.Info.defineParameter(GROUP);
196            XLogFilter.reset();
197            XLogFilter.defineParameter(USER);
198            XLogFilter.defineParameter(GROUP);
199
200            // Getting configuration for oozie log via WS
201            ClassLoader cl = Thread.currentThread().getContextClassLoader();
202            InputStream is = (fromClasspath) ? cl.getResourceAsStream(log4jFileName) : new FileInputStream(log4jFile);
203            extractInfoForLogWebService(is);
204        }
205        catch (IOException ex) {
206            throw new ServiceException(ErrorCode.E0010, ex.getMessage(), ex);
207        }
208    }
209
210    private void extractInfoForLogWebService(InputStream is) throws IOException {
211        Properties props = new Properties();
212        props.load(is);
213
214        Configuration conf = new XConfiguration();
215        for (Map.Entry entry : props.entrySet()) {
216            conf.set((String) entry.getKey(), (String) entry.getValue());
217        }
218
219        XLogUtil logUtil = new XLogUtil(conf, "oozie");
220        logOverWS = logUtil.isLogOverEnable();
221        oozieLogRotation = logUtil.getLogRotation() == 0 ? oozieLogRotation : logUtil.getLogRotation();
222        oozieLogPath = logUtil.getLogPath() == null ? oozieLogPath : logUtil.getLogPath();
223        oozieLogName = logUtil.getLogFileName() == null ? oozieLogName : logUtil.getLogFileName();
224
225        logUtil = new XLogUtil(conf, "oozieError");
226        errorLogEnabled = logUtil.isLogOverEnable();
227        oozieErrorLogRotation = logUtil.getLogRotation() == 0 ? oozieErrorLogRotation : logUtil.getLogRotation();
228        oozieErrorLogPath = logUtil.getLogPath() == null ? oozieErrorLogPath : logUtil.getLogPath();
229        oozieErrorLogName = logUtil.getLogFileName() == null ? oozieErrorLogName : logUtil.getLogFileName();
230
231        logUtil = new XLogUtil(conf, "oozieaudit");
232        auditLogEnabled = logUtil.isLogOverEnable();
233        oozieAuditLogRotation = logUtil.getLogRotation() == 0 ? oozieAuditLogRotation : logUtil.getLogRotation();
234        oozieAuditLogPath = logUtil.getLogPath() == null ? oozieAuditLogPath : logUtil.getLogPath();
235        oozieAuditLogName = logUtil.getLogFileName() == null ? oozieAuditLogName : logUtil.getLogFileName();
236
237    }
238
239    /**
240     * Destroy the log service.
241     */
242    public void destroy() {
243        LogManager.shutdown();
244        XLog.Info.reset();
245        XLogFilter.reset();
246    }
247
248    /**
249     * Group log info constant.
250     */
251    public static final String USER = "USER";
252
253    /**
254     * Group log info constant.
255     */
256    public static final String GROUP = "GROUP";
257
258    /**
259     * Return the public interface for log service.
260     *
261     * @return {@link XLogService}.
262     */
263    public Class<? extends Service> getInterface() {
264        return XLogService.class;
265    }
266
267    /**
268     * Instruments the log service.
269     * <p>
270     * It sets instrumentation variables indicating the config file, reload interval and if loaded from the classpath.
271     *
272     * @param instr instrumentation to use.
273     */
274    public void instrument(Instrumentation instr) {
275
276        instr.addVariable(INSTRUMENTATION_GROUP, "config.file", new Instrumentation.Variable<String>() {
277            public String getValue() {
278                return log4jFileName;
279            }
280        });
281        instr.addVariable(INSTRUMENTATION_GROUP, "reload.interval", new Instrumentation.Variable<Long>() {
282            public Long getValue() {
283                return interval;
284            }
285        });
286        instr.addVariable(INSTRUMENTATION_GROUP, "from.classpath", new Instrumentation.Variable<Boolean>() {
287            public Boolean getValue() {
288                return fromClasspath;
289            }
290        });
291        instr.addVariable(INSTRUMENTATION_GROUP, "log.over.web-service", new Instrumentation.Variable<Boolean>() {
292            public Boolean getValue() {
293                return logOverWS;
294            }
295        });
296    }
297
298    boolean getLogOverWS() {
299        return logOverWS;
300    }
301
302    boolean isErrorLogEnabled(){
303        return errorLogEnabled;
304    }
305
306    int getOozieLogRotation() {
307        return oozieLogRotation;
308    }
309
310    int getOozieErrorLogRotation() {
311        return oozieErrorLogRotation;
312    }
313
314    int getOozieAuditLogRotation() {
315        return oozieAuditLogRotation;
316    }
317
318    public String getOozieAuditLogPath() {
319        return oozieAuditLogPath;
320    }
321
322    public String getOozieAuditLogName() {
323        return oozieAuditLogName;
324    }
325
326    boolean isAuditLogEnabled() {
327        return auditLogEnabled;
328    }
329
330    String getLog4jProperties() {
331        return log4jFileName;
332    }
333
334    boolean getFromClasspath() {
335        return fromClasspath;
336    }
337
338}