You are viewing a plain text version of this content. The canonical link for it is here.
Posted to log4j-cvs@jakarta.apache.org by ce...@apache.org on 2001/09/27 23:24:49 UTC
cvs commit: jakarta-log4j/src/docbook architecture.xml trouble.xml
ceki 01/09/27 14:24:49
Modified: . build.sh
build build.xml
docs HISTORY
src/docbook architecture.xml trouble.xml
Log:
DOc improvements
Revision Changes Path
1.5 +4 -1 jakarta-log4j/build.sh
Index: build.sh
===================================================================
RCS file: /home/cvs/jakarta-log4j/build.sh,v
retrieving revision 1.4
retrieving revision 1.5
diff -u -r1.4 -r1.5
--- build.sh 2001/09/25 10:29:04 1.4
+++ build.sh 2001/09/27 21:24:48 1.5
@@ -33,5 +33,8 @@
BUILDFILE=build/build.xml
-${JAVA_HOME}/bin/java -classpath ${CLASSPATH} org.apache.tools.ant.Main \
+${JAVA_HOME}/bin/java -classpath ${CLASSPATH} \
+ org.apache.tools.ant.Main \
-buildfile ${BUILDFILE} "$@"
+
+# -Djavax.xml.transform.TransformerFactory=com.icl.saxon.TransformerFactoryImpl\
1.52 +1 -2 jakarta-log4j/build/build.xml
Index: build.xml
===================================================================
RCS file: /home/cvs/jakarta-log4j/build/build.xml,v
retrieving revision 1.51
retrieving revision 1.52
diff -u -r1.51 -r1.52
--- build.xml 2001/09/26 10:42:07 1.51
+++ build.xml 2001/09/27 21:24:48 1.52
@@ -228,6 +228,7 @@
${stem}/varia/*.class,
${stem}/nt/*.class,
${stem}/xml/*.class,
+ ${stem}/jmx/*.class,
${stem}/or/*.class,
${stem}/or/sax/*.class,
${stem}/config/*.class,
@@ -385,8 +386,6 @@
<delete dir="${dist.tmp}" />
-
</target>
-
</project>
1.67 +3 -0 jakarta-log4j/docs/HISTORY
Index: HISTORY
===================================================================
RCS file: /home/cvs/jakarta-log4j/docs/HISTORY,v
retrieving revision 1.66
retrieving revision 1.67
diff -u -r1.66 -r1.67
--- HISTORY 2001/09/26 10:42:07 1.66
+++ HISTORY 2001/09/27 21:24:48 1.67
@@ -61,6 +61,9 @@
deprecated methods. They have become totally redundant after we
moved to JavaBeans style configuration in log4j 1.1. [**]
+ - The disable(Level) methods in Hierarchy have been removed and been
+ replaced by enable counterparts. [**]
+
- Removed deprecated methods disable(Priority), disableAll,
disableDebug, disableInfo and enableAll in BasicConfigurator. [*]
1.10 +279 -170 jakarta-log4j/src/docbook/architecture.xml
Index: architecture.xml
===================================================================
RCS file: /home/cvs/jakarta-log4j/src/docbook/architecture.xml,v
retrieving revision 1.9
retrieving revision 1.10
diff -u -r1.9 -r1.10
--- architecture.xml 2001/09/27 09:01:21 1.9
+++ architecture.xml 2001/09/27 21:24:49 1.10
@@ -37,7 +37,7 @@
<para>Loggers are named entities. Logger names are
- case-sensitive and they follow the >Named Hierarchy Rule:
+ case-sensitive and they follow the Named Hierarchy Rule:
</para>
<para>
@@ -134,7 +134,7 @@
<para>The <emphasis>effective level</emphasis> of a logger is
given by its assigned level, if it is assigned one. Otherwise, if
the logger has not been assigned a level, then it inherits the
- level of its closest ancestor with an assigned priority. More
+ level of its closest ancestor with an assigned level. More
formally,
</para>
@@ -157,7 +157,7 @@
</para>
<para>To ensure that all loggers can eventually inherit a level,
- the root logger always has an assigned priority.</para>
+ the root logger always has an assigned level.</para>
<para>Below are four tables with various assigned levels and the
resulting effective levels according to the above rule.</para>
@@ -350,34 +350,34 @@
<ulink
url="../api/org/apache/log4j/Category.html#fatal(java.lang.Object)">fatal</ulink>
and <ulink
- url="../api/org/apache/log4j/Category.html#log(org.apache.log4j.Priority,
- java.lang.Object)">log</ulink>.
+ url="../api/org/apache/log4j/Category.html#log(org.apache.log4j.Priority, java.lang.Object)">log</ulink>.
</para>
-
- <para>By definition, the printing method determines the level of
- a logging request. For example, if <varname>x</varname> is a
- logger instance, then the statement
- <function>x.info("..")</function> is a logging request of
- priority <emphasis>info</emphasis>.
- </para>
-
- <para>A logging request is said to be <emphasis>enabled</emphasis>
- if its level is higher than or equal to the effective level of
- its logger. Otherwise, the request is said to be
- <emphasis>disabled</emphasis>. Remember that a logger without an
- assigned level will inherit one from the hierarchy. This rule is
- summarized below.
- </para>
-
+ <sect2 id="basicSelectionRule" xreflabel="Basic Selection Rule">
+ <title>Basic Selection rule</title>
+
+
+ <para>By definition, the printing method determines the level of
+ a logging request. For example, if <varname>x</varname> is a
+ logger instance, then the statement
+ <function>x.info("..")</function> is a logging request of
+ level <emphasis>info</emphasis>.
+ </para>
- <para id="basicSelectionRule">
- <variablelist>
- <varlistentry>
- <term><emphasis role="strong">Basic Selection Rule</emphasis></term>
-
- <listitem>
- <para>A log request of level
+ <para>A logging request is said to be <emphasis>enabled</emphasis>
+ if its level is higher than or equal to the effective level of
+ its logger. Otherwise, the request is said to be
+ <emphasis>disabled</emphasis>. Remember that a logger without an
+ assigned level will inherit one from the hierarchy. This rule is
+ summarized below.
+ </para>
+ <para>
+ <variablelist>
+ <varlistentry>
+ <term><emphasis role="strong">Basic Selection Rule</emphasis></term>
+
+ <listitem>
+ <para>A log request of level
<emphasis>p</emphasis> in a logger with
effective level <emphasis>q</emphasis>, is
enabled if <emphasis> p >= q</emphasis>.
@@ -400,7 +400,7 @@
// get a logger instance named "com.foo"
Logger cat = Logger.getLogger("com.foo");
- // Now set its priority. Normally you do not need to set the
+ // Now set its level. Normally you do not need to set the
// level of a logger programatically but rather in a configuration file.
cat.setLevel(Level.INFO);
@@ -413,7 +413,7 @@
cat.debug("Starting search for nearest gas station.");
// The logger instance barcat, named "com.foo.Bar",
- // will inherit its priority from the logger named
+ // will inherit its level from the logger named
// "com.foo" Thus, the following request is enabled
// because INFO >= INFO.
barcat.info("Located nearest gas station.");
@@ -425,33 +425,34 @@
<para>You really should take the time to understand the basic
selection rule.</para>
-
+ </sect2>
+
<sect2>
<title>Once only creation</title>
-
- <para>Calling the <varname>getLogger</varname> method
- with the same name will always return a reference to
- the exact same logger object.
- </para>
-
- <para>For example, in</para>
-
+
+ <para>Calling the <varname>getLogger</varname> method
+ with the same name will always return a reference to
+ the exact same logger object.
+ </para>
+
+ <para>For example, in</para>
+
<para><programlisting>
Logger x = Logger.getLogger("wombat");
Logger y = Logger.getLogger("wombat");</programlisting></para>
- <para>
- <varname>x</varname> and <varname>y</varname> refer to
- <emphasis>exactly</emphasis> the same logger object.
- </para>
+ <para>
+ <varname>x</varname> and <varname>y</varname> refer to
+ <emphasis>exactly</emphasis> the same logger object.
+ </para>
- <para>Thus, it is possible to configure a logger and then to
- retrieve the same instance somewhere else in the code without
- passing around references. In contrast to biological parenthood,
- where parents always preceed their children, log4j loggers can
- be created and configured in any order. In particular, a
- "parent" logger will find and link to its descendants even if it
- is instantiated after them.
+ <para>Thus, it is possible to configure a logger and then to
+ retrieve the same instance somewhere else in the code without
+ passing around references. In contrast to biological parenthood,
+ where parents always preceed their children, log4j loggers can
+ be created and configured in any order. In particular, a
+ "parent" logger will find and link to its descendants even if it
+ is instantiated after them.
</para>
<para>Configuration of the log4j environment is typically done
@@ -477,73 +478,77 @@
are located seems to be the best strategy known so far.</para>
</sect2>
</sect1>
+
- <sect1>
- <title>Appenders and Layouts</title>
+ <sect1>
+ <title>Appenders</title>
- <para>The ability to selectively enable or disable logging
- requests based on their logger is only part of the
- picture. Log4j allows logging requests to print to multiple
- destinations. In log4j speak, an output destination is called
- an <emphasis>appender</emphasis>. Currently, appenders exist
- for the <ulink
+ <para>The ability to selectively enable or disable logging
+ requests based on their logger is only part of the
+ picture. Log4j allows logging requests to print to multiple
+ destinations. In log4j speak, an output destination is called
+ an <emphasis>appender</emphasis>. Currently, appenders exist
+ for the <ulink
url="../api/org/apache/log4j/ConsoleAppender.html">console</ulink>,
- <ulink
+ <ulink
url="../api/org/apache/log4j/FileAppender.html">files</ulink>,
- GUI components, <ulink
+ GUI components, <ulink
url="../api/org/apache/log4j/net/SocketAppender.html">remote
socket</ulink> servers, <ulink
url="../api/org/apache/log4j/net/JMSAppender.html">JMS</ulink>,
+
+ <ulink
+ url="../api/org/apache/log4j/nt/NTEventLogAppender.html"> NT
+ Event Loggers</ulink>, and remote UNIX <ulink
+ url="../api/org/apache/log4j/net/SyslogAppender.html">Syslog</ulink>
+ daemons. It is also possible to log <ulink
+ url="../api/org/apache/log4j/AsyncAppender.html">asynchronously</ulink>.
+ </para>
- <ulink
- url="../api/org/apache/log4j/nt/NTEventLogAppender.html"> NT
- Event Loggers</ulink>, and remote UNIX <ulink
- url="../api/org/apache/log4j/net/SyslogAppender.html">Syslog</ulink>
- daemons. It is also possible to log <ulink
- url="../api/org/apache/log4j/AsyncAppender.html">asynchronously</ulink>.
- </para>
-
- <para>More than one appender can be attached to a logger.</para>
-
- <para>The <ulink
- url="../api/org/apache/log4j/Logger.html#addAppender(org.apache.log4j.Appender)">addAppender</ulink>
- method adds an appender to a given logger.
-
- <emphasis>Each enabled logging request for a given logger will
- be forwarded to all the appenders in that logger as well as
- the appenders higher in the hierarchy.</emphasis> In other
- words, appenders are inherited additively from the logger
- hierarchy. For example, if a console appender is added to
- the root logger, then all enabled logging requests will at
- least print on the console. If in addition a file appender
- is added to a logger, say <emphasis>C</emphasis>, then
- enabled logging requests for <emphasis>C</emphasis> and
- <emphasis>C</emphasis>'s children will print on a file
- <emphasis>and</emphasis> on the console. It is possible to
- override this default behavior so that appender accumulation
- is no longer additive by <ulink
- url="../api/org/apache/log4j/Logger.html#setAdditivity(boolean)">setting
- the additivity flag</ulink> to <varname>false</varname>.
+ <para>More than one appender can be attached to a
+ logger. Appenders can be added to and removed from a logger at
+ any time.</para>
+
+ <sect2 id="additivityRule" xreflabel="Appender Additivity Rule">
+ <title>Appedner Additivity</title>
+
+ <para>The <ulink
+ url="../api/org/apache/log4j/Category.html#addAppender(org.apache.log4j.Appender)">addAppender</ulink>
+ method adds an appender to a given logger. <emphasis>Each
+ enabled logging request for a given logger will be forwarded
+ to all the appenders in that logger as well as the appenders
+ higher in the hierarchy.</emphasis> In other words, appenders
+ are inherited additively from the logger hierarchy. For
+ example, if a console appender is added to the root logger,
+ then all enabled logging requests will at least print on the
+ console. If in addition a file appender is added to a logger,
+ say <emphasis>L</emphasis>, then enabled logging requests for
+ <emphasis>L</emphasis> and <emphasis>L</emphasis>'s children
+ will print on a file <emphasis>and</emphasis> on the
+ console. It is possible to override this default behavior so
+ that appender accumulation is no longer additive by <ulink
+ url="../api/org/apache/log4j/Category.html#setAdditivity(boolean)">setting
+ the additivity flag</ulink> to <varname>false</varname>.
</para>
<para>The rules governing appender additivity are summarized below.</para>
-
- <para id="additivityRule">
+
+ <para >
<variablelist>
<varlistentry>
- <term><emphasis role="strong">Appender Additivity</emphasis></term>
+ <term><emphasis role="strong">Appender Additivity Rule</emphasis></term>
<listitem>
<para>The output of a log statement of logger
- <emphasis>C</emphasis> will go to all the appenders in
- <emphasis>C</emphasis> and its ancestors. This is the
+ <emphasis>L</emphasis> will go to all the appenders in
+ <emphasis>L</emphasis> and its ancestors. This is the
meaning of the term "appender additivity".</para>
<para>However, if an ancestor of logger
- <emphasis>C</emphasis>, say <emphasis>P</emphasis>,
+ <emphasis>L</emphasis>, say <emphasis>P</emphasis>,
has the additivity flag set to
- <varname>false</varname>, then <emphasis>C</emphasis>'s
+ <varname>false</varname>, then <emphasis>L</emphasis>'s
output will be directed to all the appenders in
<emphasis>C</emphasis> and it's ancestors upto and
including <emphasis>P</emphasis> but not the appenders
@@ -556,80 +561,81 @@
</varlistentry>
</variablelist>
</para>
-
- <para>The table below shows an example:</para>
-
- <table>
- <title></title>
- <tgroup cols="5">
- <thead>
- <row>
- <entry>Logger Name</entry>
- <entry>Added Appenders</entry>
- <entry>Additivity Flag</entry>
- <entry>Output Targets </entry>
- <entry>Comment</entry>
- </row>
- </thead>
-
- <tbody>
- <row>
- <entry>root</entry>
- <entry>A1</entry>
- <entry>not applicable</entry>
- <entry>A1</entry>
- <entry>The root logger is anonymous but can be accessed with the
- Logger.getRoot() method. There is no default appender
- attached to root.
+
+ <para>The table below shows an application of this rule:</para>
+
+ <table>
+ <title>Examples of Appender Additivity</title>
+ <tgroup cols="5">
+ <thead>
+ <row>
+ <entry>Logger Name</entry>
+ <entry>Added Appenders</entry>
+ <entry>Additivity Flag</entry>
+ <entry>Output on</entry>
+ <entry>Comment</entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry>root</entry>
+ <entry>A1</entry>
+ <entry>not applicable</entry>
+ <entry>A1</entry>
+ <entry>Since the root logger has no parents the additivity
+ flag is not applicable to the root logger.
</entry>
+
</row>
- <row>
- <entry>x</entry>
- <entry>A-x1, A-x2</entry>
- <entry>true</entry>
- <entry>A1, A-x1, A-x2</entry>
- <entry>Appenders of "x" and root.</entry>
- </row>
-
- <row>
- <entry>x.y</entry>
- <entry>none</entry>
- <entry>true</entry>
- <entry>A1, A-x1, A-x2</entry>
- <entry>Appenders of "x" and root.</entry>
- </row>
-
- <row>
- <entry>x.y.z</entry>
- <entry>A-xyz1</entry>
- <entry>true</entry>
- <entry>A1, A-x1, A-x2, A-xyz1</entry>
- <entry>Appenders in "x.y.z", "x" and root.</entry>
- </row>
- <row>
- <entry>security</entry>
- <entry>A-sec</entry>
- <entry>false</entry>
- <entry>A-sec</entry>
+ <row>
+ <entry>x</entry>
+ <entry>A-x1, A-x2</entry>
+ <entry>true</entry>
+ <entry>A1, A-x1, A-x2</entry>
+ <entry>Appenders of "x" and root.</entry>
+ </row>
- <entry>No appender accumulation since the additivity flag is set to
- <varname>false</varname>.
- </entry>
- </row>
-
- <row>
- <entry>security.access</entry>
- <entry>none</entry>
- <entry> true</entry>
- <entry> A-sec</entry>
+ <row>
+ <entry>x.y</entry>
+ <entry>none</entry>
+ <entry>true</entry>
+ <entry>A1, A-x1, A-x2</entry>
+ <entry>Appenders of "x" and root.</entry>
+ </row>
- <entry>Only appenders of "security" because the additivity
- flag in "security" is set to <varname>false</varname>.</entry>
- </row>
- </tbody>
- </tgroup>
- </table>
+ <row>
+ <entry>x.y.z</entry>
+ <entry>A-xyz1</entry>
+ <entry>true</entry>
+ <entry>A1, A-x1, A-x2, A-xyz1</entry>
+ <entry>Appenders in "x.y.z", "x" and root.</entry>
+ </row>
+ <row>
+ <entry>security</entry>
+ <entry>A-sec</entry>
+ <entry>false</entry>
+ <entry>A-sec</entry>
+
+ <entry>No appender accumulation since the additivity flag is set to
+ <varname>false</varname>.
+ </entry>
+ </row>
+
+ <row>
+ <entry>security.access</entry>
+ <entry>none</entry>
+ <entry> true</entry>
+ <entry> A-sec</entry>
+
+ <entry>Only appenders of "security" because the additivity
+ flag in "security" is set to <varname>false</varname>.</entry>
+ </row>
+ </tbody>
+ </tgroup>
+ </table>
+ </sect2>
</sect1>
<sect1>
@@ -661,7 +667,7 @@
<para>The first field is the number of milliseconds elapsed
since the start of the program. The second field is the
thread making the log request. The third field is the
- priority of the log statement. The fourth field is the name of
+ level of the log statement. The fourth field is the name of
the logger associated with the log request. The text after the
'-' is the message of the statement.
</para>
@@ -725,7 +731,110 @@
</para>
</sect1>
+
+ <sect1>
+ <title>A Peek Under the Hood</title>
+
+
+ <para>
+ In this section we describe the steps that the log4j framework
+ follows when the user invokes printing method on a logger.
+ </para>
+
+ <procedure>
+ <title>Walk through a printing method invocation</title>
+
+ <para>Let us assume that the user invokes the <ulink
+ url="../api/org/apache/log4j/Category.html#info(java.lang.Object)">info</ulink>
+ method on a logger named "wombat.foo".</para>
+
+ <step performance="required">
+ <title>Repository wide check</title>
+
+ <para>Every single logger has a reference to the repository
+ that created it. A logger will immediately exit the printing
+ method if the repository is not enabled for the request level.
+ </para>
+
+ <para>See XXXXXXXXXXXXX for more details.</para>
+ </step>
+
+ <step performance="required">
+ <title>Apply the basic selecton rule</title>
+
+ <para>Next, log4j compares the level of the logger with the
+ level of the request according to the <xref
+ linkend="basicSelectionRule"/>. If the logging request is
+ disabled, then log4j will drop the request without any
+ further processing by exiting the printing method.
+ </para>
+ </step>
+
+ <step>
+ <title>Creating a LoggingEvent object</title>
+
+ <para>If the request is enabled, then log4j will create a
+ <ulink
+ url="../api/org/apache/log4j/spi/LoggingEvent.html">LoggingEvent</ulink>
+ containing all the relevant paramets of the request such as
+ the logger of the request, the level of the request, the
+ message as an object, the current thread and the current
+ time while other fields are initialized lazily.
+ </para>
+ </step>
+
+ <step>
+ <title>Invoking appenders</title>
+
+ <para>After the creation of a
+ <classname>LoggingEvent</classname> object, log4j will
+ proceed to invoke the <ulink
+ url="../api/org/apache/log4j/Appender.html#doAppend(org.apache.log4j.spi.LoggingEvent)">doAppend</ulink>
+ methods of all the applicable appenders. The applicable
+ appenders are given by the <xref linkend="additivityRule"/>.
+ </para>
+
+ <para>All appenders shipped with the log4j distribution extend
+ the <ulink
+ url="../api/org/apache/log4j/AppenderSkeleton.html">AppenderSkeleton</ulink>
+ abstract class which implements the
+ <function>doAppend</function> method in a synchronized block
+ ensuring thread-safety.
+ </para>
+ </step>
+
+ <step>
+ <title>Formatting the LoggingEvent</title>
+
+ <para>It is reponsability of the invoked appender to format
+ the logging event. However, most (but not all) appender
+ delegate most of the formatting task to their
+ <classname>Layout</classname>. This layout formats the
+ <classname>LoggingEvent</classname> instance and returns
+ the event's information in a string form. The formatting
+ of event message is delegated to object renderers attached
+ to the repository of the logger
+ </para>
+
+ <para>Note that some appenders, such as the SocketAppender,
+ do not transform the logging event into a string but
+ serailize it instead. Consequently, they do not require
+ nor have a layout.
+ </para>
+ </step>
+
+ <step>
+ <title>Sending out the LoggingEvent</title>
+
+ <para>After the logging event is formatted it is sent to its
+ destination depending on the appender.
+ </para>
+ </step>
+ </procedure>
+
+
+ </sect1>
<sect1>
<title>Performance</title>
@@ -750,7 +859,7 @@
url="api/org/apache/log4j/Hierarchy.html#disableAll()">turned
off entirely</ulink> or just for a <ulink
url="api/org/apache/log4j/Hierarchy.html#disable(org.apache.log4j.Priority)">set
- of priorities</ulink>, the cost of a log request consists of a
+ of levels</ulink>, the cost of a log request consists of a
method invocation plus an integer comparison. On a 233
MHz Pentium II machine this cost is typically in the 5 to
50 nanosecond range.
@@ -812,10 +921,10 @@
<para>This is essentially the performance of walking the logger
hierarchy. When logging is turned on, log4j still needs to compare
- the priority of the log request with the priority of the request
+ the level of the log request with the level of the request
logger. However, loggers may not have an assigned
- priority; they can inherit them from the logger hierarchy. Thus,
- before inheriting a priority, the logger may need to search its
+ level; they can inherit them from the logger hierarchy. Thus,
+ before inheriting a level, the logger may need to search its
ancestors.</para>
<para>There has been a serious effort to make this hierarchy
1.3 +31 -7 jakarta-log4j/src/docbook/trouble.xml
Index: trouble.xml
===================================================================
RCS file: /home/cvs/jakarta-log4j/src/docbook/trouble.xml,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- trouble.xml 2001/09/20 22:37:10 1.2
+++ trouble.xml 2001/09/27 21:24:49 1.3
@@ -1,10 +1,13 @@
<chapter id="troubleShooting">
<title>Trouble Shooting Guide</title>
- <para>
- This chapter contains a list of commonly encountered problems
- when using log4j.
- </para>
+ <para>This chapter contains a list of commonly encountered
+ problems when using log4j. Before reporting bugs make sure that
+ you have made an honest effort to study existing
+ documentation. Please also see Eric S. Raymond's essay on <ulink
+ url="http://www.tuxedo.org/~esr/faqs/smart-questions.html">asking
+ questions the smart way</ulink>.
+ </para>
<qandaset>
<qandaentry>
@@ -77,7 +80,30 @@
</answer>
</qandaentry>
+
+ <qandaentry>
+ <question>
+ <para>Deadlocks occurring after the introduction of log
+ statements into an application.</para>
+ </question>
+
+ <answer>
+ <para>On numerous occasions users have complained about
+ deadlocks after introducing log statements into their
+ code. However, without exception the bug was always in the
+ application code not in log4j. This is not to say that log4j
+ is totally bug-free but you need to provide some proof
+ before jumping to conclusions.
+ </para>
+ <para>In the typical case, the introduction of logging
+ statements reveals <emphasis>existing</emphasis> concurrency
+ problems because log statements add delay which may change the
+ order of execution among threads, causing an existing
+ concurrency problem to come up to the surface. </para>
+ </answer>
+
+ </qandaentry>
<qandaentry>
<question>
@@ -234,9 +260,7 @@
</listitem>
</orderedlist>
</answer>
- </qandaentry>
-
-
+ </qandaentry>
</qandaset>
</chapter>
---------------------------------------------------------------------
To unsubscribe, e-mail: log4j-cvs-unsubscribe@jakarta.apache.org
For additional commands, e-mail: log4j-cvs-help@jakarta.apache.org