├── .gitignore ├── API.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICES.md ├── README.md ├── pom.xml ├── src ├── main │ └── java │ │ └── io │ │ └── resurface │ │ ├── BaseLogger.java │ │ ├── BaseServletRequestImpl.java │ │ ├── BaseServletResponseImpl.java │ │ ├── BaseSessionImpl.java │ │ ├── Dispatcher.java │ │ ├── HttpLogger.java │ │ ├── HttpLoggerForJersey.java │ │ ├── HttpLoggerForServlets.java │ │ ├── HttpMessage.java │ │ ├── HttpRule.java │ │ ├── HttpRules.java │ │ ├── HttpServletRequestImpl.java │ │ ├── HttpServletResponseImpl.java │ │ ├── HttpSessionImpl.java │ │ ├── Json.java │ │ ├── LoggedInputStream.java │ │ ├── LoggedOutputStream.java │ │ ├── LoggedResponseWrapper.java │ │ └── UsageLoggers.java └── test │ └── java │ └── io │ └── resurface │ └── tests │ ├── BaseLoggerTest.java │ ├── Helper.java │ ├── HelperTest.java │ ├── HttpLoggerForServletsTest.java │ ├── HttpLoggerRulesTest.java │ ├── HttpLoggerTest.java │ ├── HttpMessageTest.java │ ├── HttpRulesTest.java │ ├── HttpServletRequestImplTest.java │ ├── HttpServletResponseImplTest.java │ ├── HttpSessionImplTest.java │ ├── JsonTest.java │ ├── LoggedInputStreamTest.java │ ├── LoggedOutputStreamTest.java │ ├── LoggedResponseWrapperTest.java │ ├── ThroughputTest.java │ └── UsageLoggersTest.java └── test ├── rules1.txt ├── rules2.txt └── rules3.txt /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .idea 3 | *.iml 4 | target/ 5 | -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | # API 2 | 3 | ## Contents 4 | 5 | 12 | 13 | 14 | 15 | ## Creating Loggers 16 | 17 | To get started, first you'll need to create a `HttpLogger` instance. Here there are options to specify a URL (for where JSON 18 | messages will be sent) and/or a specific set of logging rules (for what privacy 19 | protections to apply). Default values will be used for either of these if specific values are not provided. 20 | 21 | ```java 22 | import io.resurface.*; 23 | 24 | // with default url and rules 25 | HttpLogger logger = new HttpLogger(); 26 | 27 | // with specific url and default rules 28 | logger = new HttpLogger("https://..."); 29 | 30 | // with specific url and rules 31 | logger = new HttpLogger("https://...", "include strict"); 32 | 33 | // with specific url and rules from local file 34 | logger = new HttpLogger("https://...", "file://./rules.txt"); 35 | ``` 36 | 37 | 38 | 39 | ## Logging HTTP Calls 40 | 41 | Now that you have a logger instance, let's do some logging. Here you can pass standard request/response objects, as well 42 | as response body and request body content when these are available. 43 | 44 | ```java 45 | // with standard objects 46 | HttpMessage.send(logger, request, response); 47 | 48 | // with response body 49 | HttpMessage.send(logger, request, response, "my-response-body"); 50 | 51 | // with response and request body 52 | HttpMessage.send(logger, request, response, "my-response-body", "my-request-body"); 53 | ``` 54 | 55 | If standard request and response objects aren't available in your case, create mock implementations to pass instead. 56 | 57 | ```java 58 | // define request to log 59 | HttpServletRequest request = new HttpServletRequestImpl(); 60 | request.setCharacterEncoding("UTF-8"); 61 | request.setContentType("application/json"); 62 | request.setHeader("A", "123"); 63 | request.setMethod("POST"); 64 | request.setParam("B", "234"); // POST param 65 | request.setRequestURL("http://resurface.io"); 66 | 67 | // define response to log 68 | HttpServletResponse response = new HttpServletResponseImpl(); 69 | response.setCharacterEncoding("UTF-8"); 70 | response.setContentType("text/html; charset=utf-8"); 71 | response.setHeader("B", "234"); 72 | response.setStatus(200); 73 | 74 | // log objects defined above 75 | HttpMessage.send(logger, request, response); 76 | ``` 77 | 78 | 79 | 80 | ## Setting Default Rules 81 | 82 | If no rules are provided when creating a logger, the default value of 83 | `include strict` will be applied. A different default value can be specified as shown below. 84 | 85 | ```java 86 | HttpRules.setDefaultRules("include debug"); 87 | ``` 88 | 89 | When specifying multiple default rules, put each on a separate line. 90 | 91 | ```java 92 | HttpRules.setDefaultRules( 93 | "include debug\n" + 94 | "sample 10\n" 95 | ); 96 | ``` 97 | 98 | 99 | 100 | ## Setting Default URL 101 | 102 | If your application creates more than one logger, or requires different URLs for different environments (development vs 103 | testing vs production), then set the `USAGE_LOGGERS_URL` environment variable. This value will be applied if no other URL 104 | is specified when creating a logger. 105 | 106 | ```bash 107 | # when launching Java app 108 | java -DUSAGE_LOGGERS_URL="https://..." ... 109 | 110 | # from command line 111 | export USAGE_LOGGERS_URL="https://..." 112 | 113 | # for Heroku app 114 | heroku config:set USAGE_LOGGERS_URL=https://... 115 | ``` 116 | 117 | 118 | 119 | ## Enabling and Disabling Loggers 120 | 121 | Individual loggers can be controlled through their `enable` and `disable` methods. When disabled, loggers will 122 | not send any logging data, and the result returned by the `log` method will always be true (success). 123 | 124 | All loggers for an application can be enabled or disabled at once with the `UsageLoggers` class. This even controls 125 | loggers that have not yet been created by the application. 126 | 127 | ```java 128 | UsageLoggers.disable(); // disable all loggers 129 | UsageLoggers.enable(); // enable all loggers 130 | ``` 131 | 132 | All loggers can be permanently disabled with the `USAGE_LOGGERS_DISABLE` environment variable. When set to true, 133 | loggers will never become enabled, even if `UsageLoggers.enable()` is called by the application. This is primarily 134 | done by automated tests to disable all logging even if other control logic exists. 135 | 136 | ```bash 137 | # when launching Java app 138 | java -DUSAGE_LOGGERS_DISABLE="true" ... 139 | 140 | # from command line 141 | export USAGE_LOGGERS_DISABLE="true" 142 | 143 | # for Heroku app 144 | heroku config:set USAGE_LOGGERS_DISABLE=true 145 | ``` 146 | 147 | --- 148 | © 2016-2024 Graylog, Inc. 149 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Coding Conventions 4 | 5 | Our code style is whatever IntelliJ IDEA does by default, with the exception of allowing lines up to 130 characters. 6 | If you don't use IDEA, that's ok, but your code may get reformatted. 7 | 8 | ## Git Workflow 9 | 10 | Initial setup: 11 | 12 | ``` 13 | git clone git@github.com:resurfaceio/logger-java.git resurfaceio-logger-java 14 | cd resurfaceio-logger-java 15 | ``` 16 | 17 | Running unit tests: 18 | 19 | ``` 20 | mvn test 21 | ``` 22 | 23 | Committing changes: 24 | 25 | ``` 26 | git add -A 27 | git commit -m "#123 Updated readme" (123 is the GitHub issue number) 28 | git pull --rebase (avoid merge bubbles) 29 | git push origin master 30 | ``` 31 | 32 | Check if any newer dependencies are available: 33 | 34 | ``` 35 | mvn versions:display-dependency-updates 36 | ``` 37 | 38 | ## Release Process 39 | 40 | Configure environment variables (in `.bash_profile`): 41 | 42 | ``` 43 | export JAVA_HOME="/Library/Java/JavaVirtualMachines/jdk1.8.0_131.jdk/Contents/Home" 44 | export GPG_TTY=$(tty) 45 | ``` 46 | 47 | Configure Maven settings (in `.m2/settings.xml`): 48 | 49 | ``` 50 | 51 | 52 | 53 | ossrh 54 | 55 | true 56 | 57 | 58 | [public-key-id] 59 | 60 | 61 | 62 | 63 | 64 | ossrh 65 | resurfaceio 66 | [ask-rob] 67 | 68 | 69 | 70 | ``` 71 | 72 | Push artifacts to [Maven Central](https://search.maven.org/): 73 | 74 | ``` 75 | git add -A 76 | git commit -m "Update version to 2.2.#" 77 | mvn deploy 78 | ``` 79 | 80 | Log into `oss.sonatype.org` and close/release the latest staging repository. 81 | 82 | Tag release version: 83 | 84 | ``` 85 | git tag v2.2.# 86 | git push origin master --tags 87 | ``` 88 | 89 | Start the next version by incrementing the version number in `pom.xml` and `Baselogger.java`. 90 | -------------------------------------------------------------------------------- /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 (c) 2016-2024 Graylog, Inc. 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 | -------------------------------------------------------------------------------- /NOTICES.md: -------------------------------------------------------------------------------- 1 | # Notices 2 | 3 | ## Use of open source 4 | 5 | We rely on the following open source projects. You can find links to the source code for these open source projects along with license information below. We are truly 6 | grateful to these developers for all their efforts. 7 | 8 | ### Apache 9 | 10 | https://maven.apache.org - Apache Maven
11 | Used under Apache License, Version 2 12 | 13 | Copyright © 2016 The Apache Software Foundation 14 | 15 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. You may obtain a copy of the 16 | License here: http://www.apache.org/licenses/LICENSE-2.0 17 | 18 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 19 | OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 20 | 21 | ### Gson 22 | 23 | https://github.com/google/gson
Used under Apache License, Version 2 24 | 25 | Copyright © 2008 Google Inc. 26 | 27 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. You may obtain a copy of the 28 | License here: http://www.apache.org/licenses/LICENSE-2.0 29 | 30 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 31 | OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 32 | 33 | ### Jersey 34 | 35 | https://jersey.github.io
Used under CDDL 36 | 37 | Copyright © 2012-2017 Oracle and/or its affiliates 38 | 39 | Licensed under the Common Development and Distribution License ("CDDL") (the "License"). You may not use this software except in compliance with the License. You can 40 | obtain a copy of the License here: [https://oss.oracle.com/licenses/CDDL+GPL-1.1](https://oss.oracle.com/licenses/CDDL+GPL-1.1) See the License for the specific 41 | language governing permissions and limitations under the License. 42 | 43 | ### JUnit 44 | 45 | http://junit.org/
Used under Eclipse Public License 46 | 47 | Copyright © 2002-2016 JUnit 48 | 49 | All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse Public License v1.0 which accompanies this distribution, 50 | and is available at http://www.eclipse.org/legal/epl-v10.html. 51 | 52 | ### json-simple 53 | 54 | https://code.google.com/archive/p/json-simple/
Used under Apache License, Version 2 55 | 56 | Copyright © 2014 Yidong Fang 57 | 58 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. You may obtain a copy of the 59 | License here: http://www.apache.org/licenses/LICENSE-2.0 60 | 61 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 62 | OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 63 | 64 | ### Oleaster Matcher 65 | 66 | https://github.com/mscharhag/oleaster/tree/master/oleaster-matcher
Used under Apache License, Version 2 67 | 68 | Copyright © 2014 Michael Scharhag 69 | 70 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this software except in compliance with the License. You may obtain a copy of the 71 | License here: http://www.apache.org/licenses/LICENSE-2.0 72 | 73 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 74 | OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 75 | 76 | ### OpenJDK 77 | 78 | http://openjdk.java.net/
Used under GPLv2 with Classpath Exception 79 | 80 | Copyright © 2016 Oracle Corporation and/or its affiliates 81 | 82 | This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; 83 | either version 2 of the License, or (at your option) any later version. 84 | 85 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 86 | PURPOSE. See http://openjdk.java.net/legal/gplv2+ce.html for more details. 87 | 88 | As a special exception, the copyright holders of this library give you permission to link this library with independent modules to produce an executable, regardless of 89 | the license terms of these independent modules, and to copy and distribute the resulting executable under terms of your choice, provided that you also meet, for each 90 | linked independent module, the terms and conditions of the license of that module. An independent module is a module which is not derived from or based on this library. 91 | If you modify this library, you may extend this exception to your version of the library, but you are not obligated to do so. If you do not wish to do so, delete this 92 | exception statement from your version. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # resurfaceio-logger-java 2 | Easily log API requests and responses to your own security data lake. 3 | 4 | [![Maven Central](https://img.shields.io/maven-central/v/io.resurface/resurfaceio-logger)](https://maven-badges.herokuapp.com/maven-central/io.resurface/resurfaceio-logger) 5 | [![CodeFactor](https://www.codefactor.io/repository/github/resurfaceio/logger-java/badge)](https://www.codefactor.io/repository/github/resurfaceio/logger-java) 6 | [![License](https://img.shields.io/github/license/resurfaceio/logger-java)](https://github.com/resurfaceio/logger-java/blob/master/LICENSE) 7 | [![Contributing](https://img.shields.io/badge/contributions-welcome-green.svg)](https://github.com/resurfaceio/logger-java/blob/master/CONTRIBUTING.md) 8 | 9 | ## Contents 10 | 11 | 21 | 22 | 23 | 24 | ## Dependencies 25 | 26 | Requires Java 8+ and Java servlet classes, which are not included. No other dependencies to conflict with your app. 27 | 28 | 29 | 30 | ## Installing with Maven 31 | 32 | Add this section to `pom.xml`: 33 | 34 | ```xml 35 | 36 | io.resurface 37 | resurfaceio-logger 38 | 2.2.0 39 | 40 | ``` 41 | 42 | Add this section too if servlet classes are not already available. 43 | 44 | ```xml 45 | 46 | javax.servlet 47 | javax.servlet-api 48 | RELEASE 49 | 50 | ``` 51 | 52 | 53 | 54 | ## Logging From Servlet Filter 55 | 56 | After installing the library, add a logging filter to `web.xml`. 57 | 58 | ```xml 59 | 60 | HttpLoggerForServlets 61 | io.resurface.HttpLoggerForServlets 62 | 63 | url 64 | http://localhost:7701/message 65 | 66 | 67 | rules 68 | include debug 69 | 70 | 71 | 72 | HttpLoggerForServlets 73 | /* 74 | 75 | ``` 76 | 77 | Add a CDATA section when specifying multiple rules at once like this: 78 | 79 | ```xml 80 | 81 | rules 82 | 86 | 87 | ``` 88 | 89 | 90 | 91 | ## Logging From Spring Boot 92 | 93 | After installing the library, configure a `FilterRegistrationBean` 94 | to add a logging servlet filter. 95 | 96 | ```java 97 | @Bean 98 | public FilterRegistrationBean httpLoggerFilter() { 99 | FilterRegistrationBean frb = new FilterRegistrationBean(); 100 | frb.setFilter(new io.resurface.HttpLoggerForServlets()); 101 | frb.setName("HttpLoggerForServlets"); 102 | frb.addUrlPatterns("/*"); 103 | frb.addInitParameter("url", "http://localhost:7701/message"); 104 | frb.addInitParameter("rules", "include debug"); 105 | return frb; 106 | } 107 | ``` 108 | 109 | 110 | 111 | ## Logging From Spark Framework 112 | 113 | After installing the library, create a logger and call it from the routes of interest. 114 | 115 | ```java 116 | import io.resurface.*; 117 | 118 | HttpLogger logger = new HttpLogger("http://localhost:7701/message", "include debug"); 119 | 120 | get("/hello", (request, response) -> { 121 | String response_body = "Hello World"; 122 | HttpMessage.send(logger, request.raw(), response.raw(), response_body); 123 | return response_body; 124 | }); 125 | 126 | post("/hello_post", (request, response) -> { 127 | String response_body = "POSTED: " + request.body(); 128 | HttpMessage.send(logger, request.raw(), response.raw(), response_body, request.body()); 129 | return response_body; 130 | }); 131 | ``` 132 | 133 | Alternatively configure an `after` filter to log across multiple routes at once. 134 | 135 | ```java 136 | after((request, response) -> { 137 | if (response.body() != null) { // log successful responses only, not 404/500s 138 | HttpMessage.send(logger, request.raw(), response.raw(), response.body(), request.body()); 139 | } 140 | }); 141 | ``` 142 | 143 | 144 | 145 | ## Logging From Jersey 146 | 147 | After installing the library, register a logger as a Jersey filter/interceptor. 148 | Note this will only log usage when a response body is returned. 149 | 150 | ```java 151 | ResourceConfig resourceConfig = new ResourceConfig(...); 152 | resourceConfig.register(new io.resurface.HttpLoggerForJersey("http://localhost:7701/message", "include debug")); 153 | HttpServer server = GrizzlyHttpServerFactory.createHttpServer(BASE_URI, resourceConfig, false); 154 | ``` 155 | 156 | 157 | 158 | ## Logging With API 159 | 160 | Loggers can be directly integrated into your application using our [API](API.md). This requires the most effort compared with 161 | the options described above, but also offers the greatest flexibility and control. 162 | 163 | [API documentation](API.md) 164 | 165 | 166 | 167 | ## Protecting User Privacy 168 | 169 | Loggers always have an active set of rules that control what data is logged 170 | and how sensitive data is masked. All of the examples above apply a predefined set of rules (`include debug`), 171 | but logging rules are easily customized to meet the needs of any application. 172 | 173 | Logging rules documentation 174 | 175 | --- 176 | © 2016-2024 Graylog, Inc. 177 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.resurface 8 | resurfaceio-logger 9 | 2.2.1 10 | resurfaceio-logger 11 | Library for usage logging 12 | https://github.com/resurfaceio/logger-java 13 | 14 | 15 | 16 | 17 | org.apache.maven.plugins 18 | maven-compiler-plugin 19 | 3.5 20 | 21 | 1.8 22 | 1.8 23 | 24 | 25 | 26 | org.apache.maven.plugins 27 | maven-gpg-plugin 28 | 1.5 29 | 30 | 31 | sign-artifacts 32 | verify 33 | 34 | sign 35 | 36 | 37 | 38 | 39 | 40 | org.apache.maven.plugins 41 | maven-javadoc-plugin 42 | 2.9.1 43 | 44 | 45 | attach-javadocs 46 | 47 | jar 48 | 49 | 50 | 51 | 52 | 53 | org.apache.maven.plugins 54 | maven-source-plugin 55 | 2.2.1 56 | 57 | 58 | attach-sources 59 | 60 | jar-no-fork 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | com.google.code.gson 71 | gson 72 | 2.9.1 73 | test 74 | 75 | 76 | com.mscharhag.oleaster 77 | oleaster-matcher 78 | 0.2.0 79 | test 80 | 81 | 82 | javax.servlet 83 | javax.servlet-api 84 | 4.0.1 85 | provided 86 | 87 | 88 | javax.ws.rs 89 | javax.ws.rs-api 90 | 2.1.1 91 | provided 92 | 93 | 94 | junit 95 | junit 96 | 4.13.2 97 | test 98 | 99 | 100 | 101 | 102 | GitHub 103 | https://github.com/resurfaceio/logger-java/issues 104 | 105 | 106 | 107 | 108 | Apache License, Version 2.0 109 | http://www.apache.org/licenses/LICENSE-2.0.txt 110 | repo 111 | 112 | 113 | 114 | 115 | UTF-8 116 | 117 | 118 | 119 | resurface.io 120 | https://resurface.io 121 | 122 | 123 | 124 | scm:git:git@github.com:resurfaceio/logger-java.git 125 | scm:git:git@github.com:resurfaceio/logger-java.git 126 | scm:git:git@github.com:resurfaceio/logger-java.git 127 | 128 | 129 | 130 | 131 | ossrh 132 | https://oss.sonatype.org/content/repositories/snapshots 133 | 134 | 135 | ossrh 136 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/BaseLogger.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import java.io.OutputStream; 6 | import java.net.HttpURLConnection; 7 | import java.net.InetAddress; 8 | import java.net.URL; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.List; 11 | import java.util.concurrent.ArrayBlockingQueue; 12 | import java.util.concurrent.BlockingQueue; 13 | import java.util.concurrent.atomic.AtomicInteger; 14 | import java.util.zip.DeflaterOutputStream; 15 | 16 | /** 17 | * Basic usage logger to embed or extend. 18 | */ 19 | public class BaseLogger { 20 | 21 | /** 22 | * Initialize enabled logger using default url. 23 | */ 24 | public BaseLogger(String agent) { 25 | this(agent, true); 26 | } 27 | 28 | /** 29 | * Initialize enabled/disabled logger using default url. 30 | */ 31 | public BaseLogger(String agent, boolean enabled) { 32 | this(agent, UsageLoggers.urlByDefault(), enabled); 33 | } 34 | 35 | /** 36 | * Initialize enabled logger using url. 37 | */ 38 | public BaseLogger(String agent, String url) { 39 | this(agent, url, true); 40 | } 41 | 42 | /** 43 | * Initialize enabled/disabled logger using url. 44 | */ 45 | public BaseLogger(String agent, String url, boolean enabled) { 46 | this(agent, url, enabled, 128); 47 | } 48 | 49 | /** 50 | * Initialize enabled/disabled logger using url. 51 | */ 52 | public BaseLogger(String agent, String url, boolean enabled, int max_queue_depth) { 53 | this.agent = agent; 54 | this.host = host_lookup(); 55 | this.version = version_lookup(); 56 | this.queue = null; 57 | 58 | // set options in priority order 59 | this.enabled = enabled; 60 | if (url == null) { 61 | this.url = UsageLoggers.urlByDefault(); 62 | if (this.url == null) this.enabled = false; 63 | } else { 64 | this.url = url; 65 | } 66 | 67 | // validate url when present 68 | if (this.url != null) { 69 | try { 70 | this.url_parsed = new URL(this.url); 71 | if (!this.url_parsed.getProtocol().contains("http")) throw new RuntimeException(); 72 | } catch (Exception e) { 73 | this.url = null; 74 | this.url_parsed = null; 75 | this.enabled = false; 76 | } 77 | } 78 | 79 | // finalize internal properties 80 | this.enableable = (this.url != null); 81 | this.max_queue_depth = max_queue_depth; 82 | } 83 | 84 | /** 85 | * Initialize enabled logger using queue. 86 | */ 87 | public BaseLogger(String agent, List queue) { 88 | this(agent, queue, true); 89 | } 90 | 91 | /** 92 | * Initialize enabled/disabled logger using queue. 93 | */ 94 | public BaseLogger(String agent, List queue, boolean enabled) { 95 | this(agent, queue, enabled, 128); 96 | } 97 | 98 | 99 | /** 100 | * Initialize enabled/disabled logger using queue. 101 | */ 102 | public BaseLogger(String agent, List queue, boolean enabled, int max_queue_depth) { 103 | this.agent = agent; 104 | this.host = host_lookup(); 105 | this.version = version_lookup(); 106 | this.enabled = enabled; 107 | this.queue = queue; 108 | this.url = null; 109 | this.enableable = (this.queue != null); 110 | this.max_queue_depth = max_queue_depth; 111 | setMessageQueue(); 112 | } 113 | 114 | /** 115 | * Disable this logger. 116 | */ 117 | public T disable() { 118 | enabled = false; 119 | return (T) this; 120 | } 121 | 122 | /** 123 | * Enable this logger. 124 | */ 125 | public T enable() { 126 | if (enableable) enabled = true; 127 | return (T) this; 128 | } 129 | 130 | /** 131 | * Returns agent string identifying this logger. 132 | */ 133 | public String getAgent() { 134 | return agent; 135 | } 136 | 137 | /** 138 | * Returns cached host identifier. 139 | */ 140 | public String getHost() { 141 | return host; 142 | } 143 | 144 | /** 145 | * Returns queue destination where messages are sent. 146 | */ 147 | public List getQueue() { 148 | return queue; 149 | } 150 | 151 | /** 152 | * Returns true if message compression is being skipped. 153 | */ 154 | public boolean getSkipCompression() { 155 | return skip_compression; 156 | } 157 | 158 | /** 159 | * Returns true if message submission is being skipped. 160 | */ 161 | public boolean getSkipSubmission() { 162 | return skip_submission; 163 | } 164 | 165 | /** 166 | * Returns url destination where messages are sent. 167 | */ 168 | public String getUrl() { 169 | return url; 170 | } 171 | 172 | /** 173 | * Returns cached version number. 174 | */ 175 | public String getVersion() { 176 | return version; 177 | } 178 | 179 | /** 180 | * Returns bounded queue used as message buffer for background submissions. 181 | * @return Message bounded queue. 182 | */ 183 | public BlockingQueue getMessageQueue() { 184 | return this.msg_queue; 185 | } 186 | 187 | /** 188 | * Returns true if this logger can ever be enabled. 189 | */ 190 | public boolean isEnableable() { 191 | return enableable; 192 | } 193 | 194 | /** 195 | * Returns true if this logger is currently enabled. 196 | */ 197 | public boolean isEnabled() { 198 | return enabled && UsageLoggers.isEnabled(); 199 | } 200 | 201 | /** 202 | * Returns true if the worker thread is currently alive. 203 | */ 204 | public boolean isWorkerAlive() { return worker.isAlive(); } 205 | 206 | /** 207 | * Sets if message compression will be skipped. 208 | */ 209 | public void setSkipCompression(boolean skip_compression) { 210 | this.skip_compression = skip_compression; 211 | } 212 | 213 | /** 214 | * Sets if message submission will be skipped. 215 | */ 216 | public void setSkipSubmission(boolean skip_submission) { 217 | this.skip_submission = skip_submission; 218 | } 219 | 220 | /** 221 | * Creates a new bounded queue with the max depth passed to the constructor. 222 | */ 223 | public void setMessageQueue() { 224 | this.setMessageQueue(this.max_queue_depth); 225 | } 226 | 227 | /** 228 | * Creates a new bounded queue using a specific depth. 229 | * @param max_queue_depth size of the bounded queue 230 | */ 231 | public synchronized void setMessageQueue(int max_queue_depth) { 232 | if (this.msg_queue == null) { 233 | this.msg_queue = new ArrayBlockingQueue<>(max_queue_depth); 234 | } else { 235 | this.msg_queue = new ArrayBlockingQueue<>(max_queue_depth, false, this.msg_queue); 236 | } 237 | } 238 | 239 | /** 240 | * Adds message to queue for dispatcher thread. 241 | * @param msg String with tuple-JSON formatted message. More info: https://resurface.io/docs#json-format 242 | */ 243 | public void submit(String msg) { 244 | if (queue == null && (worker == null || !worker.isAlive())) { 245 | init_dispatcher(); 246 | } 247 | try { 248 | this.msg_queue.put(msg); 249 | } catch (InterruptedException e) { 250 | submit_failures.incrementAndGet(); 251 | } 252 | } 253 | 254 | /** 255 | * Sends JSON message to intended destination. 256 | */ 257 | public void dispatch(String msg) { 258 | if (msg == null || this.skip_submission || !isEnabled()) { 259 | // do nothing 260 | } else if (queue != null) { 261 | queue.add(msg); 262 | submit_successes.incrementAndGet(); 263 | } else { 264 | try { 265 | HttpURLConnection url_connection = (HttpURLConnection) this.url_parsed.openConnection(); 266 | url_connection.setConnectTimeout(5000); 267 | url_connection.setReadTimeout(1000); 268 | url_connection.setRequestMethod("POST"); 269 | url_connection.setRequestProperty("Content-Type", "application/ndjson; charset=UTF-8"); 270 | url_connection.setRequestProperty("User-Agent", "Resurface/" + version + " (" + agent + ")"); 271 | url_connection.setDoOutput(true); 272 | if (!this.skip_compression) url_connection.setRequestProperty("Content-Encoding", "deflated"); 273 | try (OutputStream os = url_connection.getOutputStream()) { 274 | if (this.skip_compression) { 275 | os.write(msg.getBytes(StandardCharsets.UTF_8)); 276 | } else { 277 | try (DeflaterOutputStream dos = new DeflaterOutputStream(os, true)) { 278 | dos.write(msg.getBytes(StandardCharsets.UTF_8)); 279 | dos.finish(); 280 | dos.flush(); 281 | } 282 | } 283 | os.flush(); 284 | } 285 | if (url_connection.getResponseCode() == 204) { 286 | submit_successes.incrementAndGet(); 287 | } else { 288 | submit_failures.incrementAndGet(); 289 | } 290 | } catch (Exception e) { 291 | submit_failures.incrementAndGet(); 292 | } 293 | } 294 | } 295 | 296 | /** 297 | * Returns count of submissions that failed. 298 | */ 299 | public int getSubmitFailures() { 300 | return submit_failures.get(); 301 | } 302 | 303 | /** 304 | * Returns count of submissions that succeeded. 305 | */ 306 | public int getSubmitSuccesses() { 307 | return submit_successes.get(); 308 | } 309 | 310 | /** 311 | * Initializes message queue and starts dispatcher thread. 312 | */ 313 | public void init_dispatcher() { 314 | this.init_dispatcher(50 * 1024); 315 | } 316 | 317 | /** 318 | * Initializes message queue and starts dispatcher thread. 319 | * @param batchSize threshold for the NDJSON batch 320 | */ 321 | public void init_dispatcher(int batchSize) { 322 | setMessageQueue(); 323 | worker = new Thread(new Dispatcher(this, batchSize)); 324 | worker.start(); 325 | } 326 | 327 | /** 328 | * Stops worker thread using poison pill. 329 | */ 330 | public void stop_dispatcher() { 331 | try { 332 | msg_queue.put("POISON PILL"); 333 | worker.join(); 334 | } catch (InterruptedException e) { 335 | e.printStackTrace(); 336 | } 337 | } 338 | 339 | /** 340 | * Returns host identifier for this logger. 341 | */ 342 | public static String host_lookup() { 343 | String dyno = System.getenv("DYNO"); 344 | if (dyno != null) return dyno; 345 | try { 346 | return InetAddress.getLocalHost().getHostName(); 347 | } catch (Exception e) { 348 | return "unknown"; 349 | } 350 | } 351 | 352 | /** 353 | * Returns version number for this logger. 354 | */ 355 | public static String version_lookup() { 356 | return "2.2.1"; 357 | } 358 | 359 | protected final String agent; 360 | protected boolean enableable; 361 | protected boolean enabled; 362 | protected final String host; 363 | protected final List queue; 364 | protected boolean skip_compression = false; 365 | protected boolean skip_submission = false; 366 | protected final AtomicInteger submit_failures = new AtomicInteger(); 367 | protected final AtomicInteger submit_successes = new AtomicInteger(); 368 | protected String url; 369 | protected URL url_parsed; 370 | protected final String version; 371 | protected int max_queue_depth; 372 | protected BlockingQueue msg_queue; 373 | private Thread worker; 374 | } 375 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/BaseServletRequestImpl.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import javax.servlet.*; 6 | import javax.servlet.http.*; 7 | import java.io.BufferedReader; 8 | import java.io.IOException; 9 | import java.io.UnsupportedEncodingException; 10 | import java.security.Principal; 11 | import java.util.Collection; 12 | import java.util.Enumeration; 13 | import java.util.Locale; 14 | import java.util.Map; 15 | 16 | /** 17 | * Base class for mock HttpServletRequest implementations. 18 | */ 19 | public class BaseServletRequestImpl implements HttpServletRequest { 20 | 21 | @Override 22 | public boolean authenticate(HttpServletResponse response) throws IOException, ServletException { 23 | throw new UnsupportedOperationException(); 24 | } 25 | 26 | @Override 27 | public AsyncContext getAsyncContext() { 28 | throw new UnsupportedOperationException(); 29 | } 30 | 31 | @Override 32 | public Object getAttribute(String name) { 33 | throw new UnsupportedOperationException(); 34 | } 35 | 36 | @Override 37 | public Enumeration getAttributeNames() { 38 | throw new UnsupportedOperationException(); 39 | } 40 | 41 | @Override 42 | public String getAuthType() { 43 | throw new UnsupportedOperationException(); 44 | } 45 | 46 | @Override 47 | public String getCharacterEncoding() { 48 | throw new UnsupportedOperationException(); 49 | } 50 | 51 | @Override 52 | public int getContentLength() { 53 | throw new UnsupportedOperationException(); 54 | } 55 | 56 | @Override 57 | public long getContentLengthLong() { throw new UnsupportedOperationException(); } 58 | 59 | @Override 60 | public String getContentType() { 61 | throw new UnsupportedOperationException(); 62 | } 63 | 64 | @Override 65 | public String getContextPath() { 66 | throw new UnsupportedOperationException(); 67 | } 68 | 69 | @Override 70 | public Cookie[] getCookies() { 71 | throw new UnsupportedOperationException(); 72 | } 73 | 74 | @Override 75 | public long getDateHeader(String name) { 76 | throw new UnsupportedOperationException(); 77 | } 78 | 79 | @Override 80 | public DispatcherType getDispatcherType() { 81 | throw new UnsupportedOperationException(); 82 | } 83 | 84 | @Override 85 | public String getHeader(String name) { 86 | throw new UnsupportedOperationException(); 87 | } 88 | 89 | @Override 90 | public Enumeration getHeaders(String name) { 91 | throw new UnsupportedOperationException(); 92 | } 93 | 94 | @Override 95 | public Enumeration getHeaderNames() { 96 | throw new UnsupportedOperationException(); 97 | } 98 | 99 | @Override 100 | public ServletInputStream getInputStream() throws IOException { 101 | throw new UnsupportedOperationException(); 102 | } 103 | 104 | @Override 105 | public int getIntHeader(String name) { 106 | throw new UnsupportedOperationException(); 107 | } 108 | 109 | @Override 110 | public Locale getLocale() { 111 | throw new UnsupportedOperationException(); 112 | } 113 | 114 | @Override 115 | public Enumeration getLocales() { 116 | throw new UnsupportedOperationException(); 117 | } 118 | 119 | @Override 120 | public String getLocalAddr() { 121 | throw new UnsupportedOperationException(); 122 | } 123 | 124 | @Override 125 | public String getLocalName() { 126 | throw new UnsupportedOperationException(); 127 | } 128 | 129 | @Override 130 | public int getLocalPort() { 131 | throw new UnsupportedOperationException(); 132 | } 133 | 134 | @Override 135 | public String getMethod() { 136 | throw new UnsupportedOperationException(); 137 | } 138 | 139 | @Override 140 | public String getParameter(String name) { 141 | throw new UnsupportedOperationException(); 142 | } 143 | 144 | @Override 145 | public Map getParameterMap() { 146 | throw new UnsupportedOperationException(); 147 | } 148 | 149 | @Override 150 | public Enumeration getParameterNames() { 151 | throw new UnsupportedOperationException(); 152 | } 153 | 154 | @Override 155 | public String[] getParameterValues(String name) { 156 | throw new UnsupportedOperationException(); 157 | } 158 | 159 | @Override 160 | public Part getPart(String name) { 161 | throw new UnsupportedOperationException(); 162 | } 163 | 164 | @Override 165 | public T upgrade(Class aClass) throws IOException, ServletException { 166 | throw new UnsupportedOperationException(); 167 | } 168 | 169 | @Override 170 | public Collection getParts() { 171 | throw new UnsupportedOperationException(); 172 | } 173 | 174 | @Override 175 | public String getPathInfo() { 176 | throw new UnsupportedOperationException(); 177 | } 178 | 179 | @Override 180 | public String getPathTranslated() { 181 | throw new UnsupportedOperationException(); 182 | } 183 | 184 | @Override 185 | public String getProtocol() { 186 | throw new UnsupportedOperationException(); 187 | } 188 | 189 | @Override 190 | public String getQueryString() { 191 | throw new UnsupportedOperationException(); 192 | } 193 | 194 | @Override 195 | public BufferedReader getReader() throws IOException { 196 | throw new UnsupportedOperationException(); 197 | } 198 | 199 | @Override 200 | public String getRealPath(String path) { 201 | throw new UnsupportedOperationException(); 202 | } 203 | 204 | @Override 205 | public String getRemoteAddr() { 206 | throw new UnsupportedOperationException(); 207 | } 208 | 209 | @Override 210 | public String getRemoteHost() { 211 | throw new UnsupportedOperationException(); 212 | } 213 | 214 | @Override 215 | public int getRemotePort() { 216 | throw new UnsupportedOperationException(); 217 | } 218 | 219 | @Override 220 | public String getRemoteUser() { 221 | throw new UnsupportedOperationException(); 222 | } 223 | 224 | @Override 225 | public RequestDispatcher getRequestDispatcher(String path) { 226 | throw new UnsupportedOperationException(); 227 | } 228 | 229 | @Override 230 | public String getRequestedSessionId() { 231 | throw new UnsupportedOperationException(); 232 | } 233 | 234 | @Override 235 | public String getRequestURI() { 236 | throw new UnsupportedOperationException(); 237 | } 238 | 239 | @Override 240 | public StringBuffer getRequestURL() { 241 | throw new UnsupportedOperationException(); 242 | } 243 | 244 | @Override 245 | public String getScheme() { 246 | throw new UnsupportedOperationException(); 247 | } 248 | 249 | @Override 250 | public String getServletPath() { 251 | throw new UnsupportedOperationException(); 252 | } 253 | 254 | @Override 255 | public String getServerName() { 256 | throw new UnsupportedOperationException(); 257 | } 258 | 259 | @Override 260 | public int getServerPort() { 261 | throw new UnsupportedOperationException(); 262 | } 263 | 264 | @Override 265 | public ServletContext getServletContext() { 266 | throw new UnsupportedOperationException(); 267 | } 268 | 269 | @Override 270 | public HttpSession getSession() { 271 | return getSession(true); 272 | } 273 | 274 | @Override 275 | public HttpSession getSession(boolean create) { 276 | throw new UnsupportedOperationException(); 277 | } 278 | 279 | @Override 280 | public String changeSessionId() { throw new UnsupportedOperationException(); } 281 | 282 | @Override 283 | public Principal getUserPrincipal() { 284 | throw new UnsupportedOperationException(); 285 | } 286 | 287 | @Override 288 | public boolean isRequestedSessionIdFromCookie() { 289 | throw new UnsupportedOperationException(); 290 | } 291 | 292 | @Override 293 | public boolean isRequestedSessionIdFromUrl() { 294 | throw new UnsupportedOperationException(); 295 | } 296 | 297 | @Override 298 | public boolean isRequestedSessionIdFromURL() { 299 | throw new UnsupportedOperationException(); 300 | } 301 | 302 | @Override 303 | public boolean isRequestedSessionIdValid() { 304 | throw new UnsupportedOperationException(); 305 | } 306 | 307 | @Override 308 | public boolean isAsyncStarted() { 309 | throw new UnsupportedOperationException(); 310 | } 311 | 312 | @Override 313 | public boolean isAsyncSupported() { 314 | throw new UnsupportedOperationException(); 315 | } 316 | 317 | @Override 318 | public boolean isSecure() { 319 | throw new UnsupportedOperationException(); 320 | } 321 | 322 | @Override 323 | public boolean isUserInRole(String role) { 324 | throw new UnsupportedOperationException(); 325 | } 326 | 327 | @Override 328 | public void login(String username, String password) throws ServletException { 329 | throw new UnsupportedOperationException(); 330 | } 331 | 332 | @Override 333 | public void logout() throws ServletException { 334 | throw new UnsupportedOperationException(); 335 | } 336 | 337 | @Override 338 | public void removeAttribute(String name) { 339 | throw new UnsupportedOperationException(); 340 | } 341 | 342 | @Override 343 | public void setAttribute(String name, Object o) { 344 | throw new UnsupportedOperationException(); 345 | } 346 | 347 | @Override 348 | public void setCharacterEncoding(String characterEncoding) throws UnsupportedEncodingException { 349 | throw new UnsupportedOperationException(); 350 | } 351 | 352 | @Override 353 | public AsyncContext startAsync() throws IllegalStateException { 354 | throw new UnsupportedOperationException(); 355 | } 356 | 357 | @Override 358 | public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse) 359 | throws IllegalStateException { 360 | throw new UnsupportedOperationException(); 361 | } 362 | 363 | } 364 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/BaseServletResponseImpl.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import javax.servlet.ServletOutputStream; 6 | import javax.servlet.http.Cookie; 7 | import javax.servlet.http.HttpServletResponse; 8 | import java.io.IOException; 9 | import java.io.PrintWriter; 10 | import java.util.Collection; 11 | import java.util.Locale; 12 | 13 | /** 14 | * Base class for mock HttpServletResponse implementations. 15 | */ 16 | public class BaseServletResponseImpl implements HttpServletResponse { 17 | 18 | @Override 19 | public void addCookie(Cookie cookie) { 20 | throw new UnsupportedOperationException(); 21 | } 22 | 23 | @Override 24 | public void addDateHeader(String name, long date) { 25 | throw new UnsupportedOperationException(); 26 | } 27 | 28 | @Override 29 | public void addHeader(String name, String value) { 30 | throw new UnsupportedOperationException(); 31 | } 32 | 33 | @Override 34 | public void addIntHeader(String name, int value) { 35 | throw new UnsupportedOperationException(); 36 | } 37 | 38 | @Override 39 | public boolean containsHeader(String name) { 40 | throw new UnsupportedOperationException(); 41 | } 42 | 43 | @Override 44 | public String encodeRedirectUrl(String url) { 45 | throw new UnsupportedOperationException(); 46 | } 47 | 48 | @Override 49 | public String encodeRedirectURL(String url) { 50 | throw new UnsupportedOperationException(); 51 | } 52 | 53 | @Override 54 | public String encodeUrl(String url) { 55 | throw new UnsupportedOperationException(); 56 | } 57 | 58 | @Override 59 | public String encodeURL(String url) { 60 | throw new UnsupportedOperationException(); 61 | } 62 | 63 | @Override 64 | public void flushBuffer() throws IOException { 65 | throw new UnsupportedOperationException(); 66 | } 67 | 68 | @Override 69 | public int getBufferSize() { 70 | throw new UnsupportedOperationException(); 71 | } 72 | 73 | @Override 74 | public String getCharacterEncoding() { 75 | throw new UnsupportedOperationException(); 76 | } 77 | 78 | @Override 79 | public String getContentType() { 80 | throw new UnsupportedOperationException(); 81 | } 82 | 83 | @Override 84 | public String getHeader(String name) { 85 | throw new UnsupportedOperationException(); 86 | } 87 | 88 | @Override 89 | public Collection getHeaders(String name) { 90 | throw new UnsupportedOperationException(); 91 | } 92 | 93 | @Override 94 | public Collection getHeaderNames() { 95 | throw new UnsupportedOperationException(); 96 | } 97 | 98 | @Override 99 | public Locale getLocale() { 100 | throw new UnsupportedOperationException(); 101 | } 102 | 103 | @Override 104 | public ServletOutputStream getOutputStream() throws IOException { 105 | throw new UnsupportedOperationException(); 106 | } 107 | 108 | @Override 109 | public int getStatus() { 110 | throw new UnsupportedOperationException(); 111 | } 112 | 113 | @Override 114 | public PrintWriter getWriter() throws IOException { 115 | throw new UnsupportedOperationException(); 116 | } 117 | 118 | @Override 119 | public boolean isCommitted() { 120 | throw new UnsupportedOperationException(); 121 | } 122 | 123 | @Override 124 | public void reset() { 125 | throw new UnsupportedOperationException(); 126 | } 127 | 128 | @Override 129 | public void resetBuffer() { 130 | throw new UnsupportedOperationException(); 131 | } 132 | 133 | @Override 134 | public void sendError(int sc) throws IOException { 135 | throw new UnsupportedOperationException(); 136 | } 137 | 138 | @Override 139 | public void sendError(int sc, String msg) throws IOException { 140 | throw new UnsupportedOperationException(); 141 | } 142 | 143 | @Override 144 | public void sendRedirect(String location) throws IOException { 145 | throw new UnsupportedOperationException(); 146 | } 147 | 148 | @Override 149 | public void setBufferSize(int size) { 150 | throw new UnsupportedOperationException(); 151 | } 152 | 153 | @Override 154 | public void setCharacterEncoding(String characterEncoding) { 155 | throw new UnsupportedOperationException(); 156 | } 157 | 158 | @Override 159 | public void setContentLength(int len) { 160 | throw new UnsupportedOperationException(); 161 | } 162 | 163 | @Override 164 | public void setContentLengthLong(long l) { throw new UnsupportedOperationException(); } 165 | 166 | @Override 167 | public void setContentType(String contentType) { 168 | throw new UnsupportedOperationException(); 169 | } 170 | 171 | @Override 172 | public void setDateHeader(String name, long date) { 173 | throw new UnsupportedOperationException(); 174 | } 175 | 176 | @Override 177 | public void setHeader(String name, String value) { 178 | throw new UnsupportedOperationException(); 179 | } 180 | 181 | @Override 182 | public void setIntHeader(String name, int value) { 183 | throw new UnsupportedOperationException(); 184 | } 185 | 186 | @Override 187 | public void setLocale(Locale loc) { 188 | throw new UnsupportedOperationException(); 189 | } 190 | 191 | @Override 192 | public void setStatus(int status) { 193 | throw new UnsupportedOperationException(); 194 | } 195 | 196 | @Override 197 | public void setStatus(int status, String sm) { 198 | throw new UnsupportedOperationException(); 199 | } 200 | 201 | } 202 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/BaseSessionImpl.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import javax.servlet.ServletContext; 6 | import javax.servlet.http.HttpSession; 7 | import javax.servlet.http.HttpSessionContext; 8 | import java.util.Enumeration; 9 | 10 | /** 11 | * Base class for mock HttpSession implementations. 12 | */ 13 | public class BaseSessionImpl implements HttpSession { 14 | 15 | @Override 16 | public long getCreationTime() { 17 | throw new UnsupportedOperationException(); 18 | } 19 | 20 | @Override 21 | public String getId() { 22 | throw new UnsupportedOperationException(); 23 | } 24 | 25 | @Override 26 | public long getLastAccessedTime() { 27 | throw new UnsupportedOperationException(); 28 | } 29 | 30 | @Override 31 | public ServletContext getServletContext() { 32 | throw new UnsupportedOperationException(); 33 | } 34 | 35 | @Override 36 | public void setMaxInactiveInterval(int interval) { 37 | throw new UnsupportedOperationException(); 38 | } 39 | 40 | @Override 41 | public int getMaxInactiveInterval() { 42 | throw new UnsupportedOperationException(); 43 | } 44 | 45 | @Override 46 | public HttpSessionContext getSessionContext() { 47 | throw new UnsupportedOperationException(); 48 | } 49 | 50 | @Override 51 | public Object getAttribute(String name) { 52 | throw new UnsupportedOperationException(); 53 | } 54 | 55 | @Override 56 | public Object getValue(String name) { 57 | throw new UnsupportedOperationException(); 58 | } 59 | 60 | @Override 61 | public Enumeration getAttributeNames() { 62 | throw new UnsupportedOperationException(); 63 | } 64 | 65 | @Override 66 | public String[] getValueNames() { 67 | throw new UnsupportedOperationException(); 68 | } 69 | 70 | @Override 71 | public void setAttribute(String name, Object value) { 72 | throw new UnsupportedOperationException(); 73 | } 74 | 75 | @Override 76 | public void putValue(String name, Object value) { 77 | throw new UnsupportedOperationException(); 78 | } 79 | 80 | @Override 81 | public void removeAttribute(String name) { 82 | throw new UnsupportedOperationException(); 83 | } 84 | 85 | @Override 86 | public void removeValue(String name) { 87 | throw new UnsupportedOperationException(); 88 | } 89 | 90 | @Override 91 | public void invalidate() { 92 | throw new UnsupportedOperationException(); 93 | } 94 | 95 | @Override 96 | public boolean isNew() { 97 | throw new UnsupportedOperationException(); 98 | } 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/Dispatcher.java: -------------------------------------------------------------------------------- 1 | package io.resurface; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | 5 | public class Dispatcher implements Runnable { 6 | 7 | /** 8 | * Initialize dispatcher using buffer size. 9 | * @param logger Resurface logger. 10 | * @param threshold NDJSON buffer max size - flushAndDispatch will be triggered after reaching this point. 11 | */ 12 | public Dispatcher(BaseLogger logger, int threshold) { 13 | this.logger = logger; 14 | this.batchingThreshold = threshold; 15 | this.buffer = new StringBuilder(this.batchingThreshold + 5 * 1024); 16 | } 17 | 18 | public void run() { 19 | try { 20 | while (true) { 21 | if (buffer.length() >= batchingThreshold || logger.msg_queue.peek() == null) { 22 | flushAndDispatch(); 23 | } 24 | String msg = (String) logger.msg_queue.take(); 25 | if (msg.equals("POISON PILL")) { 26 | flushAndDispatch(); 27 | break; 28 | } 29 | buffer.append(msg).append("\n"); 30 | } 31 | } catch (InterruptedException e) { 32 | flushAndDispatch(); 33 | } 34 | } 35 | 36 | /** 37 | * Builds message as an NDJSON-formatted string, and dispatches it. Buffer is reset. 38 | */ 39 | private void flushAndDispatch() { 40 | if (buffer.length() != 0) { 41 | if (buffer.length() >= batchingThreshold) full_buffer_count.incrementAndGet(); 42 | if (logger.msg_queue.peek() == null) empty_queue_count.incrementAndGet(); 43 | String msg = buffer.toString(); 44 | buffer = new StringBuilder(); 45 | logger.dispatch(msg); 46 | } 47 | } 48 | 49 | private final BaseLogger logger; 50 | private StringBuilder buffer; 51 | private final int batchingThreshold; 52 | private final AtomicInteger full_buffer_count = new AtomicInteger(); 53 | private final AtomicInteger empty_queue_count = new AtomicInteger(); 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/HttpLogger.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import java.util.HashMap; 6 | import java.util.List; 7 | 8 | /** 9 | * Usage logger for HTTP/HTTPS protocol. 10 | */ 11 | public class HttpLogger extends BaseLogger { 12 | 13 | /** 14 | * Agent string identifying this logger. 15 | */ 16 | public static final String AGENT = "HttpLogger.java"; 17 | 18 | /** 19 | * Initialize logger using default url and default rules. 20 | */ 21 | public HttpLogger() { 22 | super(AGENT); 23 | initialize(null); 24 | } 25 | 26 | /** 27 | * Initialize enabled/disabled logger using default url and default rules. 28 | */ 29 | public HttpLogger(boolean enabled) { 30 | super(AGENT, enabled); 31 | initialize(null); 32 | } 33 | 34 | /** 35 | * Initialize logger using specified url and default rules. 36 | */ 37 | public HttpLogger(String url) { 38 | super(AGENT, url); 39 | initialize(null); 40 | } 41 | 42 | /** 43 | * Initialize logger using specified url and specified rules. 44 | */ 45 | public HttpLogger(String url, String rules) { 46 | super(AGENT, url); 47 | initialize(rules); 48 | } 49 | 50 | /** 51 | * Initialize enabled/disabled logger using specified url and default rules. 52 | */ 53 | public HttpLogger(String url, boolean enabled) { 54 | super(AGENT, url, enabled); 55 | initialize(null); 56 | } 57 | 58 | /** 59 | * Initialize enabled/disabled logger using specified url and specified rules. 60 | */ 61 | public HttpLogger(String url, boolean enabled, String rules) { 62 | super(AGENT, url, enabled); 63 | initialize(rules); 64 | } 65 | 66 | /** 67 | * Initialize enabled logger using queue and default rules. 68 | */ 69 | public HttpLogger(List queue) { 70 | super(AGENT, queue); 71 | initialize(null); 72 | } 73 | 74 | /** 75 | * Initialize enabled logger using queue and specified rules. 76 | */ 77 | public HttpLogger(List queue, String rules) { 78 | super(AGENT, queue); 79 | initialize(rules); 80 | } 81 | 82 | /** 83 | * Initialize enabled/disabled logger using queue and default rules. 84 | */ 85 | public HttpLogger(List queue, boolean enabled) { 86 | super(AGENT, queue, enabled); 87 | initialize(null); 88 | } 89 | 90 | /** 91 | * Initialize enabled/disabled logger using queue and specified rules. 92 | */ 93 | public HttpLogger(List queue, boolean enabled, String rules) { 94 | super(AGENT, queue, enabled); 95 | initialize(rules); 96 | } 97 | 98 | /** 99 | * Initialize a new logger. 100 | */ 101 | private void initialize(String rules) { 102 | // parse specified rules 103 | this.rules = new HttpRules(rules); 104 | 105 | // apply configuration rules 106 | this.skip_compression = this.rules.skip_compression; 107 | this.skip_submission = this.rules.skip_submission; 108 | if ((url != null) && (url.startsWith("http:") && !this.rules.allow_http_url)) { 109 | this.enableable = false; 110 | this.enabled = false; 111 | } 112 | } 113 | 114 | /** 115 | * Returns rules specified when creating this logger. 116 | */ 117 | public HttpRules getRules() { 118 | return rules; 119 | } 120 | 121 | /** 122 | * Apply logging rules to message details and submit JSON message. 123 | */ 124 | public void submitIfPassing(List details, HashMap customFields) { 125 | // apply active rules 126 | details = rules.apply(details); 127 | if (details == null) return; 128 | 129 | for (String field: customFields.keySet()) { 130 | details.add(new String[]{"custom_field:" + field, customFields.get(field)}); 131 | } 132 | 133 | // finalize message 134 | details.add(new String[]{"host", this.host}); 135 | 136 | // let's do this thing 137 | submit(Json.stringify(details)); 138 | } 139 | 140 | protected HttpRules rules; 141 | 142 | } 143 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/HttpLoggerForJersey.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import javax.ws.rs.WebApplicationException; 6 | import javax.ws.rs.container.ContainerRequestContext; 7 | import javax.ws.rs.container.ContainerRequestFilter; 8 | import javax.ws.rs.container.ContainerResponseContext; 9 | import javax.ws.rs.container.ContainerResponseFilter; 10 | import javax.ws.rs.ext.*; 11 | import java.io.IOException; 12 | import java.nio.charset.StandardCharsets; 13 | import java.util.ArrayList; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | @Provider 19 | public class HttpLoggerForJersey implements ContainerRequestFilter, ContainerResponseFilter, ReaderInterceptor, WriterInterceptor { 20 | 21 | /** 22 | * Initialize logger using specified url and default rules. 23 | */ 24 | public HttpLoggerForJersey(String url) { 25 | logger = new HttpLogger(url); 26 | } 27 | 28 | /** 29 | * Initialize logger using specified url and specified rules. 30 | */ 31 | public HttpLoggerForJersey(String url, String rules) { 32 | logger = new HttpLogger(url, rules); 33 | } 34 | 35 | /** 36 | * Initialize enabled logger using queue and default rules. 37 | */ 38 | public HttpLoggerForJersey(List queue) { 39 | logger = new HttpLogger(queue); 40 | } 41 | 42 | /** 43 | * Initialize enabled logger using queue and specified rules. 44 | */ 45 | public HttpLoggerForJersey(List queue, String rules) { 46 | logger = new HttpLogger(queue, rules); 47 | } 48 | 49 | /** 50 | * Returns wrapped logger instance. 51 | */ 52 | public HttpLogger getLogger() { 53 | return this.logger; 54 | } 55 | 56 | /** 57 | * Interceptor method called when request is first accepted. 58 | */ 59 | @Override 60 | public void filter(ContainerRequestContext context) { 61 | context.setProperty("resurfaceio.start", System.nanoTime()); 62 | } 63 | 64 | /** 65 | * Interceptor method called when reading from the request body. 66 | */ 67 | @Override 68 | public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException { 69 | if (logger.enabled) { 70 | LoggedInputStream lis = new LoggedInputStream(context.getInputStream()); 71 | context.setProperty("resurfaceio.requestBodyBytes", lis.logged()); 72 | context.setInputStream(lis); 73 | } 74 | return context.proceed(); 75 | } 76 | 77 | /** 78 | * Filter method called after a response has been provided for a request. 79 | */ 80 | @Override 81 | public void filter(ContainerRequestContext request, ContainerResponseContext response) { 82 | if (!logger.enabled) return; 83 | List message = new ArrayList<>(); 84 | String method = request.getMethod(); 85 | if (method != null) message.add(new String[]{"request_method", method}); 86 | String formatted_url = request.getUriInfo().getRequestUri().toString(); 87 | if (formatted_url != null) message.add(new String[]{"request_url", formatted_url}); 88 | message.add(new String[]{"response_code", String.valueOf(response.getStatus())}); 89 | appendRequestHeaders(message, request); 90 | appendRequestParams(message, request); 91 | appendResponseHeaders(message, response); 92 | request.setProperty("resurfaceio.message", message); 93 | } 94 | 95 | /** 96 | * Adds request headers to message. 97 | */ 98 | private static void appendRequestHeaders(List message, ContainerRequestContext request) { 99 | for (Map.Entry> x : request.getHeaders().entrySet()) { 100 | String name = "request_header:" + x.getKey().toLowerCase(); 101 | for (String xv : x.getValue()) message.add(new String[]{name, xv}); 102 | } 103 | } 104 | 105 | /** 106 | * Adds request params to message. 107 | */ 108 | private static void appendRequestParams(List message, ContainerRequestContext request) { 109 | for (Map.Entry> x : request.getUriInfo().getQueryParameters(true).entrySet()) { 110 | String name = "request_param:" + x.getKey().toLowerCase(); 111 | for (String xv : x.getValue()) message.add(new String[]{name, xv}); 112 | } 113 | } 114 | 115 | /** 116 | * Adds response headers to message. 117 | */ 118 | private static void appendResponseHeaders(List message, ContainerResponseContext response) { 119 | for (Map.Entry> x : response.getStringHeaders().entrySet()) { 120 | String name = "response_header:" + x.getKey().toLowerCase(); 121 | for (String xv : x.getValue()) message.add(new String[]{name, xv}); 122 | } 123 | } 124 | 125 | /** 126 | * Interceptor method called when writing out the response body. 127 | */ 128 | @Override 129 | public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException { 130 | if (logger.enabled) { 131 | List message = (List) context.getProperty("resurfaceio.message"); 132 | LoggedOutputStream los = new LoggedOutputStream(context.getOutputStream()); 133 | context.setOutputStream(los); 134 | context.proceed(); 135 | byte[] rbb = (byte[]) context.getProperty("resurfaceio.requestBodyBytes"); 136 | String request_body = (rbb == null) ? null : new String(rbb, StandardCharsets.UTF_8); 137 | if (request_body != null && !request_body.equals("")) message.add(new String[]{"request_body", request_body}); 138 | String response_body = new String(los.logged(), StandardCharsets.UTF_8); 139 | if (!response_body.equals("")) message.add(new String[]{"response_body", response_body}); 140 | message.add(new String[]{"now", String.valueOf(System.currentTimeMillis())}); 141 | double interval = (System.nanoTime() - (Long) context.getProperty("resurfaceio.start")) / 1000000.0; 142 | message.add(new String[]{"interval", String.valueOf(interval)}); 143 | HashMap customFields = new HashMap<>(); 144 | logger.submitIfPassing(message, customFields); 145 | } else { 146 | context.proceed(); 147 | } 148 | } 149 | 150 | protected final HttpLogger logger; 151 | 152 | } 153 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/HttpLoggerForServlets.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import javax.servlet.*; 6 | import javax.servlet.http.HttpServletRequest; 7 | import javax.servlet.http.HttpServletResponse; 8 | import java.io.IOException; 9 | import java.util.List; 10 | 11 | /** 12 | * Servlet filter for HTTP usage logging. 13 | */ 14 | public class HttpLoggerForServlets implements Filter { 15 | 16 | /** 17 | * Initialize with default parameters. 18 | */ 19 | public HttpLoggerForServlets() { 20 | this.queue = null; 21 | this.rules = null; 22 | } 23 | 24 | /** 25 | * Initialize filter using supplied queue. 26 | */ 27 | public HttpLoggerForServlets(List queue, String rules) { 28 | this.queue = queue; 29 | this.rules = rules; 30 | } 31 | 32 | /** 33 | * Returns wrapped logger instance. 34 | */ 35 | public HttpLogger getLogger() { 36 | return this.logger; 37 | } 38 | 39 | /** 40 | * Called when filter is placed into service. 41 | */ 42 | public void init(FilterConfig config) { 43 | this.config = config; 44 | if (this.queue != null) { 45 | this.logger = new HttpLogger(queue, rules); 46 | } else if (config != null) { 47 | this.logger = new HttpLogger(config.getInitParameter("url"), config.getInitParameter("rules")); 48 | } else { 49 | this.logger = new HttpLogger(); 50 | } 51 | } 52 | 53 | /** 54 | * Called when filter is taken out of service. 55 | */ 56 | public void destroy() { 57 | this.config = null; 58 | this.logger = null; 59 | } 60 | 61 | /** 62 | * Called when request/response passes through the filter chain. 63 | */ 64 | public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 65 | throws IOException, ServletException { 66 | if (logger.isEnabled()) { 67 | log((HttpServletRequest) request, (HttpServletResponse) response, chain); 68 | } else { 69 | chain.doFilter(request, response); 70 | } 71 | } 72 | 73 | /** 74 | * Logs the request/response from within the filter chain. 75 | */ 76 | protected void log(HttpServletRequest request, HttpServletResponse response, FilterChain chain) 77 | throws IOException, ServletException { 78 | long start = System.nanoTime(); 79 | 80 | // Construct response wrapper and pass through filter chain 81 | LoggedResponseWrapper response_wrapper = new LoggedResponseWrapper(response); 82 | chain.doFilter(request, response_wrapper); 83 | response_wrapper.flushBuffer(); 84 | 85 | // Log successful responses having string content types 86 | String response_encoding = response.getCharacterEncoding(); 87 | response_encoding = (response_encoding == null) ? "ISO-8859-1" : response_encoding; 88 | String response_body = new String(response_wrapper.logged(), response_encoding); 89 | long now = System.currentTimeMillis(); 90 | double interval = (System.nanoTime() - start) / 1000000.0; 91 | HttpMessage.send(logger, request, response, response_body, null, now, interval); 92 | } 93 | 94 | protected FilterConfig config; 95 | protected HttpLogger logger; 96 | protected final List queue; 97 | protected final String rules; 98 | 99 | } 100 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/HttpMessage.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | import javax.servlet.http.HttpServletResponse; 7 | import javax.servlet.http.HttpSession; 8 | import java.util.ArrayList; 9 | import java.util.Enumeration; 10 | import java.util.Iterator; 11 | import java.util.List; 12 | import java.util.HashMap; 13 | import java.util.regex.Pattern; 14 | 15 | /** 16 | * Message implementation for HTTP logger. 17 | */ 18 | public class HttpMessage { 19 | 20 | /** 21 | * Submits request and response through logger. 22 | */ 23 | public static void send(HttpLogger logger, HttpServletRequest request, HttpServletResponse response) { 24 | send(logger, request, response, null, null, 0, 0, new HashMap<>()); 25 | } 26 | 27 | /** 28 | * Submits request and response through logger. 29 | */ 30 | public static void send(HttpLogger logger, HttpServletRequest request, HttpServletResponse response, String response_body) { 31 | send(logger, request, response, response_body, null, 0, 0, new HashMap<>()); 32 | } 33 | 34 | /** 35 | * Submits request and response through logger. 36 | */ 37 | public static void send(HttpLogger logger, HttpServletRequest request, HttpServletResponse response, 38 | String response_body, String request_body) { 39 | send(logger, request, response, response_body, request_body, 0, 0, new HashMap<>()); 40 | } 41 | 42 | /** 43 | * Submits request and response through logger. 44 | */ 45 | public static void send(HttpLogger logger, HttpServletRequest request, HttpServletResponse response, 46 | String response_body, String request_body, long now, double interval) { 47 | send(logger, request, response, response_body, request_body, now, interval, new HashMap<>()); 48 | } 49 | 50 | /** 51 | * Submits request and response through logger. 52 | */ 53 | public static void send(HttpLogger logger, HttpServletRequest request, HttpServletResponse response, 54 | String response_body, String request_body, long now, double interval, 55 | HashMap customFields) { 56 | 57 | if (!logger.isEnabled()) return; 58 | 59 | // copy details from request & response 60 | List message = HttpMessage.build(request, response, response_body, request_body); 61 | 62 | // copy data from session if configured 63 | if (!logger.getRules().copy_session_field.isEmpty()) { 64 | HttpSession ssn = request.getSession(false); 65 | if (ssn != null) { 66 | for (HttpRule r : logger.getRules().copy_session_field) { 67 | Enumeration names = ssn.getAttributeNames(); 68 | while (names.hasMoreElements()) { 69 | String d = names.nextElement(); 70 | if (((Pattern) r.param1).matcher(d).matches()) { 71 | String val = ssn.getAttribute(d).toString(); 72 | message.add(new String[]{"session_field:" + d.toLowerCase(), val}); 73 | } 74 | } 75 | } 76 | } 77 | } 78 | 79 | // add timing details 80 | if (now == 0) now = System.currentTimeMillis(); 81 | message.add(new String[]{"now", String.valueOf(now)}); 82 | if (interval != 0) message.add(new String[]{"interval", String.valueOf(interval)}); 83 | 84 | logger.submitIfPassing(message, customFields); 85 | } 86 | 87 | /** 88 | * Builds list of key/value pairs for HTTP request and response. 89 | */ 90 | public static List build(HttpServletRequest request, HttpServletResponse response, 91 | String response_body, String request_body) { 92 | List message = new ArrayList<>(); 93 | String method = request.getMethod(); 94 | if (method != null) message.add(new String[]{"request_method", method}); 95 | String formatted_url = formatURL(request); 96 | if (formatted_url != null) message.add(new String[]{"request_url", formatted_url}); 97 | message.add(new String[]{"response_code", String.valueOf(response.getStatus())}); 98 | appendRequestHeaders(message, request); 99 | appendRequestParams(message, request); 100 | appendResponseHeaders(message, response); 101 | if (request_body != null && !request_body.equals("")) message.add(new String[]{"request_body", request_body}); 102 | if (response_body != null && !response_body.equals("")) message.add(new String[]{"response_body", response_body}); 103 | return message; 104 | } 105 | 106 | /** 107 | * Adds request headers to message. 108 | */ 109 | private static void appendRequestHeaders(List message, HttpServletRequest request) { 110 | Enumeration header_names = request.getHeaderNames(); 111 | while (header_names.hasMoreElements()) { 112 | String name = header_names.nextElement(); 113 | Enumeration e = request.getHeaders(name); 114 | name = "request_header:" + name.toLowerCase(); 115 | while (e.hasMoreElements()) message.add(new String[]{name, e.nextElement()}); 116 | } 117 | } 118 | 119 | /** 120 | * Adds request params to message. 121 | */ 122 | private static void appendRequestParams(List message, HttpServletRequest request) { 123 | Enumeration param_names = request.getParameterNames(); 124 | while (param_names.hasMoreElements()) { 125 | String name = param_names.nextElement(); 126 | String[] values = request.getParameterValues(name); 127 | if (values != null) { 128 | name = "request_param:" + name.toLowerCase(); 129 | for (String value : values) message.add(new String[]{name, value}); 130 | } 131 | } 132 | } 133 | 134 | /** 135 | * Adds response headers to message. 136 | */ 137 | private static void appendResponseHeaders(List message, HttpServletResponse response) { 138 | for (String name : response.getHeaderNames()) { 139 | Iterator i = response.getHeaders(name).iterator(); 140 | name = "response_header:" + name.toLowerCase(); 141 | while (i.hasNext()) message.add(new String[]{name, i.next()}); 142 | } 143 | } 144 | 145 | /** 146 | * Returns complete request URL without query string. 147 | */ 148 | private static String formatURL(HttpServletRequest request) { 149 | StringBuffer url = request.getRequestURL(); 150 | return (url == null) ? null : url.toString().split("\\?")[0]; 151 | } 152 | 153 | } 154 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/HttpRule.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import java.util.regex.Pattern; 6 | 7 | /** 8 | * Parsed rule for HTTP logger. 9 | */ 10 | public class HttpRule { 11 | 12 | public HttpRule(String verb, Pattern scope, Object param1, Object param2) { 13 | this.verb = verb; 14 | this.scope = scope; 15 | this.param1 = param1; 16 | this.param2 = param2; 17 | } 18 | 19 | public final String verb; 20 | public final Pattern scope; 21 | public final Object param1; 22 | public final Object param2; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/HttpRules.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import java.nio.file.Files; 6 | import java.nio.file.Paths; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Random; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | import java.util.regex.PatternSyntaxException; 13 | 14 | import static java.util.stream.Collectors.toList; 15 | 16 | /** 17 | * Parser and utilities for HTTP logger rules. 18 | */ 19 | public class HttpRules { 20 | 21 | public static final String DEBUG_RULES = "allow_http_url\ncopy_session_field /.*/\n"; 22 | 23 | public static final String STANDARD_RULES = "/request_header:cookie|response_header:set-cookie/remove\n" + 24 | "/(request|response)_body|request_param/ replace /[a-zA-Z0-9.!#$%&’*+\\/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)/, /x@y.com/\n" + 25 | "/request_body|request_param|response_body/ replace /[0-9\\.\\-\\/]{9,}/, /xyxy/\n"; 26 | 27 | public static final String STRICT_RULES = "/request_url/ replace /([^\\?;]+).*/, /$1/\n" + 28 | "/request_body|response_body|request_param:.*|request_header:(?!user-agent).*|response_header:(?!(content-length)|(content-type)).*/ remove\n"; 29 | 30 | private static String defaultRules = STRICT_RULES; 31 | 32 | /** 33 | * Returns rules used by default when none are declared. 34 | */ 35 | public static String getDefaultRules() { 36 | return defaultRules; 37 | } 38 | 39 | /** 40 | * Updates rules used by default when none are declared. 41 | */ 42 | public static void setDefaultRules(String r) { 43 | defaultRules = r.replaceAll("(?m)^\\s*include default\\s*$", ""); 44 | } 45 | 46 | /** 47 | * Rules providing all details for debugging an application. 48 | */ 49 | public static String getDebugRules() { 50 | return DEBUG_RULES; 51 | } 52 | 53 | /** 54 | * Rules that block common kinds of sensitive data. 55 | */ 56 | public static String getStandardRules() { 57 | return STANDARD_RULES; 58 | } 59 | 60 | /** 61 | * Rules providing minimal details, used by default. 62 | */ 63 | public static String getStrictRules() { 64 | return STRICT_RULES; 65 | } 66 | 67 | /** 68 | * Parses rule from single line. 69 | */ 70 | public static HttpRule parseRule(String r) { 71 | if ((r == null) || REGEX_BLANK_OR_COMMENT.matcher(r).matches()) return null; 72 | Matcher m = REGEX_ALLOW_HTTP_URL.matcher(r); 73 | if (m.matches()) return new HttpRule("allow_http_url", null, null, null); 74 | m = REGEX_COPY_SESSION_FIELD.matcher(r); 75 | if (m.matches()) return new HttpRule("copy_session_field", null, parseRegex(r, m.group(1)), null); 76 | m = REGEX_REMOVE.matcher(r); 77 | if (m.matches()) return new HttpRule("remove", parseRegex(r, m.group(1)), null, null); 78 | m = REGEX_REMOVE_IF.matcher(r); 79 | if (m.matches()) return new HttpRule("remove_if", parseRegex(r, m.group(1)), parseRegex(r, m.group(2)), null); 80 | m = REGEX_REMOVE_IF_FOUND.matcher(r); 81 | if (m.matches()) return new HttpRule("remove_if_found", parseRegex(r, m.group(1)), parseRegexFind(r, m.group(2)), null); 82 | m = REGEX_REMOVE_UNLESS.matcher(r); 83 | if (m.matches()) return new HttpRule("remove_unless", parseRegex(r, m.group(1)), parseRegex(r, m.group(2)), null); 84 | m = REGEX_REMOVE_UNLESS_FOUND.matcher(r); 85 | if (m.matches()) 86 | return new HttpRule("remove_unless_found", parseRegex(r, m.group(1)), parseRegexFind(r, m.group(2)), null); 87 | m = REGEX_REPLACE.matcher(r); 88 | if (m.matches()) 89 | return new HttpRule("replace", parseRegex(r, m.group(1)), parseRegexFind(r, m.group(2)), parseString(r, m.group(3))); 90 | m = REGEX_SAMPLE.matcher(r); 91 | if (m.matches()) { 92 | Integer m1 = Integer.valueOf(m.group(1)); 93 | if (m1 < 1 || m1 > 99) throw new IllegalArgumentException(String.format("Invalid sample percent: %d", m1)); 94 | return new HttpRule("sample", null, m1, null); 95 | } 96 | m = REGEX_SKIP_COMPRESSION.matcher(r); 97 | if (m.matches()) return new HttpRule("skip_compression", null, null, null); 98 | m = REGEX_SKIP_SUBMISSION.matcher(r); 99 | if (m.matches()) return new HttpRule("skip_submission", null, null, null); 100 | m = REGEX_STOP.matcher(r); 101 | if (m.matches()) return new HttpRule("stop", parseRegex(r, m.group(1)), null, null); 102 | m = REGEX_STOP_IF.matcher(r); 103 | if (m.matches()) return new HttpRule("stop_if", parseRegex(r, m.group(1)), parseRegex(r, m.group(2)), null); 104 | m = REGEX_STOP_IF_FOUND.matcher(r); 105 | if (m.matches()) return new HttpRule("stop_if_found", parseRegex(r, m.group(1)), parseRegexFind(r, m.group(2)), null); 106 | m = REGEX_STOP_UNLESS.matcher(r); 107 | if (m.matches()) return new HttpRule("stop_unless", parseRegex(r, m.group(1)), parseRegex(r, m.group(2)), null); 108 | m = REGEX_STOP_UNLESS_FOUND.matcher(r); 109 | if (m.matches()) return new HttpRule("stop_unless_found", parseRegex(r, m.group(1)), parseRegexFind(r, m.group(2)), null); 110 | throw new IllegalArgumentException(String.format("Invalid rule: %s", r)); 111 | } 112 | 113 | /** 114 | * Parses regex for matching. 115 | */ 116 | private static Pattern parseRegex(String r, String regex) { 117 | String s = parseString(r, regex); 118 | if ("*".equals(s) || "+".equals(s) || "?".equals(s)) 119 | throw new IllegalArgumentException(String.format("Invalid regex (%s) in rule: %s", regex, r)); 120 | if (!s.startsWith("^")) s = "^" + s; 121 | if (!s.endsWith("$")) s = s + "$"; 122 | try { 123 | return Pattern.compile(s); 124 | } catch (PatternSyntaxException pse) { 125 | throw new IllegalArgumentException(String.format("Invalid regex (%s) in rule: %s", regex, r)); 126 | } 127 | } 128 | 129 | /** 130 | * Parses regex for finding. 131 | */ 132 | private static Pattern parseRegexFind(String r, String regex) { 133 | try { 134 | return Pattern.compile(parseString(r, regex)); 135 | } catch (PatternSyntaxException pse) { 136 | throw new IllegalArgumentException(String.format("Invalid regex (%s) in rule: %s", regex, r)); 137 | } 138 | } 139 | 140 | /** 141 | * Parses delimited string expression. 142 | */ 143 | private static String parseString(String r, String expr) { 144 | for (String sep : new String[]{"~", "!", "%", "|", "/"}) { 145 | Matcher m = Pattern.compile(String.format("^[%s](.*)[%s]$", sep, sep)).matcher(expr); 146 | if (m.matches()) { 147 | String m1 = m.group(1); 148 | if (Pattern.compile(String.format("^[%s].*|.*[^\\\\][%s].*", sep, sep)).matcher(m1).matches()) 149 | throw new IllegalArgumentException(String.format("Unescaped separator (%s) in rule: %s", sep, r)); 150 | return m1.replace("\\" + sep, sep); 151 | } 152 | } 153 | throw new IllegalArgumentException(String.format("Invalid expression (%s) in rule: %s", expr, r)); 154 | } 155 | 156 | /** 157 | * Initialize a new set of rules. 158 | */ 159 | public HttpRules(String rules) { 160 | if (rules == null) rules = HttpRules.getDefaultRules(); 161 | 162 | // load rules from external files 163 | if (rules.startsWith("file://")) { 164 | String rfile = rules.substring(7).trim(); 165 | try { 166 | rules = new String(Files.readAllBytes(Paths.get(rfile))); 167 | } catch (Exception e) { 168 | throw new IllegalArgumentException("Failed to load rules: " + rfile); 169 | } 170 | } 171 | 172 | // force default rules if necessary 173 | rules = rules.replaceAll("(?m)^\\s*include default\\s*$", Matcher.quoteReplacement(HttpRules.getDefaultRules())); 174 | if (rules.trim().length() == 0) rules = HttpRules.getDefaultRules(); 175 | 176 | // expand rule includes 177 | rules = rules.replaceAll("(?m)^\\s*include debug\\s*$", Matcher.quoteReplacement(getDebugRules())); 178 | rules = rules.replaceAll("(?m)^\\s*include standard\\s*$", Matcher.quoteReplacement(getStandardRules())); 179 | rules = rules.replaceAll("(?m)^\\s*include strict\\s*$", Matcher.quoteReplacement(getStrictRules())); 180 | this.text = rules; 181 | 182 | // parse all rules 183 | List prs = new ArrayList<>(); 184 | for (String rule : this.text.split("\\r?\\n")) { 185 | HttpRule parsed = parseRule(rule); 186 | if (parsed != null) prs.add(parsed); 187 | } 188 | this.size = prs.size(); 189 | 190 | // break out rules by verb 191 | this.allow_http_url = prs.stream().anyMatch(r -> "allow_http_url".equals(r.verb)); 192 | this.copy_session_field = prs.stream().filter(r -> "copy_session_field".equals(r.verb)).collect(toList()); 193 | this.remove = prs.stream().filter(r -> "remove".equals(r.verb)).collect(toList()); 194 | this.remove_if = prs.stream().filter(r -> "remove_if".equals(r.verb)).collect(toList()); 195 | this.remove_if_found = prs.stream().filter(r -> "remove_if_found".equals(r.verb)).collect(toList()); 196 | this.remove_unless = prs.stream().filter(r -> "remove_unless".equals(r.verb)).collect(toList()); 197 | this.remove_unless_found = prs.stream().filter(r -> "remove_unless_found".equals(r.verb)).collect(toList()); 198 | this.replace = prs.stream().filter(r -> "replace".equals(r.verb)).collect(toList()); 199 | this.sample = prs.stream().filter(r -> "sample".equals(r.verb)).collect(toList()); 200 | this.skip_compression = prs.stream().anyMatch(r -> "skip_compression".equals(r.verb)); 201 | this.skip_submission = prs.stream().anyMatch(r -> "skip_submission".equals(r.verb)); 202 | this.stop = prs.stream().filter(r -> "stop".equals(r.verb)).collect(toList()); 203 | this.stop_if = prs.stream().filter(r -> "stop_if".equals(r.verb)).collect(toList()); 204 | this.stop_if_found = prs.stream().filter(r -> "stop_if_found".equals(r.verb)).collect(toList()); 205 | this.stop_unless = prs.stream().filter(r -> "stop_unless".equals(r.verb)).collect(toList()); 206 | this.stop_unless_found = prs.stream().filter(r -> "stop_unless_found".equals(r.verb)).collect(toList()); 207 | 208 | // finish validating rules 209 | if (this.sample.size() > 1) throw new IllegalArgumentException("Multiple sample rules"); 210 | } 211 | 212 | public final boolean allow_http_url; 213 | public final List copy_session_field; 214 | public final List remove; 215 | public final List remove_if; 216 | public final List remove_if_found; 217 | public final List remove_unless; 218 | public final List remove_unless_found; 219 | public final List replace; 220 | public final List sample; 221 | public final boolean skip_compression; 222 | public final boolean skip_submission; 223 | public final int size; 224 | public final List stop; 225 | public final List stop_if; 226 | public final List stop_if_found; 227 | public final List stop_unless; 228 | public final List stop_unless_found; 229 | public final String text; 230 | 231 | /** 232 | * Apply current rules to message details. 233 | */ 234 | public List apply(List details) { 235 | // stop rules come first 236 | for (HttpRule r : stop) 237 | for (String[] d : details) 238 | if (r.scope.matcher(d[0]).matches()) return null; 239 | for (HttpRule r : stop_if_found) 240 | for (String[] d : details) 241 | if (r.scope.matcher(d[0]).matches() && ((Pattern) r.param1).matcher(d[1]).find()) return null; 242 | for (HttpRule r : stop_if) 243 | for (String[] d : details) 244 | if (r.scope.matcher(d[0]).matches() && ((Pattern) r.param1).matcher(d[1]).matches()) return null; 245 | int passed = 0; 246 | for (HttpRule r : stop_unless_found) 247 | for (String[] d : details) 248 | if (r.scope.matcher(d[0]).matches() && ((Pattern) r.param1).matcher(d[1]).find()) passed++; 249 | if (passed != stop_unless_found.size()) return null; 250 | passed = 0; 251 | for (HttpRule r : stop_unless) 252 | for (String[] d : details) 253 | if (r.scope.matcher(d[0]).matches() && ((Pattern) r.param1).matcher(d[1]).matches()) passed++; 254 | if (passed != stop_unless.size()) return null; 255 | 256 | // do sampling if configured 257 | if ((sample.size() == 1) && (RANDOM.nextInt(100) >= (Integer) sample.get(0).param1)) return null; 258 | 259 | // winnow sensitive details based on remove rules if configured 260 | for (HttpRule r : remove) 261 | details.removeIf(d -> r.scope.matcher(d[0]).matches()); 262 | for (HttpRule r : remove_unless_found) 263 | details.removeIf(d -> r.scope.matcher(d[0]).matches() && !((Pattern) r.param1).matcher(d[1]).find()); 264 | for (HttpRule r : remove_if_found) 265 | details.removeIf(d -> r.scope.matcher(d[0]).matches() && ((Pattern) r.param1).matcher(d[1]).find()); 266 | for (HttpRule r : remove_unless) 267 | details.removeIf(d -> r.scope.matcher(d[0]).matches() && !((Pattern) r.param1).matcher(d[1]).matches()); 268 | for (HttpRule r : remove_if) 269 | details.removeIf(d -> r.scope.matcher(d[0]).matches() && ((Pattern) r.param1).matcher(d[1]).matches()); 270 | if (details.isEmpty()) return null; 271 | 272 | // mask sensitive details based on replace rules if configured 273 | for (HttpRule r : replace) 274 | for (String[] d : details) 275 | if (r.scope.matcher(d[0]).matches()) d[1] = ((Pattern) r.param1).matcher(d[1]).replaceAll((String) r.param2); 276 | 277 | // remove any details with empty values 278 | details.removeIf(d -> "".equals(d[1])); 279 | if (details.isEmpty()) return null; 280 | 281 | return details; 282 | } 283 | 284 | private static final Random RANDOM = new Random(); 285 | private static final Pattern REGEX_ALLOW_HTTP_URL = Pattern.compile("^\\s*allow_http_url\\s*(#.*)?$"); 286 | private static final Pattern REGEX_BLANK_OR_COMMENT = Pattern.compile("^\\s*([#].*)*$"); 287 | private static final Pattern REGEX_COPY_SESSION_FIELD = Pattern.compile("^\\s*copy_session_field\\s+([~!%|/].+[~!%|/])\\s*(#.*)?$"); 288 | private static final Pattern REGEX_REMOVE = Pattern.compile("^\\s*([~!%|/].+[~!%|/])\\s*remove\\s*(#.*)?$"); 289 | private static final Pattern REGEX_REMOVE_IF = Pattern.compile("^\\s*([~!%|/].+[~!%|/])\\s*remove_if\\s+([~!%|/].+[~!%|/])\\s*(#.*)?$"); 290 | private static final Pattern REGEX_REMOVE_IF_FOUND = Pattern.compile("^\\s*([~!%|/].+[~!%|/])\\s*remove_if_found\\s+([~!%|/].+[~!%|/])\\s*(#.*)?$"); 291 | private static final Pattern REGEX_REMOVE_UNLESS = Pattern.compile("^\\s*([~!%|/].+[~!%|/])\\s*remove_unless\\s+([~!%|/].+[~!%|/])\\s*(#.*)?$"); 292 | private static final Pattern REGEX_REMOVE_UNLESS_FOUND = Pattern.compile("^\\s*([~!%|/].+[~!%|/])\\s*remove_unless_found\\s+([~!%|/].+[~!%|/])\\s*(#.*)?$"); 293 | private static final Pattern REGEX_REPLACE = Pattern.compile("^\\s*([~!%|/].+[~!%|/])\\s*replace[\\s]+([~!%|/].+[~!%|/]),[\\s]+([~!%|/].*[~!%|/])\\s*(#.*)?$"); 294 | private static final Pattern REGEX_SAMPLE = Pattern.compile("^\\s*sample\\s+(\\d+)\\s*(#.*)?$"); 295 | private static final Pattern REGEX_SKIP_COMPRESSION = Pattern.compile("^\\s*skip_compression\\s*(#.*)?$"); 296 | private static final Pattern REGEX_SKIP_SUBMISSION = Pattern.compile("^\\s*skip_submission\\s*(#.*)?$"); 297 | private static final Pattern REGEX_STOP = Pattern.compile("^\\s*([~!%|/].+[~!%|/])\\s*stop\\s*(#.*)?$"); 298 | private static final Pattern REGEX_STOP_IF = Pattern.compile("^\\s*([~!%|/].+[~!%|/])\\s*stop_if\\s+([~!%|/].+[~!%|/])\\s*(#.*)?$"); 299 | private static final Pattern REGEX_STOP_IF_FOUND = Pattern.compile("^\\s*([~!%|/].+[~!%|/])\\s*stop_if_found\\s+([~!%|/].+[~!%|/])\\s*(#.*)?$"); 300 | private static final Pattern REGEX_STOP_UNLESS = Pattern.compile("^\\s*([~!%|/].+[~!%|/])\\s*stop_unless\\s+([~!%|/].+[~!%|/])\\s*(#.*)?$"); 301 | private static final Pattern REGEX_STOP_UNLESS_FOUND = Pattern.compile("^\\s*([~!%|/].+[~!%|/])\\s*stop_unless_found\\s+([~!%|/].+[~!%|/])\\s*(#.*)?$"); 302 | 303 | } 304 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/HttpServletRequestImpl.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import javax.servlet.http.HttpSession; 6 | import java.util.*; 7 | 8 | /** 9 | * Mock HttpServletRequest implementation. 10 | */ 11 | public class HttpServletRequestImpl extends BaseServletRequestImpl { 12 | 13 | public void addHeader(String name, String value) { 14 | if (headers.containsKey(name)) { 15 | headers.get(name).add(value); 16 | } else { 17 | setHeader(name, value); 18 | } 19 | } 20 | 21 | public void addParam(String name, String value) { 22 | if (params.containsKey(name)) { 23 | params.get(name).add(value); 24 | } else { 25 | setParam(name, value); 26 | } 27 | } 28 | 29 | @Override 30 | public String getContentType() { 31 | return getHeader("Content-Type"); 32 | } 33 | 34 | @Override 35 | public String getHeader(String name) { 36 | return headers.containsKey(name) ? headers.get(name).get(0) : null; 37 | } 38 | 39 | @Override 40 | public Enumeration getHeaders(String name) { 41 | return headers.containsKey(name) ? Collections.enumeration(headers.get(name)) : null; 42 | } 43 | 44 | @Override 45 | public Enumeration getHeaderNames() { 46 | return Collections.enumeration(headers.keySet()); 47 | } 48 | 49 | @Override 50 | public String getMethod() { 51 | return method; 52 | } 53 | 54 | @Override 55 | public String getParameter(String name) { 56 | String[] values = getParameterValues(name); 57 | return (values == null || values.length == 0) ? null : values[0]; 58 | } 59 | 60 | @Override 61 | public Enumeration getParameterNames() { 62 | return java.util.Collections.enumeration(params.keySet()); 63 | } 64 | 65 | @Override 66 | public String[] getParameterValues(String name) { 67 | List values = params.get(name); 68 | if (values == null) { 69 | return null; 70 | } else { 71 | String[] results = new String[values.size()]; 72 | results = values.toArray(results); 73 | return results; 74 | } 75 | } 76 | 77 | @Override 78 | public String getQueryString() { 79 | return queryString; 80 | } 81 | 82 | @Override 83 | public StringBuffer getRequestURL() { 84 | return requestURL == null ? null : new StringBuffer(requestURL); 85 | } 86 | 87 | @Override 88 | public HttpSession getSession(boolean create) { 89 | if (create && this.session == null) this.session = new HttpSessionImpl(); 90 | return this.session; 91 | } 92 | 93 | public void setContentType(String contentType) { 94 | setHeader("Content-Type", contentType); 95 | } 96 | 97 | public void setHeader(String name, String value) { 98 | List values = new ArrayList<>(); 99 | values.add(value); 100 | headers.put(name, values); 101 | } 102 | 103 | public void setMethod(String method) { 104 | this.method = method; 105 | } 106 | 107 | public void setParam(String name, String value) { 108 | List values = new ArrayList<>(); 109 | values.add(value); 110 | params.put(name, values); 111 | } 112 | 113 | public void setQueryString(String queryString) { 114 | this.queryString = queryString; 115 | } 116 | 117 | public void setRequestURL(String requestURL) { 118 | this.requestURL = requestURL; 119 | } 120 | 121 | private final Map> headers = new HashMap<>(); 122 | private String method; 123 | private final Map> params = new HashMap<>(); 124 | private String queryString; 125 | private String requestURL; 126 | private HttpSession session; 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/HttpServletResponseImpl.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import javax.servlet.ServletOutputStream; 6 | import javax.servlet.WriteListener; 7 | import java.io.IOException; 8 | import java.io.PrintWriter; 9 | import java.util.*; 10 | 11 | /** 12 | * Mock HttpServletResponse implementation. 13 | */ 14 | public class HttpServletResponseImpl extends BaseServletResponseImpl { 15 | 16 | public HttpServletResponseImpl() { 17 | stream = new ServletOutputStream() { 18 | @Override 19 | public boolean isReady() { 20 | throw new UnsupportedOperationException(); 21 | } 22 | 23 | @Override 24 | public void setWriteListener(WriteListener writeListener) { 25 | throw new UnsupportedOperationException(); 26 | } 27 | 28 | @Override 29 | public void write(int b) throws IOException { 30 | // do nothing 31 | } 32 | }; 33 | } 34 | 35 | @Override 36 | public void addHeader(String name, String value) { 37 | if (headers.containsKey(name)) { 38 | headers.get(name).add(value); 39 | } else { 40 | setHeader(name, value); 41 | } 42 | } 43 | 44 | @Override 45 | public boolean containsHeader(String name) { 46 | return headers.containsKey(name); 47 | } 48 | 49 | @Override 50 | public void flushBuffer() throws IOException { 51 | // ignore, nothing to do 52 | } 53 | 54 | @Override 55 | public String getCharacterEncoding() { 56 | return characterEncoding; 57 | } 58 | 59 | @Override 60 | public String getContentType() { 61 | return getHeader("Content-Type"); 62 | } 63 | 64 | @Override 65 | public String getHeader(String name) { 66 | return headers.containsKey(name) ? headers.get(name).get(0) : null; 67 | } 68 | 69 | @Override 70 | public Collection getHeaders(String name) { 71 | return headers.getOrDefault(name, null); 72 | } 73 | 74 | @Override 75 | public Collection getHeaderNames() { 76 | return headers.keySet(); 77 | } 78 | 79 | @Override 80 | public ServletOutputStream getOutputStream() throws IOException { 81 | return stream; 82 | } 83 | 84 | @Override 85 | public int getStatus() { 86 | return status; 87 | } 88 | 89 | @Override 90 | public PrintWriter getWriter() throws IOException { 91 | return new PrintWriter(getOutputStream()); 92 | } 93 | 94 | @Override 95 | public void setCharacterEncoding(String characterEncoding) { 96 | this.characterEncoding = characterEncoding; 97 | } 98 | 99 | @Override 100 | public void setContentType(String contentType) { 101 | setHeader("Content-Type", contentType); 102 | } 103 | 104 | @Override 105 | public void setHeader(String name, String value) { 106 | List values = new ArrayList<>(); 107 | values.add(value); 108 | headers.put(name, values); 109 | } 110 | 111 | @Override 112 | public void setStatus(int status) { 113 | this.status = status; 114 | } 115 | 116 | @Override 117 | public void setStatus(int status, String sm) { 118 | this.status = status; 119 | } 120 | 121 | private String characterEncoding; 122 | private final Map> headers = new HashMap<>(); 123 | private int status; 124 | private final ServletOutputStream stream; 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/HttpSessionImpl.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import java.util.Collections; 6 | import java.util.Enumeration; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | /** 11 | * Mock HttpSession implementation. 12 | */ 13 | public class HttpSessionImpl extends BaseSessionImpl { 14 | 15 | @Override 16 | public Object getAttribute(String name) { 17 | return attributes.get(name); 18 | } 19 | 20 | @Override 21 | public Enumeration getAttributeNames() { 22 | return Collections.enumeration(attributes.keySet()); 23 | } 24 | 25 | @Override 26 | public void setAttribute(String name, Object value) { 27 | attributes.put(name, value); 28 | } 29 | 30 | private final Map attributes = new HashMap<>(); 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/Json.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * Utility methods for formatting JSON messages. 9 | */ 10 | public class Json { 11 | 12 | /** 13 | * Adds comma-delimited key/value pair to message. 14 | */ 15 | public static StringBuilder append(StringBuilder json, CharSequence key, CharSequence value) { 16 | if ((key != null) && (value != null)) { 17 | json.append("\"").append(key.toString()).append("\",\""); 18 | escape(json, value).append("\""); 19 | } 20 | return json; 21 | } 22 | 23 | /** 24 | * Escapes quotes and control characters in string value for use in message. 25 | * Adapted from https://code.google.com/archive/p/json-simple (JSONValue.java) 26 | * This version has several changes from the original: 27 | * 1) Uses StringBuilder in place of StringBuffer 28 | * 2) Takes CharSequence so either String or StringBuilder can be supplied 29 | * 3) Skips escaping forward slashes 30 | * 4) Returns StringBuilder rather than void 31 | */ 32 | public static StringBuilder escape(StringBuilder json, CharSequence value) { 33 | if (value == null) return json; 34 | final int len = value.length(); 35 | for (int i = 0; i < len; i++) { 36 | char ch = value.charAt(i); 37 | switch (ch) { 38 | case '"': 39 | json.append("\\\""); 40 | break; 41 | case '\\': 42 | json.append("\\\\"); 43 | break; 44 | case '\b': 45 | json.append("\\b"); 46 | break; 47 | case '\f': 48 | json.append("\\f"); 49 | break; 50 | case '\n': 51 | json.append("\\n"); 52 | break; 53 | case '\r': 54 | json.append("\\r"); 55 | break; 56 | case '\t': 57 | json.append("\\t"); 58 | break; 59 | default: 60 | if ((ch <= '\u001F') || (ch >= '\u007F' && ch <= '\u009F') || (ch >= '\u2000' && ch <= '\u20FF')) { 61 | json.append("\\u"); 62 | String ss = Integer.toHexString(ch); 63 | for (int k = 0; k < 4 - ss.length(); k++) json.append('0'); 64 | json.append(ss.toUpperCase()); 65 | } else { 66 | json.append(ch); 67 | } 68 | } 69 | } 70 | return json; 71 | } 72 | 73 | /** 74 | * Formats list of string arrays as JSON. 75 | */ 76 | public static String stringify(List message) { 77 | StringBuilder json = new StringBuilder(1024); 78 | json.append('['); 79 | int idx = 0; 80 | for (String[] entry : message) { 81 | json.append(idx++ > 0 ? ",[" : '['); 82 | append(json, entry[0], entry[1]).append(']'); 83 | } 84 | json.append(']'); 85 | return json.toString(); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/LoggedInputStream.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import javax.servlet.ReadListener; 6 | import java.io.*; 7 | 8 | /** 9 | * Servlet input stream allowing data to be read more than once. 10 | */ 11 | public class LoggedInputStream extends javax.servlet.ServletInputStream { 12 | 13 | /** 14 | * Constructor taking original input stream to wrap. 15 | */ 16 | public LoggedInputStream(InputStream input) throws IOException { 17 | this(input, 1024 * 1024); 18 | } 19 | 20 | /** 21 | * Constructor taking original input stream and limit in bytes. 22 | */ 23 | public LoggedInputStream(InputStream input, int limit) throws IOException { 24 | if (input == null) throw new IllegalArgumentException("Null input"); 25 | 26 | int logged_bytes = 0; 27 | boolean overflowed = false; 28 | 29 | // consume entire input stream, but enforce limit on copying 30 | ByteArrayOutputStream os = new ByteArrayOutputStream(); 31 | byte[] buf = new byte[1024]; 32 | int len; 33 | while ((len = input.read(buf)) > 0) { 34 | logged_bytes += len; 35 | if (logged_bytes > limit) { 36 | overflowed = true; 37 | } else { 38 | os.write(buf, 0, len); 39 | } 40 | } 41 | 42 | if (overflowed) { 43 | os = new ByteArrayOutputStream(); 44 | PrintWriter pw = new PrintWriter(os); 45 | pw.print("{ \"overflowed\": "); 46 | pw.print(logged_bytes); 47 | pw.print(" }"); 48 | pw.flush(); 49 | } 50 | 51 | this.logged = os.toByteArray(); 52 | this.stream = new ByteArrayInputStream(logged); 53 | } 54 | 55 | /** 56 | * Returns the number of bytes that can be read (or skipped over) from this input stream without blocking. 57 | */ 58 | @Override 59 | public int available() { 60 | return stream.available(); 61 | } 62 | 63 | /** 64 | * Closes this input stream and releases any system resources associated with the stream. 65 | */ 66 | @Override 67 | public void close() throws IOException { 68 | logged = null; 69 | stream.close(); 70 | } 71 | 72 | /** 73 | * Return raw data logged so far. 74 | */ 75 | public byte[] logged() { 76 | return logged; 77 | } 78 | 79 | /** 80 | * Reads the next byte of data from the input stream. 81 | */ 82 | @Override 83 | public int read() { 84 | return stream.read(); 85 | } 86 | 87 | /** 88 | * Reads up to len bytes of data from the input stream into an array of bytes. 89 | */ 90 | @Override 91 | public int read(byte[] b, int off, int len) throws IOException { 92 | return stream.read(b, off, len); 93 | } 94 | 95 | @Override 96 | public boolean isFinished() { 97 | return stream.available() == 0; 98 | } 99 | 100 | @Override 101 | public boolean isReady() { 102 | return stream.available() != 0; 103 | } 104 | 105 | @Override 106 | public void setReadListener(ReadListener readListener) { 107 | throw new UnsupportedOperationException(); 108 | } 109 | 110 | private byte[] logged; 111 | private final ByteArrayInputStream stream; 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/LoggedOutputStream.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import javax.servlet.WriteListener; 6 | import java.io.ByteArrayOutputStream; 7 | import java.io.IOException; 8 | import java.io.OutputStream; 9 | import java.io.PrintWriter; 10 | 11 | /** 12 | * Servlet output stream allowing data to be read after being written/flushed. 13 | */ 14 | public class LoggedOutputStream extends javax.servlet.ServletOutputStream { 15 | 16 | /** 17 | * Constructor taking original output stream to wrap. 18 | */ 19 | public LoggedOutputStream(OutputStream output) { 20 | this(output, 1024 * 1024); 21 | } 22 | 23 | /** 24 | * Constructor taking original output stream and limit in bytes. 25 | */ 26 | public LoggedOutputStream(OutputStream output, int limit) { 27 | if (output == null) throw new IllegalArgumentException("Null output"); 28 | this.limit = limit; 29 | this.logged = new ByteArrayOutputStream(); 30 | this.output = output; 31 | this.isClosed = false; 32 | } 33 | 34 | /** 35 | * Closes this output stream and releases any system resources associated with this stream. 36 | */ 37 | @Override 38 | public void close() throws IOException { 39 | try { 40 | output.close(); 41 | } finally { 42 | try { 43 | logged.close(); 44 | } catch (IOException ioe) { 45 | // do nothing 46 | } finally { 47 | isClosed = true; 48 | } 49 | } 50 | } 51 | 52 | /** 53 | * Flushes this output stream and forces any buffered output bytes to be written out. 54 | */ 55 | @Override 56 | public void flush() throws IOException { 57 | try { 58 | output.flush(); 59 | } finally { 60 | try { 61 | logged.flush(); 62 | } catch (IOException ioe) { 63 | // do nothing 64 | } 65 | } 66 | } 67 | 68 | /** 69 | * Return raw data logged so far. 70 | */ 71 | public byte[] logged() { 72 | if (overflowed) { 73 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 74 | PrintWriter pw = new PrintWriter(baos); 75 | pw.print("{ \"overflowed\": "); 76 | pw.print(logged_bytes); 77 | pw.print(" }"); 78 | pw.flush(); 79 | return baos.toByteArray(); 80 | } else { 81 | return logged.toByteArray(); 82 | } 83 | } 84 | 85 | /** 86 | * Returns true if data limit has been reached. 87 | */ 88 | public boolean overflowed() { 89 | return overflowed; 90 | } 91 | 92 | /** 93 | * Writes the specified byte to this output stream. 94 | */ 95 | @Override 96 | public void write(int b) throws IOException { 97 | try { 98 | output.write(b); 99 | } finally { 100 | logged_bytes += 4; 101 | if (logged_bytes > limit) { 102 | overflowed = true; 103 | logged = null; 104 | } else { 105 | logged.write(b); 106 | } 107 | } 108 | } 109 | 110 | /** 111 | * Writes the specified byte array to this output stream. 112 | */ 113 | @Override 114 | public void write(byte[] b) throws IOException { 115 | try { 116 | output.write(b); 117 | } finally { 118 | try { 119 | logged_bytes += b.length; 120 | if (logged_bytes > limit) { 121 | overflowed = true; 122 | logged = null; 123 | } else { 124 | logged.write(b); 125 | } 126 | } catch (IOException ioe) { 127 | // do nothing 128 | } 129 | } 130 | } 131 | 132 | /** 133 | * Writes len bytes from the specified byte array starting at offset off to this output stream. 134 | */ 135 | @Override 136 | public void write(byte[] b, int off, int len) throws IOException { 137 | try { 138 | output.write(b, off, len); 139 | } finally { 140 | logged_bytes += len; 141 | if (logged_bytes > limit) { 142 | overflowed = true; 143 | logged = null; 144 | } else { 145 | logged.write(b, off, len); 146 | } 147 | } 148 | } 149 | 150 | @Override 151 | public boolean isReady() { 152 | return !this.isClosed; 153 | } 154 | 155 | @Override 156 | public void setWriteListener(WriteListener writeListener) { 157 | throw new UnsupportedOperationException(); 158 | } 159 | 160 | private final int limit; 161 | private ByteArrayOutputStream logged; 162 | private int logged_bytes = 0; 163 | private final OutputStream output; 164 | private boolean overflowed = false; 165 | private boolean isClosed; 166 | } 167 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/LoggedResponseWrapper.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | import javax.servlet.ServletOutputStream; 6 | import javax.servlet.http.HttpServletResponse; 7 | import java.io.IOException; 8 | import java.io.OutputStreamWriter; 9 | import java.io.PrintWriter; 10 | 11 | /** 12 | * Servlet response wrapper for HTTP usage logging. 13 | */ 14 | public class LoggedResponseWrapper extends javax.servlet.http.HttpServletResponseWrapper { 15 | 16 | /** 17 | * Constructor taking original response to wrap. 18 | */ 19 | public LoggedResponseWrapper(HttpServletResponse response) { 20 | super(response); 21 | this.response = response; 22 | } 23 | 24 | /** 25 | * Flushes any buffered output. 26 | */ 27 | @Override 28 | public void flushBuffer() throws IOException { 29 | if (writer != null) writer.flush(); 30 | super.flushBuffer(); 31 | } 32 | 33 | /** 34 | * Returns output stream against the wrapped response. 35 | */ 36 | @Override 37 | public ServletOutputStream getOutputStream() throws IOException { 38 | if (stream == null) { 39 | stream = new LoggedOutputStream(response.getOutputStream()); 40 | } 41 | return stream; 42 | } 43 | 44 | /** 45 | * Returns writer against the wrapped response. 46 | */ 47 | @Override 48 | public PrintWriter getWriter() throws IOException { 49 | if (writer == null) { 50 | String encoding = getCharacterEncoding(); 51 | encoding = (encoding == null) ? "ISO-8859-1" : encoding; 52 | writer = new PrintWriter(new OutputStreamWriter(getOutputStream(), encoding)); 53 | } 54 | return writer; 55 | } 56 | 57 | /** 58 | * Flushes underlying stream and returns all bytes logged so far. 59 | */ 60 | public byte[] logged() { 61 | return stream == null ? LOGGED_NOTHING : stream.logged(); 62 | } 63 | 64 | /** 65 | * Value returned when nothing was logged. 66 | */ 67 | public static final byte[] LOGGED_NOTHING = new byte[0]; 68 | 69 | private final HttpServletResponse response; 70 | private LoggedOutputStream stream; 71 | private PrintWriter writer; 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/io/resurface/UsageLoggers.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface; 4 | 5 | /** 6 | * Utilities for all usage loggers. 7 | */ 8 | public final class UsageLoggers { 9 | 10 | private static final boolean BRICKED = "true".equals(System.getenv("USAGE_LOGGERS_DISABLE")); 11 | 12 | private static boolean disabled = BRICKED; 13 | 14 | /** 15 | * Disable all usage loggers. 16 | */ 17 | public static void disable() { 18 | disabled = true; 19 | } 20 | 21 | /** 22 | * Enable all usage loggers, except those explicitly disabled. 23 | */ 24 | public static void enable() { 25 | if (!BRICKED) disabled = false; 26 | } 27 | 28 | /** 29 | * Returns true if usage loggers are generally enabled. 30 | */ 31 | public static boolean isEnabled() { 32 | return !disabled; 33 | } 34 | 35 | /** 36 | * Returns url to use by default. 37 | */ 38 | public static String urlByDefault() { 39 | String url = System.getProperty("USAGE_LOGGERS_URL"); 40 | return (url == null) ? System.getenv("USAGE_LOGGERS_URL") : url; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/test/java/io/resurface/tests/BaseLoggerTest.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface.tests; 4 | 5 | import io.resurface.BaseLogger; 6 | import io.resurface.UsageLoggers; 7 | import org.junit.Test; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.concurrent.atomic.AtomicInteger; 12 | 13 | import static com.mscharhag.oleaster.matcher.Matchers.expect; 14 | import static io.resurface.tests.Helper.*; 15 | import static org.junit.Assert.fail; 16 | 17 | /** 18 | * Tests against basic usage logger to embed or extend. 19 | */ 20 | public class BaseLoggerTest { 21 | 22 | @Test 23 | public void createsInstanceTest() { 24 | BaseLogger logger = new BaseLogger(MOCK_AGENT); 25 | expect(logger).toBeNotNull(); 26 | expect(logger.getAgent()).toEqual(MOCK_AGENT); 27 | expect(logger.isEnableable()).toBeFalse(); 28 | expect(logger.isEnabled()).toBeFalse(); 29 | expect(logger.getQueue()).toBeNull(); 30 | expect(logger.getUrl()).toBeNull(); 31 | expect(logger.getMessageQueue()).toBeNull(); 32 | } 33 | 34 | @Test 35 | public void createsMultipleInstancesTest() { 36 | String agent1 = "agent1"; 37 | String agent2 = "AGENT2"; 38 | String agent3 = "aGeNt3"; 39 | String url1 = "http://resurface.io"; 40 | String url2 = "http://whatever.com"; 41 | BaseLogger logger1 = new BaseLogger(agent1, url1); 42 | BaseLogger logger2 = new BaseLogger(agent2, url2); 43 | BaseLogger logger3 = new BaseLogger(agent3, Helper.DEMO_URL); 44 | 45 | expect(logger1.getAgent()).toEqual(agent1); 46 | expect(logger1.isEnableable()).toBeTrue(); 47 | expect(logger1.isEnabled()).toBeTrue(); 48 | expect(logger1.getUrl()).toEqual(url1); 49 | expect(logger2.getAgent()).toEqual(agent2); 50 | expect(logger2.isEnableable()).toBeTrue(); 51 | expect(logger2.isEnabled()).toBeTrue(); 52 | expect(logger2.getUrl()).toEqual(url2); 53 | expect(logger3.getAgent()).toEqual(agent3); 54 | expect(logger3.isEnableable()).toBeTrue(); 55 | expect(logger3.isEnabled()).toBeTrue(); 56 | expect(logger3.getUrl()).toEqual(Helper.DEMO_URL); 57 | 58 | UsageLoggers.disable(); 59 | expect(UsageLoggers.isEnabled()).toBeFalse(); 60 | expect(logger1.isEnabled()).toBeFalse(); 61 | expect(logger2.isEnabled()).toBeFalse(); 62 | expect(logger3.isEnabled()).toBeFalse(); 63 | UsageLoggers.enable(); 64 | expect(UsageLoggers.isEnabled()).toBeTrue(); 65 | expect(logger1.isEnabled()).toBeTrue(); 66 | expect(logger2.isEnabled()).toBeTrue(); 67 | expect(logger3.isEnabled()).toBeTrue(); 68 | } 69 | 70 | @Test 71 | public void hasValidHostTest() { 72 | String host = BaseLogger.host_lookup(); 73 | expect(host).toBeNotNull(); 74 | expect(host.length()).toBeGreaterThan(0); 75 | expect(host.contentEquals("unknown")).toBeFalse(); 76 | expect(host).toEqual(new BaseLogger(MOCK_AGENT).getHost()); 77 | } 78 | 79 | @Test 80 | public void hasValidVersionTest() { 81 | String version = BaseLogger.version_lookup(); 82 | expect(version).toBeNotNull(); 83 | expect(version.length()).toBeGreaterThan(0); 84 | expect(version).toStartWith("2.2."); 85 | expect(version.contains("\\")).toBeFalse(); 86 | expect(version.contains("\"")).toBeFalse(); 87 | expect(version.contains("'")).toBeFalse(); 88 | expect(version).toEqual(new BaseLogger(MOCK_AGENT).getVersion()); 89 | } 90 | 91 | @Test 92 | public void performsEnablingWhenExpectedTest() { 93 | BaseLogger logger = new BaseLogger(MOCK_AGENT, Helper.DEMO_URL, false); 94 | expect(logger.isEnableable()).toBeTrue(); 95 | expect(logger.isEnabled()).toBeFalse(); 96 | expect(logger.getUrl()).toEqual(Helper.DEMO_URL); 97 | logger.enable(); 98 | expect(logger.isEnabled()).toBeTrue(); 99 | 100 | List queue = new ArrayList<>(); 101 | logger = new BaseLogger(MOCK_AGENT, queue, false); 102 | expect(logger.isEnableable()).toBeTrue(); 103 | expect(logger.isEnabled()).toBeFalse(); 104 | expect(logger.getUrl()).toBeNull(); 105 | logger.enable().disable().enable(); 106 | expect(logger.isEnabled()).toBeTrue(); 107 | } 108 | 109 | @Test 110 | public void skipsEnablingForInvalidUrlsTest() { 111 | for (String url : MOCK_URLS_INVALID) { 112 | BaseLogger logger = new BaseLogger(MOCK_AGENT, url); 113 | expect(logger.isEnableable()).toBeFalse(); 114 | expect(logger.isEnabled()).toBeFalse(); 115 | expect(logger.getUrl()).toBeNull(); 116 | logger.enable(); 117 | expect(logger.isEnabled()).toBeFalse(); 118 | } 119 | } 120 | 121 | @Test 122 | public void skipsEnablingForNullUrlTest() { 123 | String url = null; 124 | BaseLogger logger = new BaseLogger(MOCK_AGENT, url); 125 | expect(logger.isEnableable()).toBeFalse(); 126 | expect(logger.isEnabled()).toBeFalse(); 127 | expect(logger.getUrl()).toBeNull(); 128 | logger.enable(); 129 | expect(logger.isEnabled()).toBeFalse(); 130 | } 131 | 132 | @Test 133 | public void submitsToDeniedUrlTest() { 134 | for (String url : MOCK_URLS_DENIED) { 135 | BaseLogger logger = new BaseLogger(MOCK_AGENT, url); 136 | expect(logger.isEnableable()).toBeTrue(); 137 | expect(logger.isEnabled()).toBeTrue(); 138 | logger.submit("{}"); 139 | logger.stop_dispatcher(); 140 | expect(logger.getSubmitFailures()).toEqual(1); 141 | expect(logger.getSubmitSuccesses()).toEqual(0); 142 | } 143 | } 144 | 145 | @Test 146 | public void submitsToQueueTest() { 147 | List queue = new ArrayList<>(); 148 | BaseLogger logger = new BaseLogger(MOCK_AGENT, queue); 149 | logger.init_dispatcher(); 150 | expect(logger.getQueue()).toEqual(queue); 151 | expect(logger.getUrl()).toBeNull(); 152 | expect(logger.isEnableable()).toBeTrue(); 153 | expect(logger.isEnabled()).toBeTrue(); 154 | expect(queue.size()).toEqual(0); 155 | logger.submit("{}"); 156 | logger.stop_dispatcher(); 157 | expect(queue.size()).toEqual(1); 158 | logger.init_dispatcher(); 159 | logger.submit("{}"); 160 | logger.stop_dispatcher(); 161 | expect(queue.size()).toEqual(2); 162 | expect(logger.getSubmitFailures()).toEqual(0); 163 | expect(logger.getSubmitSuccesses()).toEqual(2); 164 | } 165 | 166 | @Test 167 | public void usesSkipOptionsTest() { 168 | BaseLogger logger = new BaseLogger(MOCK_AGENT, Helper.DEMO_URL); 169 | expect(logger.getSkipCompression()).toBeFalse(); 170 | expect(logger.getSkipSubmission()).toBeFalse(); 171 | 172 | logger.setSkipCompression(true); 173 | expect(logger.getSkipCompression()).toBeTrue(); 174 | expect(logger.getSkipSubmission()).toBeFalse(); 175 | 176 | logger.setSkipCompression(false); 177 | logger.setSkipSubmission(true); 178 | expect(logger.getSkipCompression()).toBeFalse(); 179 | expect(logger.getSkipSubmission()).toBeTrue(); 180 | } 181 | 182 | @Test 183 | public void messageQueueFillTest() { 184 | List queue = new ArrayList<>(); 185 | BaseLogger logger = new BaseLogger(MOCK_AGENT, queue); 186 | for (int i = 0; i < 128; i++) { 187 | logger.submit(MOCK_MESSAGE); 188 | } 189 | expect(logger.getMessageQueue().isEmpty()).toBeFalse(); 190 | expect(logger.getMessageQueue().size()).toEqual(128); 191 | expect(logger.getMessageQueue().remainingCapacity()).toEqual(0); 192 | } 193 | 194 | @Test 195 | public void messageQueueBlockingTakeTest() { 196 | List queue = new ArrayList<>(); 197 | BaseLogger logger = new BaseLogger(MOCK_AGENT, queue); 198 | Thread taker = new Thread(() -> { 199 | try { 200 | Object unused = logger.getMessageQueue().take(); 201 | fail(); 202 | } catch (InterruptedException success) { 203 | // success! 204 | } 205 | }); 206 | try { 207 | expect(logger.getMessageQueue().isEmpty()).toBeTrue(); 208 | taker.start(); 209 | Thread.sleep(1000); 210 | expect(logger.getMessageQueue().isEmpty()).toBeTrue(); 211 | taker.interrupt(); 212 | taker.join(1000); 213 | expect(taker.isAlive()).toBeFalse(); 214 | } catch (Exception unexpected) { 215 | unexpected.printStackTrace(); 216 | } 217 | } 218 | 219 | @Test 220 | public void messageQueueBlockingPutTest() { 221 | List queue = new ArrayList<>(); 222 | BaseLogger logger = new BaseLogger(MOCK_AGENT, queue); 223 | Thread taker = new Thread(() -> { 224 | try { 225 | logger.getMessageQueue().put(MOCK_MESSAGE); 226 | fail(); 227 | } catch (InterruptedException success) { 228 | // success! 229 | } 230 | }); 231 | try { 232 | for (int i = 0; i < 128; i++) { 233 | logger.submit(MOCK_MESSAGE); 234 | } 235 | expect(logger.getMessageQueue().remainingCapacity()).toEqual(0); 236 | taker.start(); 237 | Thread.sleep(1000); 238 | expect(logger.getMessageQueue().remainingCapacity()).toEqual(0); 239 | taker.interrupt(); 240 | taker.join(1000); 241 | expect(taker.isAlive()).toBeFalse(); 242 | } catch (Exception unexpected) { 243 | unexpected.printStackTrace(); 244 | } 245 | } 246 | 247 | @Test 248 | public void messageQueueNoBlockingTakeTest() { 249 | List queue = new ArrayList<>(); 250 | BaseLogger logger = new BaseLogger(MOCK_AGENT, queue); 251 | Thread taker = new Thread(() -> { 252 | try { 253 | Object unused = logger.getMessageQueue().take(); 254 | } catch (InterruptedException failure) { 255 | fail(); 256 | } 257 | }); 258 | try { 259 | taker.start(); 260 | Thread.sleep(1000); 261 | logger.submit(MOCK_MESSAGE); 262 | taker.join(1000); 263 | expect(taker.isAlive()).toBeFalse(); 264 | } catch (Exception unexpected) { 265 | unexpected.printStackTrace(); 266 | } 267 | } 268 | 269 | @Test 270 | public void messageQueueNoBlockingPutTest() { 271 | List queue = new ArrayList<>(); 272 | BaseLogger logger = new BaseLogger(MOCK_AGENT, queue); 273 | Thread taker = new Thread(() -> { 274 | try { 275 | logger.getMessageQueue().put(MOCK_MESSAGE); 276 | } catch (InterruptedException failure) { 277 | fail(); 278 | } 279 | }); 280 | try { 281 | for (int i = 0; i < 100; i++) { 282 | logger.submit(MOCK_MESSAGE); 283 | } 284 | taker.start(); 285 | Thread.sleep(1000); 286 | logger.submit(MOCK_MESSAGE); 287 | taker.join(1000); 288 | expect(taker.isAlive()).toBeFalse(); 289 | } catch (Exception unexpected) { 290 | unexpected.printStackTrace(); 291 | } 292 | } 293 | 294 | @Test 295 | public void messageQueuePutTakeTest() { 296 | final AtomicInteger putSum = new AtomicInteger(0); 297 | final AtomicInteger takeSum = new AtomicInteger(0); 298 | final int messageCount = 128; 299 | 300 | List queue = new ArrayList<>(); 301 | BaseLogger logger = new BaseLogger(MOCK_AGENT, queue); 302 | Thread producer = new Thread(() -> { 303 | try { 304 | int seed = (this.hashCode() ^ (int)System.nanoTime()); 305 | int sum = 0; 306 | for (int i = 0; i < messageCount; i++) { 307 | logger.getMessageQueue().put(seed); 308 | sum += seed; 309 | seed ^= (seed << 3); 310 | seed ^= (seed >>> 13); 311 | seed ^= (seed << 11); 312 | } 313 | putSum.getAndAdd(sum); 314 | } catch (Exception e) { 315 | throw new RuntimeException(e); 316 | } 317 | }); 318 | Thread consumer = new Thread(() -> { 319 | try { 320 | int sum = 0; 321 | for (int i = 0; i < messageCount; i++) { 322 | sum += (int) logger.getMessageQueue().take(); 323 | } 324 | takeSum.getAndAdd(sum); 325 | } catch (Exception e) { 326 | throw new RuntimeException(e); 327 | } 328 | }); 329 | 330 | try { 331 | producer.start(); 332 | consumer.start(); 333 | producer.join(); 334 | expect(producer.isAlive()).toBeFalse(); 335 | consumer.join(); 336 | expect(consumer.isAlive()).toBeFalse(); 337 | expect(putSum.get()).toEqual(takeSum.get()); 338 | } catch (Exception e) { 339 | e.printStackTrace(); 340 | } 341 | } 342 | 343 | @Test 344 | public void setMessageQueueTest() { 345 | BaseLogger logger = new BaseLogger(MOCK_AGENT); 346 | logger.setMessageQueue(); 347 | expect(logger.getMessageQueue().isEmpty()).toBeTrue(); 348 | expect(logger.getMessageQueue().size()).toEqual(0); 349 | expect(logger.getMessageQueue().remainingCapacity()).toEqual(128); 350 | } 351 | 352 | @Test 353 | public void dispatcherTest() { 354 | BaseLogger logger = new BaseLogger(MOCK_AGENT); 355 | 356 | logger.init_dispatcher(); 357 | expect(logger.getMessageQueue().isEmpty()).toBeTrue(); 358 | expect(logger.getMessageQueue().size()).toEqual(0); 359 | expect(logger.getMessageQueue().remainingCapacity()).toEqual(128); 360 | expect(logger.isWorkerAlive()).toBeTrue(); 361 | 362 | logger.submit(MOCK_MESSAGE); 363 | logger.stop_dispatcher(); 364 | expect(logger.isWorkerAlive()).toBeFalse(); 365 | 366 | } 367 | 368 | @Test 369 | public void singleBatchTest() { 370 | final int MESSAGE_WNL_LENGTH = (MOCK_MESSAGE + "\n").length(); 371 | StringBuilder Messages = new StringBuilder(MESSAGE_WNL_LENGTH); 372 | for (int i = 0; i < 10; i++) { 373 | Messages.append(MOCK_MESSAGE + "\n"); 374 | } 375 | final String NDJSON_BATCH = Messages.toString(); 376 | 377 | List queue = new ArrayList<>(); 378 | BaseLogger logger = new BaseLogger(MOCK_AGENT, queue); 379 | 380 | logger.init_dispatcher(); 381 | for (int i = 0; i < 10; i++) { 382 | logger.submit(MOCK_MESSAGE); 383 | } 384 | logger.stop_dispatcher(); 385 | 386 | expect(queue.size()).toEqual(1); 387 | expect(queue.get(0)).toEqual(NDJSON_BATCH); 388 | expect(queue.get(0).length()).toEqual(MESSAGE_WNL_LENGTH * 10); 389 | } 390 | 391 | @Test 392 | public void multiBatchTest() { 393 | StringBuilder Messages; 394 | final int N_BATCHES = 6; 395 | final int[] BATCH_SIZES = { 0, 1, 10, 20, 400, 420, 421, 10 * 1024, 50 * 1024 }; 396 | for (int batchSize: BATCH_SIZES) { 397 | final int MESSAGE_COUNT = batchSize <= (MOCK_MESSAGE).length() ? 1 : batchSize / (MOCK_MESSAGE).length(); 398 | 399 | Messages = new StringBuilder(MESSAGE_COUNT); 400 | for (int i = 0; i < MESSAGE_COUNT * N_BATCHES; i++) { 401 | Messages.append(MOCK_MESSAGE + "\n"); 402 | } 403 | final String NDJSON_MESSAGE = Messages.toString(); 404 | 405 | List queue = new ArrayList<>(); 406 | BaseLogger logger = new BaseLogger(MOCK_AGENT, queue); 407 | 408 | logger.init_dispatcher(batchSize); 409 | for (int i = 0; i < MESSAGE_COUNT * N_BATCHES; i++) { 410 | logger.submit(MOCK_MESSAGE); 411 | } 412 | logger.stop_dispatcher(); 413 | 414 | expect(queue.size()).toBeGreaterThan(N_BATCHES - 1); 415 | 416 | Messages = new StringBuilder(MESSAGE_COUNT); 417 | for (String s : queue) { 418 | Messages.append(s); 419 | } 420 | expect(Messages.toString()).toEqual(NDJSON_MESSAGE); 421 | } 422 | } 423 | } 424 | -------------------------------------------------------------------------------- /src/test/java/io/resurface/tests/Helper.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface.tests; 4 | 5 | import com.google.gson.Gson; 6 | import io.resurface.HttpServletRequestImpl; 7 | import io.resurface.HttpServletResponseImpl; 8 | import io.resurface.Json; 9 | 10 | import javax.servlet.FilterChain; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.UnsupportedEncodingException; 13 | 14 | /** 15 | * Provides mock objects and utilities for testing. 16 | */ 17 | public class Helper { 18 | 19 | static final String DEMO_URL = "https://demo.resurface.io/ping"; 20 | 21 | static final String MOCK_AGENT = "helper.java"; 22 | 23 | static final String MOCK_HTML = "Hello World!"; 24 | 25 | static final String MOCK_HTML2 = "Hola Mundo!"; 26 | 27 | static final String MOCK_HTML3 = "1 World 2 World Red World Blue World!"; 28 | 29 | static final String MOCK_HTML4 = "1 World\n2 World\nRed World \nBlue World!\n"; 30 | 31 | static final String MOCK_HTML5 = "\n" 32 | + "SENSITIVE\n" 33 | + "\n" 34 | + "SENSITIVE\n" 35 | + "\n" 36 | + ""; 37 | 38 | static final String MOCK_JSON = "{ \"hello\" : \"world\" }"; 39 | 40 | static final String MOCK_JSON_ESCAPED = Json.escape(new StringBuilder(), MOCK_JSON).toString(); 41 | 42 | static final long MOCK_NOW = 1455908640173L; 43 | 44 | static final String MOCK_QUERY_STRING = "foo=bar"; 45 | 46 | static final String MOCK_URL = "http://something.com:3000/index.html"; 47 | 48 | static final String[] MOCK_URLS_DENIED = {Helper.DEMO_URL + "/noway3is5this1valid2", 49 | "https://www.noway3is5this1valid2.com/"}; 50 | 51 | static final String[] MOCK_URLS_INVALID = {"", "noway3is5this1valid2", "ftp:\\www.noway3is5this1valid2.com/", 52 | "urn:ISSN:1535–3613"}; 53 | 54 | static final String MOCK_MESSAGE = "[[\"message\"], [123]]"; 55 | 56 | static FilterChain mockCustomApp() { 57 | return (req, res) -> { 58 | HttpServletResponse response = (HttpServletResponse) res; 59 | response.setContentType("application/super-troopers"); 60 | response.setStatus(200); 61 | }; 62 | } 63 | 64 | static FilterChain mockCustom404App() { 65 | return (req, res) -> { 66 | HttpServletResponse response = (HttpServletResponse) res; 67 | response.setCharacterEncoding("UTF-8"); 68 | response.setContentType("application/whatever"); 69 | response.setStatus(404); 70 | }; 71 | } 72 | 73 | static FilterChain mockExceptionApp() { 74 | return (req, res) -> { 75 | throw new UnsupportedEncodingException("simulated failure"); 76 | }; 77 | } 78 | 79 | static FilterChain mockHtmlApp() { 80 | return (req, res) -> { 81 | HttpServletResponse response = (HttpServletResponse) res; 82 | response.setCharacterEncoding("UTF-8"); 83 | response.setContentType("text/html"); 84 | response.setHeader("A", "Z"); 85 | response.getOutputStream().write(MOCK_HTML.getBytes()); 86 | response.setStatus(200); 87 | }; 88 | } 89 | 90 | static FilterChain mockHtml404App() { 91 | return (req, res) -> { 92 | HttpServletResponse response = (HttpServletResponse) res; 93 | response.setCharacterEncoding("UTF-8"); 94 | response.setContentType("text/html; charset=utf-8"); 95 | response.setStatus(404); 96 | }; 97 | } 98 | 99 | static FilterChain mockJsonApp() { 100 | return (req, res) -> { 101 | HttpServletResponse response = (HttpServletResponse) res; 102 | response.setCharacterEncoding("UTF-8"); 103 | response.setContentType("application/json; charset=utf-8"); 104 | response.getOutputStream().write(MOCK_JSON.getBytes()); 105 | response.setStatus(200); 106 | }; 107 | } 108 | 109 | static FilterChain mockJson404App() { 110 | return (req, res) -> { 111 | HttpServletResponse response = (HttpServletResponse) res; 112 | response.setCharacterEncoding("UTF-8"); 113 | response.setContentType("application/json"); 114 | response.setStatus(404); 115 | }; 116 | } 117 | 118 | static HttpServletRequestImpl mockRequest() { 119 | HttpServletRequestImpl r = new HttpServletRequestImpl(); 120 | r.setMethod("GET"); 121 | r.setRequestURL(MOCK_URL); 122 | return r; 123 | } 124 | 125 | static HttpServletRequestImpl mockRequestWithJson() { 126 | HttpServletRequestImpl r = new HttpServletRequestImpl(); 127 | r.addHeader("Content-Type", "Application/JSON"); 128 | r.addParam("message", MOCK_JSON); 129 | r.setMethod("POST"); 130 | r.setQueryString(MOCK_QUERY_STRING); 131 | r.setRequestURL(MOCK_URL); 132 | return r; 133 | } 134 | 135 | static HttpServletRequestImpl mockRequestWithJson2() { 136 | HttpServletRequestImpl r = mockRequestWithJson(); 137 | r.addHeader("ABC", "123"); 138 | r.addHeader("A", "1"); 139 | r.addHeader("A", "2"); 140 | r.addParam("ABC", "123"); 141 | r.addParam("ABC", "234"); 142 | return r; 143 | } 144 | 145 | static HttpServletResponseImpl mockResponse() { 146 | HttpServletResponseImpl r = new HttpServletResponseImpl(); 147 | r.setCharacterEncoding("UTF-8"); 148 | r.setStatus(200); 149 | return r; 150 | } 151 | 152 | static HttpServletResponseImpl mockResponseWithHtml() { 153 | HttpServletResponseImpl r = mockResponse(); 154 | r.setContentType("text/html; charset=utf-8"); 155 | return r; 156 | } 157 | 158 | static boolean parseable(String msg) { 159 | if (msg == null || !msg.trim().startsWith("[") || !msg.trim().endsWith("]") 160 | || msg.contains("[]") || (msg.contains(",,"))) return false; 161 | try { 162 | parser.fromJson(msg, Object.class); 163 | return true; 164 | } catch (com.google.gson.JsonSyntaxException ex) { 165 | return false; 166 | } 167 | } 168 | 169 | private static final Gson parser = new Gson(); 170 | 171 | } 172 | -------------------------------------------------------------------------------- /src/test/java/io/resurface/tests/HelperTest.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface.tests; 4 | 5 | import org.junit.Test; 6 | 7 | import static com.mscharhag.oleaster.matcher.Matchers.expect; 8 | import static io.resurface.tests.Helper.parseable; 9 | 10 | /** 11 | * Tests for mock objects and utilities for testing. 12 | */ 13 | public class HelperTest { 14 | 15 | @Test 16 | public void detectsGoodJsonTest() { 17 | expect(parseable("[ ]")).toBeTrue(); 18 | expect(parseable("[\n]")).toBeTrue(); 19 | expect(parseable("[\n\t\n]")).toBeTrue(); 20 | expect(parseable("[\"A\"]")).toBeTrue(); 21 | expect(parseable("[\"A\",\"B\"]")).toBeTrue(); 22 | } 23 | 24 | @Test 25 | public void detectsInvalidJsonTest() { 26 | expect(parseable(null)).toBeFalse(); 27 | expect(parseable("")).toBeFalse(); 28 | expect(parseable(" ")).toBeFalse(); 29 | expect(parseable("\n\n\n\n")).toBeFalse(); 30 | expect(parseable("1234")).toBeFalse(); 31 | expect(parseable("archer")).toBeFalse(); 32 | expect(parseable("\"sterling archer\"")).toBeFalse(); 33 | expect(parseable(",,")).toBeFalse(); 34 | expect(parseable("[]")).toBeFalse(); 35 | expect(parseable("[,,]")).toBeFalse(); 36 | expect(parseable("[\"]")).toBeFalse(); 37 | expect(parseable("[:,]")).toBeFalse(); 38 | expect(parseable(",")).toBeFalse(); 39 | expect(parseable("exact words")).toBeFalse(); 40 | expect(parseable("his exact words")).toBeFalse(); 41 | expect(parseable("\"exact words")).toBeFalse(); 42 | expect(parseable("his exact words\"")).toBeFalse(); 43 | expect(parseable("\"hello\":\"world\" }")).toBeFalse(); 44 | expect(parseable("{ \"hello\":\"world\"")).toBeFalse(); 45 | expect(parseable("{ \"hello world\"}")).toBeFalse(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/io/resurface/tests/HttpLoggerForServletsTest.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface.tests; 4 | 5 | import io.resurface.HttpLoggerForServlets; 6 | import org.junit.Test; 7 | 8 | import javax.servlet.ServletException; 9 | import java.io.IOException; 10 | import java.io.UnsupportedEncodingException; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import static com.mscharhag.oleaster.matcher.Matchers.expect; 15 | import static io.resurface.tests.Helper.*; 16 | import static org.junit.Assert.fail; 17 | 18 | /** 19 | * Tests against servlet filter for HTTP usage logging. 20 | */ 21 | public class HttpLoggerForServletsTest { 22 | 23 | @Test 24 | public void logsHtmlTest() throws IOException, ServletException { 25 | List queue = new ArrayList<>(); 26 | HttpLoggerForServlets filter = new HttpLoggerForServlets(queue, "include standard"); 27 | filter.init(null); 28 | filter.getLogger().init_dispatcher(); 29 | filter.doFilter(mockRequest(), mockResponse(), mockHtmlApp()); 30 | filter.getLogger().stop_dispatcher(); 31 | expect(queue.size()).toEqual(1); 32 | String msg = queue.get(0); 33 | expect(parseable(msg)).toBeTrue(); 34 | expect(msg).toContain("[\"request_method\",\"GET\"]"); 35 | expect(msg).toContain("[\"request_url\",\"" + MOCK_URL + "\"]"); 36 | expect(msg).toContain("[\"response_body\",\"" + MOCK_HTML + "\"]"); 37 | expect(msg).toContain("[\"response_code\",\"200\"]"); 38 | expect(msg).toContain("[\"response_header:a\",\"Z\"]"); 39 | expect(msg).toContain("[\"response_header:content-type\",\"text/html\"]"); 40 | expect(msg).toContain("[\"now\",\""); 41 | expect(msg).toContain("[\"interval\",\""); 42 | expect(msg.contains("request_body")).toBeFalse(); 43 | expect(msg.contains("request_header")).toBeFalse(); 44 | expect(msg.contains("request_param")).toBeFalse(); 45 | } 46 | 47 | @Test 48 | public void logsJsonTest() throws IOException, ServletException { 49 | List queue = new ArrayList<>(); 50 | HttpLoggerForServlets filter = new HttpLoggerForServlets(queue, "include standard"); 51 | filter.init(null); 52 | filter.getLogger().init_dispatcher(); 53 | filter.doFilter(mockRequest(), mockResponse(), mockJsonApp()); 54 | filter.getLogger().stop_dispatcher(); 55 | expect(queue.size()).toEqual(1); 56 | String msg = queue.get(0); 57 | expect(parseable(msg)).toBeTrue(); 58 | expect(msg).toContain("[\"request_method\",\"GET\"]"); 59 | expect(msg).toContain("[\"response_body\",\"" + MOCK_JSON_ESCAPED + "\"]"); 60 | expect(msg).toContain("[\"response_code\",\"200\"]"); 61 | expect(msg).toContain("[\"response_header:content-type\",\"application/json; charset=utf-8\"]"); 62 | expect(msg.contains("request_body")).toBeFalse(); 63 | expect(msg.contains("request_header")).toBeFalse(); 64 | expect(msg.contains("request_param")).toBeFalse(); 65 | } 66 | 67 | @Test 68 | public void logsJsonPostTest() throws IOException, ServletException { 69 | List queue = new ArrayList<>(); 70 | HttpLoggerForServlets filter = new HttpLoggerForServlets(queue, "include standard"); 71 | filter.init(null); 72 | filter.getLogger().init_dispatcher(); 73 | filter.doFilter(mockRequestWithJson(), mockResponse(), mockJsonApp()); 74 | filter.getLogger().stop_dispatcher(); 75 | expect(queue.size()).toEqual(1); 76 | String msg = queue.get(0); 77 | expect(parseable(msg)).toBeTrue(); 78 | expect(msg).toContain("[\"request_header:content-type\",\"Application/JSON\"]"); 79 | expect(msg).toContain("[\"request_method\",\"POST\"]"); 80 | expect(msg).toContain("[\"request_param:message\",\"" + MOCK_JSON_ESCAPED + "\"]"); 81 | expect(msg).toContain("[\"request_url\",\"" + MOCK_URL + "\"]"); 82 | expect(msg).toContain("[\"response_body\",\"" + MOCK_JSON_ESCAPED + "\"]"); 83 | expect(msg).toContain("[\"response_code\",\"200\"]"); 84 | expect(msg).toContain("[\"response_header:content-type\",\"application/json; charset=utf-8\"]"); 85 | } 86 | 87 | @Test 88 | public void logsJsonPostWithHeadersTest() throws IOException, ServletException { 89 | List queue = new ArrayList<>(); 90 | HttpLoggerForServlets filter = new HttpLoggerForServlets(queue, "include standard"); 91 | filter.init(null); 92 | filter.getLogger().init_dispatcher(); 93 | filter.doFilter(mockRequestWithJson2(), mockResponse(), mockHtmlApp()); 94 | filter.getLogger().stop_dispatcher(); 95 | expect(queue.size()).toEqual(1); 96 | String msg = queue.get(0); 97 | expect(parseable(msg)).toBeTrue(); 98 | expect(msg).toContain("[\"request_header:a\",\"1\"]"); 99 | expect(msg).toContain("[\"request_header:a\",\"2\"]"); 100 | expect(msg).toContain("[\"request_header:content-type\",\"Application/JSON\"]"); 101 | expect(msg).toContain("[\"request_method\",\"POST\"]"); 102 | expect(msg).toContain("[\"request_param:abc\",\"123\"]"); 103 | expect(msg).toContain("[\"request_param:abc\",\"234\"]"); 104 | expect(msg).toContain("[\"request_param:message\",\"" + MOCK_JSON_ESCAPED + "\"]"); 105 | expect(msg).toContain("[\"request_url\",\"" + MOCK_URL + "\"]"); 106 | expect(msg).toContain("[\"response_body\",\"" + MOCK_HTML + "\"]"); 107 | expect(msg).toContain("[\"response_code\",\"200\"]"); 108 | expect(msg).toContain("[\"response_header:a\",\"Z\"]"); 109 | expect(msg).toContain("[\"response_header:content-type\",\"text/html\"]"); 110 | } 111 | 112 | @Test 113 | public void skipsExceptionTest() { 114 | List queue = new ArrayList<>(); 115 | HttpLoggerForServlets filter = new HttpLoggerForServlets(queue, "include standard"); 116 | filter.init(null); 117 | filter.getLogger().init_dispatcher(); 118 | try { 119 | filter.doFilter(mockRequest(), mockResponse(), mockExceptionApp()); 120 | } catch (UnsupportedEncodingException uee) { 121 | expect(queue.size()).toEqual(0); 122 | } catch (Exception e) { 123 | fail("Unexpected exception type"); 124 | } 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/test/java/io/resurface/tests/HttpLoggerTest.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface.tests; 4 | 5 | import io.resurface.HttpLogger; 6 | import io.resurface.UsageLoggers; 7 | import org.junit.Test; 8 | 9 | import static com.mscharhag.oleaster.matcher.Matchers.expect; 10 | 11 | /** 12 | * Tests against usage logger for HTTP/HTTPS protocol. 13 | */ 14 | public class HttpLoggerTest { 15 | 16 | @Test 17 | public void createsInstanceTest() { 18 | HttpLogger logger = new HttpLogger(); 19 | expect(logger).toBeNotNull(); 20 | expect(logger.getAgent()).toEqual(HttpLogger.AGENT); 21 | expect(logger.isEnableable()).toBeFalse(); 22 | expect(logger.isEnabled()).toBeFalse(); 23 | expect(logger.getQueue()).toBeNull(); 24 | expect(logger.getUrl()).toBeNull(); 25 | } 26 | 27 | @Test 28 | public void createsMultipleInstancesTest() { 29 | String url1 = "https://resurface.io"; 30 | String url2 = "https://whatever.com"; 31 | HttpLogger logger1 = new HttpLogger(url1); 32 | HttpLogger logger2 = new HttpLogger(url2); 33 | HttpLogger logger3 = new HttpLogger(Helper.DEMO_URL); 34 | 35 | expect(logger1.getAgent()).toEqual(HttpLogger.AGENT); 36 | expect(logger1.isEnableable()).toBeTrue(); 37 | expect(logger1.isEnabled()).toBeTrue(); 38 | expect(logger1.getUrl()).toEqual(url1); 39 | expect(logger2.getAgent()).toEqual(HttpLogger.AGENT); 40 | expect(logger2.isEnableable()).toBeTrue(); 41 | expect(logger2.isEnabled()).toBeTrue(); 42 | expect(logger2.getUrl()).toEqual(url2); 43 | expect(logger3.getAgent()).toEqual(HttpLogger.AGENT); 44 | expect(logger3.isEnableable()).toBeTrue(); 45 | expect(logger3.isEnabled()).toBeTrue(); 46 | expect(logger3.getUrl()).toEqual(Helper.DEMO_URL); 47 | 48 | UsageLoggers.disable(); 49 | expect(UsageLoggers.isEnabled()).toBeFalse(); 50 | expect(logger1.isEnabled()).toBeFalse(); 51 | expect(logger2.isEnabled()).toBeFalse(); 52 | expect(logger3.isEnabled()).toBeFalse(); 53 | UsageLoggers.enable(); 54 | expect(UsageLoggers.isEnabled()).toBeTrue(); 55 | expect(logger1.isEnabled()).toBeTrue(); 56 | expect(logger2.isEnabled()).toBeTrue(); 57 | expect(logger3.isEnabled()).toBeTrue(); 58 | } 59 | 60 | @Test 61 | public void hasValidAgentTest() { 62 | String agent = HttpLogger.AGENT; 63 | expect(agent.length()).toBeGreaterThan(0); 64 | expect(agent).toEndWith(".java"); 65 | expect(agent.contains("\\")).toBeFalse(); 66 | expect(agent.contains("\"")).toBeFalse(); 67 | expect(agent.contains("'")).toBeFalse(); 68 | expect(new HttpLogger().getAgent()).toEqual(agent); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/io/resurface/tests/HttpMessageTest.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface.tests; 4 | 5 | import io.resurface.HttpLogger; 6 | import io.resurface.HttpMessage; 7 | import io.resurface.HttpServletRequestImpl; 8 | import io.resurface.HttpServletResponseImpl; 9 | import org.junit.Test; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | import static com.mscharhag.oleaster.matcher.Matchers.expect; 15 | import static io.resurface.tests.Helper.*; 16 | 17 | /** 18 | * Tests against usage logger for HTTP/HTTPS protocol. 19 | */ 20 | public class HttpMessageTest { 21 | 22 | @Test 23 | public void formatRequestTest() { 24 | List queue = new ArrayList<>(); 25 | HttpLogger logger = new HttpLogger(queue, "include debug"); 26 | logger.init_dispatcher(); 27 | HttpMessage.send(logger, mockRequest(), mockResponse(), null, null, MOCK_NOW, 0); 28 | logger.stop_dispatcher(); 29 | expect(queue.size()).toEqual(1); 30 | String msg = queue.get(0); 31 | expect(parseable(msg)).toBeTrue(); 32 | expect(msg).toContain("[\"host\",\"" + logger.getHost() + "\"]"); 33 | expect(msg).toContain("[\"now\",\"" + MOCK_NOW + "\"]"); 34 | expect(msg).toContain("[\"request_method\",\"GET\"]"); 35 | expect(msg).toContain("[\"request_url\",\"" + MOCK_URL + "\"]"); 36 | expect(msg.contains("request_body")).toBeFalse(); 37 | expect(msg.contains("request_header")).toBeFalse(); 38 | expect(msg.contains("request_param")).toBeFalse(); 39 | expect(msg.contains("interval")).toBeFalse(); 40 | } 41 | 42 | @Test 43 | public void formatRequestWithBodyTest() { 44 | List queue = new ArrayList<>(); 45 | HttpLogger logger = new HttpLogger(queue, "include debug"); 46 | logger.init_dispatcher(); 47 | HttpMessage.send(logger, mockRequestWithJson(), mockResponse(), null, MOCK_HTML); 48 | logger.stop_dispatcher(); 49 | expect(queue.size()).toEqual(1); 50 | String msg = queue.get(0); 51 | expect(parseable(msg)).toBeTrue(); 52 | expect(msg).toContain("[\"request_body\",\"" + MOCK_HTML + "\"]"); 53 | expect(msg).toContain("[\"request_header:content-type\",\"Application/JSON\"]"); 54 | expect(msg).toContain("[\"request_method\",\"POST\"]"); 55 | expect(msg).toContain("[\"request_param:message\",\"" + MOCK_JSON_ESCAPED + "\"]"); 56 | expect(msg).toContain("[\"request_url\",\"" + MOCK_URL + "\"]"); 57 | expect(msg.contains("request_param:foo")).toBeFalse(); 58 | } 59 | 60 | @Test 61 | public void formatRequestWithEmptyBodyTest() { 62 | List queue = new ArrayList<>(); 63 | HttpLogger logger = new HttpLogger(queue, "include debug"); 64 | logger.init_dispatcher(); 65 | HttpMessage.send(logger, mockRequestWithJson2(), mockResponse(), null, ""); 66 | logger.stop_dispatcher(); 67 | expect(queue.size()).toEqual(1); 68 | String msg = queue.get(0); 69 | expect(parseable(msg)).toBeTrue(); 70 | expect(msg).toContain("[\"request_header:a\",\"1\"]"); 71 | expect(msg).toContain("[\"request_header:a\",\"2\"]"); 72 | expect(msg).toContain("[\"request_header:abc\",\"123\"]"); 73 | expect(msg).toContain("[\"request_header:content-type\",\"Application/JSON\"]"); 74 | expect(msg).toContain("[\"request_method\",\"POST\"]"); 75 | expect(msg).toContain("[\"request_param:abc\",\"123\"]"); 76 | expect(msg).toContain("[\"request_param:abc\",\"234\"]"); 77 | expect(msg).toContain("[\"request_param:message\",\"" + MOCK_JSON_ESCAPED + "\"]"); 78 | expect(msg).toContain("[\"request_url\",\"" + MOCK_URL + "\"]"); 79 | expect(msg.contains("request_body")).toBeFalse(); 80 | expect(msg.contains("request_param:foo")).toBeFalse(); 81 | } 82 | 83 | @Test 84 | public void formatRequestWithMissingDetailsTest() { 85 | List queue = new ArrayList<>(); 86 | HttpLogger logger = new HttpLogger(queue, "include debug"); 87 | logger.init_dispatcher(); 88 | HttpMessage.send(logger, new HttpServletRequestImpl(), mockResponse(), null, null, MOCK_NOW, 0); 89 | logger.stop_dispatcher(); 90 | expect(queue.size()).toEqual(1); 91 | String msg = queue.get(0); 92 | expect(parseable(msg)).toBeTrue(); 93 | expect(msg.contains("request_body")).toBeFalse(); 94 | expect(msg.contains("request_header")).toBeFalse(); 95 | expect(msg.contains("request_method")).toBeFalse(); 96 | expect(msg.contains("request_param")).toBeFalse(); 97 | expect(msg.contains("request_url")).toBeFalse(); 98 | expect(msg.contains("interval")).toBeFalse(); 99 | } 100 | 101 | @Test 102 | public void formatResponseTest() { 103 | List queue = new ArrayList<>(); 104 | HttpLogger logger = new HttpLogger(queue, "include debug"); 105 | logger.init_dispatcher(); 106 | HttpMessage.send(logger, mockRequest(), mockResponse()); 107 | logger.stop_dispatcher(); 108 | expect(queue.size()).toEqual(1); 109 | String msg = queue.get(0); 110 | expect(parseable(msg)).toBeTrue(); 111 | expect(msg).toContain("[\"response_code\",\"200\"]"); 112 | expect(msg.contains("response_body")).toBeFalse(); 113 | expect(msg.contains("response_header")).toBeFalse(); 114 | } 115 | 116 | @Test 117 | public void formatResponseWithBodyTest() { 118 | List queue = new ArrayList<>(); 119 | HttpLogger logger = new HttpLogger(queue, "include debug"); 120 | logger.init_dispatcher(); 121 | HttpMessage.send(logger, mockRequest(), mockResponseWithHtml(), MOCK_HTML2); 122 | logger.stop_dispatcher(); 123 | expect(queue.size()).toEqual(1); 124 | String msg = queue.get(0); 125 | expect(parseable(msg)).toBeTrue(); 126 | expect(msg).toContain("[\"response_body\",\"" + MOCK_HTML2 + "\"]"); 127 | expect(msg).toContain("[\"response_code\",\"200\"]"); 128 | expect(msg).toContain("[\"response_header:content-type\",\"text/html; charset=utf-8\"]"); 129 | } 130 | 131 | @Test 132 | public void formatResponseWithEmptyBodyTest() { 133 | List queue = new ArrayList<>(); 134 | HttpLogger logger = new HttpLogger(queue, "include debug"); 135 | logger.init_dispatcher(); 136 | HttpMessage.send(logger, mockRequest(), mockResponseWithHtml(), ""); 137 | logger.stop_dispatcher(); 138 | expect(queue.size()).toEqual(1); 139 | String msg = queue.get(0); 140 | expect(parseable(msg)).toBeTrue(); 141 | expect(msg).toContain("[\"response_code\",\"200\"]"); 142 | expect(msg).toContain("[\"response_header:content-type\",\"text/html; charset=utf-8\"]"); 143 | expect(msg.contains("response_body")).toBeFalse(); 144 | } 145 | 146 | @Test 147 | public void formatResponseWithMissingDetailsTest() { 148 | List queue = new ArrayList<>(); 149 | HttpLogger logger = new HttpLogger(queue, "include debug"); 150 | logger.init_dispatcher(); 151 | HttpMessage.send(logger, mockRequest(), new HttpServletResponseImpl()); 152 | logger.stop_dispatcher(); 153 | expect(queue.size()).toEqual(1); 154 | String msg = queue.get(0); 155 | expect(parseable(msg)).toBeTrue(); 156 | expect(msg).toContain("[\"response_code\",\"0\"]"); 157 | expect(msg.contains("response_body")).toBeFalse(); 158 | expect(msg.contains("response_header")).toBeFalse(); 159 | expect(msg.contains("interval")).toBeFalse(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/test/java/io/resurface/tests/HttpServletRequestImplTest.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface.tests; 4 | 5 | import io.resurface.HttpServletRequestImpl; 6 | import org.junit.Test; 7 | 8 | import javax.servlet.http.HttpSession; 9 | import java.util.Enumeration; 10 | 11 | import static com.mscharhag.oleaster.matcher.Matchers.expect; 12 | 13 | /** 14 | * Tests against mock HttpServletRequest implementation. 15 | */ 16 | public class HttpServletRequestImplTest { 17 | 18 | @Test 19 | public void useContentTypeTest() { 20 | HttpServletRequestImpl impl = new HttpServletRequestImpl(); 21 | expect(impl.getHeader("CONTENT-TYPE")).toBeNull(); 22 | 23 | String val = "text/html"; 24 | impl.setContentType(val); 25 | expect(impl.getContentType()).toEqual(val); 26 | expect(impl.getHeader("Content-Type")).toEqual(val); 27 | expect(impl.getHeader("content-type")).toBeNull(); 28 | 29 | impl.setContentType(null); 30 | expect(impl.getContentType()).toBeNull(); 31 | expect(impl.getHeader("content-TYPE")).toBeNull(); 32 | } 33 | 34 | @Test 35 | public void useHeadersTest() { 36 | String key = "2345"; 37 | String key2 = "fish"; 38 | String val = "u-turn"; 39 | String val2 = "swell"; 40 | 41 | HttpServletRequestImpl impl = new HttpServletRequestImpl(); 42 | expect(impl.getHeader(key)).toBeNull(); 43 | 44 | impl.setHeader(key, val); 45 | expect(impl.getHeaderNames().nextElement()).toEqual(key); 46 | expect(impl.getHeader(key)).toEqual(val); 47 | expect(impl.getHeaders(key).nextElement()).toEqual(val); 48 | 49 | impl.setHeader(key, val2); 50 | expect(impl.getHeaderNames().nextElement()).toEqual(key); 51 | expect(impl.getHeader(key)).toEqual(val2); 52 | expect(impl.getHeaders(key).nextElement()).toEqual(val2); 53 | 54 | impl.addHeader(key, val); 55 | expect(impl.getHeaderNames().nextElement()).toEqual(key); 56 | expect(impl.getHeader(key)).toEqual(val2); 57 | Enumeration e = impl.getHeaders(key); 58 | expect(e.nextElement()).toEqual(val2); 59 | expect(e.nextElement()).toEqual(val); 60 | 61 | impl.setHeader(key2, val2); 62 | e = impl.getHeaderNames(); 63 | expect(e.nextElement()).toEqual(key); 64 | expect(e.nextElement()).toEqual(key2); 65 | expect(impl.getHeader(key2)).toEqual(val2); 66 | expect(impl.getHeader(key2.toUpperCase())).toBeNull(); 67 | } 68 | 69 | @Test 70 | public void useMethodTest() { 71 | String val = "!METHOD!"; 72 | HttpServletRequestImpl impl = new HttpServletRequestImpl(); 73 | expect(impl.getMethod()).toBeNull(); 74 | impl.setMethod(val); 75 | expect(impl.getMethod()).toEqual(val); 76 | } 77 | 78 | @Test 79 | public void useParamsTest() { 80 | String key = "2345"; 81 | String key2 = "fish"; 82 | String val = "u-turn"; 83 | String val2 = "swell"; 84 | 85 | HttpServletRequestImpl impl = new HttpServletRequestImpl(); 86 | expect(impl.getParameter(key)).toBeNull(); 87 | expect(impl.getParameterValues(key)).toBeNull(); 88 | 89 | impl.addParam(key, val); 90 | expect(impl.getParameterNames().nextElement()).toEqual(key); 91 | expect(impl.getParameter(key)).toEqual(val); 92 | expect(impl.getParameterValues(key)[0]).toEqual(val); 93 | 94 | impl.addParam(key, val2); 95 | expect(impl.getParameterNames().nextElement()).toEqual(key); 96 | expect(impl.getParameter(key)).toEqual(val); 97 | expect(impl.getParameterValues(key)[0]).toEqual(val); 98 | expect(impl.getParameterValues(key)[1]).toEqual(val2); 99 | 100 | impl.addParam(key2, val2); 101 | Enumeration e = impl.getParameterNames(); 102 | expect(e.nextElement()).toEqual(key); 103 | expect(e.nextElement()).toEqual(key2); 104 | expect(impl.getParameter(key2)).toEqual(val2); 105 | expect(impl.getParameter(key2.toUpperCase())).toBeNull(); 106 | } 107 | 108 | @Test 109 | public void useRequestURLTest() { 110 | String val = "http://resurface.io/yadda"; 111 | HttpServletRequestImpl impl = new HttpServletRequestImpl(); 112 | expect(impl.getRequestURL()).toBeNull(); 113 | impl.setRequestURL(val); 114 | expect(impl.getRequestURL().toString()).toEqual(val); 115 | } 116 | 117 | @Test 118 | public void useSessionTest() { 119 | HttpServletRequestImpl impl = new HttpServletRequestImpl(); 120 | HttpSession session = impl.getSession(false); 121 | expect(session).toBeNull(); 122 | 123 | session = impl.getSession(); 124 | expect(session).toBeNotNull(); 125 | session.setAttribute("A", "1"); 126 | expect(session.getAttribute("A")).toEqual("1"); 127 | 128 | session = impl.getSession(false); 129 | expect(session.getAttribute("A")).toEqual("1"); 130 | 131 | session = impl.getSession(true); 132 | expect(session.getAttribute("A")).toEqual("1"); 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /src/test/java/io/resurface/tests/HttpServletResponseImplTest.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface.tests; 4 | 5 | import io.resurface.HttpServletResponseImpl; 6 | import org.junit.Test; 7 | 8 | import javax.servlet.ServletOutputStream; 9 | import java.io.IOException; 10 | import java.util.Iterator; 11 | 12 | import static com.mscharhag.oleaster.matcher.Matchers.expect; 13 | 14 | /** 15 | * Tests against mock HttpServletResponse implementation. 16 | */ 17 | public class HttpServletResponseImplTest { 18 | 19 | @Test 20 | public void useCharacterEncodingTest() { 21 | String val = "UTF-8"; 22 | HttpServletResponseImpl impl = new HttpServletResponseImpl(); 23 | expect(impl.getCharacterEncoding()).toBeNull(); 24 | impl.setCharacterEncoding(val); 25 | expect(impl.getCharacterEncoding()).toEqual(val); 26 | } 27 | 28 | @Test 29 | public void useContentTypeTest() { 30 | HttpServletResponseImpl impl = new HttpServletResponseImpl(); 31 | expect(impl.getContentType()).toBeNull(); 32 | expect(impl.getHeader("CONTENT-TYPE")).toBeNull(); 33 | 34 | String val = "text/html"; 35 | impl.setContentType(val); 36 | expect(impl.getContentType()).toEqual(val); 37 | expect(impl.getHeader("Content-Type")).toEqual(val); 38 | expect(impl.getHeader("content-type")).toBeNull(); 39 | 40 | impl.setContentType(null); 41 | expect(impl.getContentType()).toBeNull(); 42 | expect(impl.getHeader("content-TYPE")).toBeNull(); 43 | } 44 | 45 | @Test 46 | public void useHeadersTest() { 47 | String key = "kenny"; 48 | String key2 = "kyle"; 49 | String val = "stan"; 50 | String val2 = "cartman"; 51 | 52 | HttpServletResponseImpl impl = new HttpServletResponseImpl(); 53 | expect(impl.getHeader(key)).toBeNull(); 54 | 55 | impl.setHeader(key, val); 56 | expect(impl.getHeaderNames().iterator().next()).toEqual(key); 57 | expect(impl.getHeader(key)).toEqual(val); 58 | expect(impl.getHeaders(key).iterator().next()).toEqual(val); 59 | 60 | impl.setHeader(key, val2); 61 | expect(impl.getHeaderNames().iterator().next()).toEqual(key); 62 | expect(impl.getHeader(key)).toEqual(val2); 63 | expect(impl.getHeaders(key).iterator().next()).toEqual(val2); 64 | 65 | impl.addHeader(key, val); 66 | expect(impl.getHeaderNames().iterator().next()).toEqual(key); 67 | expect(impl.getHeader(key)).toEqual(val2); 68 | Iterator i = impl.getHeaders(key).iterator(); 69 | expect(i.next()).toEqual(val2); 70 | expect(i.next()).toEqual(val); 71 | 72 | impl.setHeader(key2, val2); 73 | i = impl.getHeaderNames().iterator(); 74 | expect(impl.getHeader(key2.toUpperCase())).toBeNull(); 75 | expect(i.next()).toEqual(key2); 76 | expect(i.next()).toEqual(key); 77 | } 78 | 79 | @Test 80 | public void useOutputStreamTest() throws IOException { 81 | HttpServletResponseImpl impl = new HttpServletResponseImpl(); 82 | expect(impl.getOutputStream()).toBeNotNull(); 83 | expect(impl.getOutputStream() instanceof ServletOutputStream).toBeTrue(); 84 | } 85 | 86 | @Test 87 | public void useStatusTest() { 88 | int val = 123; 89 | HttpServletResponseImpl impl = new HttpServletResponseImpl(); 90 | expect(impl.getStatus()).toEqual(0); 91 | impl.setStatus(val); 92 | expect(impl.getStatus()).toEqual(val); 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/io/resurface/tests/HttpSessionImplTest.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface.tests; 4 | 5 | import io.resurface.HttpSessionImpl; 6 | import org.junit.Test; 7 | 8 | import javax.servlet.http.HttpSession; 9 | import java.util.Enumeration; 10 | 11 | import static com.mscharhag.oleaster.matcher.Matchers.expect; 12 | 13 | /** 14 | * Tests against mock HttpSession implementation. 15 | */ 16 | public class HttpSessionImplTest { 17 | 18 | @Test 19 | public void useAttributesTest() { 20 | HttpSession session = new HttpSessionImpl(); 21 | session.setAttribute("A", "1"); 22 | expect(session.getAttribute("A")).toEqual("1"); 23 | session.setAttribute("B", "2"); 24 | expect(session.getAttribute("A")).toEqual("1"); 25 | expect(session.getAttribute("B")).toEqual("2"); 26 | 27 | Enumeration e = session.getAttributeNames(); 28 | int attrs = 0; 29 | while (e.hasMoreElements()) { 30 | e.nextElement(); 31 | attrs++; 32 | } 33 | expect(attrs).toEqual(2); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/io/resurface/tests/JsonTest.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface.tests; 4 | 5 | import org.junit.Test; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import static com.mscharhag.oleaster.matcher.Matchers.expect; 11 | import static io.resurface.Json.*; 12 | 13 | /** 14 | * Tests against utility methods for formatting JSON messages. 15 | */ 16 | public class JsonTest { 17 | 18 | @Test 19 | public void appendTest() { 20 | expect(append(new StringBuilder(), null, null).toString()).toEqual(""); 21 | expect(append(new StringBuilder("XYZ"), null, null).toString()).toEqual("XYZ"); 22 | expect(append(new StringBuilder(), "A", "").toString()).toEqual("\"A\",\"\""); 23 | expect(append(new StringBuilder(), "B", " ").toString()).toEqual("\"B\",\" \""); 24 | expect(append(new StringBuilder(), "C", " ").toString()).toEqual("\"C\",\" \""); 25 | expect(append(new StringBuilder(), "D", "\t").toString()).toEqual("\"D\",\"\\t\""); 26 | expect(append(new StringBuilder(), "E", "\t\t ").toString()).toEqual("\"E\",\"\\t\\t \""); 27 | expect(append(new StringBuilder("!"), "name-2", "value1").toString()).toEqual("!\"name-2\",\"value1\""); 28 | expect(append(new StringBuilder(), "s", "the cow says \"moo").toString()).toEqual("\"s\",\"the cow says \\\"moo\""); 29 | expect(append(new StringBuilder(), "1", "the cow says \"moo\"").toString()).toEqual("\"1\",\"the cow says \\\"moo\\\"\""); 30 | } 31 | 32 | @Test 33 | public void escapeBackslashTest() { 34 | expect(escape(new StringBuilder(), "\\the cow says moo").toString()).toEqual("\\\\the cow says moo"); 35 | expect(escape(new StringBuilder(), "the cow says moo\\").toString()).toEqual("the cow says moo\\\\"); 36 | expect(escape(new StringBuilder("{"), "the cow \\says moo").toString()).toEqual("{the cow \\\\says moo"); 37 | } 38 | 39 | @Test 40 | public void escapeBackspaceTest() { 41 | expect(escape(new StringBuilder("{"), "\bthe cow says moo").toString()).toEqual("{\\bthe cow says moo"); 42 | expect(escape(new StringBuilder(), "the cow\b says moo").toString()).toEqual("the cow\\b says moo"); 43 | expect(escape(new StringBuilder(), "the cow says moo\b").toString()).toEqual("the cow says moo\\b"); 44 | } 45 | 46 | @Test 47 | public void escapeFormFeedTest() { 48 | expect(escape(new StringBuilder(), "\fthe cow says moo").toString()).toEqual("\\fthe cow says moo"); 49 | expect(escape(new StringBuilder(), "the cow\f says moo").toString()).toEqual("the cow\\f says moo"); 50 | expect(escape(new StringBuilder(), "the cow says moo\f").toString()).toEqual("the cow says moo\\f"); 51 | } 52 | 53 | @Test 54 | public void escapeNewLineTest() { 55 | expect(escape(new StringBuilder(), "\nthe cow says moo").toString()).toEqual("\\nthe cow says moo"); 56 | expect(escape(new StringBuilder(), "the cow\n says moo").toString()).toEqual("the cow\\n says moo"); 57 | expect(escape(new StringBuilder(), "the cow says moo\n").toString()).toEqual("the cow says moo\\n"); 58 | } 59 | 60 | @Test 61 | public void escapeNullsTest() { 62 | expect(escape(new StringBuilder(), null).toString()).toEqual(""); 63 | expect(escape(new StringBuilder("ABC"), null).toString()).toEqual("ABC"); 64 | } 65 | 66 | @Test 67 | public void escapeQuoteTest() { 68 | expect(escape(new StringBuilder(), "\"the cow says moo").toString()).toEqual("\\\"the cow says moo"); 69 | expect(escape(new StringBuilder(), "the cow says moo\"").toString()).toEqual("the cow says moo\\\""); 70 | expect(escape(new StringBuilder(), "the cow says \"moo").toString()).toEqual("the cow says \\\"moo"); 71 | } 72 | 73 | @Test 74 | public void escapeReturnTest() { 75 | expect(escape(new StringBuilder(), "\rthe cow says moo").toString()).toEqual("\\rthe cow says moo"); 76 | expect(escape(new StringBuilder(), "the cow\r says moo").toString()).toEqual("the cow\\r says moo"); 77 | expect(escape(new StringBuilder(), "the cow says moo\r").toString()).toEqual("the cow says moo\\r"); 78 | } 79 | 80 | @Test 81 | public void escapeTabTest() { 82 | expect(escape(new StringBuilder(), "\tthe cow says moo").toString()).toEqual("\\tthe cow says moo"); 83 | expect(escape(new StringBuilder(), "the cow\t says moo").toString()).toEqual("the cow\\t says moo"); 84 | expect(escape(new StringBuilder(), "the cow says moo\t").toString()).toEqual("the cow says moo\\t"); 85 | } 86 | 87 | @Test 88 | public void escapeUnicodeTest() { 89 | expect(escape(new StringBuilder(), "\u001B").toString()).toEqual("\\u001B"); 90 | expect(escape(new StringBuilder(" "), "\u007F").toString()).toEqual(" \\u007F"); 91 | expect(escape(new StringBuilder(), "\u204B").toString()).toEqual("\\u204B"); 92 | expect(escape(new StringBuilder(), "\u005B").toString()).toEqual("["); 93 | expect(escape(new StringBuilder(), " ö").toString()).toEqual(" ö"); 94 | expect(escape(new StringBuilder(), " \u00F6 has \ndiaeresis").toString()).toEqual(" ö has \\ndiaeresis"); 95 | expect(escape(new StringBuilder(), "\u9BE8").toString()).toEqual("鯨"); 96 | expect(escape(new StringBuilder(), "鯨 is a whale").toString()).toEqual("鯨 is a whale"); 97 | } 98 | 99 | @Test 100 | public void stringifyTest() { 101 | List message = new ArrayList<>(); 102 | message.add(new String[]{"A", "B"}); 103 | expect(stringify(message)).toEqual("[[\"A\",\"B\"]]"); 104 | message.add(new String[]{"C1", "D2"}); 105 | expect(stringify(message)).toEqual("[[\"A\",\"B\"],[\"C1\",\"D2\"]]"); 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/io/resurface/tests/LoggedInputStreamTest.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface.tests; 4 | 5 | import io.resurface.LoggedInputStream; 6 | import org.junit.Test; 7 | 8 | import java.io.ByteArrayInputStream; 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | 13 | import static com.mscharhag.oleaster.matcher.Matchers.expect; 14 | import static org.junit.Assert.assertArrayEquals; 15 | import static org.junit.Assert.fail; 16 | 17 | /** 18 | * Tests against servlet output stream allowing data to be read after being written/flushed. 19 | */ 20 | public class LoggedInputStreamTest { 21 | 22 | @Test 23 | public void badInputTest() throws IOException { 24 | try { 25 | InputStream input = null; 26 | //noinspection ConstantConditions 27 | new LoggedInputStream(input); 28 | fail("stream was created with null input"); 29 | } catch (IllegalArgumentException iae) { 30 | expect(iae.getMessage()).toContain("Null input"); 31 | } 32 | } 33 | 34 | @Test 35 | public void emptyInputTest() throws IOException { 36 | byte[] test_bytes = new byte[0]; 37 | InputStream input = new ByteArrayInputStream(test_bytes); 38 | LoggedInputStream lis = new LoggedInputStream(input); 39 | assertArrayEquals(test_bytes, lis.logged()); 40 | } 41 | 42 | @Test 43 | public void readTest() throws IOException { 44 | byte[] test_bytes = "Hello World1234567890-=!@#$%^&*()_+[]{};:,.<>/?`~|".getBytes(); 45 | InputStream input = new ByteArrayInputStream(test_bytes); 46 | LoggedInputStream lis = new LoggedInputStream(input); 47 | expect(lis.available()).toEqual(50); 48 | expect(lis.read()).toEqual(72); 49 | expect(lis.available()).toEqual(49); 50 | expect(lis.read()).toEqual(101); 51 | expect(lis.available()).toEqual(48); 52 | assertArrayEquals(test_bytes, lis.logged()); 53 | assertArrayEquals(test_bytes, lis.logged()); // can read this more than once 54 | } 55 | 56 | @Test 57 | public void readOverflowTest() throws IOException { 58 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 59 | 60 | for (int i = 1; i <= 101; i++) baos.write(0); 61 | LoggedInputStream lis = new LoggedInputStream(new ByteArrayInputStream(baos.toByteArray()), 100); 62 | expect(new String(lis.logged())).toEqual("{ \"overflowed\": 101 }"); 63 | 64 | for (int i = 1; i <= 2000; i++) baos.write(0); 65 | lis = new LoggedInputStream(new ByteArrayInputStream(baos.toByteArray()), 1024); 66 | expect(new String(lis.logged())).toEqual("{ \"overflowed\": 2101 }"); 67 | 68 | for (int i = 1; i <= 10000; i++) baos.write(0); 69 | lis = new LoggedInputStream(new ByteArrayInputStream(baos.toByteArray()), 12100); 70 | expect(new String(lis.logged())).toEqual("{ \"overflowed\": 12101 }"); 71 | } 72 | 73 | } 74 | -------------------------------------------------------------------------------- /src/test/java/io/resurface/tests/LoggedOutputStreamTest.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface.tests; 4 | 5 | import io.resurface.LoggedOutputStream; 6 | import org.junit.Test; 7 | 8 | import java.io.ByteArrayOutputStream; 9 | import java.io.IOException; 10 | 11 | import static com.mscharhag.oleaster.matcher.Matchers.expect; 12 | import static org.junit.Assert.assertArrayEquals; 13 | import static org.junit.Assert.fail; 14 | 15 | /** 16 | * Tests against servlet output stream allowing data to be read after being written/flushed. 17 | */ 18 | public class LoggedOutputStreamTest { 19 | 20 | @Test 21 | public void badOutputTest() { 22 | try { 23 | new LoggedOutputStream(null); 24 | fail("stream was created with null output"); 25 | } catch (IllegalArgumentException iae) { 26 | expect(iae.getMessage()).toContain("Null output"); 27 | } 28 | } 29 | 30 | @Test 31 | public void emptyOutputTest() { 32 | LoggedOutputStream los = new LoggedOutputStream(new ByteArrayOutputStream()); 33 | expect(los.logged().length).toEqual(0); 34 | } 35 | 36 | @Test 37 | public void readWriteTest() throws IOException { 38 | LoggedOutputStream los = new LoggedOutputStream(new ByteArrayOutputStream()); 39 | byte[] test_bytes = "Hello World".getBytes(); 40 | los.write(test_bytes); 41 | assertArrayEquals(test_bytes, los.logged()); 42 | } 43 | 44 | @Test 45 | public void readWriteAndFlushTest() throws IOException { 46 | LoggedOutputStream los = new LoggedOutputStream(new ByteArrayOutputStream()); 47 | byte[] test_bytes = "Hello World1234567890-=!@#$%^&*()_+[]{};:,.<>/?`~|".getBytes(); 48 | los.write(test_bytes); 49 | los.flush(); 50 | assertArrayEquals(test_bytes, los.logged()); 51 | } 52 | 53 | @Test 54 | public void writeOverflowByteArrayTest() throws IOException { 55 | byte[] testb = {0x00}; 56 | LoggedOutputStream los = new LoggedOutputStream(new ByteArrayOutputStream(), 100); 57 | for (int i = 1; i <= 100; i++) { 58 | los.write(testb); 59 | expect(los.overflowed()).toBeFalse(); 60 | } 61 | los.write(testb); 62 | expect(los.overflowed()).toBeTrue(); 63 | expect(new String(los.logged())).toEqual("{ \"overflowed\": 101 }"); 64 | los.write(testb); 65 | expect(los.overflowed()).toBeTrue(); 66 | expect(new String(los.logged())).toEqual("{ \"overflowed\": 102 }"); 67 | } 68 | 69 | @Test 70 | public void writeOverflowByteArrayWithOffsetTest() throws IOException { 71 | byte[] testb = {0x00, 0x01, 0x02}; 72 | LoggedOutputStream los = new LoggedOutputStream(new ByteArrayOutputStream(), 100); 73 | for (int i = 1; i <= 50; i++) { 74 | los.write(testb, 1, 2); 75 | expect(los.overflowed()).toBeFalse(); 76 | } 77 | los.write(testb, 1, 2); 78 | expect(los.overflowed()).toBeTrue(); 79 | expect(new String(los.logged())).toEqual("{ \"overflowed\": 102 }"); 80 | los.write(testb, 1, 2); 81 | expect(los.overflowed()).toBeTrue(); 82 | expect(new String(los.logged())).toEqual("{ \"overflowed\": 104 }"); 83 | } 84 | 85 | @Test 86 | public void writeOverflowIntTest() throws IOException { 87 | LoggedOutputStream los = new LoggedOutputStream(new ByteArrayOutputStream(), 100); 88 | for (int i = 1; i <= 25; i++) { 89 | los.write(0); 90 | expect(los.overflowed()).toBeFalse(); 91 | } 92 | los.write(0); 93 | expect(los.overflowed()).toBeTrue(); 94 | expect(new String(los.logged())).toEqual("{ \"overflowed\": 104 }"); 95 | los.write(0); 96 | expect(los.overflowed()).toBeTrue(); 97 | expect(new String(los.logged())).toEqual("{ \"overflowed\": 108 }"); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/test/java/io/resurface/tests/LoggedResponseWrapperTest.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface.tests; 4 | 5 | import io.resurface.LoggedOutputStream; 6 | import io.resurface.LoggedResponseWrapper; 7 | import org.junit.Test; 8 | 9 | import java.io.IOException; 10 | import java.io.PrintWriter; 11 | 12 | import static com.mscharhag.oleaster.matcher.Matchers.expect; 13 | import static io.resurface.tests.Helper.mockResponse; 14 | import static org.junit.Assert.assertArrayEquals; 15 | 16 | /** 17 | * Tests against servlet response wrapper for HTTP usage logging. 18 | */ 19 | public class LoggedResponseWrapperTest { 20 | 21 | @Test 22 | public void outputStreamClassTest() throws IOException { 23 | LoggedResponseWrapper w = new LoggedResponseWrapper(mockResponse()); 24 | expect(w.getOutputStream()).toBeNotNull(); 25 | expect(w.getOutputStream().getClass()).toEqual(LoggedOutputStream.class); 26 | } 27 | 28 | @Test 29 | public void outputStreamOutputTest() throws IOException { 30 | byte[] test_bytes = {1, 21, 66}; 31 | LoggedResponseWrapper w = new LoggedResponseWrapper(mockResponse()); 32 | assertArrayEquals(LoggedResponseWrapper.LOGGED_NOTHING, w.logged()); 33 | for (byte b : test_bytes) w.getOutputStream().write(b); 34 | w.flushBuffer(); 35 | assertArrayEquals(test_bytes, w.logged()); 36 | } 37 | 38 | @Test 39 | public void printWriterClassTest() throws IOException { 40 | LoggedResponseWrapper w = new LoggedResponseWrapper(mockResponse()); 41 | expect(w.getWriter()).toBeNotNull(); 42 | expect(w.getWriter().getClass()).toEqual(PrintWriter.class); 43 | } 44 | 45 | @Test 46 | public void printWriterOutputTest() throws IOException { 47 | String test_string = "What would Brian Boitano do?"; 48 | LoggedResponseWrapper w = new LoggedResponseWrapper(mockResponse()); 49 | assertArrayEquals(LoggedResponseWrapper.LOGGED_NOTHING, w.logged()); 50 | w.getWriter().print(test_string); 51 | w.flushBuffer(); 52 | assertArrayEquals(test_string.getBytes(), w.logged()); 53 | } 54 | 55 | @Test 56 | public void printWriterWithoutFlushTest() throws IOException { 57 | String test_string = "I bet he'd kick an ass or two"; 58 | LoggedResponseWrapper w = new LoggedResponseWrapper(mockResponse()); 59 | w.getWriter().print(test_string); 60 | expect(w.logged().length).toEqual(0); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/io/resurface/tests/ThroughputTest.java: -------------------------------------------------------------------------------- 1 | package io.resurface.tests; 2 | 3 | import io.resurface.BaseLogger; 4 | import io.resurface.Dispatcher; 5 | import org.junit.Ignore; 6 | import org.junit.Test; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.concurrent.BlockingQueue; 11 | import java.util.concurrent.CyclicBarrier; 12 | import java.util.concurrent.LinkedBlockingQueue; 13 | import java.util.concurrent.atomic.AtomicInteger; 14 | 15 | import static com.mscharhag.oleaster.matcher.Matchers.expect; 16 | import static io.resurface.tests.Helper.MOCK_AGENT; 17 | 18 | 19 | @Ignore 20 | public class ThroughputTest { 21 | @Test 22 | public void timedArrayMessageQueueTest() { 23 | for (int i = 1; i < 10000; i*=10) { 24 | System.out.printf("ARRAY BLOCKING QUEUE (message count = %d)%n", i); 25 | List queue = new ArrayList<>(); 26 | BaseLogger logger = new BaseLogger(MOCK_AGENT, queue, true, 100); 27 | timedMessageQueuePutTakeTest(logger, i, 1000); 28 | } 29 | System.out.println(); 30 | } 31 | 32 | @Test 33 | public void timedLinkedMessageQueueTest() { 34 | for (int i = 1; i < 10000; i*=10) { 35 | System.out.printf("LINKED BLOCKING QUEUE (message count = %d)%n", i); 36 | List queue = new ArrayList<>(); 37 | BaseLogger logger = new LinkedBaseLogger(queue, 100); 38 | timedMessageQueuePutTakeTest(logger, i, 1000); 39 | } 40 | System.out.println(); 41 | } 42 | 43 | private void timedMessageQueuePutTakeTest(BaseLogger logger, int messageCount, int iterations) { 44 | final long[] results = new long[iterations]; 45 | 46 | for (int i = 0; i < iterations; i++) { 47 | final BarrierTimer timer = new BarrierTimer(); 48 | final CyclicBarrier barrier = new CyclicBarrier(2, timer); 49 | final AtomicInteger putSum = new AtomicInteger(0); 50 | final AtomicInteger takeSum = new AtomicInteger(0); 51 | 52 | Thread producer = new Thread(() -> { 53 | try { 54 | int seed = (this.hashCode() ^ (int)System.nanoTime()); 55 | int sum = 0; 56 | for (int j = 0; j < messageCount; j++) { 57 | logger.getMessageQueue().put(seed); 58 | sum += seed; 59 | seed ^= (seed << 3); 60 | seed ^= (seed >>> 13); 61 | seed ^= (seed << 11); 62 | } 63 | putSum.getAndAdd(sum); 64 | barrier.await(); 65 | } catch (Exception e) { 66 | throw new RuntimeException(e); 67 | } 68 | }); 69 | 70 | Thread consumer = new Thread(() -> { 71 | try { 72 | int sum = 0; 73 | for (int j = 0; j < messageCount; j++) { 74 | sum += (int) logger.getMessageQueue().take(); 75 | } 76 | takeSum.getAndAdd(sum); 77 | barrier.await(); 78 | } catch (Exception e) { 79 | throw new RuntimeException(e); 80 | } 81 | }); 82 | 83 | try { 84 | producer.start(); 85 | consumer.start(); 86 | barrier.await(); 87 | barrier.await(); 88 | results[i] = timer.getTime() / (2L * messageCount); 89 | producer.join(); 90 | consumer.join(); 91 | expect(producer.isAlive()).toBeFalse(); 92 | expect(consumer.isAlive()).toBeFalse(); 93 | expect(putSum.get()).toEqual(takeSum.get()); 94 | } catch (Exception e) { 95 | e.printStackTrace(); 96 | } 97 | } 98 | 99 | long nsPerItem = 0; 100 | for (long result: results) { 101 | nsPerItem += result; 102 | } 103 | nsPerItem /= iterations; 104 | 105 | long stDev = 0; 106 | for (long result: results) { 107 | stDev += (long) Math.pow((result- nsPerItem), 2); 108 | } 109 | stDev /= iterations; 110 | stDev = (long) Math.sqrt(stDev); 111 | 112 | System.out.printf("Throughput: %d ns/item (SD = %d)%n", nsPerItem, stDev); 113 | } 114 | 115 | private static class BarrierTimer implements Runnable { 116 | private boolean started; 117 | private long startTime, endTime; 118 | public synchronized void run() { 119 | long t = System.nanoTime(); 120 | if (!started) { 121 | started = true; 122 | startTime = t; 123 | } else 124 | endTime = t; 125 | } 126 | public synchronized void clear() { 127 | started = false; 128 | } 129 | public synchronized long getTime() { 130 | return endTime - startTime; 131 | } 132 | } 133 | 134 | private static class LinkedBaseLogger extends BaseLogger { 135 | 136 | /** 137 | * Initialize enabled logger using default url. 138 | */ 139 | public LinkedBaseLogger() { 140 | super(MOCK_AGENT); 141 | } 142 | 143 | /** 144 | * Initialize enabled logger using queue. 145 | */ 146 | public LinkedBaseLogger(List queue) { 147 | super(MOCK_AGENT, queue); 148 | } 149 | 150 | /** 151 | * Initialize enabled logger using queue and message queue bound. 152 | */ 153 | public LinkedBaseLogger(List queue, int max_queue_depth) { 154 | super(MOCK_AGENT, queue, true, max_queue_depth); 155 | } 156 | 157 | @Override 158 | public void setMessageQueue(int max_queue_depth) { 159 | this.msg_queue = new LinkedBlockingQueue<>(max_queue_depth); 160 | } 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/test/java/io/resurface/tests/UsageLoggersTest.java: -------------------------------------------------------------------------------- 1 | // © 2016-2024 Graylog, Inc. 2 | 3 | package io.resurface.tests; 4 | 5 | import io.resurface.UsageLoggers; 6 | import org.junit.Test; 7 | 8 | import static com.mscharhag.oleaster.matcher.Matchers.expect; 9 | 10 | /** 11 | * Tests against utilities for all usage loggers. 12 | */ 13 | public class UsageLoggersTest { 14 | 15 | @Test 16 | public void providesDefaultUrlTest() { 17 | String url = UsageLoggers.urlByDefault(); 18 | expect(url).toBeNull(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /test/rules1.txt: -------------------------------------------------------------------------------- 1 | # rules for automated tests 2 | sample 55 -------------------------------------------------------------------------------- /test/rules2.txt: -------------------------------------------------------------------------------- 1 | include debug 2 | sample 56 -------------------------------------------------------------------------------- /test/rules3.txt: -------------------------------------------------------------------------------- 1 | sample 57 2 | include default 3 | --------------------------------------------------------------------------------