hslogger 1.0.5 → 1.0.6
raw patch · 11 files changed
+1192/−8 lines, 11 filesdep +old-localedep +timebinary-added
Dependencies added: old-locale, time
Files
- contrib/java/build.xml +38/−0
- contrib/java/hslogger4j-plugins.xml +22/−0
- contrib/java/hslogger4j.jar binary
- contrib/java/org/haskell/hslogger/HsloggerLevel.java +154/−0
- contrib/java/org/haskell/hslogger/LogFileXMLReceiver.java +303/−0
- contrib/java/org/haskell/hslogger/XMLDecoder.java +410/−0
- hslogger.cabal +13/−5
- src/System/Log/Handler/Log4jXML.hs +242/−0
- src/System/Log/Handler/Simple.hs +3/−0
- src/System/Log/Handler/Syslog.hs +2/−0
- src/System/Log/Logger.hs +5/−3
+ contrib/java/build.xml view
@@ -0,0 +1,38 @@+<project name="hslogger4j" default="dist" basedir=".">+ <description>+ simple example build file+ </description>+ <!-- set global properties for this build -->+ <property name="src" location="."/>+ <property name="build" location="build"/>+ <property name="dist" location="dist"/>++ <target name="init">+ <!-- Create the time stamp -->+ <tstamp/>+ <!-- Create the build directory structure used by compile -->+ <mkdir dir="${build}"/>+ </target>++ <target name="compile" depends="init"+ description="compile the source " >+ <!-- Compile the java code from ${src} into ${build} -->+ <javac srcdir="${src}" destdir="${build}"/>+ </target>++ <target name="dist" depends="compile"+ description="generate the distribution" >+ <!-- Create the distribution directory -->+ <mkdir dir="${dist}"/>++ <!-- Put everything in ${build} into the MyProject-${DSTAMP}.jar file -->+ <jar jarfile="${dist}/hslogger4j-${DSTAMP}.jar" basedir="${build}"/>+ </target>++ <target name="clean"+ description="clean up" >+ <!-- Delete the ${build} and ${dist} directory trees -->+ <delete dir="${build}"/>+ <delete dir="${dist}"/>+ </target>+</project>
+ contrib/java/hslogger4j-plugins.xml view
@@ -0,0 +1,22 @@+<?xml version="1.0" encoding="UTF-8" ?>+<!DOCTYPE log4j:configuration >+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/" debug="true">++ <plugin name="Hslogger4jSocketReceiver" + class="org.apache.log4j.net.XMLSocketReceiver">+ <param name="decoder" value="org.haskell.hslogger4j.XMLDecoder"/>+ <param name="Port" value="4448"/>+ <level value="DEBUG"/>+ </plugin>++ <plugin name="Hslogger4jFileReader" + class="org.haskell.hslogger4j.LogFileXMLReceiver">+ <param name="decoder" value="org.haskell.hslogger4j.XMLDecoder"/>+ </plugin>++ <root>+ <level value="debug"/>+ </root>++</log4j:configuration>+
+ contrib/java/hslogger4j.jar view
binary file changed (absent → 9129 bytes)
+ contrib/java/org/haskell/hslogger/HsloggerLevel.java view
@@ -0,0 +1,154 @@+/*+ * Copyright 1999,2004 The Apache Software Foundation.+ * + * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ * + * http://www.apache.org/licenses/LICENSE-2.0+ * + * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++package org.haskell.hslogger4j;++import java.util.ArrayList;+import java.util.List;++import org.apache.log4j.Level;++/**+ * An extension of the Level class that provides support for java.util.logging + * Levels.+ *+ * @author Scott Deboy <sdeboy@apache.org>+ *+ */++public class HsloggerLevel extends org.apache.log4j.Level {++ public final static int EMERGENCY_INT = 51000; // FATAL+ public final static int ALERT_INT = 50000;+ public final static int CRITICAL_INT = 41000;+ public final static int ERROR_INT = 40000; // ERROR+ public final static int WARNING_INT = 30000; // WARN+ public final static int NOTICE_INT = 21000;+ public static final int INFO_INT = 20000; // INFO+ public static final int DEBUG_INT = 10000; // DEBUG+ + public static final HsloggerLevel EMERGENCY = new HsloggerLevel(EMERGENCY_INT, "EMERGENCY", 0);+ public static final HsloggerLevel ALERT = new HsloggerLevel(ALERT_INT , "ALERT" , 1); + public static final HsloggerLevel CRITICAL = new HsloggerLevel(CRITICAL_INT , "CRITICAL" , 2); + public static final HsloggerLevel ERROR = new HsloggerLevel(ERROR_INT , "ERROR" , 3);+ public static final HsloggerLevel WARNING = new HsloggerLevel(WARNING_INT , "WARNING" , 4);+ public static final HsloggerLevel NOTICE = new HsloggerLevel(NOTICE_INT , "NOTICE" , 5);+ public static final HsloggerLevel INFO = new HsloggerLevel(INFO_INT , "INFO" , 6);+ public static final HsloggerLevel DEBUG = new HsloggerLevel(DEBUG_INT , "DEBUG" , 7);++ protected HsloggerLevel(int level, String levelStr, int syslogEquivalent) {+ super(level, levelStr, syslogEquivalent);+ }++ /**+ Convert an integer passed as argument to a level. If the+ conversion fails, then this method returns the specified default.+ */+ public static HsloggerLevel toLevel(int val, HsloggerLevel defaultLevel) {+ switch (val) {+ case EMERGENCY_INT:+ return EMERGENCY;++ case ALERT_INT:+ return ALERT;++ case CRITICAL_INT:+ return CRITICAL;++ case ERROR_INT:+ return ERROR;++ case WARNING_INT:+ return WARNING;++ case NOTICE_INT:+ return NOTICE;+ + case INFO_INT:+ return INFO;++ case DEBUG_INT:+ return DEBUG;++ default:+ return defaultLevel;+ }+ }++ public static Level toLevel(int val) {+ return toLevel(val, CRITICAL);+ }++ public static List getAllPossibleLevels() {+ ArrayList list=new ArrayList();+ list.add(DEBUG);+ list.add(INFO);+ list.add(NOTICE);+ list.add(WARNING);+ list.add(ERROR);+ list.add(CRITICAL);+ list.add(ALERT);+ list.add(EMERGENCY);+ return list;+ }++ public static Level toLevel(String s) {+ return toLevel(s, Level.DEBUG);+ }+ + public static Level toLevel(String sArg, Level defaultLevel) {+ if (sArg == null) {+ return defaultLevel;+ }++ String s = sArg.toUpperCase();++ if (s.equals("EMERGENCY")) {+ return EMERGENCY;+ }++ if (s.equals("ALERT")) {+ return ALERT;+ }++ if (s.equals("CRITICAL")) {+ return CRITICAL;+ }++ if (s.equals("ERROR")) {+ return ERROR;+ }++ if (s.equals("WARNING")) {+ return WARNING;+ }++ if (s.equals("NOTICE")) {+ return NOTICE;+ }++ if (s.equals("INFO")) {+ return INFO;+ }++ if (s.equals("DEBUG")) {+ return DEBUG;+ }+ return defaultLevel;+ }++}+
+ contrib/java/org/haskell/hslogger/LogFileXMLReceiver.java view
@@ -0,0 +1,303 @@+/* + * Copyright 1999,2004 The Apache Software Foundation. Licensed under the Apache License, Version + * 2.0 (the "License"); you may not use this file except in compliance with the License. You may + * obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by + * applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See + * the License for the specific language governing permissions and limitations under the License. + */ + +package org.haskell.hslogger4j; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Collection; +import java.util.Iterator; + +import org.apache.log4j.xml.*; +import org.apache.log4j.helpers.Constants; +import org.apache.log4j.plugins.Receiver; +import org.apache.log4j.rule.ExpressionRule; +import org.apache.log4j.rule.Rule; +import org.apache.log4j.spi.Decoder; +import org.apache.log4j.spi.LoggingEvent; + +/** + * LogFileXMLReceiver will read an xml-formated log file and make the events in the log file + * available to the log4j framework. + * <p> + * This receiver supports log files created using log4j's XMLLayout, as well as java.util.logging + * XMLFormatter (via the org.apache.log4j.spi.Decoder interface). + * <p> + * By default, log4j's XMLLayout is supported (no need to specify a decoder in that case). + * <p> + * To configure this receiver to support java.util.logging's XMLFormatter, specify a 'decoder' param + * of org.apache.log4j.xml.UtilLoggingXMLDecoder. + * <p> + * Tailing -may- work, but not in all cases (try using a file:// URL). If a process has a log file + * open, the receiver may be able to read and tail the file. If the process closes the file and + * reopens the file, the receiver may not be able to continue tailing the file. + * <p> + * An expressionFilter may be specified. Only events passing the expression will be forwarded to the + * log4j framework. + * <p> + * Once the event has been "posted", it will be handled by the appenders currently configured in the + * LoggerRespository. + * + * @author Scott Deboy <sdeboy@apache.org> + * @since 1.3 + */ + +public class LogFileXMLReceiver extends Receiver { + private String fileURL; + private Rule expressionRule; + private String filterExpression; + private String decoder = "org.apache.log4j.xml.XMLDecoder"; + private boolean tailing = false; + + private Decoder decoderInstance; + private Reader reader; + private static final String FILE_KEY = "file"; + private String host; + private String path; + private boolean useCurrentThread; + + /** + * Accessor + * + * @return file URL + */ + public String getFileURL() { + return fileURL; + } + + /** + * Specify the URL of the XML-formatted file to process. + * + * @param fileURL + */ + public void setFileURL(String fileURL) { + this.fileURL = fileURL; + } + + /** + * Accessor + * + * @return + */ + public String getDecoder() { + return decoder; + } + + /** + * Specify the class name implementing org.apache.log4j.spi.Decoder that can process the file. + * + * @param _decoder + */ + public void setDecoder(String _decoder) { + decoder = _decoder; + } + + /** + * Accessor + * + * @return filter expression + */ + public String getFilterExpression() { + return filterExpression; + } + + /** + * Accessor + * + * @return tailing flag + */ + public boolean isTailing() { + return tailing; + } + + /** + * Set the 'tailing' flag - may only work on file:// URLs and may stop tailing if the writing + * process closes the file and reopens. + * + * @param tailing + */ + public void setTailing(boolean tailing) { + this.tailing = tailing; + } + + /** + * Set the filter expression that will cause only events which pass the filter to be forwarded + * to the log4j framework. + * + * @param filterExpression + */ + public void setFilterExpression(String filterExpression) { + this.filterExpression = filterExpression; + } + + private boolean passesExpression(LoggingEvent event) { + if (event != null) { + if (expressionRule != null) { + return (expressionRule.evaluate(event)); + } + } + return true; + } + + public static void main(String[] args) { + /* + * LogFileXMLReceiver test = new LogFileXMLReceiver(); + * test.setFileURL("file:///c:/samplelog.xml"); test.setFilterExpression("level >= TRACE"); + * test.activateOptions(); + */ + } + + /** + * Close the receiver, release any resources that are accessing the file. + */ + public void shutdown() { + try { + if (reader != null) { + reader.close(); + reader = null; + } + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } + + /** + * Process the file + */ + public void activateOptions() { + Runnable runnable = new Runnable() { + public void run() { + try { + URL url = new URL(fileURL); + host = url.getHost(); + if (host != null && host.equals("")) { + host = FILE_KEY; + } + path = url.getPath(); + } catch (MalformedURLException e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + + try { + if (filterExpression != null) { + expressionRule = ExpressionRule.getRule(filterExpression); + } + } catch (Exception e) { + getLogger().warn("Invalid filter expression: " + filterExpression, e); + } + + Class c; + try { + c = Class.forName(decoder); + Object o = c.newInstance(); + if (o instanceof Decoder) { + decoderInstance = (Decoder) o; + } + } catch (ClassNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (InstantiationException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IllegalAccessException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + try { + reader = new InputStreamReader(new URL(getFileURL()).openStream()); + process(reader); + } catch (FileNotFoundException fnfe) { + getLogger().info("file not available"); + } catch (IOException ioe) { + getLogger().warn("unable to load file", ioe); + return; + } + } + }; + if (useCurrentThread) { + runnable.run(); + } else { + Thread thread = new Thread(runnable, "LogFileXMLReceiver-" + getName()); + + thread.start(); + + } + } + + private void process(Reader unbufferedReader) throws IOException { + BufferedReader bufferedReader = new BufferedReader(unbufferedReader); + char[] content = new char[10000]; + getLogger().debug("processing starting: " + fileURL); + int length = 0; + do { + System.out.println("in do loop-about to process"); + while ((length = bufferedReader.read(content)) > -1) { + processEvents(decoderInstance.decodeEvents(String.valueOf(content, 0, length))); + } + if (tailing) { + try { + Thread.sleep(5000); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } while (tailing); + getLogger().debug("processing complete: " + fileURL); + + shutdown(); + } + + private void processEvents(Collection c) { + if (c == null) { + return; + } + + for (Iterator iter = c.iterator(); iter.hasNext();) { + LoggingEvent evt = (LoggingEvent) iter.next(); + if (passesExpression(evt)) { + if (evt.getProperty(Constants.HOSTNAME_KEY) != null) { + evt.setProperty(Constants.HOSTNAME_KEY, host); + } + if (evt.getProperty(Constants.APPLICATION_KEY) != null) { + evt.setProperty(Constants.APPLICATION_KEY, path); + } + doPost(evt); + } + } + } + + /** + * When true, this property uses the current Thread to perform the import, otherwise when false + * (the default), a new Thread is created and started to manage the import. + * + * @return + */ + public final boolean isUseCurrentThread() { + return useCurrentThread; + } + + /** + * Sets whether the current Thread or a new Thread is created to perform the import, the default + * being false (new Thread created). + * + * @param useCurrentThread + */ + public final void setUseCurrentThread(boolean useCurrentThread) { + this.useCurrentThread = useCurrentThread; + } + +}
+ contrib/java/org/haskell/hslogger/XMLDecoder.java view
@@ -0,0 +1,410 @@+/*+ * Copyright 1999,2004 The Apache Software Foundation.+ * + * Licensed under the Apache License, Version 2.0 (the "License");+ * you may not use this file except in compliance with the License.+ * You may obtain a copy of the License at+ * + * http://www.apache.org/licenses/LICENSE-2.0+ * + * Unless required by applicable law or agreed to in writing, software+ * distributed under the License is distributed on an "AS IS" BASIS,+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.+ * See the License for the specific language governing permissions and+ * limitations under the License.+ */++package org.haskell.hslogger4j;++import java.awt.Component;+import java.io.IOException;+import java.io.InputStreamReader;+import java.io.LineNumberReader;+import java.io.StringReader;+import java.net.URL;+import java.util.HashMap;+import java.util.Hashtable;+import java.util.Iterator;+import java.util.Map;+import java.util.Vector;++import javax.swing.ProgressMonitorInputStream;+import javax.xml.parsers.DocumentBuilder;+import javax.xml.parsers.DocumentBuilderFactory;+import javax.xml.parsers.ParserConfigurationException;++import org.apache.log4j.xml.*;+import org.apache.log4j.Level;+import org.apache.log4j.Logger;+import org.apache.log4j.spi.Decoder;+import org.apache.log4j.spi.LoggingEvent;+import org.apache.log4j.spi.ThrowableInformation;+import org.w3c.dom.Document;+import org.w3c.dom.Node;+import org.w3c.dom.NodeList;+import org.xml.sax.InputSource;+++/**+ * Decodes Logging Events in XML formated into elements that are used by+ * Chainsaw.+ *+ * This decoder can process a collection of log4j:event nodes ONLY + * (no XML declaration nor eventSet node)+ * + * NOTE: Only a single LoggingEvent is returned from the decode method+ * even though the DTD supports multiple events nested in an eventSet.+ *+ * @author Scott Deboy <sdeboy@apache.org>+ * @author Paul Smith <psmith@apache.org>+ *+ */+public class XMLDecoder implements Decoder {+ private static final String BEGINPART =+ "<?xml version=\"1.0\" encoding=\"UTF-8\" ?><!DOCTYPE log4j:eventSet SYSTEM \"log4j.dtd\"><log4j:eventSet version=\"1.2\" xmlns:log4j=\"http://jakarta.apache.org/log4j/\">";+ private static final String ENDPART = "</log4j:eventSet>";+ private static final String RECORD_END = "</log4j:event>";+ private DocumentBuilderFactory dbf;+ private DocumentBuilder docBuilder;+ private Map additionalProperties = new HashMap();+ private String partialEvent;+ private Component owner = null;++ public XMLDecoder(Component owner) {+ this();+ this.owner = owner;+ }+ public XMLDecoder() {+ dbf = DocumentBuilderFactory.newInstance();+ dbf.setValidating(false);++ try {+ docBuilder = dbf.newDocumentBuilder();+ docBuilder.setErrorHandler(new SAXErrorHandler());+ docBuilder.setEntityResolver(new Log4jEntityResolver());+ } catch (ParserConfigurationException pce) {+ System.err.println("Unable to get document builder");+ }+ }++ /**+ * Sets an additionalProperty map, where each Key/Value pair is+ * automatically added to each LoggingEvent as it is decoded.+ *+ * This is useful, say, to include the source file name of the Logging events+ * @param additionalProperties+ */+ public void setAdditionalProperties(Map additionalProperties) {+ this.additionalProperties = additionalProperties;+ }++ /**+ * Converts the LoggingEvent data in XML string format into an actual+ * XML Document class instance.+ * @param data+ * @return dom document+ */+ private Document parse(String data) {+ if (docBuilder == null || data == null) {+ return null;+ }+ Document document = null;++ try {+ // we change the system ID to a valid URI so that Crimson won't+ // complain. Indeed, "log4j.dtd" alone is not a valid URI which+ // causes Crimson to barf. The Log4jEntityResolver only cares+ // about the "log4j.dtd" ending.+ // buf.setLength(0);++ /**+ * resetting the length of the StringBuffer is dangerous, particularly+ * on some JDK 1.4 impls, there's a known Bug that causes a memory leak+ */+ StringBuffer buf = new StringBuffer(1024);++ buf.append(BEGINPART);+ buf.append(data);+ buf.append(ENDPART);++ InputSource inputSource =+ new InputSource(new StringReader(buf.toString()));+ inputSource.setSystemId("dummy://log4j.dtd");+ document = docBuilder.parse(inputSource);+ } catch (Exception e) {+ e.printStackTrace();+ }++ return document;+ }++ /**+ * Decodes a File into a Vector of LoggingEvents+ * @param url the url of a file containing events to decode+ * @return Vector of LoggingEvents+ * @throws IOException+ */+ public Vector decode(URL url) throws IOException {+ LineNumberReader reader = null;+ if (owner != null) {+ reader = new LineNumberReader(new InputStreamReader(new ProgressMonitorInputStream(owner, "Loading " + url , url.openStream())));+ } else {+ reader = new LineNumberReader(new InputStreamReader(url.openStream()));+ }++ Vector v = new Vector();++ String line = null;+ Vector events = null;+ try {+ while ((line = reader.readLine()) != null) {+ StringBuffer buffer = new StringBuffer(line);+ for (int i = 0;i<1000;i++) {+ buffer.append(reader.readLine()).append("\n");+ }+ events = decodeEvents(buffer.toString());+ if (events != null)+ v.addAll(events);+ }+ } finally {+ partialEvent = null;+ try {+ if (reader != null) {+ reader.close();+ }+ } catch (Exception e) {+ e.printStackTrace();+ }+ }+ return v;+ }++ public Vector decodeEvents(String document) {+ if (document != null) {+ if (document.trim().equals("")) {+ return null;+ }+ String newDoc=null;+ String newPartialEvent=null;+ //separate the string into the last portion ending with </log4j:event> (which will+ //be processed) and the partial event which will be combined and processed in the next section+ + //if the document does not contain a record end, append it to the partial event string+ if (document.lastIndexOf(RECORD_END) == -1) {+ partialEvent = partialEvent + document;+ return null;+ }+ + if (document.lastIndexOf(RECORD_END) + RECORD_END.length() < document.length()) {+ newDoc = document.substring(0, document.lastIndexOf(RECORD_END) + RECORD_END.length());+ newPartialEvent = document.substring(document.lastIndexOf(RECORD_END) + RECORD_END.length());+ } else {+ newDoc = document;+ }+ if (partialEvent != null) {+ newDoc=partialEvent + newDoc;+ } + partialEvent=newPartialEvent;+ Document doc = parse(newDoc);+ if (doc == null) {+ return null;+ }+ return decodeEvents(doc);+ }+ return null;+ }++ /**+ * Converts the string data into an XML Document, and then soaks out the+ * relevant bits to form a new LoggingEvent instance which can be used+ * by any Log4j element locally.+ * @param data+ * @return a single LoggingEvent+ */+ public LoggingEvent decode(String data) {+ Document document = parse(data);++ if (document == null) {+ return null;+ }++ Vector events = decodeEvents(document);++ if (events.size() > 0) {+ return (LoggingEvent) events.firstElement();+ }++ return null;+ }++ /**+ * Given a Document, converts the XML into a Vector of LoggingEvents+ * @param document+ * @return Vector of LoggingEvents+ */+ private Vector decodeEvents(Document document) {+ Vector events = new Vector();++ Logger logger = null;+ long timeStamp = 0L;+ String level = null;+ String threadName = null;+ Object message = null;+ String ndc = null;+ String[] exception = null;+ String className = null;+ String methodName = null;+ String fileName = null;+ String lineNumber = null;+ Hashtable properties = null;++ NodeList nl = document.getElementsByTagName("log4j:eventSet");+ Node eventSet = nl.item(0);++ NodeList eventList = eventSet.getChildNodes();++ for (int eventIndex = 0; eventIndex < eventList.getLength();+ eventIndex++) {+ Node eventNode = eventList.item(eventIndex);+ //ignore carriage returns in xml + if(eventNode.getNodeType() != Node.ELEMENT_NODE) {+ continue;+ }+ logger =+ Logger.getLogger(+ eventNode.getAttributes().getNamedItem("logger").getNodeValue());+ timeStamp =+ Long.parseLong(+ eventNode.getAttributes().getNamedItem("timestamp").getNodeValue());+ level =eventNode.getAttributes().getNamedItem("level").getNodeValue();+ threadName =+ eventNode.getAttributes().getNamedItem("thread").getNodeValue();++ NodeList list = eventNode.getChildNodes();+ int listLength = list.getLength();++ for (int y = 0; y < listLength; y++) {+ String tagName = list.item(y).getNodeName();++ if (tagName.equalsIgnoreCase("log4j:message")) {+ message = getCData(list.item(y));+ }++ if (tagName.equalsIgnoreCase("log4j:NDC")) {+ ndc = getCData(list.item(y));+ }+ //still support receiving of MDC and convert to properties+ if (tagName.equalsIgnoreCase("log4j:MDC")) {+ properties = new Hashtable();+ NodeList propertyList = list.item(y).getChildNodes();+ int propertyLength = propertyList.getLength();++ for (int i = 0; i < propertyLength; i++) {+ String propertyTag = propertyList.item(i).getNodeName();++ if (propertyTag.equalsIgnoreCase("log4j:data")) {+ Node property = propertyList.item(i);+ String name =+ property.getAttributes().getNamedItem("name").getNodeValue();+ String value =+ property.getAttributes().getNamedItem("value").getNodeValue();+ properties.put(name, value);+ }+ }+ }++ if (tagName.equalsIgnoreCase("log4j:throwable")) {+ exception = new String[] { getCData(list.item(y)) };+ }++ if (tagName.equalsIgnoreCase("log4j:properties")) {+ if (properties == null) {+ properties = new Hashtable();+ }+ NodeList propertyList = list.item(y).getChildNodes();+ int propertyLength = propertyList.getLength();++ for (int i = 0; i < propertyLength; i++) {+ String propertyTag = propertyList.item(i).getNodeName();++ if (propertyTag.equalsIgnoreCase("log4j:data")) {+ Node property = propertyList.item(i);+ String name =+ property.getAttributes().getNamedItem("name").getNodeValue();+ String value =+ property.getAttributes().getNamedItem("value").getNodeValue();+ properties.put(name, value);+ }+ }+ }++ /**+ * We add all the additional properties to the properties+ * hashtable. Don't override properties that already exist+ */+ if (additionalProperties.size() > 0) {+ if (properties == null) {+ properties = new Hashtable(additionalProperties);+ } else {+ Iterator i = additionalProperties.entrySet().iterator();+ while (i.hasNext()) {+ Map.Entry e = (Map.Entry) i.next();+ if (!(properties.containsKey(e.getKey()))) {+ properties.put(e.getKey(), e.getValue());+ }+ }+ }+ }+ }+ Level levelImpl = HsloggerLevel.toLevel(level);+ + if (exception == null) {+ exception = new String[]{""};+ }+ + LoggingEvent loggingEvent = new LoggingEvent();+ loggingEvent.setLogger(logger);+ loggingEvent.setTimeStamp(timeStamp);+ loggingEvent.setLevel(levelImpl);+ loggingEvent.setThreadName(threadName);+ loggingEvent.setMessage(message);+ loggingEvent.setNDC(ndc);+ loggingEvent.setThrowableInformation(new ThrowableInformation(exception));+ loggingEvent.setProperties(properties);+ + events.add(loggingEvent);+ + logger = null;+ timeStamp = 0L;+ level = null;+ threadName = null;+ message = null;+ ndc = null;+ exception = null;+ className = null;+ methodName = null;+ fileName = null;+ lineNumber = null;+ properties = null;+ }++ return events;+ }++ private String getCData(Node n) {+ StringBuffer buf = new StringBuffer();+ NodeList nl = n.getChildNodes();++ for (int x = 0; x < nl.getLength(); x++) {+ Node innerNode = nl.item(x);++ if (+ (innerNode.getNodeType() == Node.TEXT_NODE)+ || (innerNode.getNodeType() == Node.CDATA_SECTION_NODE)) {+ buf.append(innerNode.getNodeValue());+ }+ }++ return buf.toString();+ }+}
hslogger.cabal view
@@ -1,5 +1,5 @@ Name: hslogger-Version: 1.0.5+Version: 1.0.6 License: LGPL Maintainer: John Goerzen <jgoerzen@complete.org> Author: John Goerzen@@ -17,7 +17,13 @@ or filter messages based on the priority and source. hslogger also has a syslog handler built in. Category: Interfaces-extra-source-files: COPYING+extra-source-files: COPYING,+ contrib/java/build.xml,+ contrib/java/hslogger4j.jar,+ contrib/java/hslogger4j-plugins.xml,+ contrib/java/org/haskell/hslogger/HsloggerLevel.java,+ contrib/java/org/haskell/hslogger/LogFileXMLReceiver.java,+ contrib/java/org/haskell/hslogger/XMLDecoder.java Cabal-Version: >= 1.2 flag small_base@@ -27,15 +33,17 @@ Exposed-Modules: System.Log, System.Log.Handler, System.Log.Handler.Simple, System.Log.Handler.Syslog,- System.Log.Handler.Growl, System.Log.Logger + System.Log.Handler.Growl, System.Log.Handler.Log4jXML,+ System.Log.Logger Extensions: CPP, ExistentialQuantification Build-Depends: network, mtl if !os(windows) Build-Depends: unix if flag(small_base)- build-depends: base >= 3, containers, directory, process+ build-depends: base >= 3, containers, directory, process,+ time, old-locale else- build-depends: base < 3+ build-depends: base < 3, time GHC-Options: -O2 Hs-Source-Dirs: src
+ src/System/Log/Handler/Log4jXML.hs view
@@ -0,0 +1,242 @@+{- arch-tag: log4j XMLLayout log handler+Copyright (C) 2007-2008 John Goerzen <jgoerzen@complete.org>++This program 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 program 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 program; if not, write to the Free Software+Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA+-}++{- |+ Module : System.Log.Handler.Log4jXML+ Copyright : Copyright (C) 2007-2008 John Goerzen+ License : GNU LGPL, version 2.1 or above++ Maintainer : bjorn.buckwalter@gmail.com+ Stability : experimental+ Portability: GHC only?++log4j[1] XMLLayout log handlers.++Written by Bjorn Buckwalter, bjorn.buckwalter\@gmail.com+-}+++module System.Log.Handler.Log4jXML ( + + -- * Introduction++ {- | This module provides handlers for hslogger that are+ compatible with log4j's XMLLayout. In particular log messages+ created by the handlers can be published directly to the GUI-based+ log viewer Chainsaw v2[2].++ The set of log levels in hslogger is richer than the basic set+ of log4j levels. Two sets of handlers are provided with hslogger4j,+ one which produces logs with hslogger's levels and one which+ \"demotes\" them to the basic log4j levels. If full hslogger+ levels are used some Java installation (see below) is necessary+ to make Chainsaw aware of them.++ Usage of the handlers in hslogger4j is analoguous to usage of+ the 'System.Log.Handler.Simple.StreamHandler' and+ 'System.Log.Handler.Simple.FileHandler' in "System.Log.Handler.Simple".+ The following handlers are provided: -}++ -- ** Handlers with hslogger levels + log4jStreamHandler,+ log4jFileHandler,+ + -- ** Handlers with log4j levels+ log4jStreamHandler',+ log4jFileHandler'+++ -- * Java install process++ {- | This is only necessary if you want to use the hslogger levels.++ Add @hslogger4j.jar@ from @contrib\/java@ to your classpath.+ To use you will also need to have the jars @log4j-1.3alpha-7.jar@+ and @log4j-xml-1.3alpha-7.jar@ that are distributed with Chainsaw+ on your classpath.++ (On Mac OS X I added all three jars to @~\/Library\/Java\/Extensions@.+ It seems that it is not sufficient that Chainsaw already includes+ its jars in the classpath when launching - perhaps the plugin+ classloader does not inherit Chainsaw's classpath. Adding the+ jars to @~\/.chainsaw\/plugins@ wouldn't work either.)++ If for whatever reason you have to rebuild the hslogger4j jar+ just run @ant@[3] in the @contrib\/java@ directory. The new jar+ will be created in the @contrib\/java\/dist@ directory. The Java+ source code is copyright The Apache Software Foundation and+ licensed under the Apache Licence version 2.0. -}+++ -- * Chainsaw setup++ {- | If you are only using the basic log4j levels just use+ Chainsaw's regular facilities to browse logs or listen for log+ messages (e.g. @XMLSocketReceiver@).++ If you want to use the hslogger levels the easiest way to set+ up Chainsaw is to load the plugins in @hslogger4j-plugins.xml@+ in @contrib\/java@ when launching Chainsaw. Two receivers will+ be defined, one that listens for logmessages and one for reading+ log files. Edit the properties of those receivers as needed+ (e.g. @port@, @fileURL@) and restart them. You will also want+ to modify Chainsaw's formatting preferences to display levels+ as text instead of icons. -}+++ -- * Example usage++ {- | In the IO monad:++ > lh2 <- log4jFileHandler "log.xml" DEBUG+ > updateGlobalLogger rootLoggerName (addHandler lh2)++ > h <- connectTo "localhost" (PortNumber 4448)+ > lh <- log4jStreamHandler h NOTICE+ > updateGlobalLogger rootLoggerName (addHandler lh)+ -}++ -- * References++ {- |+ (1) <http://logging.apache.org/log4j/>++ (2) <http://logging.apache.org/chainsaw/>++ (3) <http://ant.apache.org/>+ -}++ ) where++import Control.Concurrent (ThreadId, myThreadId) -- myThreadId is GHC only!+import Control.Concurrent.MVar+import Data.List (isPrefixOf)+import System.IO+import System.Locale (defaultTimeLocale)+import Data.Time+import System.Log+import System.Log.Handler.Simple (GenericHandler (..))+++-- Handler that logs to a handle rendering message priorities according+-- to the supplied function.+log4jHandler :: (Priority -> String) -> Handle -> Priority -> IO (GenericHandler Handle)+log4jHandler showPrio h pri = do+ lock <- newMVar ()+ let mywritefunc hdl (prio, msg) loggername = withMVar lock (\_ -> do+ time <- getCurrentTime+ thread <- myThreadId+ hPutStrLn hdl (show $ createMessage loggername time prio thread msg)+ hFlush hdl+ )+ return (GenericHandler { priority = pri,+ privData = h,+ writeFunc = mywritefunc,+ closeFunc = \x -> return () })+ where+ -- Creates an XML element representing a log4j event/message.+ createMessage :: String -> UTCTime -> Priority -> ThreadId -> String -> XML+ createMessage logger time prio thread msg = Elem "log4j:event"+ [ ("logger" , logger )+ , ("timestamp", millis time )+ , ("level" , showPrio prio)+ , ("thread" , show thread )+ ]+ (Just $ Elem "log4j:message" [] (Just $ CDATA msg))+ where+ -- This is an ugly hack to get a unix epoch with milliseconds.+ -- The use of "take 3" causes the milliseconds to always be+ -- rounded downwards, which I suppose may be the expected+ -- behaviour for time.+ millis t = formatTime defaultTimeLocale "%s" t+ ++ (take 3 $ formatTime defaultTimeLocale "%q" t)+++-- | Create a stream log handler that uses hslogger priorities.+log4jStreamHandler :: Handle -> Priority -> IO (GenericHandler Handle)+log4jStreamHandler = log4jHandler show++{- | Create a stream log handler that uses log4j levels (priorities). The+ priorities of messages are shoehorned into log4j levels as follows:++@+ DEBUG -> DEBUG+ INFO, NOTICE -> INFO+ WARNING -> WARN+ ERROR, CRITICAL, ALERT -> ERROR+ EMERGENCY -> FATAL+@++ This is useful when the log will only be consumed by log4j tools and+ you don't want to go out of your way transforming the log or configuring+ the tools. -}+log4jStreamHandler' :: Handle -> Priority -> IO (GenericHandler Handle)+log4jStreamHandler' = log4jHandler show' where+ show' :: Priority -> String+ show' NOTICE = "INFO"+ show' WARNING = "WARN"+ show' CRITICAL = "ERROR"+ show' ALERT = "ERROR"+ show' EMERGENCY = "FATAL"+ show' p = show p -- Identical for DEBUG, INFO, ERROR.+++-- | Create a file log handler that uses hslogger priorities.+log4jFileHandler :: FilePath -> Priority -> IO (GenericHandler Handle)+log4jFileHandler fp pri = do+ h <- openFile fp AppendMode+ sh <- log4jStreamHandler h pri+ return (sh{closeFunc = hClose})++{- | Create a file log handler that uses log4j levels (see+ 'log4jStreamHandler'' for mappings). -}+log4jFileHandler' :: FilePath -> Priority -> IO (GenericHandler Handle)+log4jFileHandler' fp pri = do+ h <- openFile fp AppendMode+ sh <- log4jStreamHandler' h pri+ return (sh{closeFunc = hClose})+++-- A type for building and showing XML elements. Could use a fancy XML+-- library but am reluctant to introduce dependencies.+data XML = Elem String [(String, String)] (Maybe XML)+ | CDATA String++instance Show XML where+ show (CDATA s) = "<![CDATA[" ++ escapeCDATA s ++ "]]>" where+ escapeCDATA = replace "]]>" "]]<" -- The best we can do, I guess.+ show (Elem name attrs child) = "<" ++ name ++ showAttrs attrs ++ showChild child where+ showAttrs [] = ""+ showAttrs ((k,v):as) = " " ++ k ++ "=\"" ++ escapeAttr v ++ "\""+ ++ showAttrs as+ where escapeAttr = replace "\"" """+ . replace "<" "<"+ . replace "&" "&"+ showChild Nothing = "/>"+ showChild (Just c) = ">" ++ show c ++ "</" ++ name ++ ">"+++-- Replaces instances of first list by second list in third list.+-- Definition blatantly stoled from jethr0's comment at+-- http://bluebones.net/2007/01/replace-in-haskell/. Can be swapped+-- with definition (or import) from MissingH.+replace :: Eq a => [a] -> [a] -> [a] -> [a]+replace _ _ [ ] = []+replace from to xs@(a:as) = if isPrefixOf from xs+ then to ++ drop (length from) xs else a : replace from to as+
src/System/Log/Handler/Simple.hs view
@@ -31,6 +31,7 @@ -} module System.Log.Handler.Simple(streamHandler, fileHandler,+ GenericHandler (..), verboseStreamHandler) where @@ -38,6 +39,8 @@ import System.Log.Handler import System.IO import Control.Concurrent.MVar++{- | A helper data type. -} data GenericHandler a = GenericHandler {priority :: Priority, privData :: a,
src/System/Log/Handler/Syslog.hs view
@@ -96,6 +96,7 @@ | UUCP -- ^ UUCP messages | CRON -- ^ Cron messages | AUTHPRIV -- ^ Private authentication messages+ | FTP -- ^ FTP messages | LOCAL0 -- ^ LOCAL0 through LOCAL7 are reserved for you to customize as you wish | LOCAL1 | LOCAL2@@ -119,6 +120,7 @@ UUCP -> 8 CRON -> 9 AUTHPRIV -> 10+ FTP -> 11 LOCAL0 -> 16 LOCAL1 -> 17 LOCAL2 -> 18
src/System/Log/Logger.hs view
@@ -180,6 +180,7 @@ import System.IO.Unsafe import Control.Concurrent.MVar import Data.List(map, isPrefixOf)+import Data.Maybe import qualified Data.Map as Map import qualified Control.Exception import Control.Monad.Error@@ -205,6 +206,7 @@ -- | The name of the root logger, which is always defined and present -- on the system.+rootLoggerName :: String rootLoggerName = "" {- | Placeholders created when a new logger must be created. This is used@@ -330,7 +332,7 @@ Nothing -> do -- Add logger(s). Then call myself to retrieve it. let newlt = createLoggers (componentsOfName lname) lt- result <- Map.lookup lname newlt+ let result = fromJust $ Map.lookup lname newlt return (newlt, result) where createLoggers :: [String] -> LogTree -> LogTree createLoggers [] lt = lt -- No names to add; return tree unmodified@@ -346,8 +348,8 @@ findmodellogger _ [] = error "findmodellogger: root logger does not exist?!" findmodellogger lt (x:xs) = case Map.lookup x lt of- Left (_::String) -> findmodellogger lt xs- Right logger -> logger {handlers = []}+ Nothing -> findmodellogger lt xs+ Just logger -> logger {handlers = []} -- | Returns the root logger.