├── .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 | [](https://maven-badges.herokuapp.com/maven-central/io.resurface/resurfaceio-logger)
5 | [](https://www.codefactor.io/repository/github/resurfaceio/logger-java)
6 | [](https://github.com/resurfaceio/logger-java/blob/master/LICENSE)
7 | [](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 |
--------------------------------------------------------------------------------