001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.main; 029 030import java.io.File; 031import java.io.IOException; 032import java.io.InterruptedIOException; 033import java.io.LineNumberReader; 034import java.io.PrintWriter; 035import java.io.StringReader; 036import java.io.StringWriter; 037import java.net.URL; 038import java.net.URLDecoder; 039import java.nio.charset.Charset; 040import java.util.ArrayList; 041import java.util.concurrent.Callable; 042import java.util.concurrent.CopyOnWriteArraySet; 043 044import org.apache.commons.logging.Log; 045import org.apache.commons.logging.LogFactory; 046import org.apache.logging.log4j.core.config.ConfigurationSource; 047import org.apache.logging.log4j.core.config.Configurator; 048import org.apache.logging.log4j.core.util.Loader; 049 050/** 051 * Provides the OpenCms logging mechanism.<p> 052 * 053 * The OpenCms logging mechanism is based on Apache Commons Logging. 054 * However, log4j is shipped with OpenCms and assumed to be used as default logging mechanism. 055 * Since apparently Commons Logging may cause issues in more complex classloader scenarios, 056 * we may switch the logging interface to log4j <code>UGLI</code> once the final release is available.<p> 057 * 058 * The log4j configuration file shipped with OpenCms is located in <code>WEB-INF/classes/log4j2.xml</code>. 059 * By default, OpenCms will configure log4j using the first {@code log4j2.xml} available found by 060 * {@link Loader#getResource(String, ClassLoader)}.<p> 061 * 062 * The following system properties are available for fine-tunning the logging configuration: 063 * <dl> 064 * <dt>{@code opencms.set.logfile} (default: {@code true})</dt> 065 * <dd>Set to {@code false} to bypass the OpenCms standard logging configuration mechanism, delegating this to the 066 * logging framework of chose. Note that in this case, 067 * <ul> 068 * <li>the Workplace logging visualization tools will not be available, and</li> 069 * <li>you may need to enable automatic configuration of log4j2 (see <a 070 * href="http://logging.apache.org/log4j/2.x/manual/webapp.html">Using Log4j 2 in Web Applications</a>. 071 * This configuration takes place before OpenCms is correctly initialized, and has been disabled in the 072 * standard OpenCms distribution because at that point, it has not yet set default values for the 073 * relevant system properties</li> 074 * </ul></dd> 075 * <dt>{@code opencms.logfolder} (default: "{@code WEB-INF/logs/}")</dt> 076 * <dd>Path to the folder where OpenCms will look for logging files to show in the Workplace GUI.</dd> 077 * <dt>{@code opencms.logfile} (default: "{@code WEB-INF/logs/opencms.log}")</dt> 078 * <dd>Absolute path to the OpenCms log file.</dd> 079 * </dl> 080 * 081 * If the logging configuration is not bypassed by setting {@code opencms.set.logfile=false}, OpenCms will first assing 082 * them default values if needed and then will make these system properties available for the configuration of log4j. 083 * In the {@code log4j2.xml} configuration file, system properties can be referenced using the prefix "{@code sys:}" as 084 * in this example: 085 * <pre> 086 * fileName="${sys:opencms.logfolder}opencms-search.log" 087 * </pre> 088 * 089 * @since 6.0.0 090 */ 091public final class CmsLog { 092 093 /** The name of the opencms.log file. */ 094 public static final String FILE_LOG = "opencms.log"; 095 096 /** Path to the "logs" folder relative to the "WEB-INF" directory of the application. */ 097 public static final String FOLDER_LOGS = "logs" + File.separatorChar; 098 099 /** Name of the system property that configures the path where OpenCms looks for logs to show in the Workplace App. */ 100 public static final String PROPERTY_LOGFOLDER = "opencms.logfolder"; 101 102 /** Name of the system property that configures the absolute path to the file where OpenCms will write its log. */ 103 public static final String PROPERTY_LOGFILE = "opencms.logfile"; 104 105 /** Log for initialization messages. */ 106 public static Log INIT; 107 108 /** The absolute path to the folder of the main OpenCms log file (in the "real" file system). */ 109 private static String m_logFileRfsFolder; 110 111 /** The absolute path to the OpenCms log file (in the "real" file system). */ 112 private static String m_logFileRfsPath; 113 114 /** Set of names of channels that should not be managed via the GUI. */ 115 private static CopyOnWriteArraySet<String> NON_MANAGEABLE_CHANNELS = new CopyOnWriteArraySet<>(); 116 117 /** 118 * Initializes the OpenCms logger configuration.<p> 119 */ 120 static { 121 // 122 // DO NOT USE ANY OPENCMS CLASSES THAT USE STATIC LOGGER INSTANCES IN THIS STATIC BLOCK 123 // OTHERWISE THEIR LOGGER WOULD NOT BE INITIALIZED PROPERLY 124 // 125 try { 126 // look for the log4j2.xml that shipped with OpenCms 127 URL log4j2Url = Loader.getResource("log4j2.xml", null); 128 if (log4j2Url != null) { 129 // found log4j properties 130 File log4jProps = new File(URLDecoder.decode(log4j2Url.getPath(), Charset.defaultCharset().name())); 131 String path = log4jProps.getAbsolutePath(); 132 133 // check if OpenCms should set the log file environment variable 134 boolean setLogFile = Boolean.parseBoolean(System.getProperty("opencms.set.logfile", "true")); 135 if (setLogFile) { 136 final String propertyLogfolder = System.getProperty(PROPERTY_LOGFOLDER); 137 final String propertyLogfile = System.getProperty(PROPERTY_LOGFILE); 138 139 // in a default OpenCms configuration, the following path would point to the OpenCms "WEB-INF" folder 140 String webInfPath = log4jProps.getParent(); 141 webInfPath = webInfPath.substring(0, webInfPath.lastIndexOf(File.separatorChar) + 1); 142 143 String logFilePath = ((propertyLogfile != null) 144 ? propertyLogfile 145 : webInfPath + FOLDER_LOGS + FILE_LOG); 146 String logFolderPath = ((propertyLogfolder != null) ? propertyLogfolder : webInfPath + FOLDER_LOGS); 147 m_logFileRfsPath = new File(logFilePath).getAbsolutePath(); 148 m_logFileRfsFolder = new File(logFolderPath).getAbsolutePath() + File.separatorChar; 149 150 // Set "opencms.log*" variables if not set. These should be used in the log42.xml config file 151 // like this: 'fileName="${sys:opencms.logfolder}opencms-search.log"' 152 if (propertyLogfile == null) { 153 System.setProperty(PROPERTY_LOGFILE, m_logFileRfsPath); 154 } 155 if (propertyLogfolder == null) { 156 System.setProperty(PROPERTY_LOGFOLDER, m_logFileRfsFolder); 157 } 158 159 // re-read the configuration with the new environment variable available 160 ConfigurationSource source = ConfigurationSource.fromUri(log4j2Url.toURI()); 161 Configurator.initialize(null, source); 162 163 // In case we have multiple OpenCms instances running in the servlet container, we don't want them to use 164 // the same log file path, so clear the system properties. Users will have to modify the log4j configuration 165 // if they want to customize the log file locations for multiple instances. 166 for (String prop : new String[] {PROPERTY_LOGFILE, PROPERTY_LOGFOLDER}) { 167 System.clearProperty(prop); 168 } 169 } 170 // can't localize this message since this would end in an endless logger init loop 171 INIT = LogFactory.getLog("org.opencms.init"); 172 INIT.info(". Log4j config file : " + path); 173 INIT.debug(". m_logFileRfsPath : " + m_logFileRfsPath); 174 INIT.debug(". m_logFileRfsFolder : " + m_logFileRfsFolder); 175 } else { 176 System.err.println("'log4j2.xml' not found. (Default location: WEB-INF/classes/log4j2.xml)"); 177 } 178 } catch (SecurityException e) { 179 // may be caused if environment can't be written 180 e.printStackTrace(System.err); 181 } catch (Exception e) { 182 // unexpected but nothing we can do about it, print stack trace and continue 183 e.printStackTrace(System.err); 184 } 185 } 186 187 /** 188 * Hides the public constructor.<p> 189 */ 190 private CmsLog() { 191 192 // hides the public constructor 193 } 194 195 /** 196 * Helper for safely evaluating lambda functions to produce log output and catch exceptions they might throw. 197 * 198 * @param log the logger to use for logging errors 199 * @param stringProvider the string provider (normally just given as a lambda function) 200 * 201 * @return the result of the function (or <ERROR> if an exception was thrown) 202 */ 203 public static String eval(Log log, Callable<String> stringProvider) { 204 205 try { 206 return stringProvider.call(); 207 } catch (Exception e) { 208 log.error(e.getLocalizedMessage(), e); 209 return "<ERROR>"; 210 } 211 } 212 213 /** 214 * Returns the log for the selected object.<p> 215 * 216 * If the provided object is a String, this String will 217 * be used as channel name. Otherwise the objects 218 * class name will be used as channel name.<p> 219 * 220 * @param obj the object channel to use 221 * @return the log for the selected object channel 222 */ 223 public static Log getLog(Object obj) { 224 225 if (obj instanceof String) { 226 return LogFactory.getLog((String)obj); 227 } else if (obj instanceof Class<?>) { 228 return LogFactory.getLog((Class<?>)obj); 229 } else { 230 return LogFactory.getLog(obj.getClass()); 231 } 232 } 233 234 /** 235 * Checks whether a log channel should be manageable through the GUI. 236 * 237 * @param channel the name of the channel 238 * 239 * @return true if the channel should be manageable through the GUI 240 */ 241 public static boolean isManageable(String channel) { 242 243 return !NON_MANAGEABLE_CHANNELS.contains(channel); 244 } 245 246 /** 247 * Adds a log channel that should not be manageable via the GUI. 248 * 249 * @param channel the channel to add 250 */ 251 public static void makeChannelNonManageable(String channel) { 252 253 NON_MANAGEABLE_CHANNELS.add(channel); 254 } 255 256 /** 257 * Render throwable using Throwable.printStackTrace. 258 * <p>This code copy from "org.apache.log4j.DefaultThrowableRenderer.render(Throwable throwable)"</p> 259 * @param throwable throwable, may not be null. 260 * @return string representation. 261 */ 262 public static String[] render(final Throwable throwable) { 263 264 StringWriter sw = new StringWriter(); 265 PrintWriter pw = new PrintWriter(sw); 266 try { 267 throwable.printStackTrace(pw); 268 } catch (RuntimeException ex) { 269 // nothing to do 270 } 271 pw.flush(); 272 LineNumberReader reader = new LineNumberReader(new StringReader(sw.toString())); 273 ArrayList<String> lines = new ArrayList<>(); 274 try { 275 String line = reader.readLine(); 276 while (line != null) { 277 lines.add(line); 278 line = reader.readLine(); 279 } 280 } catch (IOException ex) { 281 if (ex instanceof InterruptedIOException) { 282 Thread.currentThread().interrupt(); 283 } 284 lines.add(ex.toString()); 285 } 286 //String[] tempRep = new String[lines.size()]; 287 return lines.toArray(new String[0]); 288 } 289 290 /** 291 * Returns the absolute path to the folder of the main OpenCms log file 292 * (in the "real" file system).<p> 293 * 294 * If the method returns <code>null</code>, this means that the log 295 * file is not managed by OpenCms.<p> 296 * 297 * @return the absolute path to the folder of the main OpenCms log file (in 298 * the "real" file system) 299 */ 300 protected static String getLogFileRfsFolder() { 301 302 return m_logFileRfsFolder; 303 } 304 305 /** 306 * Returns the filename of the log file (in the "real" file system).<p> 307 * 308 * If the method returns <code>null</code>, this means that the log 309 * file is not managed by OpenCms.<p> 310 * 311 * @return the filename of the log file (in the "real" file system) 312 */ 313 protected static String getLogFileRfsPath() { 314 315 return m_logFileRfsPath; 316 } 317}