You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by dw...@apache.org on 2021/12/16 10:18:40 UTC

[lucene] branch main updated: LUCENE-10313: drop log4j from luke (#544)

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

dweiss pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/lucene.git


The following commit(s) were added to refs/heads/main by this push:
     new 36638dc  LUCENE-10313: drop log4j from luke (#544)
36638dc is described below

commit 36638dcb1e2510d3272c1d2526cf8c0a9dde18e3
Author: Dawid Weiss <da...@carrotsearch.com>
AuthorDate: Thu Dec 16 11:18:34 2021 +0100

    LUCENE-10313: drop log4j from luke (#544)
---
 lucene/CHANGES.txt                                 |   4 +
 .../distribution/src/binary-release/bin/luke.cmd   |   2 +-
 lucene/distribution/src/binary-release/bin/luke.sh |   2 +-
 lucene/licenses/log4j-api-2.15.0.jar.sha1          |   1 -
 lucene/licenses/log4j-api-LICENSE-ASL.txt          | 201 ---------------------
 lucene/licenses/log4j-api-NOTICE.txt               |  17 --
 lucene/licenses/log4j-core-2.15.0.jar.sha1         |   1 -
 lucene/licenses/log4j-core-LICENSE-ASL.txt         | 201 ---------------------
 lucene/licenses/log4j-core-NOTICE.txt              |  17 --
 lucene/luke/build.gradle                           |   2 -
 lucene/luke/src/distribution/README.md             |   2 +-
 .../apache/lucene/luke/app/AbstractHandler.java    |   7 +-
 .../org/apache/lucene/luke/app/IndexHandler.java   |   9 +-
 .../apache/lucene/luke/app/desktop/LukeMain.java   |  15 +-
 .../app/desktop/components/LogsPanelProvider.java  | 131 ++++++++++++--
 .../app/desktop/components/LukeWindowProvider.java |  25 +--
 .../app/desktop/components/TabbedPaneProvider.java |   9 +-
 .../dialog/documents/AddDocumentDialogFactory.java |   9 +-
 .../dialog/menubar/CheckIndexDialogFactory.java    |   5 +-
 .../dialog/menubar/CreateIndexDialogFactory.java   |   5 +-
 .../dialog/menubar/ExportTermsDialogFactory.java   |   7 +-
 .../dialog/menubar/OpenIndexDialogFactory.java     |   5 +-
 .../luke/app/desktop/util/ExceptionHandler.java    |   7 +-
 .../luke/app/desktop/util/TextAreaAppender.java    | 108 -----------
 .../lucene/luke/models/commits/CommitsImpl.java    |   6 +-
 .../luke/models/documents/DocumentsImpl.java       |  44 ++---
 .../luke/models/documents/TermVectorsAdapter.java  |   8 +-
 .../lucene/luke/models/search/SearchImpl.java      |  10 +-
 .../apache/lucene/luke/models/util/IndexUtils.java |  33 ++--
 .../util/twentynewsgroups/MessageFilesParser.java  |   9 +-
 .../lucene/luke/util/CircularLogBufferHandler.java | 114 ++++++++++++
 .../org/apache/lucene/luke/util/LoggerFactory.java |  56 ++----
 .../luke/app/desktop/messages/messages.properties  |   2 +-
 versions.lock                                      |   2 -
 versions.props                                     |   1 -
 35 files changed, 349 insertions(+), 728 deletions(-)

diff --git a/lucene/CHANGES.txt b/lucene/CHANGES.txt
index c1ce6ca..9b98105 100644
--- a/lucene/CHANGES.txt
+++ b/lucene/CHANGES.txt
@@ -68,6 +68,10 @@ New Features
 Improvements
 ---------------------
 
+* LUCENE-10313: use java util logging in Luke. Add dynamic log filtering. Drop
+  the persistent log previously written to ~/.luke.d/luke.log. Configure Java's default
+  logging handlers to persist Luke logs according to your needs. (Tomoko Uchida, Dawid Weiss)
+
 * LUCENE-10238: Upgrade icu4j dependency to 70.1. (Dawid Weiss)
 
 * LUCENE-9820: Extract BKD tree interface and move intersecting logic to the 
diff --git a/lucene/distribution/src/binary-release/bin/luke.cmd b/lucene/distribution/src/binary-release/bin/luke.cmd
index d5a3ce1..b4591ae 100644
--- a/lucene/distribution/src/binary-release/bin/luke.cmd
+++ b/lucene/distribution/src/binary-release/bin/luke.cmd
@@ -17,5 +17,5 @@
 
 SETLOCAL
 SET MODULES=%~dp0..
-start javaw --module-path "%MODULES%\modules;%MODULES%\modules-thirdparty" --add-modules jdk.unsupported,org.apache.logging.log4j --module org.apache.lucene.luke
+start javaw --module-path "%MODULES%\modules;%MODULES%\modules-thirdparty" --add-modules jdk.unsupported --module org.apache.lucene.luke
 ENDLOCAL
diff --git a/lucene/distribution/src/binary-release/bin/luke.sh b/lucene/distribution/src/binary-release/bin/luke.sh
index 0914532..053edda 100644
--- a/lucene/distribution/src/binary-release/bin/luke.sh
+++ b/lucene/distribution/src/binary-release/bin/luke.sh
@@ -17,4 +17,4 @@
 
 MODULES=`dirname "$0"`/..
 MODULES=`cd "$MODULES" && pwd`
-java --module-path "$MODULES/modules:$MODULES/modules-thirdparty" --add-modules jdk.unsupported,org.apache.logging.log4j --module org.apache.lucene.luke
+java --module-path "$MODULES/modules:$MODULES/modules-thirdparty" --add-modules jdk.unsupported --module org.apache.lucene.luke
diff --git a/lucene/licenses/log4j-api-2.15.0.jar.sha1 b/lucene/licenses/log4j-api-2.15.0.jar.sha1
deleted file mode 100644
index a699db8..0000000
--- a/lucene/licenses/log4j-api-2.15.0.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-4a5aa7e55a29391c6f66e0b259d5189aa11e45d0
\ No newline at end of file
diff --git a/lucene/licenses/log4j-api-LICENSE-ASL.txt b/lucene/licenses/log4j-api-LICENSE-ASL.txt
deleted file mode 100644
index f49a4e1..0000000
--- a/lucene/licenses/log4j-api-LICENSE-ASL.txt
+++ /dev/null
@@ -1,201 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
\ No newline at end of file
diff --git a/lucene/licenses/log4j-api-NOTICE.txt b/lucene/licenses/log4j-api-NOTICE.txt
deleted file mode 100644
index ebba5ac..0000000
--- a/lucene/licenses/log4j-api-NOTICE.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-Apache Log4j
-Copyright 1999-2017 Apache Software Foundation
-
-This product includes software developed at
-The Apache Software Foundation (http://www.apache.org/).
-
-ResolverUtil.java
-Copyright 2005-2006 Tim Fennell
-
-Dumbster SMTP test server
-Copyright 2004 Jason Paul Kitchen
-
-TypeUtil.java
-Copyright 2002-2012 Ramnivas Laddad, Juergen Hoeller, Chris Beams
-
-picocli (http://picocli.info)
-Copyright 2017 Remko Popma
\ No newline at end of file
diff --git a/lucene/licenses/log4j-core-2.15.0.jar.sha1 b/lucene/licenses/log4j-core-2.15.0.jar.sha1
deleted file mode 100644
index b0ab8d9..0000000
--- a/lucene/licenses/log4j-core-2.15.0.jar.sha1
+++ /dev/null
@@ -1 +0,0 @@
-ba55c13d7ac2fd44df9cc8074455719a33f375b9
\ No newline at end of file
diff --git a/lucene/licenses/log4j-core-LICENSE-ASL.txt b/lucene/licenses/log4j-core-LICENSE-ASL.txt
deleted file mode 100644
index f49a4e1..0000000
--- a/lucene/licenses/log4j-core-LICENSE-ASL.txt
+++ /dev/null
@@ -1,201 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
\ No newline at end of file
diff --git a/lucene/licenses/log4j-core-NOTICE.txt b/lucene/licenses/log4j-core-NOTICE.txt
deleted file mode 100644
index ebba5ac..0000000
--- a/lucene/licenses/log4j-core-NOTICE.txt
+++ /dev/null
@@ -1,17 +0,0 @@
-Apache Log4j
-Copyright 1999-2017 Apache Software Foundation
-
-This product includes software developed at
-The Apache Software Foundation (http://www.apache.org/).
-
-ResolverUtil.java
-Copyright 2005-2006 Tim Fennell
-
-Dumbster SMTP test server
-Copyright 2004 Jason Paul Kitchen
-
-TypeUtil.java
-Copyright 2002-2012 Ramnivas Laddad, Juergen Hoeller, Chris Beams
-
-picocli (http://picocli.info)
-Copyright 2017 Remko Popma
\ No newline at end of file
diff --git a/lucene/luke/build.gradle b/lucene/luke/build.gradle
index 23c843a..fe556ca 100644
--- a/lucene/luke/build.gradle
+++ b/lucene/luke/build.gradle
@@ -27,8 +27,6 @@ ext {
 dependencies {
   api project(':lucene:core')
 
-  implementation 'org.apache.logging.log4j:log4j-core'
-
   implementation project(':lucene:codecs')
   implementation project(':lucene:backward-codecs')
   implementation project(':lucene:analysis:common')
diff --git a/lucene/luke/src/distribution/README.md b/lucene/luke/src/distribution/README.md
index 0548fd1..4ff8bc7 100644
--- a/lucene/luke/src/distribution/README.md
+++ b/lucene/luke/src/distribution/README.md
@@ -27,7 +27,7 @@ java -jar ${luke.cmd}
 or, using Java modules:
 
 ```
-java --module-path . --add-modules jdk.unsupported,org.apache.logging.log4j --module org.apache.lucene.luke
+java --module-path . --add-modules jdk.unsupported --module org.apache.lucene.luke
 ```
 
 Happy index hacking!
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/AbstractHandler.java b/lucene/luke/src/java/org/apache/lucene/luke/app/AbstractHandler.java
index 5abd9d9..ad77385a 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/app/AbstractHandler.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/AbstractHandler.java
@@ -20,7 +20,8 @@ package org.apache.lucene.luke.app;
 import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.List;
-import org.apache.logging.log4j.Logger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import org.apache.lucene.luke.util.LoggerFactory;
 
 /** Abstract handler class */
@@ -32,8 +33,8 @@ public abstract class AbstractHandler<T extends Observer> {
 
   public void addObserver(T observer) {
     observers.add(observer);
-    if (log.isDebugEnabled()) {
-      log.debug("{} registered.", observer.getClass().getName());
+    if (log.isLoggable(Level.FINE)) {
+      log.fine(observer.getClass().getName() + " registered.");
     }
   }
 
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/IndexHandler.java b/lucene/luke/src/java/org/apache/lucene/luke/app/IndexHandler.java
index 46e2e3d..f35139e 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/app/IndexHandler.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/IndexHandler.java
@@ -21,7 +21,8 @@ import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.nio.file.NoSuchFileException;
 import java.util.Objects;
-import org.apache.logging.log4j.Logger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.luke.app.desktop.PreferencesFactory;
 import org.apache.lucene.luke.app.desktop.util.MessageUtils;
@@ -75,19 +76,19 @@ public final class IndexHandler extends AbstractHandler<IndexObserver> {
     try {
       reader = IndexUtils.openIndex(indexPath, dirImpl);
     } catch (NoSuchFileException e) {
-      log.error("Error opening index", e);
+      log.log(Level.SEVERE, "Error opening index", e);
       try {
         // remove the non-existing index path from history.
         PreferencesFactory.getInstance().removeHistory(indexPath);
       } catch (IOException ioe) {
-        log.error("Preference file is deleted?", ioe);
+        log.log(Level.SEVERE, "Preference file is deleted?", ioe);
       }
       throw new LukeException(
           MessageUtils.getLocalizedMessage(
               "openindex.message.index_path_does_not_exist", indexPath),
           e);
     } catch (Exception e) {
-      log.error("Error opening index", e);
+      log.log(Level.SEVERE, "Error opening index", e);
       throw new LukeException(
           MessageUtils.getLocalizedMessage("openindex.message.index_path_invalid", indexPath), e);
     }
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/LukeMain.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/LukeMain.java
index 78c5254..f64ec18 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/LukeMain.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/LukeMain.java
@@ -22,10 +22,10 @@ import static org.apache.lucene.luke.app.desktop.util.ExceptionHandler.handle;
 import java.awt.GraphicsEnvironment;
 import java.io.IOException;
 import java.lang.invoke.MethodHandles;
-import java.nio.file.FileSystems;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import javax.swing.JFrame;
 import javax.swing.UIManager;
-import org.apache.logging.log4j.Logger;
 import org.apache.lucene.luke.app.desktop.components.LukeWindowProvider;
 import org.apache.lucene.luke.app.desktop.components.dialog.menubar.OpenIndexDialogFactory;
 import org.apache.lucene.luke.app.desktop.util.DialogOpener;
@@ -36,15 +36,8 @@ import org.apache.lucene.luke.util.LoggerFactory;
 /** Entry class for desktop Luke */
 public class LukeMain {
 
-  public static final String LOG_FILE =
-      System.getProperty("user.home")
-          + FileSystems.getDefault().getSeparator()
-          + ".luke.d"
-          + FileSystems.getDefault().getSeparator()
-          + "luke.log";
-
   static {
-    LoggerFactory.initGuiLogging(LOG_FILE);
+    LoggerFactory.initGuiLogging();
   }
 
   private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
@@ -77,7 +70,7 @@ public class LukeMain {
               (factory) -> {});
     } catch (IOException e) {
       messageBroker.showUnknownErrorMessage();
-      log.error("Cannot initialize components.", e);
+      log.log(Level.SEVERE, "Cannot initialize components.", e);
     }
   }
 
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LogsPanelProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LogsPanelProvider.java
index 1f6bcf1..26bf9fb 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LogsPanelProvider.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LogsPanelProvider.java
@@ -17,24 +17,28 @@
 
 package org.apache.lucene.luke.app.desktop.components;
 
-import java.awt.BorderLayout;
-import java.awt.FlowLayout;
-import javax.swing.BorderFactory;
-import javax.swing.JLabel;
-import javax.swing.JPanel;
-import javax.swing.JScrollPane;
-import javax.swing.JTextArea;
-import org.apache.lucene.luke.app.desktop.LukeMain;
+import java.awt.*;
+import java.awt.event.HierarchyEvent;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.logging.Level;
+import java.util.stream.Collectors;
+import javax.swing.*;
 import org.apache.lucene.luke.app.desktop.util.MessageUtils;
+import org.apache.lucene.luke.util.CircularLogBufferHandler;
+import org.apache.lucene.luke.util.LoggerFactory;
 
 /** Provider of the Logs panel */
 public final class LogsPanelProvider {
 
-  private final JTextArea logTextArea;
-
-  public LogsPanelProvider(JTextArea logTextArea) {
-    this.logTextArea = logTextArea;
-  }
+  public LogsPanelProvider() {}
 
   public JPanel get() {
     JPanel panel = new JPanel(new BorderLayout());
@@ -43,14 +47,107 @@ public final class LogsPanelProvider {
 
     JPanel header = new JPanel(new FlowLayout(FlowLayout.LEADING));
     header.setOpaque(false);
-    header.add(new JLabel(MessageUtils.getLocalizedMessage("logs.label.see_also")));
+    header.add(new JLabel(MessageUtils.getLocalizedMessage("logs.label.level")));
 
-    JLabel logPathLabel = new JLabel(LukeMain.LOG_FILE);
-    header.add(logPathLabel);
+    JComboBox<Level> logFilter =
+        new JComboBox<>(
+            new Level[] {
+              Level.FINEST,
+              Level.FINER,
+              Level.FINE,
+              Level.CONFIG,
+              Level.INFO,
+              Level.WARNING,
+              Level.SEVERE,
+              Level.OFF
+            });
+    logFilter.setEditable(false);
+    logFilter.setSelectedItem(Level.INFO);
+    header.add(logFilter);
 
-    panel.add(header, BorderLayout.PAGE_START);
+    var logTextArea = createLogPanel(logFilter);
 
+    panel.add(header, BorderLayout.PAGE_START);
     panel.add(new JScrollPane(logTextArea), BorderLayout.CENTER);
     return panel;
   }
+
+  /** Prepare the component responsible for displaying logs. */
+  private JTextArea createLogPanel(JComboBox<Level> logFilter) {
+    JTextArea logTextArea = new JTextArea();
+    logTextArea.setEditable(false);
+
+    class LogRecordFormatter
+        implements Function<CircularLogBufferHandler.ImmutableLogRecord, String> {
+      @Override
+      public String apply(CircularLogBufferHandler.ImmutableLogRecord record) {
+        return String.format(
+            Locale.ROOT,
+            "%s [%s] %s: %s",
+            DateTimeFormatter.ofPattern("HH:mm:ss", Locale.ROOT)
+                .format(record.getInstant().atZone(ZoneId.systemDefault())),
+            record.getLevel(),
+            record.getLoggerName(),
+            record.getMessage()
+                + (record.getThrown() == null ? "" : "\n" + toString(record.getThrown())));
+      }
+
+      private String toString(Throwable t) {
+        try (StringWriter sw = new StringWriter();
+            PrintWriter pw = new PrintWriter(sw)) {
+          t.printStackTrace(pw);
+          pw.flush();
+          return sw.toString();
+        } catch (IOException e) {
+          return "Could not dump stack trace: " + e.getMessage();
+        }
+      }
+    }
+
+    // Hook into live data from the circular log buffer and update the initial state.
+    Function<CircularLogBufferHandler.ImmutableLogRecord, String> formatter =
+        new LogRecordFormatter();
+    CircularLogBufferHandler.LogUpdateListener updater =
+        records -> {
+          // Create an immutable copy of the logs to display in the gui thread.
+          ArrayList<CircularLogBufferHandler.ImmutableLogRecord> clonedCopy =
+              new ArrayList<>(records);
+          SwingUtilities.invokeLater(
+              () -> {
+                Level level = (Level) Objects.requireNonNull(logFilter.getSelectedItem());
+
+                String logContent =
+                    clonedCopy.stream()
+                        .filter(record -> record.getLevel().intValue() > level.intValue())
+                        .map(formatter::apply)
+                        .collect(Collectors.joining("\n"));
+
+                logTextArea.setText(logContent);
+              });
+        };
+
+    var logBuffer = Objects.requireNonNull(LoggerFactory.circularBuffer);
+
+    // Update state on filter change.
+    logFilter.addActionListener(
+        e -> {
+          updater.accept(logBuffer.getLogRecords());
+        });
+
+    // Subscribe to log events and update state only when actually displayed.
+    logTextArea.addHierarchyListener(
+        (HierarchyEvent e) -> {
+          if (e.getComponent() == logTextArea
+              && (e.getChangeFlags() & HierarchyEvent.DISPLAYABILITY_CHANGED) != 0) {
+            if (logTextArea.isDisplayable()) {
+              logBuffer.addUpdateListener(updater);
+              updater.accept(logBuffer.getLogRecords());
+            } else {
+              logBuffer.removeUpdateListener(updater);
+            }
+          }
+        });
+
+    return logTextArea;
+  }
 }
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowProvider.java
index bbea489..a7a46a8 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowProvider.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/LukeWindowProvider.java
@@ -17,22 +17,9 @@
 
 package org.apache.lucene.luke.app.desktop.components;
 
-import java.awt.BorderLayout;
-import java.awt.Color;
-import java.awt.Dimension;
-import java.awt.FlowLayout;
-import java.awt.GridBagConstraints;
-import java.awt.GridBagLayout;
-import java.awt.GridLayout;
+import java.awt.*;
 import java.io.IOException;
-import javax.swing.BorderFactory;
-import javax.swing.JFrame;
-import javax.swing.JLabel;
-import javax.swing.JMenuBar;
-import javax.swing.JPanel;
-import javax.swing.JTabbedPane;
-import javax.swing.JTextArea;
-import javax.swing.WindowConstants;
+import javax.swing.*;
 import org.apache.lucene.luke.app.DirectoryHandler;
 import org.apache.lucene.luke.app.DirectoryObserver;
 import org.apache.lucene.luke.app.IndexHandler;
@@ -44,7 +31,6 @@ import org.apache.lucene.luke.app.desktop.PreferencesFactory;
 import org.apache.lucene.luke.app.desktop.util.FontUtils;
 import org.apache.lucene.luke.app.desktop.util.ImageUtils;
 import org.apache.lucene.luke.app.desktop.util.MessageUtils;
-import org.apache.lucene.luke.app.desktop.util.TextAreaAppender;
 import org.apache.lucene.util.Version;
 
 /** Provider of the root window */
@@ -74,14 +60,9 @@ public final class LukeWindowProvider implements LukeWindowOperator {
   private JFrame frame = new JFrame();
 
   public LukeWindowProvider() throws IOException {
-    // prepare log4j appender for Logs tab.
-    JTextArea logTextArea = new JTextArea();
-    logTextArea.setEditable(false);
-    TextAreaAppender.setTextArea(logTextArea);
-
     this.prefs = PreferencesFactory.getInstance();
     this.menuBar = new MenuBarProvider().get();
-    this.tabbedPane = new TabbedPaneProvider(logTextArea).get();
+    this.tabbedPane = new TabbedPaneProvider().get();
     this.messageBroker = MessageBroker.getInstance();
     this.tabSwitcher = TabSwitcherProxy.getInstance();
 
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabbedPaneProvider.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabbedPaneProvider.java
index a64d89e..83468ab 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabbedPaneProvider.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/TabbedPaneProvider.java
@@ -20,7 +20,6 @@ package org.apache.lucene.luke.app.desktop.components;
 import java.io.IOException;
 import javax.swing.JPanel;
 import javax.swing.JTabbedPane;
-import javax.swing.JTextArea;
 import org.apache.lucene.luke.app.DirectoryHandler;
 import org.apache.lucene.luke.app.DirectoryObserver;
 import org.apache.lucene.luke.app.IndexHandler;
@@ -35,7 +34,7 @@ public final class TabbedPaneProvider implements TabSwitcherProxy.TabSwitcher {
 
   private final MessageBroker messageBroker;
 
-  private final JTabbedPane tabbedPane = new JTabbedPane();
+  private final JTabbedPane tabbedPane;
 
   private final JPanel overviewPanel;
 
@@ -49,13 +48,15 @@ public final class TabbedPaneProvider implements TabSwitcherProxy.TabSwitcher {
 
   private final JPanel logsPanel;
 
-  public TabbedPaneProvider(JTextArea logTextArea) throws IOException {
+  public TabbedPaneProvider() throws IOException {
+    this.tabbedPane = new JTabbedPane();
+
     this.overviewPanel = new OverviewPanelProvider().get();
     this.documentsPanel = new DocumentsPanelProvider().get();
     this.searchPanel = new SearchPanelProvider().get();
     this.analysisPanel = new AnalysisPanelProvider().get();
     this.commitsPanel = new CommitsPanelProvider().get();
-    this.logsPanel = new LogsPanelProvider(logTextArea).get();
+    this.logsPanel = new LogsPanelProvider().get();
 
     this.messageBroker = MessageBroker.getInstance();
 
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogFactory.java
index 535dcaf..723cd21 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogFactory.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/documents/AddDocumentDialogFactory.java
@@ -33,6 +33,8 @@ import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.lang.reflect.Constructor;
 import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 import javax.swing.BorderFactory;
@@ -51,7 +53,6 @@ import javax.swing.ListSelectionModel;
 import javax.swing.UIManager;
 import javax.swing.table.JTableHeader;
 import javax.swing.table.TableCellRenderer;
-import org.apache.logging.log4j.Logger;
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.apache.lucene.document.Document;
@@ -399,15 +400,15 @@ public final class AddDocumentDialogFactory
           doc.add(toIndexableField(nf));
         }
       } catch (NumberFormatException ex) {
-        log.error("Error converting field value", e);
+        log.log(Level.SEVERE, "Error converting field value", e);
         throw new LukeException("Invalid value: " + ex.getMessage(), ex);
       } catch (Exception ex) {
-        log.error("Error converting field value", e);
+        log.log(Level.SEVERE, "Error converting field value", e);
         throw new LukeException(ex.getMessage(), ex);
       }
 
       addDocument(doc);
-      log.info("Added document: {}", doc);
+      log.info("Added document: " + doc);
     }
 
     @SuppressWarnings("unchecked")
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CheckIndexDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CheckIndexDialogFactory.java
index ba058c5..cd05bed 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CheckIndexDialogFactory.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CheckIndexDialogFactory.java
@@ -29,6 +29,8 @@ import java.io.IOException;
 import java.lang.invoke.MethodHandles;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import javax.swing.BorderFactory;
 import javax.swing.BoxLayout;
 import javax.swing.JButton;
@@ -39,7 +41,6 @@ import javax.swing.JScrollPane;
 import javax.swing.JSeparator;
 import javax.swing.JTextArea;
 import javax.swing.SwingWorker;
-import org.apache.logging.log4j.Logger;
 import org.apache.lucene.index.CheckIndex;
 import org.apache.lucene.luke.app.DirectoryHandler;
 import org.apache.lucene.luke.app.DirectoryObserver;
@@ -304,7 +305,7 @@ public final class CheckIndexDialogFactory implements DialogOpener.DialogFactory
                 }
                 status = st;
               } catch (Exception e) {
-                log.error("Error checking index", e);
+                log.log(Level.SEVERE, "Error checking index", e);
                 statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown"));
               }
             }
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CreateIndexDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CreateIndexDialogFactory.java
index 995162e..223fc4f 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CreateIndexDialogFactory.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/CreateIndexDialogFactory.java
@@ -35,6 +35,8 @@ import java.nio.file.SimpleFileVisitor;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import javax.swing.BorderFactory;
 import javax.swing.BoxLayout;
 import javax.swing.JButton;
@@ -47,7 +49,6 @@ import javax.swing.JSeparator;
 import javax.swing.JTextArea;
 import javax.swing.JTextField;
 import javax.swing.SwingWorker;
-import org.apache.logging.log4j.Logger;
 import org.apache.lucene.luke.app.IndexHandler;
 import org.apache.lucene.luke.app.desktop.Preferences;
 import org.apache.lucene.luke.app.desktop.PreferencesFactory;
@@ -337,7 +338,7 @@ public class CreateIndexDialogFactory implements DialogOpener.DialogFactory {
                       IOException ex2) {
                   }
 
-                  log.error("Cannot create index", ex);
+                  log.log(Level.SEVERE, "Cannot create index", ex);
                   String message = "See Logs tab or log file for more details.";
                   JOptionPane.showMessageDialog(
                       dialog, message, "Cannot create index", JOptionPane.ERROR_MESSAGE);
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/ExportTermsDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/ExportTermsDialogFactory.java
index 87377e3..3bf84a2 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/ExportTermsDialogFactory.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/ExportTermsDialogFactory.java
@@ -31,6 +31,8 @@ import java.lang.invoke.MethodHandles;
 import java.util.Arrays;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import java.util.stream.Stream;
 import javax.swing.BorderFactory;
 import javax.swing.BoxLayout;
@@ -42,7 +44,6 @@ import javax.swing.JLabel;
 import javax.swing.JPanel;
 import javax.swing.JTextField;
 import javax.swing.SwingWorker;
-import org.apache.logging.log4j.Logger;
 import org.apache.lucene.luke.app.IndexHandler;
 import org.apache.lucene.luke.app.IndexObserver;
 import org.apache.lucene.luke.app.LukeState;
@@ -249,11 +250,11 @@ public final class ExportTermsDialogFactory implements DialogOpener.DialogFactor
               try {
                 filename = toolsModel.exportTerms(directory, field, selectedDelimiter);
               } catch (LukeException e) {
-                log.error("Error while exporting terms from field {}", field, e);
+                log.log(Level.SEVERE, "Error while exporting terms from field " + field, e);
                 statusLbl.setText(
                     MessageUtils.getLocalizedMessage("export.terms.label.error", e.getMessage()));
               } catch (Exception e) {
-                log.error("Error while exporting terms from field {}", field, e);
+                log.log(Level.SEVERE, "Error while exporting terms from field " + field, e);
                 statusLbl.setText(MessageUtils.getLocalizedMessage("message.error.unknown"));
                 throw e;
               } finally {
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OpenIndexDialogFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OpenIndexDialogFactory.java
index a523cd4..30e3d0b 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OpenIndexDialogFactory.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/components/dialog/menubar/OpenIndexDialogFactory.java
@@ -32,6 +32,8 @@ import java.nio.file.Paths;
 import java.util.List;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import javax.swing.BorderFactory;
 import javax.swing.BoxLayout;
 import javax.swing.ButtonGroup;
@@ -45,7 +47,6 @@ import javax.swing.JOptionPane;
 import javax.swing.JPanel;
 import javax.swing.JRadioButton;
 import javax.swing.JSeparator;
-import org.apache.logging.log4j.Logger;
 import org.apache.lucene.luke.app.DirectoryHandler;
 import org.apache.lucene.luke.app.IndexHandler;
 import org.apache.lucene.luke.app.desktop.Preferences;
@@ -345,7 +346,7 @@ public final class OpenIndexDialogFactory implements DialogOpener.DialogFactory
             MessageUtils.getLocalizedMessage("message.error.unknown"),
             "Unknown Error",
             JOptionPane.ERROR_MESSAGE);
-        log.error("Error opening index or directory", cause);
+        log.log(Level.SEVERE, "Error opening index or directory", cause);
       }
     }
 
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ExceptionHandler.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ExceptionHandler.java
index 481047c..d02e452 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ExceptionHandler.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/ExceptionHandler.java
@@ -18,7 +18,8 @@
 package org.apache.lucene.luke.app.desktop.util;
 
 import java.lang.invoke.MethodHandles;
-import org.apache.logging.log4j.Logger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import org.apache.lucene.luke.app.desktop.MessageBroker;
 import org.apache.lucene.luke.models.LukeException;
 import org.apache.lucene.luke.util.LoggerFactory;
@@ -32,10 +33,10 @@ public final class ExceptionHandler {
     if (t instanceof LukeException) {
       Throwable cause = t.getCause();
       String message = (cause == null) ? t.getMessage() : t.getMessage() + " " + cause.getMessage();
-      log.warn("Uncaught LukeException", t);
+      log.log(Level.WARNING, "Uncaught LukeException", t);
       messageBroker.showStatusMessage(message);
     } else {
-      log.error("Uncaught Exception", t);
+      log.log(Level.SEVERE, "Uncaught Exception", t);
       messageBroker.showUnknownErrorMessage();
     }
   }
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TextAreaAppender.java b/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TextAreaAppender.java
deleted file mode 100644
index 3d6964a..0000000
--- a/lucene/luke/src/java/org/apache/lucene/luke/app/desktop/util/TextAreaAppender.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * 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.lucene.luke.app.desktop.util;
-
-import java.io.Serializable;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import javax.swing.JTextArea;
-import javax.swing.SwingUtilities;
-import org.apache.logging.log4j.core.Appender;
-import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.Filter;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.StringLayout;
-import org.apache.logging.log4j.core.appender.AbstractAppender;
-import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender;
-import org.apache.logging.log4j.core.config.Property;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-
-/** Log appender for text areas */
-@Plugin(
-    name = "TextArea",
-    category = Core.CATEGORY_NAME,
-    elementType = Appender.ELEMENT_TYPE,
-    printObject = true)
-public final class TextAreaAppender extends AbstractAppender {
-
-  private static JTextArea textArea;
-
-  private static final ReadWriteLock rwLock = new ReentrantReadWriteLock();
-  private static final Lock readLock = rwLock.readLock();
-  private static final Lock writeLock = rwLock.writeLock();
-
-  protected TextAreaAppender(
-      String name,
-      Filter filter,
-      org.apache.logging.log4j.core.Layout<? extends Serializable> layout,
-      final boolean ignoreExceptions) {
-    super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
-  }
-
-  public static void setTextArea(JTextArea ta) {
-    writeLock.lock();
-    try {
-      if (textArea != null) {
-        throw new IllegalStateException("TextArea already set.");
-      }
-      textArea = ta;
-    } finally {
-      writeLock.unlock();
-    }
-  }
-
-  @Override
-  public void append(LogEvent event) {
-    readLock.lock();
-    try {
-      if (textArea == null) {
-        // just ignore any events logged before the area is available
-        return;
-      }
-
-      final String message = ((StringLayout) getLayout()).toSerializable(event);
-      SwingUtilities.invokeLater(
-          () -> {
-            textArea.append(message);
-          });
-    } finally {
-      readLock.unlock();
-    }
-  }
-
-  /**
-   * Builds TextAreaAppender instances.
-   *
-   * @param <B> The type to build
-   */
-  public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
-      implements org.apache.logging.log4j.core.util.Builder<TextAreaAppender> {
-
-    @Override
-    public TextAreaAppender build() {
-      return new TextAreaAppender(getName(), getFilter(), getOrCreateLayout(), true);
-    }
-  }
-
-  @PluginBuilderFactory
-  public static <B extends Builder<B>> B newBuilder() {
-    return new Builder<B>().asBuilder();
-  }
-}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/commits/CommitsImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/CommitsImpl.java
index 59b50a2..a103eee 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/models/commits/CommitsImpl.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/commits/CommitsImpl.java
@@ -26,8 +26,8 @@ import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.TreeMap;
+import java.util.logging.Logger;
 import java.util.stream.Collectors;
-import org.apache.logging.log4j.Logger;
 import org.apache.lucene.codecs.Codec;
 import org.apache.lucene.index.DirectoryReader;
 import org.apache.lucene.index.IndexCommit;
@@ -98,7 +98,7 @@ public final class CommitsImpl extends LukeModel implements Commits {
 
     if (ic == null) {
       String msg = String.format(Locale.ENGLISH, "Commit generation %d not exists.", commitGen);
-      log.warn(msg);
+      log.warning(msg);
       return Optional.empty();
     }
 
@@ -111,7 +111,7 @@ public final class CommitsImpl extends LukeModel implements Commits {
 
     if (ic == null) {
       String msg = String.format(Locale.ENGLISH, "Commit generation %d not exists.", commitGen);
-      log.warn(msg);
+      log.warning(msg);
       return Collections.emptyList();
     }
 
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentsImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentsImpl.java
index 84daec9..9e36d4f 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentsImpl.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/DocumentsImpl.java
@@ -25,7 +25,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Objects;
 import java.util.Optional;
-import org.apache.logging.log4j.Logger;
+import java.util.logging.Logger;
 import org.apache.lucene.document.Document;
 import org.apache.lucene.index.FieldInfo;
 import org.apache.lucene.index.IndexReader;
@@ -80,7 +80,7 @@ public final class DocumentsImpl extends LukeModel implements Documents {
   @Override
   public List<DocumentField> getDocumentFields(int docid) {
     if (!isLive(docid)) {
-      log.info("Doc #{} was deleted", docid);
+      log.info("Doc #" + docid + " was deleted");
       return Collections.emptyList();
     }
 
@@ -126,7 +126,7 @@ public final class DocumentsImpl extends LukeModel implements Documents {
         // no such field?
         resetCurrentField();
         resetTermsIterator();
-        log.warn("Terms not available for field: {}.", field);
+        log.warning("Terms not available for field: " + field);
         return Optional.empty();
       } else {
         setCurrentField(field);
@@ -135,7 +135,7 @@ public final class DocumentsImpl extends LukeModel implements Documents {
         if (tenum.next() == null) {
           // no term available for this field
           resetTermsIterator();
-          log.warn("No term available for field: {}.", field);
+          log.warning("No term available for field: " + field);
           return Optional.empty();
         } else {
           return Optional.of(new Term(curField, tenum.term()));
@@ -156,7 +156,7 @@ public final class DocumentsImpl extends LukeModel implements Documents {
   public Optional<Term> nextTerm() {
     if (tenum == null) {
       // terms enum not initialized
-      log.warn("Terms enum un-positioned.");
+      log.warning("Terms enum un-positioned.");
       return Optional.empty();
     }
 
@@ -164,7 +164,7 @@ public final class DocumentsImpl extends LukeModel implements Documents {
       if (tenum.next() == null) {
         // end of the iterator
         resetTermsIterator();
-        log.info("Reached the end of the term iterator for field: {}.", curField);
+        log.info("Reached the end of the term iterator for field: " + curField);
         return Optional.empty();
 
       } else {
@@ -186,7 +186,7 @@ public final class DocumentsImpl extends LukeModel implements Documents {
 
     if (curField == null) {
       // field is not selected
-      log.warn("Field not selected.");
+      log.warning("Field not selected.");
       return Optional.empty();
     }
 
@@ -197,7 +197,7 @@ public final class DocumentsImpl extends LukeModel implements Documents {
       if (tenum.seekCeil(new BytesRef(termText)) == TermsEnum.SeekStatus.END) {
         // reached to the end of the iterator
         resetTermsIterator();
-        log.info("Reached the end of the term iterator for field: {}.", curField);
+        log.info("Reached the end of the term iterator for field: " + curField);
         return Optional.empty();
       } else {
         return Optional.of(new Term(curField, tenum.term()));
@@ -216,7 +216,7 @@ public final class DocumentsImpl extends LukeModel implements Documents {
   public Optional<Integer> firstTermDoc() {
     if (tenum == null) {
       // terms enum is not set
-      log.warn("Terms enum un-positioned.");
+      log.warning("Terms enum un-positioned.");
       return Optional.empty();
     }
 
@@ -226,10 +226,11 @@ public final class DocumentsImpl extends LukeModel implements Documents {
       if (penum.nextDoc() == PostingsEnum.NO_MORE_DOCS) {
         // no docs available for this term
         resetPostingsIterator();
-        log.warn(
-            "No docs available for term: {} in field: {}.",
-            BytesRefUtils.decode(tenum.term()),
-            curField);
+        log.warning(
+            "No docs available for term: "
+                + BytesRefUtils.decode(tenum.term())
+                + " in field: "
+                + curField);
         return Optional.empty();
       } else {
         return Optional.of(penum.docID());
@@ -245,7 +246,7 @@ public final class DocumentsImpl extends LukeModel implements Documents {
   public Optional<Integer> nextTermDoc() {
     if (penum == null) {
       // postings enum is not initialized
-      log.warn("Postings enum un-positioned for field: {}.", curField);
+      log.warning("Postings enum un-positioned for field: " + curField);
       return Optional.empty();
     }
 
@@ -253,12 +254,11 @@ public final class DocumentsImpl extends LukeModel implements Documents {
       if (penum.nextDoc() == PostingsEnum.NO_MORE_DOCS) {
         // end of the iterator
         resetPostingsIterator();
-        if (log.isInfoEnabled()) {
-          log.info(
-              "Reached the end of the postings iterator for term: {} in field: {}",
-              BytesRefUtils.decode(tenum.term()),
-              curField);
-        }
+        log.info(
+            "Reached the end of the postings iterator for term: "
+                + BytesRefUtils.decode(tenum.term())
+                + " in field: "
+                + curField);
         return Optional.empty();
       } else {
         return Optional.of(penum.docID());
@@ -274,7 +274,7 @@ public final class DocumentsImpl extends LukeModel implements Documents {
   public List<TermPosting> getTermPositions() {
     if (penum == null) {
       // postings enum is not initialized
-      log.warn("Postings enum un-positioned for field: {}.", curField);
+      log.warning("Postings enum un-positioned for field: " + curField);
       return Collections.emptyList();
     }
 
@@ -305,7 +305,7 @@ public final class DocumentsImpl extends LukeModel implements Documents {
   public Optional<Integer> getDocFreq() {
     if (tenum == null) {
       // terms enum is not initialized
-      log.warn("Terms enum un-positioned for field: {}.", curField);
+      log.warning("Terms enum un-positioned for field: " + curField);
       return Optional.empty();
     }
 
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorsAdapter.java b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorsAdapter.java
index 64d8552..bfdd227 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorsAdapter.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/documents/TermVectorsAdapter.java
@@ -22,8 +22,9 @@ import java.lang.invoke.MethodHandles;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.Locale;
 import java.util.Objects;
-import org.apache.logging.log4j.Logger;
+import java.util.logging.Logger;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.Terms;
 import org.apache.lucene.index.TermsEnum;
@@ -53,7 +54,10 @@ final class TermVectorsAdapter {
     Terms termVector = reader.getTermVector(docid, field);
     if (termVector == null) {
       // no term vector available
-      log.warn("No term vector indexed for doc: #{} and field: {}", docid, field);
+      log.warning(
+          () ->
+              String.format(
+                  Locale.ROOT, "No term vector indexed for doc: #%s and field: %s", docid, field));
       return Collections.emptyList();
     }
 
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchImpl.java b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchImpl.java
index a1122a3..c4b9629 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchImpl.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/search/SearchImpl.java
@@ -30,8 +30,8 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
+import java.util.logging.Logger;
 import java.util.stream.Collectors;
-import org.apache.logging.log4j.Logger;
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.index.DocValuesType;
 import org.apache.lucene.index.FieldInfo;
@@ -230,7 +230,7 @@ public final class SearchImpl extends LukeModel implements Search {
         } else if (type == Float.class || type == Double.class) {
           pc = new PointsConfig(NumberFormat.getNumberInstance(Locale.ROOT), type);
         } else {
-          log.warn(
+          log.warning(
               String.format(Locale.ENGLISH, "Ignored invalid number type: %s.", type.getName()));
           continue;
         }
@@ -344,7 +344,7 @@ public final class SearchImpl extends LukeModel implements Search {
     if (totalHits.value == 0
         || (totalHits.relation == TotalHits.Relation.EQUAL_TO
             && currentPage * pageSize >= totalHits.value)) {
-      log.warn("No more next search results are available.");
+      log.warning("No more next search results are available.");
       return Optional.empty();
     }
 
@@ -375,7 +375,7 @@ public final class SearchImpl extends LukeModel implements Search {
     currentPage -= 1;
 
     if (currentPage < 0) {
-      log.warn("No more previous search results are available.");
+      log.warning("No more previous search results are available.");
       return Optional.empty();
     }
 
@@ -462,7 +462,7 @@ public final class SearchImpl extends LukeModel implements Search {
     Objects.requireNonNull(type);
     List<SortField> candidates = guessSortTypes(name);
     if (candidates.isEmpty()) {
-      log.warn(String.format(Locale.ENGLISH, "No available sort types for: %s", name));
+      log.warning(String.format(Locale.ENGLISH, "No available sort types for: %s", name));
       return Optional.empty();
     }
 
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/util/IndexUtils.java b/lucene/luke/src/java/org/apache/lucene/luke/models/util/IndexUtils.java
index 27cd107..e616116 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/models/util/IndexUtils.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/util/IndexUtils.java
@@ -34,9 +34,10 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import java.util.stream.Collectors;
 import java.util.stream.StreamSupport;
-import org.apache.logging.log4j.Logger;
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
 import org.apache.lucene.codecs.CodecUtil;
@@ -84,7 +85,7 @@ public final class IndexUtils {
               DirectoryReader dr = DirectoryReader.open(dir);
               readers.add(dr);
             } catch (IOException e) {
-              log.warn("Error opening directory", e);
+              log.log(Level.WARNING, "Error opening directory", e);
             }
             return FileVisitResult.CONTINUE;
           }
@@ -94,14 +95,12 @@ public final class IndexUtils {
       throw new RuntimeException("No valid directory at the location: " + indexPath);
     }
 
-    if (log.isInfoEnabled()) {
-      log.info(
-          String.format(
-              Locale.ENGLISH,
-              "IndexReaders (%d leaf readers) successfully opened. Index path=%s",
-              readers.size(),
-              indexPath));
-    }
+    log.info(
+        String.format(
+            Locale.ENGLISH,
+            "IndexReaders (%d leaf readers) successfully opened. Index path=%s",
+            readers.size(),
+            indexPath));
 
     if (readers.size() == 1) {
       return readers.get(0);
@@ -134,11 +133,9 @@ public final class IndexUtils {
   public static Directory openDirectory(String dirPath, String dirImpl) throws IOException {
     final Path path = FileSystems.getDefault().getPath(Objects.requireNonNull(dirPath));
     Directory dir = openDirectory(path, dirImpl);
-    if (log.isInfoEnabled()) {
-      log.info(
-          String.format(
-              Locale.ENGLISH, "DirectoryReader successfully opened. Directory path=%s", dirPath));
-    }
+    log.info(
+        String.format(
+            Locale.ENGLISH, "DirectoryReader successfully opened. Directory path=%s", dirPath));
     return dir;
   }
 
@@ -161,7 +158,7 @@ public final class IndexUtils {
           dir = (Directory) constr.newInstance(path, null);
         }
       } catch (Exception e) {
-        log.warn("Invalid directory implementation class: {}", dirImpl, e);
+        log.log(Level.WARNING, "Invalid directory implementation class: " + dirImpl, e);
         throw new IllegalArgumentException("Invalid directory implementation class: " + dirImpl);
       }
     }
@@ -180,7 +177,7 @@ public final class IndexUtils {
         log.info("Directory successfully closed.");
       }
     } catch (IOException e) {
-      log.error("Error closing directory", e);
+      log.log(Level.SEVERE, "Error closing directory", e);
     }
   }
 
@@ -201,7 +198,7 @@ public final class IndexUtils {
         }
       }
     } catch (IOException e) {
-      log.error("Error closing index reader", e);
+      log.log(Level.SEVERE, "Error closing index reader", e);
     }
   }
 
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/models/util/twentynewsgroups/MessageFilesParser.java b/lucene/luke/src/java/org/apache/lucene/luke/models/util/twentynewsgroups/MessageFilesParser.java
index c5b5d0d..41db69b 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/models/util/twentynewsgroups/MessageFilesParser.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/models/util/twentynewsgroups/MessageFilesParser.java
@@ -28,7 +28,8 @@ import java.nio.file.SimpleFileVisitor;
 import java.nio.file.attribute.BasicFileAttributes;
 import java.util.ArrayList;
 import java.util.List;
-import org.apache.logging.log4j.Logger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 import org.apache.lucene.luke.util.LoggerFactory;
 
 /**
@@ -56,10 +57,8 @@ public class MessageFilesParser extends SimpleFileVisitor<Path> {
           messages.add(parse(file));
         }
       }
-    } catch (
-        @SuppressWarnings("unused")
-        IOException e) {
-      log.warn("Invalid file? {}", file);
+    } catch (IOException e) {
+      log.log(Level.WARNING, "Invalid file? " + file, e);
     }
     return FileVisitResult.CONTINUE;
   }
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/util/CircularLogBufferHandler.java b/lucene/luke/src/java/org/apache/lucene/luke/util/CircularLogBufferHandler.java
new file mode 100644
index 0000000..e0ad6f7
--- /dev/null
+++ b/lucene/luke/src/java/org/apache/lucene/luke/util/CircularLogBufferHandler.java
@@ -0,0 +1,114 @@
+/*
+ * 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.lucene.luke.util;
+
+import java.time.Instant;
+import java.util.ArrayDeque;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Consumer;
+import java.util.logging.Handler;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+
+/** A {@link Handler} with a bounded buffer of recent log messages. */
+public class CircularLogBufferHandler extends Handler {
+  /** Provides an immutable clone of the required data from a {@link LogRecord} logged elsewhere. */
+  public static final class ImmutableLogRecord {
+    private final String loggerName;
+    private final Level level;
+    private final String message;
+    private final Throwable thrown;
+    private final Instant instant;
+
+    public ImmutableLogRecord(LogRecord record) {
+      this.loggerName = record.getLoggerName();
+      this.level = record.getLevel();
+      this.message = record.getMessage();
+      this.thrown = record.getThrown();
+      this.instant = record.getInstant();
+    }
+
+    public String getLoggerName() {
+      return loggerName;
+    }
+
+    public Level getLevel() {
+      return level;
+    }
+
+    public String getMessage() {
+      return message;
+    }
+
+    public Throwable getThrown() {
+      return thrown;
+    }
+
+    public Instant getInstant() {
+      return instant;
+    }
+  }
+
+  /** Listeners receiving log state updates. */
+  public interface LogUpdateListener extends Consumer<Collection<ImmutableLogRecord>> {}
+
+  /** A bounded buffer of immutable log records that have been recently logged by the framework. */
+  private final ArrayDeque<ImmutableLogRecord> buffer = new ArrayDeque<>();
+
+  /**
+   * Listeners interested in receiving log state updates. At the moment listeners receive an
+   * iterable with all log records in the circular buffer. We could make it incremental for each
+   * consumer but for Luke it really doesn't matter since the rate of updates is very small.
+   */
+  private final List<LogUpdateListener> listeners = new CopyOnWriteArrayList<>();
+
+  @Override
+  public void publish(LogRecord record) {
+    synchronized (buffer) {
+      buffer.addLast(new ImmutableLogRecord(record));
+      listeners.forEach(c -> c.accept(buffer));
+    }
+  }
+
+  @Override
+  public void flush() {
+    // Ignore.
+  }
+
+  @Override
+  public void close() throws SecurityException {
+    // Ignore.
+  }
+
+  public void addUpdateListener(LogUpdateListener listener) {
+    listeners.add(listener);
+  }
+
+  public void removeUpdateListener(LogUpdateListener listener) {
+    listeners.remove(listener);
+  }
+
+  /** @return Return a clone of the buffered records so far. */
+  public Collection<ImmutableLogRecord> getLogRecords() {
+    synchronized (buffer) {
+      return new ArrayDeque<>(buffer);
+    }
+  }
+}
diff --git a/lucene/luke/src/java/org/apache/lucene/luke/util/LoggerFactory.java b/lucene/luke/src/java/org/apache/lucene/luke/util/LoggerFactory.java
index a1a9184..6f134a7 100644
--- a/lucene/luke/src/java/org/apache/lucene/luke/util/LoggerFactory.java
+++ b/lucene/luke/src/java/org/apache/lucene/luke/util/LoggerFactory.java
@@ -17,54 +17,28 @@
 
 package org.apache.lucene.luke.util;
 
-import java.nio.charset.StandardCharsets;
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.Appender;
-import org.apache.logging.log4j.core.LoggerContext;
-import org.apache.logging.log4j.core.appender.FileAppender;
-import org.apache.logging.log4j.core.config.Configurator;
-import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
-import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
-import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
-import org.apache.logging.log4j.core.layout.PatternLayout;
-import org.apache.lucene.luke.app.desktop.util.TextAreaAppender;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
-/** Logger factory. This programmatically configurates logger context (Appenders etc.) */
+/** Logger factory. This configures log interceptors for the GUI. */
 public class LoggerFactory {
+  public static CircularLogBufferHandler circularBuffer;
 
-  public static void initGuiLogging(String logFile) {
-    ConfigurationBuilder<BuiltConfiguration> builder =
-        ConfigurationBuilderFactory.newConfigurationBuilder();
-    builder.add(builder.newRootLogger(Level.INFO));
-    LoggerContext context = Configurator.initialize(builder.build());
+  public static void initGuiLogging() {
+    if (circularBuffer != null) {
+      throw new RuntimeException("Double-initialization?");
+    }
 
-    PatternLayout layout =
-        PatternLayout.newBuilder()
-            .withPattern("[%d{ISO8601}] %5p (%F:%L) - %m%n")
-            .withCharset(StandardCharsets.UTF_8)
-            .build();
+    circularBuffer = new CircularLogBufferHandler();
+    circularBuffer.setLevel(Level.FINEST);
 
-    Appender fileAppender =
-        FileAppender.newBuilder()
-            .setName("File")
-            .setLayout(layout)
-            .withFileName(logFile)
-            .withAppend(false)
-            .build();
-    fileAppender.start();
-
-    Appender textAreaAppender =
-        TextAreaAppender.newBuilder().setName("TextArea").setLayout(layout).build();
-    textAreaAppender.start();
-
-    context.getRootLogger().addAppender(fileAppender);
-    context.getRootLogger().addAppender(textAreaAppender);
-    context.updateLoggers();
+    // Only capture events from Lucene logger hierarchy.
+    var luceneRoot = Logger.getLogger("org.apache.lucene");
+    luceneRoot.setLevel(Level.FINEST);
+    luceneRoot.addHandler(circularBuffer);
   }
 
   public static Logger getLogger(Class<?> clazz) {
-    return LogManager.getLogger(clazz);
+    return Logger.getLogger(clazz.getName());
   }
 }
diff --git a/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/messages/messages.properties b/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/messages/messages.properties
index e814329..8e91e51 100644
--- a/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/messages/messages.properties
+++ b/lucene/luke/src/resources/org/apache/lucene/luke/app/desktop/messages/messages.properties
@@ -280,7 +280,7 @@ commits.label.files=Files
 commits.label.segments=Segments (click rows for more details)
 commits.label.segdetails=Segment details
 # Logs
-logs.label.see_also=See also:
+logs.label.level=Log level:
 # Help dialogs
 help.fieldtype.TextField=A field that is indexed and tokenized, without term vectors.\n\n(Example Values)\n- Hello Lucene!
 help.fieldtype.StringField=A field that is indexed but not tokenized: the entire String value is indexed as a single token.\n\n(Example Values)\n- Java
diff --git a/versions.lock b/versions.lock
index 80f63aa..87c5b3f 100644
--- a/versions.lock
+++ b/versions.lock
@@ -12,8 +12,6 @@ org.antlr:antlr4-runtime:4.5.1-1 (1 constraints: 6a05b240)
 org.apache.commons:commons-compress:1.19 (1 constraints: df04fa30)
 org.apache.httpcomponents:httpclient:4.5.13 (1 constraints: 3f054e3b)
 org.apache.httpcomponents:httpcore:4.4.13 (1 constraints: 591016a2)
-org.apache.logging.log4j:log4j-api:2.15.0 (1 constraints: bb0e1f76)
-org.apache.logging.log4j:log4j-core:2.15.0 (1 constraints: 3a053e3b)
 org.apache.opennlp:opennlp-tools:1.9.1 (1 constraints: 0d050c36)
 org.carrot2:morfologik-fsa:2.1.8 (1 constraints: da0d9b36)
 org.carrot2:morfologik-polish:2.1.8 (1 constraints: 0d050036)
diff --git a/versions.props b/versions.props
index 185bcfc..c010392 100644
--- a/versions.props
+++ b/versions.props
@@ -10,7 +10,6 @@ net.sourceforge.nekohtml:nekohtml=1.9.17
 org.antlr:antlr4*=4.5.1-1
 org.apache.commons:commons-compress=1.19
 org.apache.httpcomponents:httpclient=4.5.13
-org.apache.logging.log4j:*=2.15.0
 org.apache.opennlp:opennlp-tools=1.9.1
 org.carrot2:morfologik-*=2.1.8
 org.eclipse.jetty:*=9.4.41.v20210516