LOG4NET-586 first cut at a namespace aware log4j XML layout
Project: http://git-wip-us.apache.org/repos/asf/logging-log4net/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4net/commit/df068324 Tree: http://git-wip-us.apache.org/repos/asf/logging-log4net/tree/df068324 Diff: http://git-wip-us.apache.org/repos/asf/logging-log4net/diff/df068324 Branch: refs/heads/feature/LOG4NET-586 Commit: df0683247c03d3f09f9e29085d66dbe85350b20d Parents: 403fd19 Author: Stefan Bodewig <[email protected]> Authored: Tue May 29 21:01:59 2018 +0200 Committer: Stefan Bodewig <[email protected]> Committed: Wed May 30 21:44:36 2018 +0200 ---------------------------------------------------------------------- src/Layout/XmlLayoutSchemaLog4jNS.cs | 282 ++++++++++++++++++++++++++++++ src/log4net.csproj | 3 + 2 files changed, 285 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4net/blob/df068324/src/Layout/XmlLayoutSchemaLog4jNS.cs ---------------------------------------------------------------------- diff --git a/src/Layout/XmlLayoutSchemaLog4jNS.cs b/src/Layout/XmlLayoutSchemaLog4jNS.cs new file mode 100644 index 0000000..aa7c33b --- /dev/null +++ b/src/Layout/XmlLayoutSchemaLog4jNS.cs @@ -0,0 +1,282 @@ +#region Apache License +// +// Licensed to the Apache Software Foundation (ASF) under one or more +// contributor license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright ownership. +// The ASF licenses this file to you 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. +// +#endregion + +using System; +using System.Text; +using System.Xml; +using System.IO; + +using log4net.Core; +using log4net.Util; + +namespace log4net.Layout +{ + /// <summary> + /// Layout that formats the log events as XML elements similar to the log4j 1.2 schema + /// </summary> + /// <remarks> + /// <para> + /// Formats the log events according to the + /// http://logging.apache.org/log4j schema and actually puts the + /// elements into a namespace. This may break tooling that doesn't + /// handle XML using namespaces. + /// </para> + /// </remarks> + /// <author>Nicko Cadell</author> + public class XmlLayoutSchemaLog4jNS : XmlLayoutBaseNS + { + #region Static Members + + /// <summary> + /// The 1st of January 1970 in UTC + /// </summary> + private static readonly DateTime s_date1970 = new DateTime(1970, 1, 1); + + #endregion + + #region Constructors + + /// <summary> + /// Constructs an XMLLayoutSchemaLog4jNS + /// </summary> + public XmlLayoutSchemaLog4jNS() : base() + { + } + + /// <summary> + /// Constructs an XMLLayoutSchemaLog4jNS. + /// </summary> + /// <remarks> + /// <para> + /// The <b>LocationInfo</b> option takes a boolean value. By + /// default, it is set to false which means there will be no location + /// information output by this layout. If the the option is set to + /// true, then the file name and line number of the statement + /// at the origin of the log statement will be output. + /// </para> + /// <para> + /// If you are embedding this layout within an SMTPAppender + /// then make sure to set the <b>LocationInfo</b> option of that + /// appender as well. + /// </para> + /// </remarks> + public XmlLayoutSchemaLog4jNS(bool locationInfo) : base(locationInfo) + { + } + + #endregion + + #region Public Properties + + /// <summary> + /// The version of the log4j schema to use. + /// </summary> + /// <remarks> + /// <para> + /// Only version 1.2 of the log4j schema is supported. + /// </para> + /// </remarks> + public string Version + { + get { return "1.2"; } + set + { + if (value != "1.2") + { + throw new ArgumentException("Only version 1.2 of the log4j schema is currently supported"); + } + } + } + + /// <summary> + /// The namespace URI to use for the elements and attributes written by this layout. + /// </summary> + /// <remarks> + /// <para> + /// Unless configured explicitly http://logging.apache.org/log4j is used. + /// </para> + /// </remarks> + public override string NamespaceUri + { + get { return base.NamespaceUri == null ? LOG4J_SCHEMA : base.NamespaceUri; } + } + + /// <summary> + /// The prefix to use for the elements and attributes written by this layout. + /// </summary> + /// <remarks> + /// <para> + /// Unless configured explicitly log4j is used. + /// </para> + /// </remarks> + public override string Prefix + { + get { return base.Prefix == null ? LOG4J_PREFIX : base.Prefix; } + } + #endregion + + /* Example log4j schema event + +<log4j:event logger="first logger" level="ERROR" thread="Thread-3" timestamp="1051494121460" xmlns:log4j="http://logging.apache.org/log4j"> + <log4j:message><![CDATA[errormsg 3]]></log4j:message> + <log4j:NDC><![CDATA[third]]></log4j:NDC> + <log4j:MDC> + <log4j:data name="some string" value="some valuethird"/> + </log4j:MDC> + <log4j:throwable><![CDATA[java.lang.Exception: someexception-third + at org.apache.log4j.chainsaw.Generator.run(Generator.java:94) +]]></log4j:throwable> + <log4j:locationInfo class="org.apache.log4j.chainsaw.Generator" +method="run" file="Generator.java" line="94"/> + <log4j:properties> + <log4j:data name="log4jmachinename" value="windows"/> + <log4j:data name="log4japp" value="udp-generator"/> + </log4j:properties> +</log4j:event> + + */ + + /* Since log4j 1.3 the log4j:MDC has been combined into the log4j:properties element */ + + /// <summary> + /// Actually do the writing of the xml + /// </summary> + /// <param name="writer">the writer to use</param> + /// <param name="loggingEvent">the event to write</param> + /// <remarks> + /// <para> + /// Generate XML that is compatible with the log4j schema. + /// </para> + /// </remarks> + override protected void FormatXml(XmlWriter writer, LoggingEvent loggingEvent) + { + // Translate logging events for log4j + + // Translate hostname property + if (loggingEvent.LookupProperty(LoggingEvent.HostNameProperty) != null && + loggingEvent.LookupProperty("log4jmachinename") == null) + { + loggingEvent.GetProperties()["log4jmachinename"] = loggingEvent.LookupProperty(LoggingEvent.HostNameProperty); + } + + // translate appdomain name + if (loggingEvent.LookupProperty("log4japp") == null && + loggingEvent.Domain != null && + loggingEvent.Domain.Length > 0) + { + loggingEvent.GetProperties()["log4japp"] = loggingEvent.Domain; + } + + // translate identity name + if (loggingEvent.Identity != null && + loggingEvent.Identity.Length > 0 && + loggingEvent.LookupProperty(LoggingEvent.IdentityProperty) == null) + { + loggingEvent.GetProperties()[LoggingEvent.IdentityProperty] = loggingEvent.Identity; + } + + // translate user name + if (loggingEvent.UserName != null && + loggingEvent.UserName.Length > 0 && + loggingEvent.LookupProperty(LoggingEvent.UserNameProperty) == null) + { + loggingEvent.GetProperties()[LoggingEvent.UserNameProperty] = loggingEvent.UserName; + } + + // Write the start element + writer.WriteStartElement(Prefix, "event", NamespaceUri); + writer.WriteAttributeString("logger", loggingEvent.LoggerName); + + // Calculate the timestamp as the number of milliseconds since january 1970 + // + // We must convert the TimeStamp to UTC before performing any mathematical + // operations. This allows use to take into account discontinuities + // caused by daylight savings time transitions. + TimeSpan timeSince1970 = loggingEvent.TimeStampUtc - s_date1970; + + writer.WriteAttributeString("timestamp", XmlConvert.ToString((long)timeSince1970.TotalMilliseconds)); + writer.WriteAttributeString("level", loggingEvent.Level.DisplayName); + writer.WriteAttributeString("thread", loggingEvent.ThreadName); + + // Append the message text + writer.WriteStartElement(Prefix, "message", NamespaceUri); + Transform.WriteEscapedXmlString(writer, loggingEvent.RenderedMessage,this.InvalidCharReplacement); + writer.WriteEndElement(); + + object ndcObj = loggingEvent.LookupProperty("NDC"); + if (ndcObj != null) + { + string valueStr = loggingEvent.Repository.RendererMap.FindAndRender(ndcObj); + + if (valueStr != null && valueStr.Length > 0) + { + // Append the NDC text + writer.WriteStartElement(Prefix, "NDC", NamespaceUri); + Transform.WriteEscapedXmlString(writer, valueStr,this.InvalidCharReplacement); + writer.WriteEndElement(); + } + } + + // Append the properties text + PropertiesDictionary properties = loggingEvent.GetProperties(); + if (properties.Count > 0) + { + writer.WriteStartElement(Prefix, "properties", NamespaceUri); + foreach(System.Collections.DictionaryEntry entry in properties) + { + writer.WriteStartElement(Prefix, "data", NamespaceUri); + writer.WriteAttributeString("name", (string)entry.Key); + + // Use an ObjectRenderer to convert the object to a string + string valueStr = loggingEvent.Repository.RendererMap.FindAndRender(entry.Value); + writer.WriteAttributeString("value", valueStr); + + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } + + string exceptionStr = loggingEvent.GetExceptionString(); + if (exceptionStr != null && exceptionStr.Length > 0) + { + // Append the stack trace line + writer.WriteStartElement(Prefix, "throwable", NamespaceUri); + Transform.WriteEscapedXmlString(writer, exceptionStr,this.InvalidCharReplacement); + writer.WriteEndElement(); + } + + if (LocationInfo) + { + LocationInfo locationInfo = loggingEvent.LocationInformation; + + writer.WriteStartElement(Prefix, "locationInfo", NamespaceUri); + writer.WriteAttributeString("class", locationInfo.ClassName); + writer.WriteAttributeString("method", locationInfo.MethodName); + writer.WriteAttributeString("file", locationInfo.FileName); + writer.WriteAttributeString("line", locationInfo.LineNumber); + writer.WriteEndElement(); + } + + writer.WriteEndElement(); + } + + private const string LOG4J_SCHEMA = "http://logging.apache.org/log4j"; + private const string LOG4J_PREFIX = "log4j"; + } +} http://git-wip-us.apache.org/repos/asf/logging-log4net/blob/df068324/src/log4net.csproj ---------------------------------------------------------------------- diff --git a/src/log4net.csproj b/src/log4net.csproj index b638481..2b09c54 100644 --- a/src/log4net.csproj +++ b/src/log4net.csproj @@ -515,6 +515,9 @@ <Compile Include="Layout\XmlLayoutSchemaLog4j.cs"> <SubType>Code</SubType> </Compile> + <Compile Include="Layout\XmlLayoutSchemaLog4jNS.cs"> + <SubType>Code</SubType> + </Compile> <Compile Include="LogicalThreadContext.cs"> <SubType>Code</SubType> </Compile>
