001/** 002 * 003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. 004 * 005 * This library is free software; you can redistribute it and/or 006 * modify it under the terms of the GNU Lesser General Public 007 * License as published by the Free Software Foundation; either 008 * version 2.1 of the License, or (at your option) any later version. 009 * 010 * This library is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 * Lesser General Public License for more details. 014 * 015 * You should have received a copy of the GNU Lesser General Public 016 * License along with this library. If not, see <http://www.gnu.org/licenses/>. 017 * 018 **/ 019package lucee.runtime.tag; 020 021import java.io.IOException; 022import java.nio.charset.Charset; 023import java.util.Iterator; 024import java.util.Map; 025import java.util.Set; 026import java.util.concurrent.ConcurrentHashMap; 027 028import lucee.commons.io.CharsetUtil; 029import lucee.commons.io.log.LogUtil; 030import lucee.commons.io.log.log4j.Log4jUtil; 031import lucee.commons.io.log.log4j.LogAdapter; 032import lucee.commons.io.res.Resource; 033import lucee.commons.io.retirement.RetireListener; 034import lucee.commons.io.retirement.RetireOutputStream; 035import lucee.commons.lang.ExceptionUtil; 036import lucee.commons.lang.StringUtil; 037import lucee.runtime.PageContext; 038import lucee.runtime.PageContextImpl; 039import lucee.runtime.config.Config; 040import lucee.runtime.config.ConfigImpl; 041import lucee.runtime.exp.ApplicationException; 042import lucee.runtime.exp.CasterException; 043import lucee.runtime.exp.PageException; 044import lucee.runtime.ext.tag.TagImpl; 045import lucee.runtime.op.Caster; 046import lucee.runtime.tag.util.DeprecatedUtil; 047import lucee.runtime.type.KeyImpl; 048 049import org.apache.log4j.Level; 050 051/** 052* Writes a message to a log file. 053* 054* 055* 056**/ 057public final class Log extends TagImpl { 058 059 private static final String DEfAULT_LOG = "application"; 060 061 /** If you omit the file attribute, specifies the standard log file in which to write the message. 062 ** Ignored if you specify a file attribute */ 063 private String log=DEfAULT_LOG; 064 065 /** The message text to log. */ 066 private String text; 067 068 /** The type or severity of the message. */ 069 private short type=lucee.commons.io.log.Log.LEVEL_INFO; 070 /** */ 071 private String file; 072 private Throwable exception; 073 074 /** Specifies whether to log the application name if one has been specified in a application tag. */ 075 private boolean application; 076 private Charset charset=null; 077 078 private boolean async; 079 080 @Override 081 public void release() { 082 super.release(); 083 log=DEfAULT_LOG; 084 type=lucee.commons.io.log.Log.LEVEL_INFO; 085 file=null; 086 application=false; 087 charset=null; 088 exception=null; 089 text=null; 090 async=false; 091 } 092 093 /** set the value log 094 * If you omit the file attribute, specifies the standard log file in which to write the message. 095 * Ignored if you specify a file attribute 096 * @param log value to set 097 * @throws ApplicationException 098 **/ 099 public void setLog(String log) throws ApplicationException { 100 if(StringUtil.isEmpty(log,true)) return; 101 this.log=log.trim(); 102 // throw new ApplicationException("invalid value for attribute log ["+log+"]","valid values are [application, scheduler,console]"); 103 } 104 105 /** set the value text 106 * The message text to log. 107 * @param text value to set 108 **/ 109 public void setText(String text) { 110 this.text=text; 111 } 112 public void setException(Object exception) throws PageException { 113 this.exception=Throw.toPageException(exception, null); 114 if(this.exception==null) throw new CasterException(exception,Exception.class); 115 } 116 117 118 119 /** set the value type 120 * The type or severity of the message. 121 * @param type value to set 122 * @throws ApplicationException 123 **/ 124 public void setType(String type) throws ApplicationException { 125 type=type.toLowerCase().trim(); 126 if(type.equals("information")) this.type=lucee.commons.io.log.Log.LEVEL_INFO; 127 else if(type.equals("info")) this.type=lucee.commons.io.log.Log.LEVEL_INFO; 128 else if(type.equals("warning")) this.type=lucee.commons.io.log.Log.LEVEL_WARN; 129 else if(type.equals("warn")) this.type=lucee.commons.io.log.Log.LEVEL_WARN; 130 else if(type.equals("error")) this.type=lucee.commons.io.log.Log.LEVEL_ERROR; 131 else if(type.startsWith("fatal")) this.type=lucee.commons.io.log.Log.LEVEL_FATAL; 132 else if(type.startsWith("debug")) this.type=lucee.commons.io.log.Log.LEVEL_DEBUG; 133 else if(type.startsWith("trace")) this.type=lucee.commons.io.log.LogUtil.LEVEL_TRACE; 134 else 135 throw new ApplicationException("invalid value for attribute type ["+type+"]", 136 "valid values are [information,warning,error,fatal,debug]"); 137 138 } 139 140 /** set the value time 141 * Specifies whether to log the system time. 142 * @param time value to set 143 * @throws ApplicationException 144 **/ 145 public void setTime(boolean useTime) throws ApplicationException { 146 if(useTime) return; 147 DeprecatedUtil.tagAttribute(pageContext,"Log", "time"); 148 throw new ApplicationException("attribute [time] for tag [log] is deprecated, only the value true is allowed"); 149 } 150 151 /** set the value file 152 * 153 * @param file value to set 154 * @throws ApplicationException 155 **/ 156 public void setFile(String file) throws ApplicationException { 157 if(StringUtil.isEmpty(file))return; 158 159 if(file.indexOf('/')!=-1 || file.indexOf('\\')!=-1) 160 throw new ApplicationException("value ["+file+"] from attribute [file] at tag [log] can only contain a filename, file separators like [\\/] are not allowed"); 161 if(!file.endsWith(".log"))file+=".log"; 162 this.file=file; 163 } 164 165 /** set the value date 166 * Specifies whether to log the system date. 167 * @param date value to set 168 * @throws ApplicationException 169 **/ 170 public void setDate(boolean useDate) throws ApplicationException { 171 if(useDate) return; 172 DeprecatedUtil.tagAttribute(pageContext,"Log", "date"); 173 throw new ApplicationException("attribute [date] for tag [log] is deprecated, only the value true is allowed"); 174 } 175 176 /** set the value thread 177 * Specifies whether to log the thread ID. The thread ID identifies which internal service thread logged a 178 * message. Since a service thread normally services a CFML page request to completion, then moves on to 179 * the next queued request, the thread ID serves as a rough indication of which request logged a message. 180 * Leaving thread IDs turned on can help diagnose patterns of server activity. 181 * @param thread value to set 182 * @throws ApplicationException 183 **/ 184 public void setThread(boolean thread) throws ApplicationException { 185 if(thread) return; 186 DeprecatedUtil.tagAttribute(pageContext,"Log", "thread"); 187 throw new ApplicationException("attribute [thread] for tag [log] is deprecated, only the value true is allowed"); 188 } 189 190 /** set the value application 191 * Specifies whether to log the application name if one has been specified in a application tag. 192 * @param application value to set 193 **/ 194 public void setApplication(boolean application) { 195 this.application=application; 196 } 197 198 // old function for backward compatiblity 199 public void setSpoolenable(boolean async){ 200 setAsync(async); 201 } 202 203 public void setAsync(boolean async){ 204 this.async=async; 205 } 206 207 208 @Override 209 public int doStartTag() throws PageException { 210 211 if(text==null && exception==null) 212 throw new ApplicationException("Wrong Context, you must define one of the following attributes [text, exception]"); 213 214 ConfigImpl config =(ConfigImpl) pageContext.getConfig(); 215 lucee.commons.io.log.Log logger; 216 if(file==null) { 217 logger=config.getLog(log.toLowerCase(),false); 218 if(logger==null) { 219 // for backward compatiblity 220 if("console".equalsIgnoreCase(log)) 221 logger=new LogAdapter(Log4jUtil.getConsoleLog(config, false, "cflog", Level.INFO)); 222 else { 223 Set<String> set = config.getLoggers().keySet(); 224 Iterator<String> it = set.iterator(); 225 lucee.runtime.type.Collection.Key[] keys=new lucee.runtime.type.Collection.Key[set.size()]; 226 int index=0; 227 while(it.hasNext()){ 228 keys[index++]=KeyImpl.init(it.next()); 229 } 230 231 throw new ApplicationException(ExceptionUtil.similarKeyMessage(keys, log, "attribute log", "log names", true)); 232 } 233 } 234 } 235 else { 236 logger=getFileLog(pageContext,file,charset,async); 237 } 238 239 240 String contextName = pageContext.getApplicationContext().getName(); 241 if(contextName==null || !application)contextName=""; 242 if(exception!=null) { 243 if(StringUtil.isEmpty(text)) LogUtil.log(logger, type, contextName, exception); 244 else LogUtil.log(logger, type, contextName, text, exception); 245 } 246 else if(!StringUtil.isEmpty(text)) 247 logger.log(type,contextName,text); 248 else 249 throw new ApplicationException("you must define attribute text or attribute exception with the tag cflog"); 250 //logger.write(toStringType(type),contextName,text); 251 return SKIP_BODY; 252 } 253 254 private static lucee.commons.io.log.Log getFileLog(PageContext pc, String file, Charset charset, boolean async) throws PageException { 255 Config config=pc.getConfig(); 256 Resource logDir=config.getConfigDir().getRealResource("logs"); 257 if(!logDir.exists())logDir.mkdirs(); 258 Resource res = logDir.getRealResource(file); 259 260 261 LogAdapter log= FileLogPool.instance.get(res,charset); 262 if(log!=null) return log; 263 264 if(charset==null) charset=((PageContextImpl)pc).getResourceCharset(); 265 266 try { 267 log=new LogAdapter(Log4jUtil.getResourceLog(config,res,charset , "cflog."+FileLogPool.toKey(file,charset), Level.TRACE,5,new Listener(FileLogPool.instance,res,charset),async)); 268 FileLogPool.instance.put(res,charset,log); 269 } 270 catch (IOException e) { 271 throw Caster.toPageException(e); 272 } 273 return log; 274 } 275 276 /** 277 * @param charset the charset to set 278 */ 279 public void setCharset(String charset) { 280 if(StringUtil.isEmpty(charset,true)) return; 281 this.charset = CharsetUtil.toCharset(charset); 282 } 283 284 private static class FileLogPool { 285 286 private static Map<String,LogAdapter> logs=new ConcurrentHashMap<String, LogAdapter>(); 287 private static FileLogPool instance=new FileLogPool(); 288 289 public void retire(Resource res, Charset charset) { 290 logs.remove(res.getAbsolutePath()); 291 } 292 293 public void put(Resource res, Charset charset, LogAdapter log) { 294 logs.put(res.getAbsolutePath(),log); 295 } 296 297 public LogAdapter get(Resource res, Charset charset) { 298 LogAdapter l = logs.get(res.getAbsolutePath()); 299 return l; 300 } 301 302 public static String toKey(String file, Charset charset) { 303 if(charset==null); charset=CharsetUtil.UTF8; 304 return StringUtil.toVariableName(file)+"."+StringUtil.toVariableName(charset.name()); 305 } 306 } 307 308 private static class Listener implements RetireListener { 309 310 private FileLogPool pool; 311 private Resource res; 312 private Charset charset; 313 314 public Listener(FileLogPool pool, Resource res, Charset charset){ 315 this.pool=pool; 316 this.res=res; 317 this.charset=charset; 318 } 319 320 @Override 321 public void retire(RetireOutputStream os) { 322 pool.retire(res,charset); 323 } 324 } 325}