/** * * Copyright (c) 2014, the Railo Company Ltd. All rights reserved. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library. If not, see <http://www.gnu.org/licenses/>. * **/ package lucee.runtime.tag; import java.io.IOException; import java.nio.charset.Charset; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import lucee.commons.io.CharsetUtil; import lucee.commons.io.log.LogUtil; import lucee.commons.io.log.log4j.Log4jUtil; import lucee.commons.io.log.log4j.LogAdapter; import lucee.commons.io.res.Resource; import lucee.commons.io.retirement.RetireListener; import lucee.commons.io.retirement.RetireOutputStream; import lucee.commons.lang.CharSet; import lucee.commons.lang.ExceptionUtil; import lucee.commons.lang.StringUtil; import lucee.runtime.PageContext; import lucee.runtime.PageContextImpl; import lucee.runtime.config.ConfigImpl; import lucee.runtime.exp.ApplicationException; import lucee.runtime.exp.CasterException; import lucee.runtime.exp.PageException; import lucee.runtime.ext.tag.TagImpl; import lucee.runtime.op.Caster; import lucee.runtime.tag.util.DeprecatedUtil; import lucee.runtime.type.KeyImpl; import org.apache.log4j.Level; /** * Writes a message to a log file. * * * **/ public final class Log extends TagImpl { private static final String DEfAULT_LOG = "application"; /** If you omit the file attribute, specifies the standard log file in which to write the message. ** Ignored if you specify a file attribute */ private String log=DEfAULT_LOG; /** The message text to log. */ private String text; /** The type or severity of the message. */ private short type=lucee.commons.io.log.Log.LEVEL_INFO; /** */ private String file; private Throwable exception; /** Specifies whether to log the application name if one has been specified in a application tag. */ private boolean application; private CharSet charset=null; private boolean async; @Override public void release() { super.release(); log=DEfAULT_LOG; type=lucee.commons.io.log.Log.LEVEL_INFO; file=null; application=false; charset=null; exception=null; text=null; async=false; } /** set the value log * If you omit the file attribute, specifies the standard log file in which to write the message. * Ignored if you specify a file attribute * @param log value to set * @throws ApplicationException **/ public void setLog(String log) throws ApplicationException { if(StringUtil.isEmpty(log,true)) return; this.log=log.trim(); // throw new ApplicationException("invalid value for attribute log ["+log+"]","valid values are [application, scheduler,console]"); } /** set the value text * The message text to log. * @param text value to set **/ public void setText(String text) { this.text=text; } public void setException(Object exception) throws PageException { this.exception=Throw.toPageException(exception, null); if(this.exception==null) throw new CasterException(exception,Exception.class); } /** set the value type * The type or severity of the message. * @param type value to set * @throws ApplicationException **/ public void setType(String type) throws ApplicationException { type=type.toLowerCase().trim(); if(type.equals("information")) this.type=lucee.commons.io.log.Log.LEVEL_INFO; else if(type.equals("info")) this.type=lucee.commons.io.log.Log.LEVEL_INFO; else if(type.equals("warning")) this.type=lucee.commons.io.log.Log.LEVEL_WARN; else if(type.equals("warn")) this.type=lucee.commons.io.log.Log.LEVEL_WARN; else if(type.equals("error")) this.type=lucee.commons.io.log.Log.LEVEL_ERROR; else if(type.startsWith("fatal")) this.type=lucee.commons.io.log.Log.LEVEL_FATAL; else if(type.startsWith("debug")) this.type=lucee.commons.io.log.Log.LEVEL_DEBUG; else if(type.startsWith("trace")) this.type=lucee.commons.io.log.Log.LEVEL_TRACE; else throw new ApplicationException("invalid value for attribute type ["+type+"]", "valid values are [information,warning,error,fatal,debug]"); } /** set the value time * Specifies whether to log the system time. * @param time value to set * @throws ApplicationException **/ public void setTime(boolean useTime) throws ApplicationException { if(useTime) return; //DeprecatedUtil.tagAttribute(pageContext,"Log", "time"); throw new ApplicationException("attribute [time] for tag [log] is deprecated, only the value true is allowed"); } /** set the value file * * @param file value to set * @throws ApplicationException **/ public void setFile(String file) throws ApplicationException { if(StringUtil.isEmpty(file))return; if(file.indexOf('/')!=-1 || file.indexOf('\\')!=-1) throw new ApplicationException("value ["+file+"] from attribute [file] at tag [log] can only contain a filename, file separators like [\\/] are not allowed"); if(!file.endsWith(".log"))file+=".log"; this.file=file; } /** set the value date * Specifies whether to log the system date. * @param date value to set * @throws ApplicationException **/ public void setDate(boolean useDate) throws ApplicationException { if(useDate) return; //DeprecatedUtil.tagAttribute(pageContext,"Log", "date"); throw new ApplicationException("attribute [date] for tag [log] is deprecated, only the value true is allowed"); } /** set the value thread * Specifies whether to log the thread ID. The thread ID identifies which internal service thread logged a * message. Since a service thread normally services a CFML page request to completion, then moves on to * the next queued request, the thread ID serves as a rough indication of which request logged a message. * Leaving thread IDs turned on can help diagnose patterns of server activity. * @param thread value to set * @throws ApplicationException **/ public void setThread(boolean thread) throws ApplicationException { if(thread) return; //DeprecatedUtil.tagAttribute(pageContext,"Log", "thread"); throw new ApplicationException("attribute [thread] for tag [log] is deprecated, only the value true is allowed"); } /** set the value application * Specifies whether to log the application name if one has been specified in a application tag. * @param application value to set **/ public void setApplication(boolean application) { this.application=application; } // old function for backward compatiblity public void setSpoolenable(boolean async){ setAsync(async); } public void setAsync(boolean async){ this.async=async; } @Override public int doStartTag() throws PageException { if(text==null && exception==null) throw new ApplicationException("Wrong Context, you must define one of the following attributes [text, exception]"); PageContextImpl pci=(PageContextImpl) pageContext; ConfigImpl config =(ConfigImpl) pageContext.getConfig(); lucee.commons.io.log.Log logger; if(file==null) { logger=pci.getLog(log.toLowerCase(),false); if(logger==null) { // for backward compatiblity if("console".equalsIgnoreCase(log)) logger=new LogAdapter(Log4jUtil.getConsoleLog(config, false, "cflog", Level.INFO)); else { java.util.Collection<String> set = pci.getLogNames(); Iterator<String> it = set.iterator(); lucee.runtime.type.Collection.Key[] keys=new lucee.runtime.type.Collection.Key[set.size()]; int index=0; while(it.hasNext()){ keys[index++]=KeyImpl.init(it.next()); } throw new ApplicationException(ExceptionUtil.similarKeyMessage(keys, log, "attribute log", "log names",null, true)); } } } else { logger=getFileLog(pageContext,file,charset,async); } String contextName = pageContext.getApplicationContext().getName(); if(contextName==null || !application)contextName=""; if(exception!=null) { if(StringUtil.isEmpty(text)) LogUtil.log(logger, type, contextName, exception); else LogUtil.log(logger, type, contextName, text, exception); } else if(!StringUtil.isEmpty(text)) logger.log(type,contextName,text); else throw new ApplicationException("you must define attribute text or attribute exception with the tag cflog"); //logger.write(toStringType(type),contextName,text); return SKIP_BODY; } private static lucee.commons.io.log.Log getFileLog(PageContext pc, String file, CharSet charset, boolean async) throws PageException { ConfigImpl config=(ConfigImpl) pc.getConfig(); Resource logDir=config.getLogDirectory(); Resource res = logDir.getRealResource(file); LogAdapter log= FileLogPool.instance.get(res,CharsetUtil.toCharset(charset)); if(log!=null) return log; if(charset==null) charset=CharsetUtil.toCharSet(((PageContextImpl)pc).getResourceCharset()); try { log=new LogAdapter(Log4jUtil.getResourceLog(config,res,CharsetUtil.toCharset(charset) , "cflog."+FileLogPool.toKey(file,CharsetUtil.toCharset(charset)), Level.TRACE,5,new Listener(FileLogPool.instance,res,charset),async)); FileLogPool.instance.put(res,CharsetUtil.toCharset(charset),log); } catch (IOException e) { throw Caster.toPageException(e); } return log; } /** * @param charset the charset to set */ public void setCharset(String charset) { if(StringUtil.isEmpty(charset,true)) return; this.charset = CharsetUtil.toCharSet(charset); } private static class FileLogPool { private static FileLogPool instance=new FileLogPool(); private static Map<String,LogAdapter> logs=new ConcurrentHashMap<String, LogAdapter>(); public void retire(Resource res, Charset charset) { logs.remove(res.getAbsolutePath()); } public void put(Resource res, Charset charset, LogAdapter log) { logs.put(res.getAbsolutePath(),log); } public LogAdapter get(Resource res, Charset charset) { LogAdapter l = logs.get(res.getAbsolutePath()); return l; } public static String toKey(String file, Charset charset) { if(charset==null); charset=CharsetUtil.UTF8; return StringUtil.toVariableName(file)+"."+StringUtil.toVariableName(charset.name()); } } private static class Listener implements RetireListener { private FileLogPool pool; private Resource res; private CharSet charset; public Listener(FileLogPool pool, Resource res, CharSet charset){ this.pool=pool; this.res=res; this.charset=charset; } @Override public void retire(RetireOutputStream os) { pool.retire(res,CharsetUtil.toCharset(charset)); } } }