This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch jbFixRestNpe
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/jbFixRestNpe by this push:
     new bbf4b24b1 Re-add LogResource to Jetty microservice.
bbf4b24b1 is described below

commit bbf4b24b1ed8fb83f477da703f9553b30937b42e
Author: JamesBognar <[email protected]>
AuthorDate: Sun Jul 10 07:15:24 2022 -0400

    Re-add LogResource to Jetty microservice.
---
 .../java/org/apache/juneau/internal/Cache.java     |   2 +-
 .../04.jrs.HttpParts/03.jrs.DefaultParts.html      |  48 ++-
 juneau-doc/src/main/javadoc/overview.html          |  52 +++-
 .../src/main/javadoc/resources/fragments/toc.html  |  46 +--
 .../juneau-examples-rest-jetty.cfg                 |  67 ++++
 .../juneau/examples/rest/HtmlBeansResource.java    |   1 -
 .../apache/juneau/examples/rest/RootResources.java |   1 +
 .../org/apache/juneau/microservice/LogConfig.java  | 204 +++++++++++++
 .../apache/juneau/microservice/Microservice.java   |  79 ++++-
 .../microservice/resources/LogEntryFormatter.java  | 268 ++++++++++++++++
 .../juneau/microservice/resources/LogParser.java   | 224 ++++++++++++++
 .../microservice/resources/LogsResource.java       | 338 +++++++++++++++++++++
 12 files changed, 1300 insertions(+), 30 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/Cache.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/Cache.java
index afa4f58bf..ad2785479 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/Cache.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/Cache.java
@@ -68,7 +68,7 @@ public class Cache<K,V> {
                        this.type = type;
                        disabled = env("juneau.cache.disable", false);
                        maxSize = env("juneau.cache.maxSize", 1000);
-                       logOnExit = env("juneau.cache.logOnExit", true);
+                       logOnExit = env("juneau.cache.logOnExit", false);
                }
 
                /**
diff --git 
a/juneau-doc/docs/Topics/08.juneau-rest-server/04.jrs.HttpParts/03.jrs.DefaultParts.html
 
b/juneau-doc/docs/Topics/08.juneau-rest-server/04.jrs.HttpParts/03.jrs.DefaultParts.html
index e7c1d8c46..06ae25b3f 100644
--- 
a/juneau-doc/docs/Topics/08.juneau-rest-server/04.jrs.HttpParts/03.jrs.DefaultParts.html
+++ 
b/juneau-doc/docs/Topics/08.juneau-rest-server/04.jrs.HttpParts/03.jrs.DefaultParts.html
@@ -17,7 +17,53 @@
 
 <div class='topic'>
        <p>
-               The following annotations are provided for specifying default 
part values for requests and responses:
+               By default, HTTP parts that don't have value (such as missing 
query parameters) end up with null
+               values:
+       </p>
+       <p class='bjava'>
+               |       <ja>@RestPost</ja>(<js>"/example"</js>)
+               |       <jk>public</jk> String doGetExample1(
+               |               <ja>@Query</ja>(<js>"p1"</js>) <jk>int</jk> 
<jv>p1</jv>,
+               |               <ja>@FormData</ja>(<js>"f1"</js>) MyBean 
<jv>f1</jv>,
+               |               <ja>@Header</ja>(<js>"Accept-Language"</js>) 
AcceptLanguage <jv>h1</jv>
+               |       ) {
+               |               <jk>if</jk> (<jv>p1</jv> == <jk>null</jk>) 
<jv>p1</jv> = -1;
+               |               <jk>if</jk> (<jv>f1</jv> == <jk>null</jk>) 
<jv>f1</jv> = <jsf>DEFAULT_BEAN</jsf>;
+               |               <jk>if</jk> (<jv>h1</jv> == <jk>null</jk>) 
<jv>h1</jv> = AcceptLanguage.<jsm>of</jsm>(<js>"en"</js>);
+               |       }
+       </p>
+       <p>
+               You have several options to provide default values for HTTP 
parts.  The most common is to simply
+               use {@link java.util.Optional} parameters and handle default 
values programmatically:
+       </p>
+       <p class='bjava'>
+               |       <ja>@RestPost</ja>(<js>"/example"</js>)
+               |       <jk>public</jk> String doGetExample1(
+               |               <ja>@Query</ja>(<js>"p1"</js>) 
Optional&lt;Integer&gt; <jv>p1</jv>,
+               |               <ja>@FormData</ja>(<js>"f1"</js>) 
Optional&lt;MyBean&gt; <jv>f1</jv>,
+               |               <ja>@Header</ja>(<js>"Accept-Language"</js>) 
Optional&lt;AcceptLanguage&gt; <jv>h1</jv>
+               |       ) {
+               |               <jk>int</jk> <jv>_p1</jv> = 
<jv>p1</jv>.orElse(-1);
+               |               Bean <jv>_f1</jv> = 
<jv>f1</jv>.orElse(<jsf>DEFAULT_BEAN</jsf>);
+               |               AcceptLanguage <jv>_h1</jv> = 
<jv>h1</jv>.orElse(AcceptLanguage.<jsm>of</jsm>(<js>"en"</js>));
+               |       }
+       </p>
+       <p>
+               You can also specify default values on the annotations:
+       </p>
+       <p class='bjava'>
+               |       <ja>@RestPost</ja>(<js>"/example"</js>)
+               |       <jk>public</jk> String doGetExample1(
+               |               <ja>@Query</ja>(name=<js>"p1"</js>, 
def=<js>"-1"</js>) <jk>int</jk> <jv>p1</jv>,
+               |               <ja>@FormData</ja>(name=<js>"f1"</js>, 
def=<js>"foo=bar,baz=qux"</js>) MyBean <jv>f1</jv>,
+               |               
<ja>@Header</ja>(name=<js>"Accept-Language"</js>, def=<js>"en"</js>) 
AcceptLanguage <jv>lang</jv>
+               |       ) {
+               |               ...
+               |       }
+       </p>
+       <p>
+               A third option is to specify default values via the {@link 
oajr.annotation.Rest} and
+               {@link oajr.annotation.RestOp} annotations. 
        </p>
        <ul class='javatree'>
                <li class='ja'>{@link oajr.annotation.Rest} 
diff --git a/juneau-doc/src/main/javadoc/overview.html 
b/juneau-doc/src/main/javadoc/overview.html
index a754a9902..74b303c0c 100644
--- a/juneau-doc/src/main/javadoc/overview.html
+++ b/juneau-doc/src/main/javadoc/overview.html
@@ -18195,7 +18195,53 @@
 <div class='topic'><!-- START: 8.4.3 - 
juneau-rest-server.jrs.HttpParts.jrs.DefaultParts -->
 <div class='topic'>
        <p>
-               The following annotations are provided for specifying default 
part values for requests and responses:
+               By default, HTTP parts that don't have value (such as missing 
query parameters) end up with null
+               values:
+       </p>
+       <p class='bjava'>
+       <ja>@RestPost</ja>(<js>"/example"</js>)
+       <jk>public</jk> String doGetExample1(
+               <ja>@Query</ja>(<js>"p1"</js>) <jk>int</jk> <jv>p1</jv>,
+               <ja>@FormData</ja>(<js>"f1"</js>) MyBean <jv>f1</jv>,
+               <ja>@Header</ja>(<js>"Accept-Language"</js>) AcceptLanguage 
<jv>h1</jv>
+       ) {
+               <jk>if</jk> (<jv>p1</jv> == <jk>null</jk>) <jv>p1</jv> = -1;
+               <jk>if</jk> (<jv>f1</jv> == <jk>null</jk>) <jv>f1</jv> = 
<jsf>DEFAULT_BEAN</jsf>;
+               <jk>if</jk> (<jv>h1</jv> == <jk>null</jk>) <jv>h1</jv> = 
AcceptLanguage.<jsm>of</jsm>(<js>"en"</js>);
+       }
+       </p>
+       <p>
+               You have several options to provide default values for HTTP 
parts.  The most common is to simply
+               use {@link java.util.Optional} parameters and handle default 
values programmatically:
+       </p>
+       <p class='bjava'>
+       <ja>@RestPost</ja>(<js>"/example"</js>)
+       <jk>public</jk> String doGetExample1(
+               <ja>@Query</ja>(<js>"p1"</js>) Optional&lt;Integer&gt; 
<jv>p1</jv>,
+               <ja>@FormData</ja>(<js>"f1"</js>) Optional&lt;MyBean&gt; 
<jv>f1</jv>,
+               <ja>@Header</ja>(<js>"Accept-Language"</js>) 
Optional&lt;AcceptLanguage&gt; <jv>h1</jv>
+       ) {
+               <jk>int</jk> <jv>_p1</jv> = <jv>p1</jv>.orElse(-1);
+               Bean <jv>_f1</jv> = <jv>f1</jv>.orElse(<jsf>DEFAULT_BEAN</jsf>);
+               AcceptLanguage <jv>_h1</jv> = 
<jv>h1</jv>.orElse(AcceptLanguage.<jsm>of</jsm>(<js>"en"</js>));
+       }
+       </p>
+       <p>
+               You can also specify default values on the annotations:
+       </p>
+       <p class='bjava'>
+       <ja>@RestPost</ja>(<js>"/example"</js>)
+       <jk>public</jk> String doGetExample1(
+               <ja>@Query</ja>(name=<js>"p1"</js>, def=<js>"-1"</js>) 
<jk>int</jk> <jv>p1</jv>,
+               <ja>@FormData</ja>(name=<js>"f1"</js>, 
def=<js>"foo=bar,baz=qux"</js>) MyBean <jv>f1</jv>,
+               <ja>@Header</ja>(name=<js>"Accept-Language"</js>, 
def=<js>"en"</js>) AcceptLanguage <jv>lang</jv>
+       ) {
+               ...
+       }
+       </p>
+       <p>
+               A third option is to specify default values via the {@link 
org.apache.juneau.rest.annotation.Rest} and
+               {@link org.apache.juneau.rest.annotation.RestOp} annotations. 
        </p>
        <ul class='javatree'>
                <li class='ja'>{@link org.apache.juneau.rest.annotation.Rest} 
@@ -27731,7 +27777,7 @@
        
        <cc># Various look-and-feel settings used in the BasicRestConfig 
interface.</cc>
        <ck>headerIcon</ck> = <cv>servlet:/htdocs/images/juneau.png</cv>
-                       v       <ck>headerLink</ck> = 
<cv>http://juneau.apache.org</cv>
+       <ck>headerLink</ck> = <cv>http://juneau.apache.org</cv>
        <ck>footerIcon</ck> = <cv>servlet:/htdocs/images/asf.png</cv>
        <ck>footerLink</ck> = <cv>http://www.apache.org</cv>
        <ck>favicon</ck> = <cv>$C{REST/headerIcon}</cv>
@@ -27743,7 +27789,7 @@
                <cv>&lt;a href='$U{$C{REST/footerLink}}'&gt;
                        &lt;img src='$U{$C{REST/footerIcon}}' 
style='float:right;padding-right:20px;height:32px'/&gt;
                &lt;/a&gt;</cv>
-               </p>
+                       </p>
        </ul>
        <p>
                At this point, you're ready to start the microservice from your 
workspace.
diff --git a/juneau-doc/src/main/javadoc/resources/fragments/toc.html 
b/juneau-doc/src/main/javadoc/resources/fragments/toc.html
index cf81c7cff..143c5cf22 100644
--- a/juneau-doc/src/main/javadoc/resources/fragments/toc.html
+++ b/juneau-doc/src/main/javadoc/resources/fragments/toc.html
@@ -328,34 +328,34 @@
        <ol>
                <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-rest-mock.jrm.MockRestClient'>MockRestClient</a><span
 class='update'>created: 8.2.0, updated: <b>9.0.0</b></span></p>
        </ol>
-       <li><p class='toc2'><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core'>juneau-microservice-core</a><span
 class='update'>created: 8.1.0, deprecated: 8.1.2</span></p>
+       <li><p class='toc2'><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core'>juneau-microservice-core</a><span
 class='update'>created: 8.1.0</span></p>
        <ol>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.Overview'>Microservice 
Overview</a><span class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.LifecycleMethods'>Lifecycle 
Methods</a><span class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.Args'>Args</a><span 
class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.Manifest'>Manifest</a><span 
class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.Config'>Config</a><span 
class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.SystemProperties'>System 
properties</a><span class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.VarResolver'>VarResolver</a><span
 class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.ConsoleCommands'>Console 
Commands</a><span class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.Listeners'>Listeners</a><span 
class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.Overview'>Microservice 
Overview</a><span class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.LifecycleMethods'>Lifecycle 
Methods</a><span class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.Args'>Args</a><span 
class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.Manifest'>Manifest</a><span 
class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.Config'>Config</a><span 
class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.SystemProperties'>System 
properties</a><span class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.VarResolver'>VarResolver</a><span
 class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.ConsoleCommands'>Console 
Commands</a><span class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-core.jmc.Listeners'>Listeners</a><span 
class='update'>created: 8.0.0</span></p>
        </ol>
-       <li><p class='toc2'><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty'>juneau-microservice-jetty</a><span
 class='update'>created: 8.1.0, deprecated: 8.1.2</span></p>
+       <li><p class='toc2'><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty'>juneau-microservice-jetty</a><span
 class='update'>created: 8.1.0</span></p>
        <ol>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.Overview'>Overview</a><span 
class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.LifecycleMethods'>Lifecycle 
Methods</a><span class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.ResourceClasses'>Resource 
Classes</a><span class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.PredefinedResourceClasses'>Predefined
 Resource Classes</a><span class='update'>created: 8.0.0, deprecated: 
8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.Config'>Config</a><span 
class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.JettyXml'>Jetty.xml 
file</a><span class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.UiCustomization'>UI 
Customization</a><span class='update'>created: 8.0.0, deprecated: 
8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.Extending'>Extending 
JettyMicroservice</a><span class='update'>created: 8.0.0, deprecated: 
8.1.2</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.Overview'>Overview</a><span 
class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.LifecycleMethods'>Lifecycle 
Methods</a><span class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.ResourceClasses'>Resource 
Classes</a><span class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.PredefinedResourceClasses'>Predefined
 Resource Classes</a><span class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.Config'>Config</a><span 
class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.JettyXml'>Jetty.xml 
file</a><span class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.UiCustomization'>UI 
Customization</a><span class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#juneau-microservice-jetty.jmj.Extending'>Extending 
JettyMicroservice</a><span class='update'>created: 8.0.0</span></p>
        </ol>
-       <li><p class='toc2'><a class='doclink' 
href='{OVERVIEW_URL}#my-jetty-microservice'>my-jetty-microservice</a><span 
class='update'>created: 8.1.0, deprecated: 8.1.2</span></p>
+       <li><p class='toc2'><a class='doclink' 
href='{OVERVIEW_URL}#my-jetty-microservice'>my-jetty-microservice</a><span 
class='update'>created: 8.1.0</span></p>
        <ol>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#my-jetty-microservice.mjm.Installing'>Installing in 
Eclipse</a><span class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#my-jetty-microservice.mjm.Running'>Running in 
Eclipse</a><span class='update'>created: 8.0.0, deprecated: 8.1.2</span></p>
-               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#my-jetty-microservice.mjm.Building'>Building and Running 
from Command-Line</a><span class='update'>created: 8.0.0, deprecated: 
8.1.2</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#my-jetty-microservice.mjm.Installing'>Installing in 
Eclipse</a><span class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#my-jetty-microservice.mjm.Running'>Running in 
Eclipse</a><span class='update'>created: 8.0.0</span></p>
+               <li><p><a class='doclink' 
href='{OVERVIEW_URL}#my-jetty-microservice.mjm.Building'>Building and Running 
from Command-Line</a><span class='update'>created: 8.0.0</span></p>
        </ol>
        <li><p class='toc2'><a class='doclink' 
href='{OVERVIEW_URL}#my-springboot-microservice'>my-springboot-microservice</a><span
 class='update'>created: 8.0.0</span></p>
        <ol>
diff --git 
a/juneau-examples/juneau-examples-rest-jetty/juneau-examples-rest-jetty.cfg 
b/juneau-examples/juneau-examples-rest-jetty/juneau-examples-rest-jetty.cfg
index f11c0cd1b..e09a2d53b 100755
--- a/juneau-examples/juneau-examples-rest-jetty/juneau-examples-rest-jetty.cfg
+++ b/juneau-examples/juneau-examples-rest-jetty/juneau-examples-rest-jetty.cfg
@@ -89,6 +89,73 @@ commands =
        org.apache.juneau.microservice.console.ConfigCommand,
        org.apache.juneau.examples.rest.command.EchoCommand
 
+#=======================================================================================================================
+# Logger settings
+#-----------------------------------------------------------------------------------------------------------------------
+# See FileHandler Java class for details.
+#=======================================================================================================================
+[Logging]
+
+# The directory where to create the log file.
+# Default is "."
+logDir = ./target/logs
+
+# The name of the log file to create for the main logger.
+# The logDir and logFile make up the pattern that's passed to the FileHandler
+# constructor.
+# If value is not specified, then logging to a file will not be set up.
+logFile = microservice.%g.log
+
+# Whether to append to the existing log file or create a new one.
+append = false
+
+# The SimpleDateFormat format to use for dates.
+dateFormat = yyyy.MM.dd hh:mm:ss
+
+# The log message format.
+# The value can contain any of the following variables:
+#      {date} - The date, formatted per dateFormat.
+#      {class} - The class name.
+#      {method} - The method name.
+#      {logger} - The logger name.
+#      {level} - The log level name.
+#      {msg} - The log message.
+#      {threadid} - The thread ID.
+#      {exception} - The localized exception message.
+format = [{date} {level}] {msg}%n
+
+# The maximum log file size.
+# Suffixes available for numbers.
+# See Config.getInt(String,int) for details.
+limit = 1M
+
+# Max number of log files.
+count = 5
+
+# Default log levels.
+# Format is lax-JSON.
+# Keys are logger names.
+# Values are serialized Level POJOs (SEVERE, WARNING, INFO, CONFIG, FINE, 
FINER, FINEST)
+levels = 
+       { 
+               '': 'WARNING', 
+               org.apache.juneau: 'WARNING', 
+               org.eclipse.jetty: 'WARNING' 
+       }
+
+# Only print unique stack traces once and then refer to them by a simple 8 
character hash identifier.
+# Useful for preventing log files from filling up with duplicate stack traces.
+useStackTraceHashes = true
+
+# The default level for the console logger.
+# Values are serialized Level POJOs (SEVERE, WARNING, INFO, CONFIG, FINE, 
FINER, FINEST)
+consoleLevel = WARNING
+
+# The default level for the file logger.
+# Values are serialized Level POJOs (SEVERE, WARNING, INFO, CONFIG, FINE, 
FINER, FINEST)
+# Default is INFO.
+fileLevel = INFO
+
 
#=======================================================================================================================
 # System properties
 
#-----------------------------------------------------------------------------------------------------------------------
diff --git 
a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HtmlBeansResource.java
 
b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HtmlBeansResource.java
index f59773a0a..1d3800fc4 100644
--- 
a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HtmlBeansResource.java
+++ 
b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HtmlBeansResource.java
@@ -54,7 +54,6 @@ import org.apache.juneau.rest.widget.*;
        },
        asideFloat="RIGHT"
 )
-@SuppressWarnings("javadoc")
 public class HtmlBeansResource extends BasicRestObject implements 
BasicUniversalConfig {
 
        @SuppressWarnings("unused")
diff --git 
a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RootResources.java
 
b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RootResources.java
index 2cbb0f8c6..0550b02be 100644
--- 
a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RootResources.java
+++ 
b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RootResources.java
@@ -39,6 +39,7 @@ import org.apache.juneau.serializer.annotation.*;
                UtilityBeansResource.class,
                HtmlBeansResource.class,
                ConfigResource.class,
+               LogsResource.class,
                ShutdownResource.class
        }
 )
diff --git 
a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/LogConfig.java
 
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/LogConfig.java
new file mode 100644
index 000000000..e558bc143
--- /dev/null
+++ 
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/LogConfig.java
@@ -0,0 +1,204 @@
+// 
***************************************************************************************************************************
+// * 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.                                              *
+// 
***************************************************************************************************************************
+package org.apache.juneau.microservice;
+
+import java.util.*;
+import java.util.logging.*;
+import java.util.logging.Formatter;
+
+import org.apache.juneau.microservice.resources.*;
+
+/**
+ * Can be used for configuration of simple logging in the microservice.
+ *
+ * <p>
+ * Instances of this class can be created using {@link #create()} and passing 
the result to
+ * {@link Microservice.Builder#logConfig(LogConfig)}.
+ *
+ * <p>
+ * These values override values specified in the <js>"Logging"</js> 
configuration section.
+ */
+public class LogConfig {
+
+       String logFile, logDir;
+       Boolean append;
+       Integer limit, count;
+       Level fileLevel, consoleLevel;
+       Map<String,Level> levels = new LinkedHashMap<>();
+       Formatter formatter;
+
+       LogConfig() {}
+
+       /**
+        * Copy constructor.
+        *
+        * @param copyFrom The log config to copy from.
+        */
+       protected LogConfig(LogConfig copyFrom) {
+               this.logFile = copyFrom.logFile;
+               this.logDir = copyFrom.logDir;
+               this.append = copyFrom.append;
+               this.limit = copyFrom.limit;
+               this.count = copyFrom.count;
+               this.fileLevel = copyFrom.fileLevel;
+               this.consoleLevel = copyFrom.consoleLevel;
+               this.levels = new LinkedHashMap<>(copyFrom.levels);
+               this.formatter = copyFrom.formatter;
+       }
+
+       /**
+        * Creates a copy of this log configuration.
+        *
+        * @return A new copy of this log configuration.
+        */
+       public LogConfig copy() {
+               return new LogConfig(this);
+       }
+
+       /**
+        * Creates a new instance of this config.
+        *
+        * @return A new instance of this config.
+        */
+       public static LogConfig create() {
+               return new LogConfig();
+       }
+
+       /**
+        * Returns the name of the log file on the file system to store the log 
file for this microservice.
+        *
+        * <p>
+        * This overrides the configuration value <js>"Logging/logFile"</js>.
+        * If not specified, no file system logging will be used.
+        *
+        * @param logFile The log file.
+        * @return This object (for method chaining).
+        */
+       public LogConfig logFile(String logFile) {
+               this.logFile = logFile;
+               return this;
+       }
+
+       /**
+        * The location of the log directory to create the log file.
+        *
+        * <p>
+        * This overrides the configuration value <js>"Logging/logDir"</js>.
+        * If not specified, uses the JVM working directory.
+        *
+        * @param logDir The log directory location as a path relative to the 
working directory.
+        * @return This object (for method chaining).
+        */
+       public LogConfig logDir(String logDir) {
+               this.logDir = logDir;
+               return this;
+       }
+
+       /**
+        * The log entry formatter.
+        *
+        * <p>
+        * If not specified, uses the following values pulled from the 
configuration to construct a {@link LogEntryFormatter}:
+        * <ul>
+        *      <li><js><js>"Logging/format"</js> (default is <js>"[{date} 
{level}] {msg}%n"</js>)
+        *      <li><js><js>"Logging/dateFormat"</js> (default is 
<js>"yyyy.MM.dd hh:mm:ss"</js>)
+        *      <li><js><js>"Logging/useStackTraceHashes"</js> (default is 
<jk>false</jk>)
+        * </ul>
+        *
+        *
+        * @param formatter The log entry formatter.
+        * @return This object (for method chaining).
+        * @see LogEntryFormatter
+        */
+       public LogConfig formatter(Formatter formatter) {
+               this.formatter = formatter;
+               return this;
+       }
+
+       /**
+        * Specified append mode for the log file.
+        *
+        * @return This object (for method chaining).
+        */
+       public LogConfig append() {
+               this.append = true;
+               return this;
+       }
+
+       /**
+        * The maximum number of bytes to write to any one log file.
+        *
+        * @param limit The number of bytes.
+        * @return This object (for method chaining).
+        */
+       public LogConfig limit(int limit) {
+               this.limit = limit;
+               return this;
+       }
+
+       /**
+        * The number of log files to use.
+        *
+        * @param count The number of log files.
+        * @return This object (for method chaining).
+        */
+       public LogConfig count(int count) {
+               this.count = count;
+               return this;
+       }
+
+       /**
+        * The default logging level for the log file.
+        *
+        * @param fileLevel The logging level.
+        * @return This object (for method chaining).
+        */
+       public LogConfig fileLevel(Level fileLevel) {
+               this.fileLevel = fileLevel;
+               return this;
+       }
+
+       /**
+        * The default logging level for the console.
+        *
+        * @param consoleLevel The logging level.
+        * @return This object (for method chaining).
+        */
+       public LogConfig consoleLevel(Level consoleLevel) {
+               this.consoleLevel = consoleLevel;
+               return this;
+       }
+
+       /**
+        * Default logging levels for loggers.
+        *
+        * @param levels A map of logger names to logger levels.
+        * @return This object (for method chaining).
+        */
+       public LogConfig levels(Map<String,Level> levels) {
+               this.levels.putAll(levels);
+               return this;
+       }
+
+       /**
+        * Default logging level for logger.
+        *
+        * @param logger Logger name.
+        * @param level Logger level.
+        * @return This object (for method chaining).
+        */
+       public LogConfig level(String logger, Level level) {
+               this.levels.put(logger, level);
+               return this;
+       }
+}
diff --git 
a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/Microservice.java
 
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/Microservice.java
index ea8d48feb..5319ed20b 100755
--- 
a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/Microservice.java
+++ 
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/Microservice.java
@@ -14,8 +14,11 @@ package org.apache.juneau.microservice;
 
 import static org.apache.juneau.internal.ClassUtils.*;
 import static org.apache.juneau.internal.CollectionUtils.*;
+import static org.apache.juneau.internal.FileUtils.*;
 import static org.apache.juneau.internal.IOUtils.*;
+import static org.apache.juneau.internal.ObjectUtils.*;
 import static org.apache.juneau.internal.StringUtils.*;
+
 import java.io.*;
 import java.io.Console;
 import java.net.*;
@@ -25,6 +28,7 @@ import java.util.*;
 import java.util.concurrent.*;
 import java.util.jar.*;
 import java.util.logging.*;
+import java.util.logging.Formatter;
 
 import org.apache.juneau.*;
 import org.apache.juneau.collections.*;
@@ -36,6 +40,7 @@ import org.apache.juneau.config.vars.*;
 import org.apache.juneau.cp.*;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.microservice.console.*;
+import org.apache.juneau.microservice.resources.*;
 import org.apache.juneau.parser.ParseException;
 import org.apache.juneau.svl.*;
 import org.apache.juneau.svl.vars.*;
@@ -140,6 +145,7 @@ public class Microservice implements ConfigEventListener {
                Args args;
                ManifestFile manifest;
                Logger logger;
+               LogConfig logConfig;
                Config config;
                String configName;
                ConfigStore configStore;
@@ -167,6 +173,7 @@ public class Microservice implements ConfigEventListener {
                        this.manifest = copyFrom.manifest;
                        this.logger = copyFrom.logger;
                        this.configName = copyFrom.configName;
+                       this.logConfig = copyFrom.logConfig == null ? null : 
copyFrom.logConfig.copy();
                        this.consoleEnabled = copyFrom.consoleEnabled;
                        this.configBuilder = copyFrom.configBuilder;
                        this.varResolver = copyFrom.varResolver;
@@ -283,6 +290,9 @@ public class Microservice implements ConfigEventListener {
                /**
                 * Specifies the logger used by the microservice and returned 
by the {@link Microservice#getLogger()} method.
                 *
+                * <p>
+                * Calling this method overrides the default logging mechanism 
controlled by the {@link #logConfig(LogConfig)} method.
+                *
                 * @param logger The logger to use for logging microservice 
messages.
                 * @return This object.
                 */
@@ -291,6 +301,23 @@ public class Microservice implements ConfigEventListener {
                        return this;
                }
 
+               /**
+                * Specifies logging instructions for the microservice.
+                *
+                * <p>
+                * If not specified, the values are taken from the 
<js>"Logging"</js> section of the configuration.
+                *
+                * <p>
+                * This method is ignored if {@link #logger(Logger)} is used to 
set the microservice logger.
+                *
+                * @param logConfig The log configuration.
+                * @return This object.
+                */
+               public Builder logConfig(LogConfig logConfig) {
+                       this.logConfig = logConfig;
+                       return this;
+               }
+
                /**
                 * Specifies the config for initializing this microservice.
                 *
@@ -522,6 +549,7 @@ public class Microservice implements ConfigEventListener {
 
        final Messages messages = Messages.of(Microservice.class);
 
+       private final Builder builder;
        private final Args args;
        private final Config config;
        private final ManifestFile manifest;
@@ -535,7 +563,7 @@ public class Microservice implements ConfigEventListener {
        final File workingDir;
        private final String configName;
 
-       private volatile Logger logger = Logger.getGlobal();
+       private volatile Logger logger;
 
        /**
         * Constructor.
@@ -544,8 +572,10 @@ public class Microservice implements ConfigEventListener {
         * @throws IOException Problem occurred reading file.
         * @throws ParseException Malformed input encountered.
         */
+       @SuppressWarnings("resource")
        protected Microservice(Builder builder) throws IOException, 
ParseException {
                setInstance(this);
+               this.builder = builder.copy();
                this.workingDir = builder.workingDir;
                this.configName = builder.configName;
 
@@ -734,6 +764,53 @@ public class Microservice implements ConfigEventListener {
                        for (String key : spKeys)
                                System.setProperty(key, 
config.get("SystemProperties/"+key).orElse(null));
 
+               // 
--------------------------------------------------------------------------------
+               // Initialize logging.
+               // 
--------------------------------------------------------------------------------
+               this.logger = builder.logger;
+               LogConfig logConfig = builder.logConfig != null ? 
builder.logConfig : new LogConfig();
+               if (this.logger == null) {
+                       LogManager.getLogManager().reset();
+                       this.logger = Logger.getLogger("");
+                       String logFile = firstNonNull(logConfig.logFile, 
config.get("Logging/logFile").orElse(null));
+
+                       if (isNotEmpty(logFile)) {
+                               String logDir = firstNonNull(logConfig.logDir, 
config.get("Logging/logDir").orElse("."));
+                               File logDirFile = resolveFile(logDir);
+                               mkdirs(logDirFile, false);
+                               logDir = logDirFile.getAbsolutePath();
+                               System.setProperty("juneau.logDir", logDir);
+
+                               boolean append = firstNonNull(logConfig.append, 
config.get("Logging/append").asBoolean().orElse(false));
+                               int limit = firstNonNull(logConfig.limit, 
config.get("Logging/limit").asInteger().orElse(1024*1024));
+                               int count = firstNonNull(logConfig.count, 
config.get("Logging/count").asInteger().orElse(1));
+
+                               FileHandler fh = new FileHandler(logDir + '/' + 
logFile, limit, count, append);
+
+                               Formatter f = logConfig.formatter;
+                               if (f == null) {
+                                       String format = 
config.get("Logging/format").orElse("[{date} {level}] {msg}%n");
+                                       String dateFormat = 
config.get("Logging/dateFormat").orElse("yyyy.MM.dd hh:mm:ss");
+                                       boolean useStackTraceHashes = 
config.get("Logging/useStackTraceHashes").asBoolean().orElse(false);
+                                       f = new LogEntryFormatter(format, 
dateFormat, useStackTraceHashes);
+                               }
+                               fh.setFormatter(f);
+                               fh.setLevel(firstNonNull(logConfig.fileLevel, 
config.get("Logging/fileLevel").as(Level.class).orElse(Level.INFO)));
+                               logger.addHandler(fh);
+
+                               ConsoleHandler ch = new ConsoleHandler();
+                               
ch.setLevel(firstNonNull(logConfig.consoleLevel, 
config.get("Logging/consoleLevel").as(Level.class).orElse(Level.WARNING)));
+                               ch.setFormatter(f);
+                               logger.addHandler(ch);
+                       }
+               }
+
+               JsonMap loggerLevels = 
config.get("Logging/levels").as(JsonMap.class).orElseGet(JsonMap::new);
+               for (String l : loggerLevels.keySet())
+                       Logger.getLogger(l).setLevel(loggerLevels.get(l, 
Level.class));
+               for (String l : logConfig.levels.keySet())
+                       Logger.getLogger(l).setLevel(logConfig.levels.get(l));
+
                return this;
        }
 
diff --git 
a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogEntryFormatter.java
 
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogEntryFormatter.java
new file mode 100644
index 000000000..0b8d85fb5
--- /dev/null
+++ 
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogEntryFormatter.java
@@ -0,0 +1,268 @@
+// 
***************************************************************************************************************************
+// * 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.                                              *
+// 
***************************************************************************************************************************
+package org.apache.juneau.microservice.resources;
+
+import static org.apache.juneau.internal.CollectionUtils.*;
+
+import java.text.*;
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.*;
+import java.util.logging.*;
+import java.util.logging.Formatter;
+import java.util.regex.*;
+
+import org.apache.juneau.internal.*;
+
+/**
+ * Log entry formatter.
+ *
+ * <p>
+ * Uses three simple parameter for configuring log entry formats:
+ * <ul class='spaced-list'>
+ *     <li>
+ *             <c>dateFormat</c> - A {@link SimpleDateFormat} string 
describing the format for dates.
+ *     <li>
+ *             <c>format</c> - A string with <c>{...}</c> replacement 
variables representing predefined fields.
+ *     <li>
+ *             <c>useStackTraceHashes</c> - A setting that causes duplicate 
stack traces to be replaced with 8-character
+ *              hash strings.
+ * </ul>
+ *
+ * <p>
+ * This class converts the format strings into a regular expression that can 
be used to parse the resulting log file.
+ */
+public class LogEntryFormatter extends Formatter {
+
+       private ConcurrentHashMap<String,AtomicInteger> hashes;
+       private DateFormat df;
+       private String format;
+       private Pattern rePattern;
+       private Map<String,Integer> fieldIndexes;
+
+       /**
+        * Create a new formatter.
+        *
+        * @param format
+        *      The log entry format.  e.g. <js>"[{date} {level}] {msg}%n"</js>
+        *      The string can contain any of the following variables:
+        *      <ol>
+        *              <li><js>"{date}"</js> - The date, formatted per 
<js>"Logging/dateFormat"</js>.
+        *              <li><js>"{class}"</js> - The class name.
+        *              <li><js>"{method}"</js> - The method name.
+        *              <li><js>"{logger}"</js> - The logger name.
+        *              <li><js>"{level}"</js> - The log level name.
+        *              <li><js>"{msg}"</js> - The log message.
+        *              <li><js>"{threadid}"</js> - The thread ID.
+        *              <li><js>"{exception}"</js> - The localized exception 
message.
+        *      </ol>
+        * @param dateFormat
+        *      The {@link SimpleDateFormat} format to use for dates.  e.g. 
<js>"yyyy.MM.dd hh:mm:ss"</js>.
+        * @param useStackTraceHashes
+        *      If <jk>true</jk>, only print unique stack traces once and then 
refer to them by a simple 8 character hash
+        *      identifier.
+        */
+       public LogEntryFormatter(String format, String dateFormat, boolean 
useStackTraceHashes) {
+               this.df = new SimpleDateFormat(dateFormat);
+               if (useStackTraceHashes)
+                       hashes = new ConcurrentHashMap<>();
+
+               fieldIndexes = new HashMap<>();
+
+               format = format
+                       .replaceAll("\\{date\\}", "%1\\$s")
+                       .replaceAll("\\{class\\}", "%2\\$s")
+                       .replaceAll("\\{method\\}", "%3\\$s")
+                       .replaceAll("\\{logger\\}", "%4\\$s")
+                       .replaceAll("\\{level\\}", "%5\\$s")
+                       .replaceAll("\\{msg\\}", "%6\\$s")
+                       .replaceAll("\\{threadid\\}", "%7\\$s")
+                       .replaceAll("\\{exception\\}", "%8\\$s");
+
+               this.format = format;
+
+               // Construct a regular expression to match this log entry.
+               int index = 1;
+               StringBuilder re = new StringBuilder();
+               int S1 = 1; // Looking for %
+               int S2 = 2; // Found %, looking for number.
+               int S3 = 3; // Found number, looking for $.
+               int S4 = 4; // Found $, looking for s.
+               int state = 1;
+               int i1 = 0;
+               for (int i = 0; i < format.length(); i++) {
+                       char c = format.charAt(i);
+                       if (state == S1) {
+                               if (c == '%')
+                                       state = S2;
+                               else {
+                                       if (! (Character.isLetterOrDigit(c) || 
Character.isWhitespace(c)))
+                                               re.append('\\');
+                                       re.append(c);
+                               }
+                       } else if (state == S2) {
+                               if (Character.isDigit(c)) {
+                                       i1 = i;
+                                       state = S3;
+                               } else {
+                                       re.append("\\%").append(c);
+                                       state = S1;
+                               }
+                       } else if (state == S3) {
+                               if (c == '$') {
+                                       state = S4;
+                               } else {
+                                       
re.append("\\%").append(format.substring(i1, i));
+                                       state = S1;
+                               }
+                       } else if (state == S4) {
+                               if (c == 's') {
+                                       int group = 
Integer.parseInt(format.substring(i1, i-1));
+                                       switch (group) {
+                                               case 1:
+                                                       
fieldIndexes.put("date", index++);
+                                                       re.append("(" + 
dateFormat.replaceAll("[mHhsSdMy]", "\\\\d").replaceAll("\\.", "\\\\.") + ")");
+                                                       break;
+                                               case 2:
+                                                       
fieldIndexes.put("class", index++);
+                                                       
re.append("([\\p{javaJavaIdentifierPart}\\.]+)");
+                                                       break;
+                                               case 3:
+                                                       
fieldIndexes.put("method", index++);
+                                                       
re.append("([\\p{javaJavaIdentifierPart}\\.]+)");
+                                                       break;
+                                               case 4:
+                                                       
fieldIndexes.put("logger", index++);
+                                                       
re.append("([\\w\\d\\.\\_]+)");
+                                                       break;
+                                               case 5:
+                                                       
fieldIndexes.put("level", index++);
+                                                       
re.append("(SEVERE|WARNING|INFO|CONFIG|FINE|FINER|FINEST)");
+                                                       break;
+                                               case 6:
+                                                       fieldIndexes.put("msg", 
index++);
+                                                       re.append("(.*)");
+                                                       break;
+                                               case 7:
+                                                       
fieldIndexes.put("threadid", index++);
+                                                       re.append("(\\\\d+)");
+                                                       break;
+                                               case 8:
+                                                       
fieldIndexes.put("exception", index++);
+                                                       re.append("(.*)");
+                                                       break;
+                                               default: // Fall through.
+                                       }
+                               } else {
+                                       
re.append("\\%").append(format.substring(i1, i));
+                               }
+                               state = S1;
+                       }
+               }
+
+               // The log parser
+               String sre = re.toString();
+               if (sre.endsWith("\\%n"))
+                       sre = sre.substring(0, sre.length()-3);
+
+               // Replace instances of %n.
+               sre = sre.replaceAll("\\\\%n", "\\\\n");
+
+               rePattern = Pattern.compile(sre);
+               fieldIndexes = mapFrom(fieldIndexes);
+       }
+
+       /**
+        * Returns the regular expression pattern used for matching log entries.
+        *
+        * @return The regular expression pattern used for matching log entries.
+        */
+       public Pattern getLogEntryPattern() {
+               return rePattern;
+       }
+
+       /**
+        * Returns the {@link DateFormat} used for matching dates.
+        *
+        * @return The {@link DateFormat} used for matching dates.
+        */
+       public DateFormat getDateFormat() {
+               return df;
+       }
+
+       /**
+        * Given a matcher that has matched the pattern specified by {@link 
#getLogEntryPattern()}, returns the field value
+        * from the match.
+        *
+        * @param fieldName
+        *      The field name.
+        *      Possible values are:
+        *      <ul>
+        *              <li><js>"date"</js>
+        *              <li><js>"class"</js>
+        *              <li><js>"method"</js>
+        *              <li><js>"logger"</js>
+        *              <li><js>"level"</js>
+        *              <li><js>"msg"</js>
+        *              <li><js>"threadid"</js>
+        *              <li><js>"exception"</js>
+        *      </ul>
+        * @param m The matcher.
+        * @return The field value, or <jk>null</jk> if the specified field 
does not exist.
+        */
+       public String getField(String fieldName, Matcher m) {
+               Integer i = fieldIndexes.get(fieldName);
+               return (i == null ? null : m.group(i));
+       }
+
+       @Override /* Formatter */
+       public String format(LogRecord r) {
+               String msg = formatMessage(r);
+               Throwable t = r.getThrown();
+               String hash = null;
+               int c = 0;
+               if (hashes != null && t != null) {
+                       hash = hashCode(t);
+                       hashes.putIfAbsent(hash, new AtomicInteger(0));
+                       c = hashes.get(hash).incrementAndGet();
+                       if (c == 1) {
+                               msg = '[' + hash + '.' + c + "] " + msg;
+                       } else {
+                               msg = '[' + hash + '.' + c + "] " + msg + ", " 
+ t.getLocalizedMessage();
+                               t = null;
+                       }
+               }
+               String s = String.format(format,
+                       df.format(new Date(r.getMillis())),
+                       r.getSourceClassName(),
+                       r.getSourceMethodName(),
+                       r.getLoggerName(),
+                       r.getLevel(),
+                       msg,
+                       r.getThreadID(),
+                       r.getThrown() == null ? "" : 
r.getThrown().getMessage());
+               if (t != null)
+                       s += String.format("%n%s", 
ThrowableUtils.getStackTrace(r.getThrown()));
+               return s;
+       }
+
+       private static String hashCode(Throwable t) {
+               int i = 0;
+               while (t != null) {
+                       for (StackTraceElement e : t.getStackTrace())
+                               i ^= e.hashCode();
+                       t = t.getCause();
+               }
+               return Integer.toHexString(i);
+       }
+}
diff --git 
a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogParser.java
 
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogParser.java
new file mode 100644
index 000000000..78b36488e
--- /dev/null
+++ 
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogParser.java
@@ -0,0 +1,224 @@
+// 
***************************************************************************************************************************
+// * 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.                                              *
+// 
***************************************************************************************************************************
+package org.apache.juneau.microservice.resources;
+
+import java.io.*;
+import java.nio.charset.*;
+import java.text.*;
+import java.util.*;
+import java.util.regex.*;
+
+/**
+ * Utility class for reading log files.
+ *
+ * <p>
+ * Provides the capability of returning splices of log files based on dates 
and filtering based on thread and logger
+ * names.
+ */
+public final class LogParser implements Iterable<LogParser.Entry>, 
Iterator<LogParser.Entry>, Closeable {
+       private BufferedReader br;
+       LogEntryFormatter formatter;
+       Date start, end;
+       Set<String> loggerFilter, severityFilter;
+       String threadFilter;
+       private Entry next;
+
+       /**
+        * Constructor.
+        *
+        * @param formatter The log entry formatter.
+        * @param f The log file.
+        * @param start Don't return rows before this date.  If <jk>null</jk>, 
start from the beginning of the file.
+        * @param end Don't return rows after this date.  If <jk>null</jk>, go 
to the end of the file.
+        * @param thread Only return log entries with this thread name.
+        * @param loggers Only return log entries produced by these loggers 
(simple class names).
+        * @param severity Only return log entries with the specified severity.
+        * @throws IOException Thrown by underlying stream.
+        */
+       public LogParser(LogEntryFormatter formatter, File f, Date start, Date 
end, String thread, String[] loggers, String[] severity) throws IOException {
+               br = new BufferedReader(new InputStreamReader(new 
FileInputStream(f), Charset.defaultCharset()));
+               this.formatter = formatter;
+               this.start = start;
+               this.end = end;
+               this.threadFilter = thread;
+               if (loggers != null)
+                       this.loggerFilter = new 
LinkedHashSet<>(Arrays.asList(loggers));
+               if (severity != null)
+                       this.severityFilter = new 
LinkedHashSet<>(Arrays.asList(severity));
+
+               // Find the first line.
+               String line;
+               while (next == null && (line = br.readLine()) != null) {
+                       Entry e = new Entry(line);
+                       if (e.matches())
+                               next = e;
+               }
+       }
+
+       @Override /* Iterator */
+       public boolean hasNext() {
+               return next != null;
+       }
+
+       @Override /* Iterator */
+       public Entry next() {
+               Entry current = next;
+               Entry prev = next;
+               try {
+                       next = null;
+                       String line = null;
+                       while (next == null && (line = br.readLine()) != null) {
+                               Entry e = new Entry(line);
+                               if (e.isRecord) {
+                                       if (e.matches())
+                                               next = e;
+                                       prev = null;
+                               } else {
+                                       if (prev != null)
+                                               prev.addText(e.line);
+                               }
+                       }
+               } catch (IOException e) {
+                       throw new RuntimeException(e);
+               }
+               return current;
+       }
+
+       @Override /* Iterator */
+       public void remove() {
+               throw new NoSuchMethodError();
+       }
+
+       @Override /* Iterable */
+       public Iterator<Entry> iterator() {
+               return this;
+       }
+
+       @Override /* Closeable */
+       public void close() throws IOException {
+               br.close();
+       }
+
+       /**
+        * Serializes the contents of the parsed log file to the specified 
writer and then closes the underlying reader.
+        *
+        * @param w The writer to write the log file to.
+        * @throws IOException Thrown by underlying stream.
+        */
+       public void writeTo(Writer w) throws IOException {
+               try {
+                       if (! hasNext())
+                               w.append("[EMPTY]");
+                       else for (LogParser.Entry le : this)
+                               le.append(w);
+               } finally {
+                       close();
+               }
+       }
+
+       /**
+        * Represents a single line from the log file.
+        */
+       @SuppressWarnings("javadoc")
+       public final class Entry {
+               public Date date;
+               public String severity, logger;
+               protected String line, text;
+               protected String thread;
+               protected List<String> additionalText;
+               protected boolean isRecord;
+
+               Entry(String line) throws IOException {
+                       try {
+                               this.line = line;
+                               Matcher m = 
formatter.getLogEntryPattern().matcher(line);
+                               if (m.matches()) {
+                                       isRecord = true;
+                                       String s = formatter.getField("date", 
m);
+                                       if (s != null)
+                                               date = 
formatter.getDateFormat().parse(s);
+                                       thread = formatter.getField("thread", 
m);
+                                       severity = formatter.getField("level", 
m);
+                                       logger = formatter.getField("logger", 
m);
+                                       text = formatter.getField("msg", m);
+                                       if (logger != null && 
logger.indexOf('.') > -1)
+                                               logger = 
logger.substring(logger.lastIndexOf('.')+1);
+                               }
+                       } catch (ParseException e) {
+                               throw new IOException(e);
+                       }
+               }
+
+               void addText(String t) {
+                       if (additionalText == null)
+                               additionalText = new LinkedList<>();
+                       additionalText.add(t);
+               }
+
+               public String getText() {
+                       if (additionalText == null)
+                               return text;
+                       int i = text.length();
+                       for (String s : additionalText)
+                               i += s.length() + 1;
+                       StringBuilder sb = new StringBuilder(i);
+                       sb.append(text);
+                       for (String s : additionalText)
+                               sb.append('\n').append(s);
+                       return sb.toString();
+               }
+
+               public String getThread() {
+                       return thread;
+               }
+
+               public Writer appendHtml(Writer w) throws IOException {
+                       w.append(toHtml(line)).append("<br>");
+                       if (additionalText != null)
+                               for (String t : additionalText)
+                                       w.append(toHtml(t)).append("<br>");
+                       return w;
+               }
+
+               protected Writer append(Writer w) throws IOException {
+                       w.append(line).append('\n');
+                       if (additionalText != null)
+                               for (String t : additionalText)
+                                       w.append(t).append('\n');
+                       return w;
+               }
+
+               boolean matches() {
+                       if (! isRecord)
+                               return false;
+                       if (start != null && date.before(start))
+                               return false;
+                       if (end != null && date.after(end))
+                               return false;
+                       if (threadFilter != null && ! 
threadFilter.equals(thread))
+                               return false;
+                       if (loggerFilter != null && ! 
loggerFilter.contains(logger))
+                               return false;
+                       if (severityFilter != null && ! 
severityFilter.contains(severity))
+                               return false;
+                       return true;
+               }
+       }
+
+       static final String toHtml(String s) {
+               if (s.indexOf('<') != -1)
+                       return s.replaceAll("<", "&lt;");//$NON-NLS-2$
+               return s;
+       }
+}
+
diff --git 
a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogsResource.java
 
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogsResource.java
new file mode 100644
index 000000000..07bd4f8bc
--- /dev/null
+++ 
b/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogsResource.java
@@ -0,0 +1,338 @@
+// 
***************************************************************************************************************************
+// * 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.                                              *
+// 
***************************************************************************************************************************
+package org.apache.juneau.microservice.resources;
+
+import static org.apache.juneau.rest.annotation.HookEvent.*;
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.io.*;
+import java.nio.charset.*;
+import java.util.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.config.*;
+import org.apache.juneau.dto.*;
+import org.apache.juneau.html.annotation.*;
+import org.apache.juneau.http.annotation.Path;
+import org.apache.juneau.http.annotation.Query;
+import org.apache.juneau.http.annotation.Response;
+import org.apache.juneau.http.response.*;
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.beans.*;
+import org.apache.juneau.rest.config.*;
+import org.apache.juneau.rest.converter.*;
+import org.apache.juneau.rest.servlet.*;
+
+/**
+ * REST resource for viewing and accessing log files.
+ */
+@Rest(
+       path="/logs",
+       title="Log files",
+       description="Log files from this service",
+       allowedMethodParams="*"
+)
+@HtmlConfig(uriAnchorText="PROPERTY_NAME")
+@SuppressWarnings("javadoc")
+public class LogsResource extends BasicRestServlet implements 
BasicUniversalConfig {
+       private static final long serialVersionUID = 1L;
+
+       
//-------------------------------------------------------------------------------------------------------------------
+       // Instance
+       
//-------------------------------------------------------------------------------------------------------------------
+
+       private File logDir;
+       private LogEntryFormatter leFormatter;
+       boolean allowDeletes;
+
+       @RestHook(INIT)
+       public void init(Config config) throws Exception {
+               logDir = new 
File(config.get("Logging/logDir").asString().orElse("logs"));
+               allowDeletes = 
config.get("Logging/allowDeletes").asBoolean().orElse(true);
+               leFormatter = new LogEntryFormatter(
+                       config.get("Logging/format").asString().orElse("[{date} 
{level}] {msg}%n"),
+                       
config.get("Logging/dateFormat").asString().orElse("yyyy.MM.dd hh:mm:ss"),
+                       
config.get("Logging/useStackTraceHashes").asBoolean().orElse(true)
+               );
+       }
+
+       @RestGet(
+               path="/*",
+               summary="View information on file or directory",
+               description="Returns information about the specified file or 
directory."
+       )
+       @HtmlDocConfig(
+               nav={"<h5>Folder:  $RA{fullPath}</h5>"}
+       )
+       public FileResource getFile(RestRequest req, @Path("/*") String path) 
throws NotFound, Exception {
+
+               File dir = getFile(path);
+               req.setAttribute("fullPath", dir.getAbsolutePath());
+
+               return new FileResource(dir, path, allowDeletes, true);
+       }
+
+       @RestOp(
+               method="VIEW",
+               path="/*",
+               summary="View contents of log file",
+               description="View the contents of a log file."
+       )
+       public void viewFile(
+                       RestResponse res,
+                       @Path("/*") String path,
+                       @Query(name="highlight", schema=@Schema(d="Add severity 
color highlighting.")) boolean highlight,
+                       @Query(name="start", schema=@Schema(d="Start timestamp 
(ISO8601, full or partial).\nDon't print lines logged before the specified 
timestamp.\nUse any of the following formats: yyyy, yyyy-MM, yyyy-MM-dd, 
yyyy-MM-ddThh, yyyy-MM-ddThh:mm, yyyy-MM-ddThh:mm:ss, 
yyyy-MM-ddThh:mm:ss.SSS")) String start,
+                       @Query(name="end", schema=@Schema(d="End timestamp 
(ISO8601, full or partial).\nDon't print lines logged after the specified 
timestamp.\nUse any of the following formats: yyyy, yyyy-MM, yyyy-MM-dd, 
yyyy-MM-ddThh, yyyy-MM-ddThh:mm, yyyy-MM-ddThh:mm:ss, 
yyyy-MM-ddThh:mm:ss.SSS")) String end,
+                       @Query(name="thread", schema=@Schema(d="Thread name 
filter.\nOnly show log entries with the specified thread name.")) String thread,
+                       @Query(name="loggers", schema=@Schema(d="Logger filter 
(simple class name).\nOnly show log entries if they were produced by one of the 
specified loggers.")) String[] loggers,
+                       @Query(name="severity",schema=@Schema( d="Severity 
filter.\nOnly show log entries with the specified severity.")) String[] severity
+               ) throws NotFound, MethodNotAllowed, IOException {
+
+               File f = getFile(path);
+
+               Date startDate = parseIsoDate(start), endDate = 
parseIsoDate(end);
+
+               if (! highlight) {
+                       Object o = getReader(f, startDate, endDate, thread, 
loggers, severity);
+                       res.setContentType("text/plain");
+                       if (o instanceof Reader)
+                               res.setContent(o);
+                       else {
+                               try (LogParser p = (LogParser)o; Writer w = 
res.getNegotiatedWriter()) {
+                                       p.writeTo(w);
+                               }
+                       }
+                       return;
+               }
+
+               res.setContentType("text/html");
+               try (PrintWriter w = res.getNegotiatedWriter()) {
+                       w.println("<html><body 
style='font-family:monospace;font-size:8pt;white-space:pre;'>");
+                       try (LogParser lp = getLogParser(f, startDate, endDate, 
thread, loggers, severity)) {
+                               if (! lp.hasNext())
+                                       w.append("<span 
style='color:gray'>[EMPTY]</span>");
+                               else for (LogParser.Entry le : lp) {
+                                       char s = le.severity.charAt(0);
+                                       String color = "black";
+                                       
//SEVERE|WARNING|INFO|CONFIG|FINE|FINER|FINEST
+                                       if (s == 'I')
+                                               color = "#006400";
+                                       else if (s == 'W')
+                                               color = "#CC8400";
+                                       else if (s == 'E' || s == 'S')
+                                               color = "#DD0000";
+                                       else if (s == 'D' || s == 'F' || s == 
'T')
+                                               color = "#000064";
+                                       w.append("<span 
style='color:").append(color).append("'>");
+                                       le.appendHtml(w).append("</span>");
+                               }
+                               w.append("</body></html>");
+                       }
+               }
+       }
+
+       @RestOp(
+               method="PARSE",
+               path="/*",
+               converters=Queryable.class,
+               summary="View parsed contents of file",
+               description="View the parsed contents of a file.",
+               swagger=@OpSwagger(
+                       parameters={
+                                Queryable.SWAGGER_PARAMS
+                       }
+               )
+       )
+       @HtmlDocConfig(
+               nav={"<h5>Folder:  $RA{fullPath}</h5>"}
+       )
+       public LogParser viewParsedEntries(
+                       RestRequest req,
+                       @Path("/*") String path,
+                       @Query(name="start", schema=@Schema(d="Start timestamp 
(ISO8601, full or partial).\nDon't print lines logged before the specified 
timestamp.\nUse any of the following formats: yyyy, yyyy-MM, yyyy-MM-dd, 
yyyy-MM-ddThh, yyyy-MM-ddThh:mm, yyyy-MM-ddThh:mm:ss, 
yyyy-MM-ddThh:mm:ss.SSS")) String start,
+                       @Query(name="end", schema=@Schema(d="End timestamp 
(ISO8601, full or partial).\nDon't print lines logged after the specified 
timestamp.\nUse any of the following formats: yyyy, yyyy-MM, yyyy-MM-dd, 
yyyy-MM-ddThh, yyyy-MM-ddThh:mm, yyyy-MM-ddThh:mm:ss, 
yyyy-MM-ddThh:mm:ss.SSS")) String end,
+                       @Query(name="thread", schema=@Schema(d="Thread name 
filter.\nOnly show log entries with the specified thread name.")) String thread,
+                       @Query(name="loggers", schema=@Schema(d="Logger filter 
(simple class name).\nOnly show log entries if they were produced by one of the 
specified loggers.")) String[] loggers,
+                       @Query(name="severity", schema=@Schema(d="Severity 
filter.\nOnly show log entries with the specified severity.")) String[] severity
+               ) throws NotFound, IOException {
+
+               File f = getFile(path);
+               req.setAttribute("fullPath", f.getAbsolutePath());
+
+               Date startDate = parseIsoDate(start), endDate = 
parseIsoDate(end);
+
+               return getLogParser(f, startDate, endDate, thread, loggers, 
severity);
+       }
+
+       @RestOp(
+               method="DOWNLOAD",
+               path="/*",
+               summary="Download file",
+               description="Download the contents of a file.\nContent-Type is 
set to 'application/octet-stream'."
+       )
+       public FileContents downloadFile(RestResponse res, @Path("/*") String 
path) throws NotFound, MethodNotAllowed {
+               res.setContentType("application/octet-stream");
+               try {
+                       return new FileContents(getFile(path));
+               } catch (FileNotFoundException e) {
+                       throw new NotFound("File not found");
+               }
+       }
+
+       @RestDelete(
+               path="/*",
+               summary="Delete log file",
+               description="Delete a log file on the file system."
+       )
+       public RedirectToRoot deleteFile(@Path("/*") String path) throws 
MethodNotAllowed {
+               deleteFile(getFile(path));
+               return new RedirectToRoot();
+       }
+
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Helper beans
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       
@Response(schema=@Schema(type="string",format="binary",description="Contents of 
file"))
+       static class FileContents extends FileInputStream {
+               public FileContents(File file) throws FileNotFoundException {
+                       super(file);
+               }
+       }
+
+       @Response(schema=@Schema(description="Redirect to root page on 
success"))
+       static class RedirectToRoot extends SeeOtherRoot {}
+
+       @Response(schema=@Schema(description="File action"))
+       public static class Action extends LinkString {
+               public Action(String name, String uri, Object...uriArgs) {
+                       super(name, uri, uriArgs);
+               }
+       }
+
+       @Response(schema=@Schema(description="File or directory details"))
+       @Bean(properties="type,name,size,lastModified,actions,files")
+       public static class FileResource {
+               private final File f;
+               private final String path;
+               private final String uri;
+               private final boolean includeChildren, allowDeletes;
+
+               public FileResource(File f, String path, boolean allowDeletes, 
boolean includeChildren) {
+                       this.f = f;
+                       this.path = path;
+                       this.uri = "servlet:/"+(path == null ? "" : path);
+                       this.includeChildren = includeChildren;
+                       this.allowDeletes = allowDeletes;
+               }
+
+               public String getType() {
+                       return (f.isDirectory() ? "dir" : "file");
+               }
+
+               public LinkString getName() {
+                       return new LinkString(f.getName(), uri);
+               }
+
+               public long getSize() {
+                       return f.isDirectory() ? f.listFiles().length : 
f.length();
+               }
+
+               public Date getLastModified() {
+                       return new Date(f.lastModified());
+               }
+
+               @Html(format=HtmlFormat.HTML_CDC)
+               public List<Action> getActions() throws Exception {
+                       List<Action> l = new ArrayList<>();
+                       if (f.canRead() && ! f.isDirectory()) {
+                               l.add(new Action("view", uri + "?method=VIEW"));
+                               l.add(new Action("highlighted", uri + 
"?method=VIEW&highlight=true"));
+                               l.add(new Action("parsed", uri + 
"?method=PARSE"));
+                               l.add(new Action("download", uri + 
"?method=DOWNLOAD"));
+                               if (allowDeletes)
+                                       l.add(new Action("delete", uri + 
"?method=DELETE"));
+                       }
+                       return l;
+               }
+
+               public Set<FileResource> getFiles() {
+                       if (f.isFile() || ! includeChildren)
+                               return null;
+                       Set<FileResource> s = new TreeSet<>(FILE_COMPARATOR);
+                       for (File fc : f.listFiles(FILE_FILTER))
+                               s.add(new FileResource(fc, (path != null ? 
(path + '/') : "") + urlEncode(fc.getName()), allowDeletes, false));
+                       return s;
+               }
+
+               static final FileFilter FILE_FILTER = new FileFilter() {
+                       @Override /* FileFilter */
+                       public boolean accept(File f) {
+                               return f.isDirectory() || 
f.getName().endsWith(".log");
+                       }
+               };
+
+               static final Comparator<FileResource> FILE_COMPARATOR = new 
Comparator<FileResource>() {
+                       @Override /* Comparator */
+                       public int compare(FileResource o1, FileResource o2) {
+                               int c = o1.getType().compareTo(o2.getType());
+                               return c != 0 ? c : 
o1.getName().compareTo(o2.getName());
+                       }
+               };
+       }
+
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Helper methods
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       private File getFile(String path) throws NotFound {
+               if (path == null)
+                       return logDir;
+               File f = new File(logDir.getAbsolutePath() + '/' + path);
+               if (f.exists())
+                       return f;
+               throw new NotFound("File not found.");
+       }
+
+       private void deleteFile(File f) {
+               if (! allowDeletes)
+                       throw new MethodNotAllowed("DELETE not enabled");
+               if (f.isDirectory()) {
+                       File[] files = f.listFiles();
+                       if (files != null) {
+                               for (File fc : files)
+                                       deleteFile(fc);
+                       }
+               }
+               if (! f.delete())
+                       throw new Forbidden("Could not delete file {0}", 
f.getAbsolutePath()) ;
+       }
+
+       private static BufferedReader getReader(File f) throws IOException {
+               return new BufferedReader(new InputStreamReader(new 
FileInputStream(f), Charset.defaultCharset()));
+       }
+
+       private Object getReader(File f, final Date start, final Date end, 
final String thread, final String[] loggers, final String[] severity) throws 
IOException {
+               if (start == null && end == null && thread == null && loggers 
== null)
+                       return getReader(f);
+               return getLogParser(f, start, end, thread, loggers, severity);
+       }
+
+       private LogParser getLogParser(File f, final Date start, final Date 
end, final String thread, final String[] loggers, final String[] severity) 
throws IOException {
+               return new LogParser(leFormatter, f, start, end, thread, 
loggers, severity);
+       }
+}

Reply via email to