├── .all-contributorsrc ├── .github ├── CODEOWNERS ├── dependabot.yml ├── project.yml └── workflows │ ├── build.yml │ ├── codeql-analysis.yml │ ├── pre-release.yml │ ├── quarkus-snapshot.yaml │ ├── release-perform.yml │ └── release-prepare.yml ├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── SECURITY.md ├── deployment ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── quarkiverse │ │ └── logging │ │ └── cloudwatch │ │ └── deployment │ │ └── LoggingCloudwatchProcessor.java │ └── test │ └── resources │ └── application.properties ├── docs ├── antora.yml └── modules │ └── ROOT │ ├── nav.adoc │ └── pages │ ├── config.adoc │ └── index.adoc ├── integration-tests ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── quarkiverse │ │ └── logging │ │ └── cloudwatch │ │ └── it │ │ └── LoggingCloudwatchHandlerResource.java │ └── test │ └── resources │ └── application.properties ├── mvnw ├── mvnw.cmd ├── pom.xml └── runtime ├── pom.xml └── src ├── main ├── java │ └── io │ │ └── quarkiverse │ │ └── logging │ │ └── cloudwatch │ │ ├── LoggingCloudWatchConfig.java │ │ ├── LoggingCloudWatchHandler.java │ │ ├── LoggingCloudWatchHandlerValueFactory.java │ │ ├── auth │ │ ├── CloudWatchCredentials.java │ │ └── CloudWatchCredentialsProvider.java │ │ └── format │ │ └── ElasticCommonSchemaLogFormatter.java └── resources │ └── META-INF │ └── quarkus-extension.yaml └── test └── java └── io └── quarkiverse └── logging └── cloudwatch └── LoggingCloudWatchHandlerTest.java /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "pilhuhn", 10 | "name": "Heiko W. Rupp", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/208246?v=4", 12 | "profile": "https://medium.com/@pilhuhn", 13 | "contributions": [ 14 | "code", 15 | "maintenance" 16 | ] 17 | }, 18 | { 19 | "login": "bennetelli", 20 | "name": "Bennet Schulz", 21 | "avatar_url": "https://avatars.githubusercontent.com/u/8372856?v=4", 22 | "profile": "http://bennet-schulz.com", 23 | "contributions": [ 24 | "code", 25 | "maintenance" 26 | ] 27 | }, 28 | { 29 | "login": "gwenneg", 30 | "name": "Gwenneg Lepage", 31 | "avatar_url": "https://avatars.githubusercontent.com/u/10584698?v=4", 32 | "profile": "https://github.com/gwenneg", 33 | "contributions": [ 34 | "code", 35 | "maintenance" 36 | ] 37 | } 38 | ], 39 | "contributorsPerLine": 7, 40 | "projectName": "quarkus-logging-cloudwatch", 41 | "projectOwner": "quarkiverse", 42 | "repoType": "github", 43 | "repoHost": "https://github.com", 44 | "skipCi": true 45 | } 46 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Lines starting with '#' are comments. 2 | # Each line is a file pattern followed by one or more owners. 3 | 4 | # More details are here: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 5 | 6 | # The '*' pattern is global owners. 7 | 8 | # Order is important. The last matching pattern has the most precedence. 9 | # The folders are ordered as follows: 10 | 11 | # In each subsection folders are ordered first by depth, then alphabetically. 12 | # This should make it easy to add new rules without breaking existing ones. 13 | 14 | * @quarkiverse/quarkiverse-logging-cloudwatch 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "maven" 9 | directory: "/" 10 | schedule: 11 | interval: "daily" 12 | ignore: 13 | - dependency-name: "org.apache.maven.plugins:maven-compiler-plugin" 14 | - package-ecosystem: "github-actions" 15 | directory: "/" 16 | schedule: 17 | interval: "weekly" 18 | -------------------------------------------------------------------------------- /.github/project.yml: -------------------------------------------------------------------------------- 1 | release: 2 | current-version: 6.13.0 3 | next-version: 6.13.1-SNAPSHOT 4 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - "main" 7 | paths-ignore: 8 | - '.gitignore' 9 | - 'CODEOWNERS' 10 | - 'LICENSE' 11 | - '*.md' 12 | - '*.adoc' 13 | - '*.txt' 14 | - '.all-contributorsrc' 15 | pull_request: 16 | paths-ignore: 17 | - '.gitignore' 18 | - 'CODEOWNERS' 19 | - 'LICENSE' 20 | - '*.md' 21 | - '*.adoc' 22 | - '*.txt' 23 | - '.all-contributorsrc' 24 | 25 | jobs: 26 | build: 27 | 28 | runs-on: ubuntu-latest 29 | 30 | steps: 31 | - uses: actions/checkout@v4 32 | 33 | - name: Set up JDK 17 34 | uses: actions/setup-java@v4 35 | with: 36 | distribution: temurin 37 | java-version: 17 38 | 39 | - name: Get Date 40 | id: get-date 41 | run: | 42 | echo "::set-output name=date::$(/bin/date -u "+%Y-%m")" 43 | shell: bash 44 | - name: Cache Maven Repository 45 | id: cache-maven 46 | uses: actions/cache@v4 47 | with: 48 | path: ~/.m2/repository 49 | # refresh cache every month to avoid unlimited growth 50 | key: maven-repo-${{ runner.os }}-${{ steps.get-date.outputs.date }} 51 | 52 | - name: Build with Maven 53 | run: mvn -B formatter:validate verify --file pom.xml 54 | 55 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '45 9 * * 1' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'java' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v4 43 | 44 | - name: Set up JDK 17 45 | uses: actions/setup-java@v4 46 | with: 47 | distribution: temurin 48 | java-version: 17 49 | 50 | # Initializes the CodeQL tools for scanning. 51 | - name: Initialize CodeQL 52 | uses: github/codeql-action/init@v3 53 | with: 54 | languages: ${{ matrix.language }} 55 | # If you wish to specify custom queries, you can do so here or in a config file. 56 | # By default, queries listed here will override any specified in a config file. 57 | # Prefix the list here with "+" to use these queries and those in the config file. 58 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 59 | 60 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 61 | # If this step fails, then you should remove it and run the build manually (see below) 62 | - name: Autobuild 63 | uses: github/codeql-action/autobuild@v3 64 | 65 | # ℹ️ Command-line programs to run using the OS shell. 66 | # 📚 https://git.io/JvXDl 67 | 68 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 69 | # and modify them (or add more) to build your code if your project 70 | # uses a compiled language 71 | 72 | #- run: | 73 | # make bootstrap 74 | # make release 75 | 76 | - name: Perform CodeQL Analysis 77 | uses: github/codeql-action/analyze@v3 78 | -------------------------------------------------------------------------------- /.github/workflows/pre-release.yml: -------------------------------------------------------------------------------- 1 | name: Quarkiverse Pre Release 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - '.github/project.yml' 7 | 8 | concurrency: 9 | group: ${{ github.workflow }}-${{ github.ref }} 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | pre-release: 14 | name: Pre-Release 15 | uses: quarkiverse/.github/.github/workflows/pre-release.yml@main 16 | secrets: inherit 17 | -------------------------------------------------------------------------------- /.github/workflows/quarkus-snapshot.yaml: -------------------------------------------------------------------------------- 1 | name: "Quarkus ecosystem CI" 2 | on: 3 | workflow_dispatch: 4 | watch: 5 | types: [started] 6 | 7 | # For this CI to work, ECOSYSTEM_CI_TOKEN needs to contain a GitHub with rights to close the Quarkus issue that the user/bot has opened, 8 | # while 'ECOSYSTEM_CI_REPO_PATH' needs to be set to the corresponding path in the 'quarkusio/quarkus-ecosystem-ci' repository 9 | 10 | env: 11 | ECOSYSTEM_CI_REPO: quarkusio/quarkus-ecosystem-ci 12 | ECOSYSTEM_CI_REPO_FILE: context.yaml 13 | JAVA_VERSION: 17 14 | 15 | ######################### 16 | # Repo specific setting # 17 | ######################### 18 | 19 | ECOSYSTEM_CI_REPO_PATH: quarkiverse-logging-cloudwatch 20 | 21 | jobs: 22 | build: 23 | name: "Build against latest Quarkus snapshot" 24 | runs-on: ubuntu-latest 25 | # Allow to manually launch the ecosystem CI in addition to the bots 26 | if: github.actor == 'quarkusbot' || github.actor == 'quarkiversebot' || github.actor == '' 27 | 28 | steps: 29 | - name: Set up Java 30 | uses: actions/setup-java@v4 31 | with: 32 | distribution: temurin 33 | java-version: ${{ env.JAVA_VERSION }} 34 | 35 | - name: Checkout repo 36 | uses: actions/checkout@v4 37 | with: 38 | path: current-repo 39 | 40 | - name: Checkout Ecosystem 41 | uses: actions/checkout@v4 42 | with: 43 | repository: ${{ env.ECOSYSTEM_CI_REPO }} 44 | path: ecosystem-ci 45 | 46 | - name: Setup and Run Tests 47 | run: ./ecosystem-ci/setup-and-test 48 | env: 49 | ECOSYSTEM_CI_TOKEN: ${{ secrets.ECOSYSTEM_CI_TOKEN }} 50 | -------------------------------------------------------------------------------- /.github/workflows/release-perform.yml: -------------------------------------------------------------------------------- 1 | name: Quarkiverse Prepare Release 2 | 3 | on: 4 | pull_request: 5 | types: [ closed ] 6 | paths: 7 | - '.github/project.yml' 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | prepare-release: 15 | name: Prepare Release 16 | if: ${{ github.event.pull_request.merged == true}} 17 | uses: quarkiverse/.github/.github/workflows/prepare-release.yml@main 18 | secrets: inherit 19 | -------------------------------------------------------------------------------- /.github/workflows/release-prepare.yml: -------------------------------------------------------------------------------- 1 | name: Quarkiverse Perform Release 2 | run-name: Perform ${{github.event.inputs.tag || github.ref_name}} Release 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | workflow_dispatch: 8 | inputs: 9 | tag: 10 | description: 'Tag to release' 11 | required: true 12 | 13 | permissions: 14 | attestations: write 15 | id-token: write 16 | contents: read 17 | 18 | concurrency: 19 | group: ${{ github.workflow }}-${{ github.ref }} 20 | cancel-in-progress: true 21 | 22 | jobs: 23 | perform-release: 24 | name: Perform Release 25 | uses: quarkiverse/.github/.github/workflows/perform-release.yml@main 26 | secrets: inherit 27 | with: 28 | version: ${{github.event.inputs.tag || github.ref_name}} 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | !/.mvn/wrapper/maven-wrapper.jar 16 | *.war 17 | *.nar 18 | *.ear 19 | *.zip 20 | *.tar.gz 21 | *.rar 22 | 23 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 24 | hs_err_pid* 25 | 26 | # Eclipse 27 | .project 28 | .classpath 29 | .settings/ 30 | bin/ 31 | 32 | # IntelliJ 33 | .idea 34 | *.ipr 35 | *.iml 36 | *.iws 37 | 38 | # NetBeans 39 | nb-configuration.xml 40 | 41 | # Visual Studio Code 42 | .vscode 43 | .factorypath 44 | 45 | # OSX 46 | .DS_Store 47 | 48 | # Vim 49 | *.swp 50 | *.swo 51 | 52 | # patch 53 | *.orig 54 | *.rej 55 | 56 | # Gradle 57 | .gradle/ 58 | build/ 59 | 60 | # Maven 61 | target/ 62 | pom.xml.tag 63 | pom.xml.releaseBackup 64 | pom.xml.versionsBackup 65 | release.properties -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quarkiverse/quarkus-logging-cloudwatch/1782bd26d434e3dfb638ca69330565f55e2e3e2c/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Quarkus - Logging Cloudwatch 2 | [![Build](https://github.com/quarkiverse/quarkus-logging-cloudwatch/workflows/Build/badge.svg)](https://github.com/quarkiverse/quarkus-logging-cloudwatch/actions?query=workflow%3ABuild) 3 | [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) 4 | 5 | 6 | [![All Contributors](https://img.shields.io/badge/all_contributors-3-orange.svg?style=flat-square)](#contributors-) 7 | 8 | 9 | ## Welcome to the CloudWatch logging extension! 10 | 11 | This extension allows you to send your application logs to AWS CloudWatch. 12 | 13 | ## Documentation 14 | 15 | Check out [the documentation](https://github.com/quarkiverse/quarkus-logging-cloudwatch/blob/main/docs/modules/ROOT/pages/index.adoc) on how 16 | to use the extension. 17 | 18 | ## Contributors ✨ 19 | 20 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |

Heiko W. Rupp

💻 🚧

Bennet Schulz

💻 🚧

Gwenneg Lepage

💻 🚧
32 | 33 | 34 | 35 | 36 | 37 | 38 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! 39 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /deployment/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse.logging.cloudwatch 6 | quarkus-logging-cloudwatch-parent 7 | 6.13.1-SNAPSHOT 8 | 9 | quarkus-logging-cloudwatch-deployment 10 | Quarkus - Logging Cloudwatch - Deployment 11 | 12 | 13 | io.quarkus 14 | quarkus-arc-deployment 15 | 16 | 17 | io.quarkus 18 | quarkus-apache-httpclient-deployment 19 | 20 | 21 | io.quarkiverse.amazonservices 22 | quarkus-amazon-common-deployment 23 | 24 | 25 | io.quarkiverse.logging.cloudwatch 26 | quarkus-logging-cloudwatch 27 | ${project.version} 28 | 29 | 30 | io.quarkus 31 | quarkus-junit5-internal 32 | test 33 | 34 | 35 | 36 | 37 | 38 | maven-compiler-plugin 39 | 40 | 41 | 42 | io.quarkus 43 | quarkus-extension-processor 44 | ${quarkus.version} 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /deployment/src/main/java/io/quarkiverse/logging/cloudwatch/deployment/LoggingCloudwatchProcessor.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logging.cloudwatch.deployment; 2 | 3 | import io.quarkiverse.logging.cloudwatch.LoggingCloudWatchConfig; 4 | import io.quarkiverse.logging.cloudwatch.LoggingCloudWatchHandlerValueFactory; 5 | import io.quarkus.deployment.annotations.BuildStep; 6 | import io.quarkus.deployment.annotations.ExecutionTime; 7 | import io.quarkus.deployment.annotations.Record; 8 | import io.quarkus.deployment.builditem.FeatureBuildItem; 9 | import io.quarkus.deployment.builditem.LogHandlerBuildItem; 10 | 11 | class LoggingCloudwatchProcessor { 12 | 13 | private static final String FEATURE = "logging-cloudwatch"; 14 | 15 | @BuildStep 16 | FeatureBuildItem feature() { 17 | return new FeatureBuildItem(FEATURE); 18 | } 19 | 20 | @BuildStep 21 | @Record(ExecutionTime.RUNTIME_INIT) 22 | LogHandlerBuildItem addCloudwatchLogHandler(final LoggingCloudWatchConfig config, 23 | final LoggingCloudWatchHandlerValueFactory cloudWatchHandlerValueFactory) { 24 | return new LogHandlerBuildItem(cloudWatchHandlerValueFactory.create(config)); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /deployment/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | quarkus.log.level=INFO 2 | quarkus.log.cloudwatch.enabled=true 3 | quarkus.log.cloudwatch.access-key-id=keyId 4 | quarkus.log.cloudwatch.access-key-secret=keySecret 5 | quarkus.log.cloudwatch.region=eu-central-1 6 | quarkus.log.cloudwatch.log-group=logGroup 7 | quarkus.log.cloudwatch.log-stream-name=logStreamName 8 | quarkus.log.cloudwatch.level=WARN -------------------------------------------------------------------------------- /docs/antora.yml: -------------------------------------------------------------------------------- 1 | name: quarkus-logging-cloudwatch 2 | title: Logging Cloudwatch 3 | version: dev 4 | nav: 5 | - modules/ROOT/nav.adoc 6 | -------------------------------------------------------------------------------- /docs/modules/ROOT/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:index.adoc[Quarkus - Logging Cloudwatch] 2 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/config.adoc: -------------------------------------------------------------------------------- 1 | [.configuration-legend] 2 | icon:lock[title=Fixed at build time] Configuration property fixed at build time - All other configuration properties are overridable at runtime. Hint: If you want to use this extension in a cloud environment containing multiple instances (e.g. multiple pods, multiple instances and so on) you should not use the same log-stream-name and log-group for all the instances. The log-stream-name and log-group combination must be unique! 3 | [.configuration-reference.searchable, cols="80,.^10,.^10"] 4 | |=== 5 | 6 | h|[[quarkus-log-handler-cloudwatch_configuration]]link:#quarkus-log-handler-cloudwatch_configuration[Configuration property] 7 | 8 | h|Type 9 | h|Default 10 | 11 | a| [[quarkus-log-cloudwatch-enabled]]`link:#quarkus-log-cloudwatch-enabled[quarkus.log.cloudwatch.enabled]` 12 | 13 | [.description] 14 | -- 15 | Set to true to enable logging to Cloudwatch 16 | --|boolean 17 | |`true` 18 | 19 | 20 | a| [[quarkus-log-cloudwatch-level]]`link:#quarkus-log-cloudwatch-level[quarkus.log.cloudwatch.level]` 21 | 22 | [.description] 23 | -- 24 | The java.util.logging level 25 | --|link:https://docs.jboss.org/jbossas/javadoc/7.1.2.Final/org/jboss/logmanager/Level.html[Level] 26 | 27 | |`WARN` 28 | 29 | 30 | a| [[quarkus-log-cloudwatch-access-key-id]]`link:#quarkus-log-cloudwatch-access-key-id[quarkus.log.cloudwatch.access-key-id]` 31 | 32 | [.description] 33 | -- 34 | The AWS access key 35 | --|string 36 | |`` 37 | 38 | 39 | a| [[quarkus-log-cloudwatch-access-key-]]`link:#quarkus-log-cloudwatch-access-key-[quarkus.log.cloudwatch.access-key-secret]` 40 | 41 | [.description] 42 | -- 43 | The AWS secret for the key 44 | --|string 45 | |`` 46 | 47 | 48 | a| [[quarkus-log-cloudwatch-region]]`link:#quarkus-log-cloudwatch-region[quarkus.log.cloudwatch.region]` 49 | 50 | [.description] 51 | -- 52 | The region to log to 53 | --|string 54 | |`` 55 | 56 | 57 | a| [[quarkus-log-cloudwatch-log-group]]`link:#quarkus-log-cloudwatch-log-group[quarkus.log.cloudwatch.log-group]` 58 | 59 | [.description] 60 | -- 61 | The log-group to log to. This has to exist 62 | --|string 63 | |`` 64 | 65 | 66 | a| [[quarkus-log-cloudwatch-log-stream-name]]`link:#quarkus-log-cloudwatch-log-stream-name[quarkus.log.cloudwatch.log-stream-name]` 67 | 68 | [.description] 69 | -- 70 | The log-stream name. This is created if it does not exist 71 | --|string 72 | |`` 73 | 74 | 75 | a| [[quarkus-log-cloudwatch-batch-size]]`link:#quarkus-log-cloudwatch-batch-size[quarkus.log.cloudwatch.batch-size]` 76 | 77 | [.description] 78 | -- 79 | Number of log events sent to CloudWatch per batch. 80 | Defaults to 10,000 which is the maximum number of log events per batch allowed by CloudWatch. 81 | --|int 82 | |`10000` 83 | 84 | 85 | a| [[quarkus-log-cloudwatch-batch-period]]`link:#quarkus-log-cloudwatch-batch-period[quarkus.log.cloudwatch.batch-period]` 86 | 87 | [.description] 88 | -- 89 | Period between two batch executions. 90 | --|Duration 91 | |`5s` 92 | 93 | 94 | a| [[quarkus-log-cloudwatch-max-queue-size]]`link:#quarkus-log-cloudwatch-max-queue-size[quarkus.log.cloudwatch.max-queue-size]` 95 | 96 | [.description] 97 | -- 98 | Optional maximum size of the log events queue. 99 | If this is not set, the queue will have a capacity of `Integer#MAX_VALUE`. 100 | --|int 101 | | 102 | 103 | 104 | a| [[quarkus-log-cloudwatch-service-environment]]`link:#quarkus-log-cloudwatch-service-environment[quarkus.log.cloudwatch.service-environment]` 105 | 106 | [.description] 107 | -- 108 | Optional service environment added to each log record when available. 109 | --|string 110 | | 111 | 112 | 113 | a| [[quarkus-log-cloudwatch-api-call-timeout]]`link:#quarkus-log-cloudwatch-api-call-timeout[quarkus.log.cloudwatch.api-call-timeout]` 114 | 115 | [.description] 116 | -- 117 | Optional amount of time to allow the CloudWatch client to complete the execution of an API call. 118 | This timeout covers the entire client execution except for marshalling. 119 | This includes request handler execution, all HTTP requests including retries, unmarshalling, etc. 120 | This value should always be positive, if present. 121 | --|Duration 122 | | 123 | 124 | a| [[quarkus-log-cloudwatch-default-credentials-provider-enabled]]`link:#quarkus-log-cloudwatch-default-credentials-provider-enabled[quarkus.log.cloudwatch.default-credentials-provider.enabled]` 125 | 126 | [.description] 127 | -- 128 | This property allows you to configure the default aws credentials provider for the Quarkus CloudWatch logging feature. By default, it is set to false, indicating that the default credentials provider is disabled. 129 | --|boolean 130 | |`false` 131 | 132 | |=== 133 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/index.adoc: -------------------------------------------------------------------------------- 1 | = Quarkus - Logging Cloudwatch 2 | :extension-status: preview 3 | 4 | This https://quarkus.io[Quarkus extension] provides support for sending log records to Amazon CloudWatch. 5 | Just add it to your pom, enable it in `application.properties` and you are ready to go. 6 | 7 | == Installation 8 | 9 | If you want to use this extension, you need to add the `io.quarkiverse.logging.cloudwatch:quarkus-logging-cloudwatch` extension first. 10 | In your `pom.xml` file, add: 11 | 12 | [source,xml] 13 | ---- 14 | 15 | io.quarkiverse.logging.cloudwatch 16 | quarkus-logging-cloudwatch 17 | 18 | ---- 19 | 20 | [[extension-configuration-reference]] 21 | == Extension Configuration Reference 22 | 23 | include::config.adoc[leveloffset=+1, opts=optional] 24 | -------------------------------------------------------------------------------- /integration-tests/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse.logging.cloudwatch 6 | quarkus-logging-cloudwatch-parent 7 | 6.13.1-SNAPSHOT 8 | 9 | quarkus-logging-cloudwatch-integration-tests 10 | Quarkus - Logging Cloudwatch - Integration Tests 11 | 12 | 13 | io.quarkus 14 | quarkus-resteasy 15 | 16 | 17 | io.quarkiverse.logging.cloudwatch 18 | quarkus-logging-cloudwatch 19 | ${project.version} 20 | 21 | 22 | org.testcontainers 23 | localstack 24 | 1.20.6 25 | test 26 | 27 | 28 | io.quarkus 29 | quarkus-junit5 30 | test 31 | 32 | 33 | io.rest-assured 34 | rest-assured 35 | test 36 | 37 | 38 | 39 | 40 | 41 | io.quarkus 42 | quarkus-maven-plugin 43 | 44 | 45 | 46 | build 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | native-image 56 | 57 | 58 | native 59 | 60 | 61 | 62 | 63 | 64 | maven-surefire-plugin 65 | 66 | ${native.surefire.skip} 67 | 68 | 69 | 70 | maven-failsafe-plugin 71 | 72 | 73 | 74 | integration-test 75 | verify 76 | 77 | 78 | 79 | ${project.build.directory}/${project.build.finalName}-runner 80 | org.jboss.logmanager.LogManager 81 | ${maven.home} 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | native 91 | 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /integration-tests/src/main/java/io/quarkiverse/logging/cloudwatch/it/LoggingCloudwatchHandlerResource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Licensed to the Apache Software Foundation (ASF) under one or more 3 | * contributor license agreements. See the NOTICE file distributed with 4 | * this work for additional information regarding copyright ownership. 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 6 | * (the "License"); you may not use this file except in compliance with 7 | * the License. You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.quarkiverse.logging.cloudwatch.it; 18 | 19 | import jakarta.enterprise.context.ApplicationScoped; 20 | import jakarta.ws.rs.GET; 21 | import jakarta.ws.rs.Path; 22 | 23 | import org.jboss.logging.MDC; 24 | 25 | import io.quarkus.logging.Log; 26 | 27 | @Path("/logging-cloudwatch") 28 | @ApplicationScoped 29 | public class LoggingCloudwatchHandlerResource { 30 | 31 | @GET 32 | public String hello() { 33 | MDC.put("mdc-key", "mdc-value"); 34 | Log.info("hello cloudwatch"); 35 | 36 | return "Hello logging-cloudwatch"; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /integration-tests/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | quarkus.log.level=INFO 2 | quarkus.log.cloudwatch.enabled=true 3 | quarkus.log.cloudwatch.access-key-id=keyId 4 | quarkus.log.cloudwatch.access-key-secret=keySecret 5 | quarkus.log.cloudwatch.region=eu-central-1 6 | quarkus.log.cloudwatch.log-group=logGroup 7 | quarkus.log.cloudwatch.log-stream-name=logStreamName 8 | quarkus.log.cloudwatch.level=WARN -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.1.1 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | # e.g. to debug Maven itself, use 32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | # ---------------------------------------------------------------------------- 35 | 36 | if [ -z "$MAVEN_SKIP_RC" ] ; then 37 | 38 | if [ -f /usr/local/etc/mavenrc ] ; then 39 | . /usr/local/etc/mavenrc 40 | fi 41 | 42 | if [ -f /etc/mavenrc ] ; then 43 | . /etc/mavenrc 44 | fi 45 | 46 | if [ -f "$HOME/.mavenrc" ] ; then 47 | . "$HOME/.mavenrc" 48 | fi 49 | 50 | fi 51 | 52 | # OS specific support. $var _must_ be set to either true or false. 53 | cygwin=false; 54 | darwin=false; 55 | mingw=false 56 | case "`uname`" in 57 | CYGWIN*) cygwin=true ;; 58 | MINGW*) mingw=true;; 59 | Darwin*) darwin=true 60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 62 | if [ -z "$JAVA_HOME" ]; then 63 | if [ -x "/usr/libexec/java_home" ]; then 64 | JAVA_HOME="`/usr/libexec/java_home`"; export JAVA_HOME 65 | else 66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME 67 | fi 68 | fi 69 | ;; 70 | esac 71 | 72 | if [ -z "$JAVA_HOME" ] ; then 73 | if [ -r /etc/gentoo-release ] ; then 74 | JAVA_HOME=`java-config --jre-home` 75 | fi 76 | fi 77 | 78 | # For Cygwin, ensure paths are in UNIX format before anything is touched 79 | if $cygwin ; then 80 | [ -n "$JAVA_HOME" ] && 81 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 82 | [ -n "$CLASSPATH" ] && 83 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 84 | fi 85 | 86 | # For Mingw, ensure paths are in UNIX format before anything is touched 87 | if $mingw ; then 88 | [ -n "$JAVA_HOME" ] && 89 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 90 | fi 91 | 92 | if [ -z "$JAVA_HOME" ]; then 93 | javaExecutable="`which javac`" 94 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 95 | # readlink(1) is not available as standard on Solaris 10. 96 | readLink=`which readlink` 97 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 98 | if $darwin ; then 99 | javaHome="`dirname \"$javaExecutable\"`" 100 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 101 | else 102 | javaExecutable="`readlink -f \"$javaExecutable\"`" 103 | fi 104 | javaHome="`dirname \"$javaExecutable\"`" 105 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 106 | JAVA_HOME="$javaHome" 107 | export JAVA_HOME 108 | fi 109 | fi 110 | fi 111 | 112 | if [ -z "$JAVACMD" ] ; then 113 | if [ -n "$JAVA_HOME" ] ; then 114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 115 | # IBM's JDK on AIX uses strange locations for the executables 116 | JAVACMD="$JAVA_HOME/jre/sh/java" 117 | else 118 | JAVACMD="$JAVA_HOME/bin/java" 119 | fi 120 | else 121 | JAVACMD="`\\unset -f command; \\command -v java`" 122 | fi 123 | fi 124 | 125 | if [ ! -x "$JAVACMD" ] ; then 126 | echo "Error: JAVA_HOME is not defined correctly." >&2 127 | echo " We cannot execute $JAVACMD" >&2 128 | exit 1 129 | fi 130 | 131 | if [ -z "$JAVA_HOME" ] ; then 132 | echo "Warning: JAVA_HOME environment variable is not set." 133 | fi 134 | 135 | # traverses directory structure from process work directory to filesystem root 136 | # first directory with .mvn subdirectory is considered project base directory 137 | find_maven_basedir() { 138 | if [ -z "$1" ] 139 | then 140 | echo "Path not specified to find_maven_basedir" 141 | return 1 142 | fi 143 | 144 | basedir="$1" 145 | wdir="$1" 146 | while [ "$wdir" != '/' ] ; do 147 | if [ -d "$wdir"/.mvn ] ; then 148 | basedir=$wdir 149 | break 150 | fi 151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 152 | if [ -d "${wdir}" ]; then 153 | wdir=`cd "$wdir/.."; pwd` 154 | fi 155 | # end of workaround 156 | done 157 | printf '%s' "$(cd "$basedir"; pwd)" 158 | } 159 | 160 | # concatenates all lines of a file 161 | concat_lines() { 162 | if [ -f "$1" ]; then 163 | echo "$(tr -s '\n' ' ' < "$1")" 164 | fi 165 | } 166 | 167 | BASE_DIR=$(find_maven_basedir "$(dirname $0)") 168 | if [ -z "$BASE_DIR" ]; then 169 | exit 1; 170 | fi 171 | 172 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR 173 | if [ "$MVNW_VERBOSE" = true ]; then 174 | echo $MAVEN_PROJECTBASEDIR 175 | fi 176 | 177 | ########################################################################################## 178 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 179 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 180 | ########################################################################################## 181 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 182 | if [ "$MVNW_VERBOSE" = true ]; then 183 | echo "Found .mvn/wrapper/maven-wrapper.jar" 184 | fi 185 | else 186 | if [ "$MVNW_VERBOSE" = true ]; then 187 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 188 | fi 189 | if [ -n "$MVNW_REPOURL" ]; then 190 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 191 | else 192 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 193 | fi 194 | while IFS="=" read key value; do 195 | case "$key" in (wrapperUrl) wrapperUrl="$value"; break ;; 196 | esac 197 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 198 | if [ "$MVNW_VERBOSE" = true ]; then 199 | echo "Downloading from: $wrapperUrl" 200 | fi 201 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 202 | if $cygwin; then 203 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 204 | fi 205 | 206 | if command -v wget > /dev/null; then 207 | QUIET="--quiet" 208 | if [ "$MVNW_VERBOSE" = true ]; then 209 | echo "Found wget ... using wget" 210 | QUIET="" 211 | fi 212 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 213 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" 214 | else 215 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" 216 | fi 217 | [ $? -eq 0 ] || rm -f "$wrapperJarPath" 218 | elif command -v curl > /dev/null; then 219 | QUIET="--silent" 220 | if [ "$MVNW_VERBOSE" = true ]; then 221 | echo "Found curl ... using curl" 222 | QUIET="" 223 | fi 224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L 226 | else 227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L 228 | fi 229 | [ $? -eq 0 ] || rm -f "$wrapperJarPath" 230 | else 231 | if [ "$MVNW_VERBOSE" = true ]; then 232 | echo "Falling back to using Java to download" 233 | fi 234 | javaSource="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 235 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" 236 | # For Cygwin, switch paths to Windows format before running javac 237 | if $cygwin; then 238 | javaSource=`cygpath --path --windows "$javaSource"` 239 | javaClass=`cygpath --path --windows "$javaClass"` 240 | fi 241 | if [ -e "$javaSource" ]; then 242 | if [ ! -e "$javaClass" ]; then 243 | if [ "$MVNW_VERBOSE" = true ]; then 244 | echo " - Compiling MavenWrapperDownloader.java ..." 245 | fi 246 | # Compiling the Java class 247 | ("$JAVA_HOME/bin/javac" "$javaSource") 248 | fi 249 | if [ -e "$javaClass" ]; then 250 | # Running the downloader 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo " - Running MavenWrapperDownloader.java ..." 253 | fi 254 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 255 | fi 256 | fi 257 | fi 258 | fi 259 | ########################################################################################## 260 | # End of extension 261 | ########################################################################################## 262 | 263 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 264 | 265 | # For Cygwin, switch paths to Windows format before running java 266 | if $cygwin; then 267 | [ -n "$JAVA_HOME" ] && 268 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 269 | [ -n "$CLASSPATH" ] && 270 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 271 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 272 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 273 | fi 274 | 275 | # Provide a "standardized" way to retrieve the CLI args that will 276 | # work with both Windows and non-Windows executions. 277 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 278 | export MAVEN_CMD_LINE_ARGS 279 | 280 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 281 | 282 | exec "$JAVACMD" \ 283 | $MAVEN_OPTS \ 284 | $MAVEN_DEBUG_OPTS \ 285 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 286 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 287 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 288 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM http://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Apache Maven Wrapper startup batch script, version 3.1.1 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 30 | @REM e.g. to debug Maven itself, use 31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 33 | @REM ---------------------------------------------------------------------------- 34 | 35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 36 | @echo off 37 | @REM set title of command window 38 | title %0 39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 41 | 42 | @REM set %HOME% to equivalent of $HOME 43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 44 | 45 | @REM Execute a user defined script before this one 46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* 49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* 50 | :skipRcPre 51 | 52 | @setlocal 53 | 54 | set ERROR_CODE=0 55 | 56 | @REM To isolate internal variables from possible post scripts, we use another setlocal 57 | @setlocal 58 | 59 | @REM ==== START VALIDATION ==== 60 | if not "%JAVA_HOME%" == "" goto OkJHome 61 | 62 | echo. 63 | echo Error: JAVA_HOME not found in your environment. >&2 64 | echo Please set the JAVA_HOME variable in your environment to match the >&2 65 | echo location of your Java installation. >&2 66 | echo. 67 | goto error 68 | 69 | :OkJHome 70 | if exist "%JAVA_HOME%\bin\java.exe" goto init 71 | 72 | echo. 73 | echo Error: JAVA_HOME is set to an invalid directory. >&2 74 | echo JAVA_HOME = "%JAVA_HOME%" >&2 75 | echo Please set the JAVA_HOME variable in your environment to match the >&2 76 | echo location of your Java installation. >&2 77 | echo. 78 | goto error 79 | 80 | @REM ==== END VALIDATION ==== 81 | 82 | :init 83 | 84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 85 | @REM Fallback to current working directory if not found. 86 | 87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 89 | 90 | set EXEC_DIR=%CD% 91 | set WDIR=%EXEC_DIR% 92 | :findBaseDir 93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 94 | cd .. 95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 96 | set WDIR=%CD% 97 | goto findBaseDir 98 | 99 | :baseDirFound 100 | set MAVEN_PROJECTBASEDIR=%WDIR% 101 | cd "%EXEC_DIR%" 102 | goto endDetectBaseDir 103 | 104 | :baseDirNotFound 105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 106 | cd "%EXEC_DIR%" 107 | 108 | :endDetectBaseDir 109 | 110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 111 | 112 | @setlocal EnableExtensions EnableDelayedExpansion 113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 115 | 116 | :endReadAdditionalConfig 117 | 118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 121 | 122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 123 | 124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B 126 | ) 127 | 128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 130 | if exist %WRAPPER_JAR% ( 131 | if "%MVNW_VERBOSE%" == "true" ( 132 | echo Found %WRAPPER_JAR% 133 | ) 134 | ) else ( 135 | if not "%MVNW_REPOURL%" == "" ( 136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar" 137 | ) 138 | if "%MVNW_VERBOSE%" == "true" ( 139 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 140 | echo Downloading from: %WRAPPER_URL% 141 | ) 142 | 143 | powershell -Command "&{"^ 144 | "$webclient = new-object System.Net.WebClient;"^ 145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 147 | "}"^ 148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ 149 | "}" 150 | if "%MVNW_VERBOSE%" == "true" ( 151 | echo Finished downloading %WRAPPER_JAR% 152 | ) 153 | ) 154 | @REM End of extension 155 | 156 | @REM Provide a "standardized" way to retrieve the CLI args that will 157 | @REM work with both Windows and non-Windows executions. 158 | set MAVEN_CMD_LINE_ARGS=%* 159 | 160 | %MAVEN_JAVA_EXE% ^ 161 | %JVM_CONFIG_MAVEN_PROPS% ^ 162 | %MAVEN_OPTS% ^ 163 | %MAVEN_DEBUG_OPTS% ^ 164 | -classpath %WRAPPER_JAR% ^ 165 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ 166 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 167 | if ERRORLEVEL 1 goto error 168 | goto end 169 | 170 | :error 171 | set ERROR_CODE=1 172 | 173 | :end 174 | @endlocal & set ERROR_CODE=%ERROR_CODE% 175 | 176 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost 177 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 178 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" 179 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" 180 | :skipRcPost 181 | 182 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 183 | if "%MAVEN_BATCH_PAUSE%"=="on" pause 184 | 185 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% 186 | 187 | cmd /C exit /B %ERROR_CODE% 188 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse 6 | quarkiverse-parent 7 | 20 8 | 9 | io.quarkiverse.logging.cloudwatch 10 | quarkus-logging-cloudwatch-parent 11 | 6.13.1-SNAPSHOT 12 | pom 13 | Quarkus - Logging Cloudwatch - Parent 14 | 15 | deployment 16 | runtime 17 | 18 | 19 | scm:git:git@github.com:quarkiverse/quarkus-logging-cloudwatch.git 20 | scm:git:git@github.com:quarkiverse/quarkus-logging-cloudwatch.git 21 | https://github.com/quarkiverse/quarkus-logging-cloudwatch 22 | HEAD 23 | 24 | 25 | 3.8.1 26 | true 27 | 17 28 | 17 29 | UTF-8 30 | UTF-8 31 | 3.21.3 32 | 2.31.27 33 | 3.4.0 34 | 35 | 36 | 37 | 38 | io.quarkus 39 | quarkus-bom 40 | ${quarkus.version} 41 | pom 42 | import 43 | 44 | 45 | software.amazon.awssdk 46 | bom 47 | ${awssdk.version} 48 | pom 49 | import 50 | 51 | 52 | io.quarkiverse.amazonservices 53 | quarkus-amazon-common 54 | ${aws.common.version} 55 | pom 56 | import 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | io.quarkus 65 | quarkus-maven-plugin 66 | ${quarkus.version} 67 | 68 | 69 | maven-compiler-plugin 70 | ${compiler-plugin.version} 71 | 72 | 73 | net.revelc.code.formatter 74 | formatter-maven-plugin 75 | 2.26.0 76 | 77 | 78 | 79 | format 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | it 90 | 91 | 92 | performRelease 93 | !true 94 | 95 | 96 | 97 | integration-tests 98 | 99 | 100 | 101 | native 102 | 103 | native 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /runtime/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | io.quarkiverse.logging.cloudwatch 6 | quarkus-logging-cloudwatch-parent 7 | 6.13.1-SNAPSHOT 8 | 9 | quarkus-logging-cloudwatch 10 | Quarkus - Logging Cloudwatch - Runtime 11 | Send your application logs to AWS CloudWatch 12 | 13 | 14 | io.quarkus 15 | quarkus-arc 16 | 17 | 18 | io.quarkus 19 | quarkus-apache-httpclient 20 | 21 | 22 | io.quarkiverse.amazonservices 23 | quarkus-amazon-common 24 | 25 | 26 | software.amazon.awssdk 27 | cloudwatchlogs 28 | 29 | 30 | co.elastic.logging 31 | ecs-logging-core 32 | 1.7.0 33 | 34 | 35 | org.graalvm.sdk 36 | graal-sdk 37 | 38 | 39 | org.junit.jupiter 40 | junit-jupiter-api 41 | test 42 | 43 | 44 | 45 | 46 | 47 | io.quarkus 48 | quarkus-extension-maven-plugin 49 | ${quarkus.version} 50 | 51 | 52 | compile 53 | 54 | extension-descriptor 55 | 56 | 57 | ${project.groupId}:${project.artifactId}-deployment:${project.version} 58 | 59 | 60 | 61 | 62 | 63 | 64 | maven-compiler-plugin 65 | 66 | 67 | 68 | io.quarkus 69 | quarkus-extension-processor 70 | ${quarkus.version} 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/logging/cloudwatch/LoggingCloudWatchConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.quarkiverse.logging.cloudwatch; 18 | 19 | import java.time.Duration; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Optional; 23 | import java.util.logging.Level; 24 | 25 | import io.quarkus.runtime.annotations.ConfigPhase; 26 | import io.quarkus.runtime.annotations.ConfigRoot; 27 | import io.smallrye.config.ConfigMapping; 28 | import io.smallrye.config.WithDefault; 29 | import io.smallrye.config.WithName; 30 | 31 | /** 32 | * Configuration for CloudWatch logging. 33 | */ 34 | @ConfigRoot(phase = ConfigPhase.RUN_TIME) 35 | @ConfigMapping(prefix = "quarkus.log.cloudwatch") 36 | public interface LoggingCloudWatchConfig { 37 | 38 | /** 39 | * Determine whether to enable the Cloudwatch logging extension. 40 | */ 41 | @WithName("enabled") 42 | @WithDefault("true") 43 | boolean enabled(); 44 | 45 | /** 46 | * The AWS CloudWatch access key id 47 | */ 48 | @WithName("access-key-id") 49 | Optional accessKeyId(); 50 | 51 | /** 52 | * The AWS CloudWatch access key secret 53 | */ 54 | @WithName("access-key-secret") 55 | Optional accessKeySecret(); 56 | 57 | /** 58 | * Region of deployment 59 | */ 60 | @WithName("region") 61 | Optional region(); 62 | 63 | /** 64 | * CW log group 65 | */ 66 | @WithName("log-group") 67 | Optional logGroup(); 68 | 69 | /** 70 | * CW log stream 71 | */ 72 | @WithName("log-stream-name") 73 | Optional logStreamName(); 74 | 75 | /** 76 | * The CW log level. 77 | */ 78 | @WithName("level") 79 | @WithDefault("WARN") 80 | Level level(); 81 | 82 | /** 83 | * Number of log events sent to CloudWatch per batch. 84 | * Defaults to 10,000 which is the maximum number of log events per batch allowed by CloudWatch. 85 | */ 86 | @WithDefault("10000") 87 | int batchSize(); 88 | 89 | /** 90 | * Period between two batch executions. 91 | * Defaults to 5 seconds. 92 | */ 93 | @WithDefault("5s") 94 | Duration batchPeriod(); 95 | 96 | /** 97 | * Maximum size of the log events queue. 98 | * If this is not set, the queue will have a capacity of {@link Integer#MAX_VALUE}. 99 | */ 100 | @WithName("max-queue-size") 101 | Optional maxQueueSize(); 102 | 103 | /** 104 | * Service environment added as a {@code service.environment} field to each log record when available. 105 | */ 106 | @WithName("service-environment") 107 | Optional serviceEnvironment(); 108 | 109 | /** 110 | * Amount of time to allow the CloudWatch client to complete the execution of an API call. This timeout covers the 111 | * entire client execution except for marshalling. This includes request handler execution, all HTTP requests 112 | * including retries, unmarshalling, etc. This value should always be positive, if present. 113 | */ 114 | @WithName("api-call-timeout") 115 | Optional apiCallTimeout(); 116 | 117 | /** 118 | * Default credentials provider enabled added as a {@code quarkus.log.cloudwatch.default-credentials-provider.enabled} 119 | */ 120 | @WithName("default-credentials-provider.enabled") 121 | @WithDefault("false") 122 | boolean defaultCredentialsProviderEnabled(); 123 | 124 | /** 125 | * Endpoint override added as {@code endpoint-override} 126 | */ 127 | @WithName("endpoint-override") 128 | Optional endpointOverride(); 129 | 130 | /* 131 | * We need to validate that the values are present, even if marked as optional. 132 | * We need to mark them as optional, as otherwise the config would mark them 133 | * as bad even before the extension can check if the values are needed at all. 134 | */ 135 | default List validate() { 136 | List errors = new ArrayList<>(); 137 | if (!defaultCredentialsProviderEnabled()) { 138 | if (accessKeyId().isEmpty()) { 139 | errors.add("quarkus.log.cloudwatch.access-key-id"); 140 | } 141 | if (accessKeySecret().isEmpty()) { 142 | errors.add("quarkus.log.cloudwatch.access-key-secret"); 143 | } 144 | } 145 | if (region().isEmpty()) { 146 | errors.add("quarkus.log.cloudwatch.region"); 147 | } 148 | if (logGroup().isEmpty()) { 149 | errors.add("quarkus.log.cloudwatch.log-group"); 150 | } 151 | if (logStreamName().isEmpty()) { 152 | errors.add("quarkus.log.cloudwatch.log-stream-name"); 153 | } 154 | return errors; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/logging/cloudwatch/LoggingCloudWatchHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.quarkiverse.logging.cloudwatch; 18 | 19 | import java.time.Duration; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Optional; 23 | import java.util.concurrent.*; 24 | import java.util.logging.Handler; 25 | import java.util.logging.LogRecord; 26 | 27 | import org.jboss.logging.Logger; 28 | 29 | import io.quarkiverse.logging.cloudwatch.format.ElasticCommonSchemaLogFormatter; 30 | import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient; 31 | import software.amazon.awssdk.services.cloudwatchlogs.model.InputLogEvent; 32 | import software.amazon.awssdk.services.cloudwatchlogs.model.InvalidSequenceTokenException; 33 | import software.amazon.awssdk.services.cloudwatchlogs.model.PutLogEventsRequest; 34 | 35 | class LoggingCloudWatchHandler extends Handler { 36 | 37 | private static final Logger LOGGER = Logger.getLogger(LoggingCloudWatchHandler.class); 38 | private static final int BATCH_MAX_ATTEMPTS = 10; 39 | 40 | private CloudWatchLogsClient cloudWatchLogsClient; 41 | private String logStreamName; 42 | private String logGroupName; 43 | private String sequenceToken; 44 | private int batchSize; 45 | private Optional serviceEnvironment; 46 | 47 | private BlockingQueue eventBuffer; 48 | 49 | private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); 50 | 51 | private Publisher publisher; 52 | 53 | LoggingCloudWatchHandler() { 54 | } 55 | 56 | LoggingCloudWatchHandler(CloudWatchLogsClient cloudWatchLogsClient, String logGroup, String logStreamName, String token, 57 | Optional maxQueueSize, int batchSize, Duration batchPeriod, Optional serviceEnvironment) { 58 | this.logGroupName = logGroup; 59 | this.cloudWatchLogsClient = cloudWatchLogsClient; 60 | this.logStreamName = logStreamName; 61 | this.sequenceToken = token; 62 | eventBuffer = maxQueueSize.> map(LinkedBlockingQueue::new) 63 | .orElseGet(LinkedBlockingQueue::new); 64 | this.batchSize = batchSize; 65 | this.serviceEnvironment = serviceEnvironment; 66 | 67 | this.publisher = new Publisher(); 68 | scheduler.scheduleAtFixedRate(publisher, 5, batchPeriod.toMillis(), TimeUnit.MILLISECONDS); 69 | } 70 | 71 | @Override 72 | public void publish(LogRecord record) { 73 | if (isBelowThreshold(record)) { 74 | return; 75 | } 76 | 77 | String body = formatMessage(record); 78 | 79 | InputLogEvent logEvent = InputLogEvent.builder() 80 | .message(body) 81 | .timestamp(System.currentTimeMillis()) 82 | .build(); 83 | 84 | // Queue this up, so that it can be flushed later in batch asynchronously 85 | boolean inserted = eventBuffer.offer(logEvent); 86 | if (!inserted) { 87 | LOGGER.warn( 88 | "Maximum size of the CloudWatch log events queue reached. Consider increasing that size from the configuration."); 89 | } 90 | } 91 | 92 | String formatMessage(LogRecord record) { 93 | String format; 94 | if (isLogWithoutFormatPlaceholder(record)) { 95 | // e.g. log.info("blabla") 96 | format = String.format("%s", record.getMessage()); 97 | } else { 98 | // e.g. log.info("info logging: %", info) 99 | format = String.format(record.getMessage(), record.getParameters()); 100 | } 101 | 102 | record.setMessage(format); 103 | ElasticCommonSchemaLogFormatter formatter = new ElasticCommonSchemaLogFormatter(serviceEnvironment); 104 | 105 | return formatter.format(record); 106 | } 107 | 108 | private static boolean isLogWithoutFormatPlaceholder(LogRecord record) { 109 | return record.getParameters() == null; 110 | } 111 | 112 | /** 113 | * Skip messages that are below the configured threshold. 114 | */ 115 | boolean isBelowThreshold(LogRecord record) { 116 | return record.getLevel().intValue() < getLevel().intValue(); 117 | } 118 | 119 | @Override 120 | public void flush() { 121 | } 122 | 123 | @Override 124 | public void close() throws SecurityException { 125 | LOGGER.info("Shutting down and awaiting termination"); 126 | shutdownAndAwaitTermination(scheduler); 127 | 128 | LOGGER.info("Trying to send of last log messages after shutdown."); 129 | publisher.run(); 130 | } 131 | 132 | private class Publisher implements Runnable { 133 | 134 | @Override 135 | public void run() { 136 | try { 137 | // First, let's poll from the queue the events that will be part of the batch. 138 | List events = new ArrayList<>(Math.min(eventBuffer.size(), batchSize)); 139 | eventBuffer.drainTo(events, batchSize); 140 | if (events.size() > 0) { 141 | 142 | // The sequence token needed for this request is set below. 143 | PutLogEventsRequest request = PutLogEventsRequest.builder() 144 | .logGroupName(logGroupName) 145 | .logStreamName(logStreamName) 146 | .logEvents(events) 147 | .build(); 148 | 149 | /* 150 | * The current sequence token may not be valid if it was used by another application or pod. 151 | * If that happens, we'll retry using the token from the InvalidSequenceTokenException. 152 | */ 153 | for (int i = 1; i <= BATCH_MAX_ATTEMPTS; i++) { 154 | 155 | request = request.toBuilder() 156 | .sequenceToken(sequenceToken) 157 | .build(); 158 | 159 | try { 160 | /* 161 | * It's time to put the log events into CloudWatch. 162 | * If that works, we'll use the sequence token from the response for the next put call. 163 | */ 164 | sequenceToken = cloudWatchLogsClient.putLogEvents(request).nextSequenceToken(); 165 | // The sequence token was accepted, we don't need to retry. 166 | break; 167 | } catch (InvalidSequenceTokenException e) { 168 | LOGGER.debugf("PutLogEvents call failed because of an invalid sequence token", e); 169 | 170 | // We'll use the sequence token from the exception for the next put call. 171 | sequenceToken = e.expectedSequenceToken(); 172 | 173 | // If the last attempt failed, the log events from the current batch are lost. 174 | if (i == BATCH_MAX_ATTEMPTS) { 175 | LOGGER.warn( 176 | "Too many retries for a PutLogEvents call, log events from the current batch will not be sent to CloudWatch"); 177 | } 178 | } 179 | } 180 | } 181 | } catch (Throwable t) { 182 | LOGGER.error("PutLogEvents call failed, log events from the current batch will not be sent to CloudWatch", t); 183 | } 184 | } 185 | } 186 | 187 | private void shutdownAndAwaitTermination(ExecutorService pool) { 188 | pool.shutdown(); // Disable new tasks from being submitted 189 | try { 190 | // Wait a while for existing tasks to terminate 191 | if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { 192 | pool.shutdownNow(); // Cancel currently executing tasks 193 | // Wait a while for tasks to respond to being cancelled 194 | if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { 195 | System.err.println("Pool did not terminate"); 196 | } 197 | } 198 | } catch (InterruptedException ie) { 199 | // (Re-)Cancel if current thread also interrupted 200 | pool.shutdownNow(); 201 | // Preserve interrupt status 202 | Thread.currentThread().interrupt(); 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/logging/cloudwatch/LoggingCloudWatchHandlerValueFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.quarkiverse.logging.cloudwatch; 18 | 19 | import static io.quarkus.runtime.LaunchMode.DEVELOPMENT; 20 | 21 | import java.net.URI; 22 | import java.util.List; 23 | import java.util.Optional; 24 | import java.util.logging.Handler; 25 | 26 | import org.jboss.logging.Logger; 27 | 28 | import io.quarkiverse.logging.cloudwatch.auth.CloudWatchCredentialsProvider; 29 | import io.quarkus.runtime.LaunchMode; 30 | import io.quarkus.runtime.RuntimeValue; 31 | import io.quarkus.runtime.annotations.Recorder; 32 | import software.amazon.awssdk.core.client.config.ClientOverrideConfiguration; 33 | import software.amazon.awssdk.regions.Region; 34 | import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClient; 35 | import software.amazon.awssdk.services.cloudwatchlogs.CloudWatchLogsClientBuilder; 36 | import software.amazon.awssdk.services.cloudwatchlogs.model.CreateLogStreamRequest; 37 | import software.amazon.awssdk.services.cloudwatchlogs.model.DescribeLogStreamsRequest; 38 | import software.amazon.awssdk.services.cloudwatchlogs.model.LogStream; 39 | 40 | @Recorder 41 | public class LoggingCloudWatchHandlerValueFactory { 42 | 43 | private static final Logger LOGGER = Logger.getLogger(LoggingCloudWatchHandlerValueFactory.class); 44 | 45 | public RuntimeValue> create(final LoggingCloudWatchConfig config) { 46 | if (!config.enabled()) { 47 | LOGGER.info("Quarkus Logging Cloudwatch Extension is not enabled"); 48 | return new RuntimeValue<>(Optional.empty()); 49 | } 50 | 51 | LOGGER.info("Initializing Quarkus Logging Cloudwatch Extension"); 52 | 53 | List errors = config.validate(); 54 | if (!errors.isEmpty()) { 55 | String errorMsg = "The Quarkus Logging Cloudwatch extension is unable to start because of missing configuration values: " 56 | + String.join(", ", errors); 57 | if (LaunchMode.current() == DEVELOPMENT) { 58 | LOGGER.error(errorMsg); 59 | return new RuntimeValue<>(Optional.empty()); 60 | } else { 61 | throw new IllegalStateException(errorMsg); 62 | } 63 | } 64 | 65 | LOGGER.infof("Logging to log-group: %s and log-stream: %s", config.logGroup().get(), config.logStreamName().get()); 66 | 67 | CloudWatchLogsClientBuilder cloudWatchLogsClientBuilder = CloudWatchLogsClient.builder() 68 | .credentialsProvider(new CloudWatchCredentialsProvider(config)) 69 | .region(Region.of(config.region().get())); 70 | if (config.apiCallTimeout().isPresent()) { 71 | cloudWatchLogsClientBuilder = cloudWatchLogsClientBuilder.overrideConfiguration( 72 | ClientOverrideConfiguration.builder().apiCallTimeout(config.apiCallTimeout().get()).build()); 73 | } 74 | if (config.endpointOverride().isPresent()) { 75 | cloudWatchLogsClientBuilder.endpointOverride(URI.create(config.endpointOverride().get())); 76 | } 77 | 78 | CloudWatchLogsClient cloudWatchLogsClient = cloudWatchLogsClientBuilder.build(); 79 | 80 | String token = createLogStreamIfNeeded(cloudWatchLogsClient, config); 81 | 82 | LoggingCloudWatchHandler handler = new LoggingCloudWatchHandler(cloudWatchLogsClient, config.logGroup().get(), 83 | config.logStreamName().get(), token, config.maxQueueSize(), config.batchSize(), config.batchPeriod(), 84 | config.serviceEnvironment()); 85 | handler.setLevel(config.level()); 86 | 87 | return new RuntimeValue<>(Optional.of(handler)); 88 | } 89 | 90 | private String createLogStreamIfNeeded(CloudWatchLogsClient cloudWatchLogsClient, LoggingCloudWatchConfig config) { 91 | String token = null; 92 | 93 | DescribeLogStreamsRequest describeLogStreamsRequest = DescribeLogStreamsRequest.builder() 94 | .logGroupName(config.logGroup().get()) 95 | // We need to filter down, as CW returns by default only 50 streams and ours may not be in it. 96 | .logStreamNamePrefix(config.logStreamName().get()) 97 | .build(); 98 | List logStreams = cloudWatchLogsClient.describeLogStreams(describeLogStreamsRequest).logStreams(); 99 | 100 | boolean found = false; 101 | for (LogStream ls : logStreams) { 102 | if (ls.logStreamName().equals(config.logStreamName().get())) { 103 | found = true; 104 | token = ls.uploadSequenceToken(); 105 | } 106 | } 107 | 108 | if (!found) { 109 | CreateLogStreamRequest createLogStreamRequest = CreateLogStreamRequest.builder() 110 | .logGroupName(config.logGroup().get()) 111 | .logStreamName(config.logStreamName().get()) 112 | .build(); 113 | cloudWatchLogsClient.createLogStream(createLogStreamRequest); 114 | } 115 | return token; 116 | } 117 | 118 | } 119 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/logging/cloudwatch/auth/CloudWatchCredentials.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.quarkiverse.logging.cloudwatch.auth; 18 | 19 | import io.quarkiverse.logging.cloudwatch.LoggingCloudWatchConfig; 20 | import software.amazon.awssdk.auth.credentials.AwsCredentials; 21 | 22 | class CloudWatchCredentials implements AwsCredentials { 23 | 24 | private final LoggingCloudWatchConfig config; 25 | 26 | CloudWatchCredentials(LoggingCloudWatchConfig config) { 27 | this.config = config; 28 | } 29 | 30 | @Override 31 | public String accessKeyId() { 32 | return config.accessKeyId().get(); 33 | } 34 | 35 | @Override 36 | public String secretAccessKey() { 37 | return config.accessKeySecret().get(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/logging/cloudwatch/auth/CloudWatchCredentialsProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.quarkiverse.logging.cloudwatch.auth; 18 | 19 | import io.quarkiverse.logging.cloudwatch.LoggingCloudWatchConfig; 20 | import software.amazon.awssdk.auth.credentials.AwsCredentials; 21 | import software.amazon.awssdk.auth.credentials.AwsCredentialsProvider; 22 | import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider; 23 | 24 | public class CloudWatchCredentialsProvider implements AwsCredentialsProvider { 25 | 26 | private final LoggingCloudWatchConfig config; 27 | 28 | public CloudWatchCredentialsProvider(LoggingCloudWatchConfig config) { 29 | this.config = config; 30 | } 31 | 32 | @Override 33 | public AwsCredentials resolveCredentials() { 34 | return config.defaultCredentialsProviderEnabled() ? DefaultCredentialsProvider.create().resolveCredentials() 35 | : new CloudWatchCredentials(config); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /runtime/src/main/java/io/quarkiverse/logging/cloudwatch/format/ElasticCommonSchemaLogFormatter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Red Hat, Inc. and/or its affiliates 3 | * and other contributors as indicated by the @author tags. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package io.quarkiverse.logging.cloudwatch.format; 18 | 19 | import static co.elastic.logging.EcsJsonSerializer.toNullSafeString; 20 | 21 | import java.util.Optional; 22 | 23 | import org.jboss.logmanager.ExtFormatter; 24 | import org.jboss.logmanager.ExtLogRecord; 25 | 26 | import co.elastic.logging.EcsJsonSerializer; 27 | import co.elastic.logging.JsonUtils; 28 | 29 | public class ElasticCommonSchemaLogFormatter extends ExtFormatter { 30 | 31 | private Optional serviceEnvironment; 32 | 33 | public ElasticCommonSchemaLogFormatter(Optional serviceEnvironment) { 34 | this.serviceEnvironment = serviceEnvironment; 35 | } 36 | 37 | @Override 38 | public String format(ExtLogRecord record) { 39 | StringBuilder builder = new StringBuilder(); 40 | 41 | EcsJsonSerializer.serializeObjectStart(builder, record.getMillis()); 42 | EcsJsonSerializer.serializeLogLevel(builder, record.getLevel().getName()); 43 | EcsJsonSerializer.serializeFormattedMessage(builder, record.getMessage()); 44 | if (serviceEnvironment != null && serviceEnvironment.isPresent()) { 45 | serializeField(builder, serviceEnvironment.get()); 46 | } 47 | EcsJsonSerializer.serializeThreadName(builder, record.getThreadName()); 48 | EcsJsonSerializer.serializeLoggerName(builder, record.getLoggerName()); 49 | EcsJsonSerializer.serializeMDC(builder, record.getMdcCopy()); 50 | 51 | boolean includeOrigin = false; 52 | if (includeOrigin && record.getSourceFileName() != null && record.getSourceMethodName() != null) { 53 | EcsJsonSerializer.serializeOrigin(builder, record.getSourceFileName(), record.getSourceMethodName(), 54 | record.getSourceLineNumber()); 55 | } 56 | 57 | boolean stackTraceAsArray = false; 58 | EcsJsonSerializer.serializeException(builder, record.getThrown(), stackTraceAsArray); 59 | EcsJsonSerializer.serializeObjectEnd(builder); 60 | 61 | return builder.toString(); 62 | } 63 | 64 | private void serializeField(StringBuilder builder, String value) { 65 | builder.append('"'); 66 | JsonUtils.quoteAsString("service.environment", builder); 67 | builder.append("\":\""); 68 | JsonUtils.quoteAsString(toNullSafeString(value), builder); 69 | builder.append("\","); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /runtime/src/main/resources/META-INF/quarkus-extension.yaml: -------------------------------------------------------------------------------- 1 | name: Logging Cloudwatch 2 | description: Send your application logs to AWS CloudWatch 3 | metadata: 4 | categories: 5 | - "logging" 6 | # keywords: 7 | # - logging-cloudwatch 8 | # guide: ... 9 | # status: "preview" 10 | -------------------------------------------------------------------------------- /runtime/src/test/java/io/quarkiverse/logging/cloudwatch/LoggingCloudWatchHandlerTest.java: -------------------------------------------------------------------------------- 1 | package io.quarkiverse.logging.cloudwatch; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertFalse; 4 | import static org.junit.jupiter.api.Assertions.assertTrue; 5 | 6 | import java.util.logging.LogRecord; 7 | 8 | import org.jboss.logmanager.Level; 9 | import org.junit.jupiter.api.Test; 10 | 11 | class LoggingCloudWatchHandlerTest { 12 | 13 | private final LoggingCloudWatchHandler testee = new LoggingCloudWatchHandler(); 14 | 15 | @Test 16 | void shouldFormatNormalLogMessage() { 17 | LogRecord record = new LogRecord(Level.WARN, "Uh oh! This error should not occur in production! :("); 18 | testee.setLevel(Level.WARN); 19 | 20 | String formattedMessage = testee.formatMessage(record); 21 | 22 | assertTrue(formattedMessage.contains("message\":\"Uh oh! This error should not occur in production! :(")); 23 | } 24 | 25 | @Test 26 | void shouldFormatPercentageAsWell() { 27 | LogRecord record = new LogRecord(Level.INFO, "Progress: 10%"); 28 | testee.setLevel(Level.INFO); 29 | 30 | String formattedMessage = testee.formatMessage(record); 31 | 32 | assertTrue(formattedMessage.contains("Progress: 10%")); 33 | } 34 | 35 | @Test 36 | void shouldFormatPercentageAndReplacePlaceholder() { 37 | // e.g. log.info("info logging: %", info) 38 | LogRecord record = new LogRecord(Level.INFO, "Progress: %s%%"); 39 | record.setParameters(new Object[] { "1337" }); 40 | testee.setLevel(Level.INFO); 41 | 42 | String formattedMessage = testee.formatMessage(record); 43 | 44 | assertTrue(formattedMessage.contains("Progress: 1337%")); 45 | } 46 | 47 | @Test 48 | void shouldBeBelowThresholdWhenBothAreInfo() { 49 | LogRecord record = new LogRecord(Level.INFO, "someMessage"); 50 | testee.setLevel(Level.INFO); 51 | assertFalse(testee.isBelowThreshold(record)); 52 | } 53 | 54 | @Test 55 | void shouldBeBelowThresholdWhenLogRecordIsWarnAndHandlerLevelIsInfo() { 56 | LogRecord record = new LogRecord(Level.WARN, "someMessage"); 57 | testee.setLevel(Level.INFO); 58 | assertFalse(testee.isBelowThreshold(record)); 59 | } 60 | 61 | @Test 62 | void shouldBeBelowThresholdWhenLogRecordIsInfoAndHandlerLevelIsWarn() { 63 | LogRecord record = new LogRecord(Level.INFO, "someMessage"); 64 | testee.setLevel(Level.WARN); 65 | assertTrue(testee.isBelowThreshold(record)); 66 | } 67 | 68 | @Test 69 | void shouldBeAboveThresholdWhenLogRecordIsWarnAndHandlerLevelIsSevere() { 70 | LogRecord record = new LogRecord(Level.WARN, "someMessage"); 71 | testee.setLevel(Level.SEVERE); 72 | assertTrue(testee.isBelowThreshold(record)); 73 | } 74 | 75 | @Test 76 | void shouldBeAboveThresholdWhenLogRecordIsInfoAndHandlerLevelIsSevere() { 77 | LogRecord record = new LogRecord(Level.INFO, "someMessage"); 78 | testee.setLevel(Level.SEVERE); 79 | assertTrue(testee.isBelowThreshold(record)); 80 | } 81 | } 82 | --------------------------------------------------------------------------------