diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE new file mode 100644 index 0000000..f1a88e9 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE @@ -0,0 +1,14 @@ +Hi! Thanks for your interest in contributing to this project. + +Clojure contrib projects do not use GitHub issues or pull requests, and +require a signed Contributor Agreement. If you would like to contribute, +please read more about the CA and sign that first (this can be done online). + +Then go to this project's issue tracker in JIRA to create tickets, update +tickets, or submit patches. For help in creating tickets and patches, +please see: + +- Contributing FAQ: https://clojure.org/dev +- Signing the CA: https://clojure.org/dev/contributor_agreement +- Creating Tickets: https://clojure.org/dev/creating_tickets +- Developing Patches: https://clojure.org/dev/developing_patches diff --git a/.github/workflows/doc-build.yml b/.github/workflows/doc-build.yml new file mode 100644 index 0000000..7bb4d73 --- /dev/null +++ b/.github/workflows/doc-build.yml @@ -0,0 +1,13 @@ +name: Build API Docs + +permissions: + contents: write + +on: + workflow_dispatch: + +jobs: + call-doc-build-workflow: + uses: clojure/build.ci/.github/workflows/doc-build.yml@master + with: + project: clojure/tools.logging diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..286cf95 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,22 @@ +name: Release on demand + +permissions: + contents: write + +on: + workflow_dispatch: + inputs: + releaseVersion: + description: "Version to release" + required: true + snapshotVersion: + description: "Snapshot version after release" + required: true + +jobs: + call-release: + uses: clojure/build.ci/.github/workflows/release.yml@master + with: + releaseVersion: ${{ github.event.inputs.releaseVersion }} + snapshotVersion: ${{ github.event.inputs.snapshotVersion }} + secrets: inherit \ No newline at end of file diff --git a/.github/workflows/snapshot.yml b/.github/workflows/snapshot.yml new file mode 100644 index 0000000..9fdad8c --- /dev/null +++ b/.github/workflows/snapshot.yml @@ -0,0 +1,11 @@ +name: Snapshot on demand + +permissions: + contents: read + +on: [workflow_dispatch] + +jobs: + call-snapshot: + uses: clojure/build.ci/.github/workflows/snapshot.yml@master + secrets: inherit diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..2cc441a --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,10 @@ +name: Test + +permissions: + contents: read + +on: [push] + +jobs: + call-test: + uses: clojure/build.ci/.github/workflows/test.yml@master diff --git a/CHANGELOG.md b/CHANGELOG.md index 356aa84..68461d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,16 +5,71 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html) ([despite its flaws](https://www.youtube.com/watch?v=oyLBGkS5ICk)). -## [Unreleased] +## [1.3.1] - 2025-12-30 + +### Changed + +- Bumped parent pom to 1.4.0 +- Bumped deps to Clojure 1.11.4 +- Bumped test deps to latest + +## [1.3.0] - 2024-02-06 + +### Changed +- TLOG-28 - Moved slf4j test deps to 2.0.6 +- Bumped Clojure dep to 1.10.3 + +## [1.2.4] - 2022-01-04 + +### Changed +- Bumped log4j test deps to 2.17.1 + +## [1.2.3] - 2021-12-18 + +### Changed +- Bumped log4j test deps to 2.17.0 + +## [1.2.2] - 2021-12-14 + +### Changed +- Bumped log4j test deps to 2.16.0 + +## [1.2.1] - 2021-12-10 + +### Changed +- Bumped all testing deps to latest (particularly log4j) + +## [1.2.0] - 2021-12-10 + +### Changed +- Bumped all testing deps to latest (particularly log4j) + +## [1.1.0] - 2020-04-25 +### Added +- Add implementation of `spyf` to `clojure.tools.logging.readable`. + +### Changed +- Decreased the per-call overhead when using SLF4J, Commons Logging, and Log4j2. + Previously, their associated `logger-factory` implementations were calling + library-specific static convenience methods for each call to `get-logger`. + These methods have some baked-in setup overhead, which now only occurs during + instantiation of the associated `logger-factory`. + +## [1.0.0] - 2020-02-27 +### Added +- Add support for printing message arguments readably in + [`clojure.tools.logging.readable`](https://clojure.github.io/tools.logging/#clojure.tools.logging.readable). ## [0.6.0] - 2020-02-07 ### Added - Add support for explicitly selecting a logger factory by setting the - `clojure.tools.logging.factory` system property. + `"clojure.tools.logging.factory"` system property. For details, see the + [README](https://github.com/clojure/tools.logging#configuration), + and the documentation for [\*logger-factory\*](https://clojure.github.io/tools.logging/#clojure.tools.logging/*logger-factory*). ## [0.5.0] - 2019-07-22 ### Added -- Add support for testing logs in `clojure.tools.logging.test` +- Add support for testing logs in [`clojure.tools.logging.test`](https://clojure.github.io/tools.logging/#clojure.tools.logging.test). ### Changed - Now passes the context classloader of current thread to `Class/forName` when @@ -78,7 +133,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. ### Added - Initial implementation of Clojure-aware logging abstraction. -[Unreleased]: https://github.com/clojure/tools.logging/compare/tools.logging-0.6.0...HEAD +[Unreleased]: https://github.com/clojure/tools.logging/compare/tools.logging-1.1.0...HEAD +[1.3.1]: https://github.com/clojure/tools.logging/compare/v1.3.0...v1.3.1 +[1.3.0]: https://github.com/clojure/tools.logging/compare/v1.2.4...v1.3.0 +[1.2.4]: https://github.com/clojure/tools.logging/compare/v1.2.3...v1.2.4 +[1.2.3]: https://github.com/clojure/tools.logging/compare/v1.2.2...v1.2.3 +[1.2.2]: https://github.com/clojure/tools.logging/compare/v1.2.1...v1.2.2 +[1.2.1]: https://github.com/clojure/tools.logging/compare/v1.2.0...v1.2.1 +[1.2.0]: https://github.com/clojure/tools.logging/compare/tools.logging-1.1.0...v1.2.0 +[1.1.0]: https://github.com/clojure/tools.logging/compare/tools.logging-1.0.0...tools.logging-1.1.0 +[1.0.0]: https://github.com/clojure/tools.logging/compare/tools.logging-0.6.0...tools.logging-1.0.0 [0.6.0]: https://github.com/clojure/tools.logging/compare/tools.logging-0.5.0...tools.logging-0.6.0 [0.5.0]: https://github.com/clojure/tools.logging/compare/tools.logging-0.4.1...tools.logging-0.5.0 [0.4.1]: https://github.com/clojure/tools.logging/compare/tools.logging-0.4.0...tools.logging-0.4.1 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d91a363..26313e3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -8,5 +8,5 @@ more information on how to contribute. [Clojure contrib]: https://clojure.org/community/contrib_libs [Contributing]: https://clojure.org/community/contributing -[JIRA]: http://dev.clojure.org/jira/browse/TLOG +[JIRA]: https://clojure.atlassian.net/browse/TLOG [guidelines]: https://clojure.org/community/contrib_howto diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e246f6a --- /dev/null +++ b/LICENSE @@ -0,0 +1,205 @@ +Eclipse Public License - v 1.0 + +THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC +LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM +CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + +1. DEFINITIONS + +"Contribution" means: + +a) in the case of the initial Contributor, the initial code and documentation + distributed under this Agreement, and +b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + + where such changes and/or additions to the Program originate from and are + distributed by that particular Contributor. A Contribution 'originates' + from a Contributor if it was added to the Program by such Contributor + itself or anyone acting on such Contributor's behalf. Contributions do not + include additions to the Program which: (i) are separate modules of + software distributed in conjunction with the Program under their own + license agreement, and (ii) are not derivative works of the Program. + +"Contributor" means any person or entity that distributes the Program. + +"Licensed Patents" mean patent claims licensable by a Contributor which are +necessarily infringed by the use or sale of its Contribution alone or when +combined with the Program. + +"Program" means the Contributions distributed in accordance with this +Agreement. + +"Recipient" means anyone who receives the Program under this Agreement, +including all Contributors. + +2. GRANT OF RIGHTS + a) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free copyright license to + reproduce, prepare derivative works of, publicly display, publicly + perform, distribute and sublicense the Contribution of such Contributor, + if any, and such derivative works, in source code and object code form. + b) Subject to the terms of this Agreement, each Contributor hereby grants + Recipient a non-exclusive, worldwide, royalty-free patent license under + Licensed Patents to make, use, sell, offer to sell, import and otherwise + transfer the Contribution of such Contributor, if any, in source code and + object code form. This patent license shall apply to the combination of + the Contribution and the Program if, at the time the Contribution is + added by the Contributor, such addition of the Contribution causes such + combination to be covered by the Licensed Patents. The patent license + shall not apply to any other combinations which include the Contribution. + No hardware per se is licensed hereunder. + c) Recipient understands that although each Contributor grants the licenses + to its Contributions set forth herein, no assurances are provided by any + Contributor that the Program does not infringe the patent or other + intellectual property rights of any other entity. Each Contributor + disclaims any liability to Recipient for claims brought by any other + entity based on infringement of intellectual property rights or + otherwise. As a condition to exercising the rights and licenses granted + hereunder, each Recipient hereby assumes sole responsibility to secure + any other intellectual property rights needed, if any. For example, if a + third party patent license is required to allow Recipient to distribute + the Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + d) Each Contributor represents that to its knowledge it has sufficient + copyright rights in its Contribution, if any, to grant the copyright + license set forth in this Agreement. + +3. REQUIREMENTS + +A Contributor may choose to distribute the Program in object code form under +its own license agreement, provided that: + + a) it complies with the terms and conditions of this Agreement; and + b) its license agreement: + i) effectively disclaims on behalf of all Contributors all warranties + and conditions, express and implied, including warranties or + conditions of title and non-infringement, and implied warranties or + conditions of merchantability and fitness for a particular purpose; + ii) effectively excludes on behalf of all Contributors all liability for + damages, including direct, indirect, special, incidental and + consequential damages, such as lost profits; + iii) states that any provisions which differ from this Agreement are + offered by that Contributor alone and not by any other party; and + iv) states that source code for the Program is available from such + Contributor, and informs licensees how to obtain it in a reasonable + manner on or through a medium customarily used for software exchange. + +When the Program is made available in source code form: + + a) it must be made available under this Agreement; and + b) a copy of this Agreement must be included with each copy of the Program. + Contributors may not remove or alter any copyright notices contained + within the Program. + +Each Contributor must identify itself as the originator of its Contribution, +if +any, in a manner that reasonably allows subsequent Recipients to identify the +originator of the Contribution. + +4. COMMERCIAL DISTRIBUTION + +Commercial distributors of software may accept certain responsibilities with +respect to end users, business partners and the like. While this license is +intended to facilitate the commercial use of the Program, the Contributor who +includes the Program in a commercial product offering should do so in a manner +which does not create potential liability for other Contributors. Therefore, +if a Contributor includes the Program in a commercial product offering, such +Contributor ("Commercial Contributor") hereby agrees to defend and indemnify +every other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits and +other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such Commercial +Contributor in connection with its distribution of the Program in a commercial +product offering. The obligations in this section do not apply to any claims +or Losses relating to any actual or alleged intellectual property +infringement. In order to qualify, an Indemnified Contributor must: +a) promptly notify the Commercial Contributor in writing of such claim, and +b) allow the Commercial Contributor to control, and cooperate with the +Commercial Contributor in, the defense and any related settlement +negotiations. The Indemnified Contributor may participate in any such claim at +its own expense. + +For example, a Contributor might include the Program in a commercial product +offering, Product X. That Contributor is then a Commercial Contributor. If +that Commercial Contributor then makes performance claims, or offers +warranties related to Product X, those performance claims and warranties are +such Commercial Contributor's responsibility alone. Under this section, the +Commercial Contributor would have to defend claims against the other +Contributors related to those performance claims and warranties, and if a +court requires any other Contributor to pay any damages as a result, the +Commercial Contributor must pay those damages. + +5. NO WARRANTY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED 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. Each +Recipient is solely responsible for determining the appropriateness of using +and distributing the Program and assumes all risks associated with its +exercise of rights under this Agreement , including but not limited to the +risks and costs of program errors, compliance with applicable laws, damage to +or loss of data, programs or equipment, and unavailability or interruption of +operations. + +6. DISCLAIMER OF LIABILITY + +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY +CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION +LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY +OF SUCH DAMAGES. + +7. GENERAL + +If any provision of this Agreement is invalid or unenforceable under +applicable law, it shall not affect the validity or enforceability of the +remainder of the terms of this Agreement, and without further action by the +parties hereto, such provision shall be reformed to the minimum extent +necessary to make such provision valid and enforceable. + +If Recipient institutes patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Program itself +(excluding combinations of the Program with other software or hardware) +infringes such Recipient's patent(s), then such Recipient's rights granted +under Section 2(b) shall terminate as of the date such litigation is filed. + +All Recipient's rights under this Agreement shall terminate if it fails to +comply with any of the material terms or conditions of this Agreement and does +not cure such failure in a reasonable period of time after becoming aware of +such noncompliance. If all Recipient's rights under this Agreement terminate, +Recipient agrees to cease use and distribution of the Program as soon as +reasonably practicable. However, Recipient's obligations under this Agreement +and any licenses granted by Recipient relating to the Program shall continue +and survive. + +Everyone is permitted to copy and distribute copies of this Agreement, but in +order to avoid inconsistency the Agreement is copyrighted and may only be +modified in the following manner. The Agreement Steward reserves the right to +publish new versions (including revisions) of this Agreement from time to +time. No one other than the Agreement Steward has the right to modify this +Agreement. The Eclipse Foundation is the initial Agreement Steward. The +Eclipse Foundation may assign the responsibility to serve as the Agreement +Steward to a suitable separate entity. Each new version of the Agreement will +be given a distinguishing version number. The Program (including +Contributions) may always be distributed subject to the version of the +Agreement under which it was received. In addition, after a new version of the +Agreement is published, Contributor may elect to distribute the Program +(including its Contributions) under the new version. Except as expressly +stated in Sections 2(a) and 2(b) above, Recipient receives no rights or +licenses to the intellectual property of any Contributor under this Agreement, +whether expressly, by implication, estoppel or otherwise. All rights in the +Program not expressly granted under this Agreement are reserved. + +This Agreement is governed by the laws of the State of New York and the +intellectual property laws of the United States of America. No party to this +Agreement will bring a legal action under this Agreement more than one year +after the cause of action arose. Each party waives its rights to a jury trial in +any resulting litigation. + + diff --git a/README.md b/README.md index b49fb77..b4eba9f 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,17 @@ at runtime when the `clojure.tools.logging` namespace is first loaded. ## Installation -Lastest stable release is [0.6.0] +Lastest stable release is [1.3.1] + +[CLI/`deps.edn`](https://clojure.org/reference/deps_edn) dependency information: +```clojure +org.clojure/tools.logging {:mvn/version "1.3.1"} +``` Leiningen: ```clojure -[org.clojure/tools.logging "0.6.0"] +[org.clojure/tools.logging "1.3.1"] ``` Maven: @@ -19,43 +24,36 @@ Maven: org.clojure tools.logging - 0.6.0 + 1.3.1 ``` -Gradle: - -```clojure -compile "org.clojure:tools.logging:0.6.0" -``` - - ## Usage -[Latest API Documentation](http://clojure.github.com/tools.logging) +[Latest API Documentation](https://clojure.github.io/tools.logging) Logging occurs with the `log` macro, or the level-specific convenience macros (e.g., `debug`, `debugf`). Only when the specified logging level is enabled will the message arguments be evaluated and the underlying logging implementation be -invoked. By default that invocation will occur via an agent when inside a running -STM transaction. +invoked. By default, that invocation will occur via an agent when inside a +running STM transaction. ### Namespacing of log entries Unless otherwise specified, the current namespace (as identified by `*ns*`) will -be used as the log-ns. This value can be emitted in the log entry, and used by most -logging implementations when using namespace-specific logging levels. +be used as the "logger name" when interacting with logging implementations. Most +logging implementations allow for varying configuration by logger name. -Note: You should configure your logging implementation to display the name that -was passed to it. If it instead performs stack-inspection you'll see some ugly -and unhelpful text in your logs. +Note: You should configure your logging implementation to display the logger +name that was passed to it. If instead the logging implementation performs +stack-inspection you'll see some ugly and unhelpful text in your logs. ### Redirecting output to logs You can redirect all java writes of `System.out` and `System.err` to the log system by calling `log-capture!`. To bind `*out*` and `*err*` to the log system -invoke `with-logs`. In both cases a log-ns value must be specified in order to -namespace the output. +invoke `with-logs`. In both cases a logger name must be provided in lieu of +using `*ns*`. ## Configuration @@ -68,7 +66,8 @@ library._ To control which logging implementation is used, set the `clojure.tools.logging.factory` system property to the fully-qualified name of a no-arg function that returns an instance of `clojure.tools.logging.impl/LoggerFactory`. There are a number of -factory functions provided in the `clojure.tools.logging.impl` namespace. +factory functions provided in the [`clojure.tools.logging.impl`](https://clojure.github.io/tools.logging/#clojure.tools.logging.impl/find-factory) +namespace. [Leiningen example]: @@ -89,6 +88,37 @@ The above approach is problematic given that applications often inadvertently pu in multiple logging implementations as transitive dependencies. As such, it is _strongly_ advised that you set the system property. +### Log4J2 + +A simple Log4j2 [configuration](https://logging.apache.org/log4j/2.x/manual/configuration.html): + +```properties +status = warn +monitorInterval = 5 + +appender.console.type = Console +appender.console.name = STDOUT +appender.console.layout.type = PatternLayout +appender.console.layout.pattern = %date %level %logger %message%n%throwable + +rootLogger.level = info +rootLogger.appenderRef.stdout.ref = STDOUT +``` + +*Note:* The above [pattern](https://logging.apache.org/log4j/2.x/manual/layouts.html#Patterns) +explicitly uses `%throwable` so that `clojure.lang.ExceptionInfo` exceptions +will be printed with their data maps. If either `%xThrowable` (the default) or +`%rThrowable` is used, the data maps will not be printed. + +## FAQ + +#### When logging an `ex-info` exception, why isn't the data map printed? + +This is likely because the logging implementation is printing the contents of +`Throwable.getMessage()`, which returns just the message arg to `ex-info`. + +Logging implementations that print the contents of `toString()` or use `Throwable.printStackTrace(...)` +will end up printing the data map. ## Thanks @@ -100,15 +130,17 @@ _strongly_ advised that you set the system property. ## License -Copyright © 2009 Alex Taggart +Copyright © Rich Hickey, Alex Taggart, and contributors Licensed under the EPL. (See the file epl.html.) -[0.6.0]: https://github.com/clojure/tools.logging/tree/tools.logging-0.6.0 +[1.3.1]: https://github.com/clojure/tools.logging/tree/v1.3.1 +[1.3.0]: https://github.com/clojure/tools.logging/tree/v1.3.0 +[1.2.4]: https://github.com/clojure/tools.logging/tree/v1.2.4 [Leiningen example]: https://github.com/technomancy/leiningen/blob/master/doc/TUTORIAL.md#setting-jvm-options -[SLF4J]: http://www.slf4j.org/ +[SLF4J]: https://www.slf4j.org/ [Apache Commons Logging]: https://commons.apache.org/logging [Log4J 2]: https://logging.apache.org/log4j/2.x/ -[Log4J]: http://logging.apache.org/log4j/1.2/ +[Log4J]: https://logging.apache.org/log4j/1.2/ [java.util.logging]: https://docs.oracle.com/en/java/javase/13/docs/api/java.logging/java/util/logging/package-summary.html diff --git a/pom.xml b/pom.xml index bcf7bb3..5d3d47b 100644 --- a/pom.xml +++ b/pom.xml @@ -1,13 +1,14 @@ 4.0.0 tools.logging - 0.6.1-SNAPSHOT + 1.3.2-SNAPSHOT tools.logging + Clojure logging API. org.clojure pom.contrib - 0.2.2 + 1.4.0 @@ -23,41 +24,39 @@ HEAD + + 1.11.4 + + org.slf4j slf4j-api - 1.6.2 + 2.0.17 test org.slf4j - slf4j-log4j12 - 1.6.2 - test - - - log4j - log4j - 1.2.16 + slf4j-reload4j + 2.0.17 test org.apache.logging.log4j log4j-api - 2.8.2 + 2.25.3 test org.apache.logging.log4j log4j-core - 2.8.2 + 2.25.3 test commons-logging commons-logging - 1.1.1 + 1.3.5 test diff --git a/project.clj b/project.clj index c3c610e..393d661 100644 --- a/project.clj +++ b/project.clj @@ -1,18 +1,18 @@ -(defproject org.clojure/tools.logging "0.6.1-SNAPSHOT" +(defproject org.clojure/tools.logging "1.1.1-SNAPSHOT" :description "Clojure logging API." :url "https://github.com/clojure/tools.logging" :license {:name "Eclipse Public License" :url "http://www.eclipse.org/legal/epl-v10.html"} :source-paths ["src/main/clojure"] :test-paths ["src/test/clojure"] - :dependencies [[org.clojure/clojure "1.3.0"]] + :dependencies [[org.clojure/clojure "1.11.4"]] :aliases {"cloverage" ["with-profile" "dev,cloverage" "cloverage" "--fail-threshold" "100"]} - :profiles {:cloverage {:plugins [[lein-cloverage "1.0.9"]]} - :dev {:dependencies [[org.clojure/clojure "1.8.0"] - [org.clojure/test.check "0.9.0"] - [org.slf4j/slf4j-api "1.6.2"] - [org.slf4j/slf4j-log4j12 "1.6.2"] - [log4j "1.2.16"] - [org.apache.logging.log4j/log4j-api "2.8.2"] - [org.apache.logging.log4j/log4j-core "2.8.2"] - [commons-logging "1.1.1"]]}}) + :profiles {:cloverage {:plugins [[lein-cloverage "1.2.4"]]} + :dev {:dependencies [[org.clojure/clojure "1.11.4"] + [org.clojure/test.check "1.1.2"] + [org.slf4j/slf4j-api "2.0.17"] + [org.slf4j/slf4j-log4j12 "2.0.17"] + [org.apache.logging.log4j/log4j-api "2.25.3"] + [org.apache.logging.log4j/log4j-core "2.25.3"] + [commons-logging "1.3.5"] + [criterium "0.4.6"]]}}) diff --git a/src/main/clojure/clojure/tools/logging.clj b/src/main/clojure/clojure/tools/logging.clj index b4cc2d3..e963f06 100644 --- a/src/main/clojure/clojure/tools/logging.clj +++ b/src/main/clojure/clojure/tools/logging.clj @@ -81,7 +81,8 @@ (defmacro logp "Logs a message using print style args. Can optionally take a throwable as its - second arg. See level-specific macros, e.g., debug." + second arg. See level-specific macros, e.g., debug. + Use the 'logging.readable' namespace to avoid wrapping args in pr-str." {:arglists '([level message & more] [level throwable message & more])} [level x & more] (if (or (instance? String x) (nil? more)) ; optimize for common case @@ -95,7 +96,8 @@ (defmacro logf "Logs a message using a format string and args. Can optionally take a - throwable as its second arg. See level-specific macros, e.g., debugf." + throwable as its second arg. See level-specific macros, e.g., debugf. + Use the 'logging.readable' namespace to avoid wrapping args in pr-str." {:arglists '([level fmt & fmt-args] [level throwable fmt & fmt-args])} [level x & more] (if (or (instance? String x) (nil? more)) ; optimize for common case @@ -134,7 +136,8 @@ (defmacro spyf "Evaluates expr and may write (format fmt result) to the log. Returns the - result of expr. Defaults to :debug log level." + result of expr. Defaults to :debug log level. + Use the 'logging.readable' namespace to avoid wrapping args in pr-str." ([fmt expr] `(spyf :debug ~fmt ~expr)) ([level fmt expr] @@ -211,73 +214,85 @@ ;; level-specific macros (defmacro trace - "Trace level logging using print-style args." + "Trace level logging using print-style args. + Use the 'logging.readable' namespace to avoid wrapping args in pr-str." {:arglists '([message & more] [throwable message & more])} [& args] `(logp :trace ~@args)) (defmacro debug - "Debug level logging using print-style args." + "Debug level logging using print-style args. + Use the 'logging.readable' namespace to avoid wrapping args in pr-str." {:arglists '([message & more] [throwable message & more])} [& args] `(logp :debug ~@args)) (defmacro info - "Info level logging using print-style args." + "Info level logging using print-style args. + Use the 'logging.readable' namespace to avoid wrapping args in pr-str." {:arglists '([message & more] [throwable message & more])} [& args] `(logp :info ~@args)) (defmacro warn - "Warn level logging using print-style args." + "Warn level logging using print-style args. + Use the 'logging.readable' namespace to avoid wrapping args in pr-str." {:arglists '([message & more] [throwable message & more])} [& args] `(logp :warn ~@args)) (defmacro error - "Error level logging using print-style args." + "Error level logging using print-style args. + Use the 'logging.readable' namespace to avoid wrapping args in pr-str." {:arglists '([message & more] [throwable message & more])} [& args] `(logp :error ~@args)) (defmacro fatal - "Fatal level logging using print-style args." + "Fatal level logging using print-style args. + Use the 'logging.readable' namespace to avoid wrapping args in pr-str." {:arglists '([message & more] [throwable message & more])} [& args] `(logp :fatal ~@args)) (defmacro tracef - "Trace level logging using format." + "Trace level logging using format. + Use the 'logging.readable' namespace to avoid wrapping args in pr-str." {:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])} [& args] `(logf :trace ~@args)) (defmacro debugf - "Debug level logging using format." + "Debug level logging using format. + Use the 'logging.readable' namespace to avoid wrapping args in pr-str." {:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])} [& args] `(logf :debug ~@args)) (defmacro infof - "Info level logging using format." + "Info level logging using format. + Use the 'logging.readable' namespace to avoid wrapping args in pr-str." {:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])} [& args] `(logf :info ~@args)) (defmacro warnf - "Warn level logging using format." + "Warn level logging using format. + Use the 'logging.readable' namespace to avoid wrapping args in pr-str." {:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])} [& args] `(logf :warn ~@args)) (defmacro errorf - "Error level logging using format." + "Error level logging using format. + Use the 'logging.readable' namespace to avoid wrapping args in pr-str." {:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])} [& args] `(logf :error ~@args)) (defmacro fatalf - "Fatal level logging using format." + "Fatal level logging using format. + Use the 'logging.readable' namespace to avoid wrapping args in pr-str." {:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])} [& args] `(logf :fatal ~@args)) diff --git a/src/main/clojure/clojure/tools/logging/impl.clj b/src/main/clojure/clojure/tools/logging/impl.clj index 087b190..cac7fb1 100644 --- a/src/main/clojure/clojure/tools/logging/impl.clj +++ b/src/main/clojure/clojure/tools/logging/impl.clj @@ -61,7 +61,8 @@ [] (when (class-found? "org.slf4j.Logger") (eval - `(do + `(let [; Same as is done inside LoggerFactory/getLogger(String). + factory# (org.slf4j.LoggerFactory/getILoggerFactory)] (extend org.slf4j.Logger Logger {:enabled? @@ -98,7 +99,7 @@ (name [_#] "org.slf4j") (get-logger [_# logger-ns#] - (org.slf4j.LoggerFactory/getLogger ^String (str logger-ns#)))))))) + (.getLogger factory# ^String (str logger-ns#)))))))) (defn cl-factory "Returns a Commons Logging-based implementation of the LoggerFactory protocol, or @@ -106,7 +107,8 @@ [] (when (class-found? "org.apache.commons.logging.Log") (eval - `(do + `(let [; Same as is done inside LogFactory/getLog(String). + factory# (org.apache.commons.logging.LogFactory/getFactory)] (extend org.apache.commons.logging.Log Logger {:enabled? @@ -142,7 +144,7 @@ (name [_#] "org.apache.commons.logging") (get-logger [_# logger-ns#] - (org.apache.commons.logging.LogFactory/getLog (str logger-ns#)))))))) + (.getInstance factory# (str logger-ns#)))))))) (defn log4j-factory "Returns a Log4j-based implementation of the LoggerFactory protocol, or nil if @@ -179,7 +181,9 @@ [] (when (class-found? "org.apache.logging.log4j.Logger") (eval - `(let [levels# {:trace org.apache.logging.log4j.Level/TRACE + `(let [; Same as is done inside LogManager/getLogger(String). + context# (org.apache.logging.log4j.LogManager/getContext false) + levels# {:trace org.apache.logging.log4j.Level/TRACE :debug org.apache.logging.log4j.Level/DEBUG :info org.apache.logging.log4j.Level/INFO :warn org.apache.logging.log4j.Level/WARN @@ -206,7 +210,7 @@ (name [_#] "org.apache.logging.log4j") (get-logger [_# logger-ns#] - (org.apache.logging.log4j.LogManager/getLogger ^String (str logger-ns#)))))))) + (.getLogger context# ^String (str logger-ns#)))))))) (defn jul-factory "Returns a java.util.logging-based implementation of the LoggerFactory protocol, diff --git a/src/main/clojure/clojure/tools/logging/readable.clj b/src/main/clojure/clojure/tools/logging/readable.clj index b1c9465..b69b1c5 100644 --- a/src/main/clojure/clojure/tools/logging/readable.clj +++ b/src/main/clojure/clojure/tools/logging/readable.clj @@ -7,7 +7,10 @@ ;; remove this notice, or any other, from this software. (ns ^{:author "Alex Taggart" - :doc "Logging macros that support printing message arguments readably. + :doc "Logging macros that support printing message (some) arguments as + if wrapped in pr-str, the goal being to preserve their data representation + distinct from the explanatory text. See logp and logf for details regarding + which args are treated in this manner. Examples: @@ -15,7 +18,7 @@ '[clojure.tools.logging.readable :as logr]) (def x \"bar\") - ; Logged as... + ; Logged as... (log/debug \"foo\" x \"baz\") ; foo bar baz (logr/debug \"foo\" x \"baz\") ; foo \"bar\" baz (log/debugf \"foo %s %s\" x \"baz\") ; foo bar baz @@ -33,8 +36,9 @@ (defmacro logp "Logs a message using print style args, where message args that are not - literal strings will be printed readably. Can optionally take a throwable as - its second arg. See level-specific macros, e.g., debug." + literal strings will be printed readably, as if wrapped in pr-str. Can + optionally take a throwable as its second arg. See level-specific macros, + e.g., debug." {:arglists '([level message & more] [level throwable message & more])} [level x & more] ; Why bind `*print-readably*` to true? @@ -65,8 +69,8 @@ (defmacro logf "Logs a message using a format string and args, where all format args will be - printed readably. Can optionally take a throwable as its second arg. See - level-specific macros, e.g., debugf." + printed readably, as if wrapped in pr-str. Can optionally take a throwable as + its second arg. See level-specific macros, e.g., debugf." {:arglists '([level fmt & fmt-args] [level throwable fmt & fmt-args])} [level x & more] (if (or (instance? String x) (nil? more)) ; optimize for non-exception case @@ -155,3 +159,13 @@ {:arglists '([fmt & fmt-args] [throwable fmt & fmt-args])} [& args] `(logf :fatal ~@args)) + +(defmacro spyf + "Evaluates expr and may write (logf level fmt result) to the log. Returns the + result of expr. Defaults to :debug log level." + ([fmt expr] + `(spyf :debug ~fmt ~expr)) + ([level fmt expr] + `(let [a# ~expr] + (logf ~level ~fmt a#) + a#))) diff --git a/src/main/clojure/clojure/tools/logging/test.clj b/src/main/clojure/clojure/tools/logging/test.clj index 9c9087f..19a8ff4 100644 --- a/src/main/clojure/clojure/tools/logging/test.clj +++ b/src/main/clojure/clojure/tools/logging/test.clj @@ -18,9 +18,9 @@ (with-log (log/info \"Hello World!\") (log/error (Exception. \"Did a thing\") \"Error: oops\") - (logged? 'user :info #\"Hello\") ; true + (logged? 'user :info #\"Hello\") ; true (logged? 'user :error [Throwable #\"thing\"] #\"Error:\") ; true - (logged? 'user :debug \"Hi\")) ; false"} + (logged? 'user :debug \"Hi\")) ; false"} clojure.tools.logging.test (:import [clojure.lang Fn Keyword Namespace Symbol] java.util.Set @@ -227,7 +227,9 @@ (defn matches - "Returns matching log entries, otherwise nil. + "Returns matching log entries, otherwise nil. See match-logger-ns?, + match-level?, match-throwable?, and match-message? for the default matching + behavior applied to the given args. Must be invoked within a context where *stateful-log* is bound to an instance of StatefulLog containing MatchableLogEntry items (e.g., inside with-log)." @@ -239,7 +241,9 @@ (defn logged? - "Returns true if the log contains matching entries. + "Returns true if the log contains matching entries. See match-logger-ns?, + match-level?, match-throwable?, and match-message? for the default matching + behavior applied to the given args. Must be invoked within a context where *stateful-log* is bound to an instance of StatefulLog containing MatchableLogEntry items (e.g., inside with-log)." diff --git a/src/test/clojure/clojure/tools/logging/test_readable.clj b/src/test/clojure/clojure/tools/logging/test_readable.clj index e2bc401..3ba09e2 100644 --- a/src/test/clojure/clojure/tools/logging/test_readable.clj +++ b/src/test/clojure/clojure/tools/logging/test_readable.clj @@ -188,6 +188,18 @@ (is (logged? "clojure.tools.logging.test-readable" :debug e "hello \"world\""))))) +(deftest spyf-default + (with-log + (spyf "result: %s" (str "hello" " " "world")) + (is (logged? "clojure.tools.logging.test-readable" :debug nil "result: \"hello world\"")))) + +(deftest spyf-level + (doseq [level #{:trace :debug :info :warn :error :fatal}] + (with-log + (spyf level "result: %s" (str "hello" " " "world")) + (is (logged? "clojure.tools.logging.test-readable" level nil "result: \"hello world\""))))) + + (deftest println-style (are [f kw] (with-log (f "hello" "world")