├── .gitignore ├── .travis.yml ├── AUTHORS.txt ├── CHANGES.txt ├── LICENSE.txt ├── README.md ├── pom.xml └── src ├── main └── java │ └── org │ └── fluentd │ └── logger │ ├── Config.java │ ├── Constants.java │ ├── FluentLogger.java │ ├── FluentLoggerFactory.java │ ├── errorhandler │ └── ErrorHandler.java │ └── sender │ ├── ConstantDelayReconnector.java │ ├── Event.java │ ├── ExponentialDelayReconnector.java │ ├── NullSender.java │ ├── RawSocketSender.java │ ├── Reconnector.java │ └── Sender.java └── test ├── java └── org │ └── fluentd │ └── logger │ ├── TestBugfixes.java │ ├── TestFluentLogFactory.java │ ├── TestFluentLogger.java │ ├── sender │ ├── TestNullSender.java │ ├── TestRawSocketSender.java │ └── TestSenderFluentdDownOperation.java │ └── util │ └── MockFluentd.java └── resources └── logback-test.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .gitignore 2 | target 3 | .project 4 | .classpath 5 | .settings 6 | *.iml 7 | *~ 8 | .idea 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - openjdk7 5 | - oraclejdk8 6 | 7 | sudo: false 8 | 9 | branches: 10 | only: 11 | - master 12 | - develop 13 | -------------------------------------------------------------------------------- /AUTHORS.txt: -------------------------------------------------------------------------------- 1 | FURUHASHI Sadayuki 2 | Muga Nishizawa 3 | -------------------------------------------------------------------------------- /CHANGES.txt: -------------------------------------------------------------------------------- 1 | Release 0.3.1 - 2014/08/17 2 | IMPROVEMENTS 3 | Support lazy connection 4 | Remove Sender#getBuffer and make RawSocketSender#getBuffer thread-safe 5 | Improve unit tests to make it more stable 6 | Do not force projects to use logback 7 | Refactor pom.xml 8 | Refactor some classes 9 | 10 | BUG FIXMES 11 | RawSocketSender#send tries to flush the buffer after it's full 12 | 13 | Release 0.3.0 - 2014/02/25 14 | IMPROVEMENTS 15 | Remove warnings in maven 3.x 16 | Improve unit tests to make sure it finishes robustly and suppress the stack trace 17 | Add settings for Travis CI 18 | Update msgpack: v0.6.7 -> v0.6.8 19 | Use slf4j-api instead of java.util.logging 20 | 21 | Release 0.2.11 - 2013/09/13 22 | BUG FIXMES 23 | Fixes RawSocketSender#connect()'s timeout 24 | 25 | Release 0.2.10 - 2013/07/10 26 | IMPROVEMENTS 27 | Fixes server reconnection and new Reconnector 28 | https://github.com/fluent/fluent-logger-java/pull/5 29 | 30 | BUG FIXMES 31 | Fixes stack overflow at FluentLoggerFactory#getLogger(String tagPrefix, String host, int port, int timeout, int bufferCapacity) 32 | 33 | Release 0.2.9 - 2013/05/09 34 | IMPROVEMENTS 35 | FluentLoggerFactory.getLogger() accepts 'tag' param 36 | 37 | Release 0.2.8 - 2013/01/15 38 | IMPROVEMENTS 39 | Upgrades version of msgpack: 0.6.6 to 0.6.7 40 | 41 | BUG FIXMES 42 | ISSUE-3: Missing timeout initialization in RawSocketSender 43 | 44 | Release 0.2.7 - 2012/11/17 45 | IMPROVEMENTS 46 | Changed pom.xml for managing jar file in Sonatype repository 47 | 48 | Release 0.2.6 - 2012/11/15 49 | IMPROVEMENTS 50 | Added FluentLoggerFactory class for fluent-logger-scala 51 | 52 | Release 0.2.5 - 2012/07/18 53 | IMPROVEMENTS 54 | Upgrades version of msgpack: 0.6.4 to 0.6.6 55 | Added a new logging API: FluentLogger#flush() and flushAll() 56 | Renamed a logging API: FluentLogger#close() to closeAll() 57 | Divides a reconnector module from a sender module 58 | 59 | Release 0.2.4 - 2012/01/30 60 | NEW FEATURES 61 | Adds NullSender class. 62 | 63 | IMPROVEMENTS 64 | Changes behavior of FluentLogger#close(). When the method is called, 65 | all logger objects are removed. 66 | 67 | Release 0.2.3 - 2012/01/22 68 | NEW FEATURES 69 | Adds FluentLogger API: flush(). 70 | 71 | Release 0.2.2 - 2012/01/13 72 | BUG FIXMES 73 | Fixes bug: wrong timestamp implementation in fluent-logger-java 74 | 75 | Release 0.2.1 - 2012/01/05 76 | NEW FEATURES 77 | Changes FluentLogger API: log(String, Map) to 78 | log(String, Map). 79 | 80 | IMPROVEMENTS 81 | Refactors some classes under org.fluentd.logger.sender package. 82 | Updates version of MessagePack: 0.6.4 83 | 84 | Release 0.2.0 - 2011/10/30 85 | NEW FEATURES 86 | Pre-defines EventTemplate class for serialization of Event objects. 87 | 88 | BUG FIXMES 89 | 90 | IMPROVEMENTS 91 | Change APIs: add close method in FluentLogger class 92 | 93 | Release 0.1.0 - 2011/10/18 94 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fluent Logger for Java 2 | 3 | [![Build Status](https://travis-ci.org/fluent/fluent-logger-java.svg?branch=master)](https://travis-ci.org/fluent/fluent-logger-java) 4 | 5 | ## Overview 6 | 7 | Many web/mobile applications generate huge amount of event logs (c,f. login, 8 | logout, purchase, follow, etc). Analyzing these event logs can be quite 9 | valuable for improving services. However, collecting these logs easily and 10 | reliably is a challenging task. 11 | 12 | Fluentd solves the problem by having: easy installation, small footprint, plugins, 13 | reliable buffering, log forwarding, etc. 14 | 15 | * Fluentd website: [http://github.com/fluent/fluentd](http://github.com/fluent/fluentd) 16 | 17 | **fluent-logger-java** is a Java library, to record events via Fluentd, from Java application. 18 | 19 | ## Requirements 20 | 21 | Java >= 1.6 22 | 23 | ## Install 24 | 25 | ### Install with all-in-one jar file 26 | 27 | You can download all-in-one jar file for Fluent Logger for Java. 28 | 29 | ```bash 30 | wget http://central.maven.org/maven2/org/fluentd/fluent-logger/${logger.version}/fluent-logger-${logger.version}-jar-with-dependencies.jar 31 | ``` 32 | 33 | To use Fluent Logger for Java, set the above jar file to your classpath. 34 | 35 | ### Install from Maven2 repository 36 | 37 | Fluent Logger for Java is released on Fluent Maven2 repository. You can 38 | configure your pom.xml or build.gradle as follows to use it: 39 | 40 | **Maven:** 41 | 42 | ```xml 43 | 44 | ... 45 | 46 | org.fluentd 47 | fluent-logger 48 | ${logger.version} 49 | 50 | ... 51 | 52 | ``` 53 | 54 | **Gradle:** 55 | 56 | ```gradle 57 | dependencies { 58 | compile 'org.fluentd:fluent-logger:'+loggerVersion 59 | } 60 | ``` 61 | 62 | ### Install from Github repository 63 | 64 | You can get latest source code using git. 65 | 66 | ```bash 67 | git clone git@github.com:fluent/fluent-logger-java.git 68 | cd fluent-logger-java 69 | mvn assembly:assembly 70 | ``` 71 | 72 | You will get the fluent logger jar file in fluent-logger-java/target 73 | directory. File name will be fluent-logger-${logger.version}-jar-with-dependencies.jar. 74 | For more detail, see pom.xml. 75 | 76 | **Replace `${logger.version}` or `loggerVersion` with the current version of Fluent Logger for Java.** 77 | 78 | ## Quickstart 79 | 80 | The following program is a small example of Fluent Logger for Java. 81 | 82 | ```java 83 | import java.util.HashMap; 84 | import java.util.Map; 85 | import org.fluentd.logger.FluentLogger; 86 | 87 | public class Main { 88 | private static FluentLogger LOG = FluentLogger.getLogger("app"); 89 | 90 | public void doApplicationLogic() { 91 | // ... 92 | Map data = new HashMap(); 93 | data.put("from", "userA"); 94 | data.put("to", "userB"); 95 | LOG.log("follow", data); 96 | // ... 97 | } 98 | } 99 | ``` 100 | 101 | To create Fluent Logger instances, users need to invoke getLogger method in 102 | FluentLogger class like org.slf4j, org.log4j logging libraries. The method 103 | should be called only once. By default, the logger assumes fluent daemon is 104 | launched locally. You can also specify remote logger by passing the following 105 | options. 106 | 107 | ```java 108 | // for remote fluentd 109 | private static FluentLogger LOG = FluentLogger.getLogger("app", "remotehost", port); 110 | ``` 111 | 112 | Then, please create the events like this. This will send the event to fluentd, 113 | with tag 'app.follow' and the attributes 'from' and 'to'. 114 | 115 | Close method in FluentLogger class should be called explicitly when application 116 | is finished. The method closes socket connection with the fluentd. 117 | 118 | ```java 119 | FluentLogger.close(); 120 | ``` 121 | 122 | ## License 123 | 124 | Apache License, Version 2.0 125 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.sonatype.oss 7 | oss-parent 8 | 7 9 | 10 | 11 | org.fluentd 12 | fluent-logger 13 | 0.3.5-SNAPSHOT 14 | jar 15 | 16 | Fluent Logger for Java 17 | Java implementation of structured logger for Fluent. 18 | https://github.com/fluent/fluent-logger-java 19 | 2011 20 | 21 | 22 | The Apache Software License, Version 2.0 23 | http://www.apache.org/licenses/LICENSE-2.0.txt 24 | repo 25 | 26 | 27 | 28 | 29 | 30 | muga 31 | Muga Nishizawa 32 | muga.nishizawa@gmail.com 33 | 34 | 35 | 36 | 37 | https://github.com/fluent/fluent-logger-java.git 38 | scm:git:git://github.com/fluent/fluent-logger-java.git 39 | scm:git:git@github.com:fluent/fluent-logger-java.git 40 | HEAD 41 | 42 | 43 | GitHub 44 | https://github.com/fluent/fluent-logger-java/issues 45 | 46 | 47 | Travis-CI 48 | https://travis-ci.org/fluent/fluent-logger-java/issues 49 | 50 | 51 | 52 | UTF-8 53 | UTF-8 54 | 55 | 56 | 6 57 | 6 58 | 59 | 60 | 61 | 62 | org.msgpack 63 | msgpack 64 | 0.6.8 65 | 66 | 67 | org.slf4j 68 | slf4j-api 69 | 1.7.6 70 | 71 | 72 | ch.qos.logback 73 | logback-classic 74 | 1.1.1 75 | test 76 | 77 | 78 | junit 79 | junit 80 | 4.8.2 81 | test 82 | 83 | 84 | 85 | 86 | 87 | 88 | maven-assembly-plugin 89 | 2.2 90 | 91 | 92 | make-assembly 93 | package 94 | 95 | attached 96 | 97 | 98 | 99 | 100 | 101 | jar-with-dependencies 102 | 103 | 104 | 105 | 106 | org.apache.felix 107 | maven-bundle-plugin 108 | 2.3.7 109 | true 110 | 111 | 112 | maven-eclipse-plugin 113 | 2.5.1 114 | 115 | 116 | org.apache.maven.plugins 117 | maven-source-plugin 118 | 2.1.2 119 | 120 | 121 | attach-sources 122 | 123 | jar 124 | 125 | 126 | 127 | 128 | 129 | org.apache.maven.plugins 130 | maven-scm-plugin 131 | 1.6 132 | 133 | false 134 | 135 | 136 | 137 | org.apache.maven.plugins 138 | maven-javadoc-plugin 139 | 2.8.1 140 | 141 | ${project.name} ${project.version} API 142 | true 143 | en_US 144 | 145 | 146 | 147 | org.apache.maven.plugins 148 | maven-surefire-plugin 149 | 2.8.1 150 | 151 | -Xmx512M 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | org.apache.maven.plugins 161 | maven-javadoc-plugin 162 | 2.9.1 163 | 164 | ${project.name} ${project.version} API 165 | true 166 | en_US 167 | 168 | 169 | 170 | org.apache.maven.plugins 171 | maven-jxr-plugin 172 | 2.3 173 | 174 | 175 | org.apache.maven.plugins 176 | maven-surefire-report-plugin 177 | 2.16 178 | 179 | 180 | 181 | 182 | 183 | -------------------------------------------------------------------------------- /src/main/java/org/fluentd/logger/Config.java: -------------------------------------------------------------------------------- 1 | // 2 | // A Structured Logger for Fluent 3 | // 4 | // Copyright (C) 2011 - 2013 Muga Nishizawa 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | package org.fluentd.logger; 19 | 20 | public class Config implements Constants { 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/fluentd/logger/Constants.java: -------------------------------------------------------------------------------- 1 | // 2 | // A Structured Logger for Fluent 3 | // 4 | // Copyright (C) 2011 - 2013 Muga Nishizawa 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | package org.fluentd.logger; 19 | 20 | public interface Constants { 21 | 22 | String FLUENT_SENDER_CLASS = "fluentd.logger.sender.class"; 23 | 24 | String FLUENT_RECONNECTOR_CLASS = "fluentd.logger.reconnector.class"; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/fluentd/logger/FluentLogger.java: -------------------------------------------------------------------------------- 1 | // 2 | // A Structured Logger for Fluent 3 | // 4 | // Copyright (C) 2011 - 2013 FURUHASHI Sadayuki 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | package org.fluentd.logger; 19 | 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | 23 | import org.fluentd.logger.errorhandler.ErrorHandler; 24 | import org.fluentd.logger.sender.Reconnector; 25 | import org.fluentd.logger.sender.Sender; 26 | 27 | public class FluentLogger { 28 | 29 | private static FluentLoggerFactory factory = new FluentLoggerFactory(); 30 | 31 | public static FluentLogger getLogger(String tagPrefix) { 32 | return factory.getLogger(tagPrefix, "localhost", 24224); 33 | } 34 | 35 | public static FluentLogger getLogger(String tagPrefix, String host, int port) { 36 | return factory.getLogger(tagPrefix, host, port, 3 * 1000, 1 * 1024 * 1024); 37 | } 38 | 39 | public static synchronized FluentLogger getLogger(String tagPrefix, String host, int port, int timeout, int bufferCapacity) { 40 | return factory.getLogger(tagPrefix, host, port, timeout, bufferCapacity); 41 | } 42 | 43 | public static synchronized FluentLogger getLogger(String tagPrefix, String host, int port, int timeout, 44 | int bufferCapacity, Reconnector reconnector) { 45 | return factory.getLogger(tagPrefix, host, port, timeout, bufferCapacity, reconnector); 46 | } 47 | 48 | /** 49 | * the method is for testing 50 | */ 51 | static Map getLoggers() { 52 | return factory.getLoggers(); 53 | } 54 | 55 | public static synchronized void closeAll() { 56 | factory.closeAll(); 57 | } 58 | 59 | public static synchronized void flushAll() { 60 | factory.flushAll(); 61 | } 62 | 63 | protected String tagPrefix; 64 | 65 | protected Sender sender; 66 | 67 | protected FluentLogger() { 68 | } 69 | 70 | protected FluentLogger(String tagPrefix, Sender sender) { 71 | this.tagPrefix = tagPrefix; 72 | this.sender = sender; 73 | } 74 | 75 | public boolean log(String tag, String key, Object value) { 76 | return log(tag, key, value, 0); 77 | } 78 | 79 | public boolean log(String tag, String key, Object value, long timestamp) { 80 | Map data = new HashMap(); 81 | data.put(key, value); 82 | return log(tag, data, timestamp); 83 | } 84 | 85 | public boolean log(String tag, Map data) { 86 | return log(tag, data, 0); 87 | } 88 | 89 | public boolean log(String tag, Map data, long timestamp) { 90 | String concatTag = null; 91 | if (tagPrefix == null || tagPrefix.length() == 0) { 92 | concatTag = tag; 93 | } 94 | else { 95 | concatTag = tagPrefix + "." + tag; 96 | } 97 | 98 | if (timestamp != 0) { 99 | return sender.emit(concatTag, timestamp, data); 100 | } else { 101 | return sender.emit(concatTag, data); 102 | } 103 | } 104 | 105 | public void flush() { 106 | sender.flush(); 107 | } 108 | 109 | public void close() { 110 | if (sender != null) { 111 | sender.flush(); 112 | sender.close(); 113 | sender = null; 114 | } 115 | factory.purgeLogger(this); 116 | } 117 | 118 | public String getName() { 119 | return String.format("%s_%s", tagPrefix, sender.getName()); 120 | } 121 | 122 | @Override 123 | public String toString() { 124 | return String.format("%s{tagPrefix=%s,sender=%s}", 125 | new Object[] { this.getClass().getName(), tagPrefix, sender.toString() }); 126 | } 127 | 128 | @Override 129 | public void finalize() { 130 | if (sender != null) { 131 | sender.close(); 132 | } 133 | } 134 | 135 | public boolean isConnected() { 136 | return sender != null && sender.isConnected(); 137 | } 138 | 139 | public synchronized void setErrorHandler(ErrorHandler errorHandler) { 140 | if (errorHandler == null) { 141 | throw new IllegalArgumentException("errorHandler is null"); 142 | } 143 | 144 | if (sender != null) { 145 | sender.setErrorHandler(errorHandler); 146 | } 147 | } 148 | 149 | public synchronized void removeErrorHandler() { 150 | if (sender != null) { 151 | sender.removeErrorHandler(); 152 | } 153 | } 154 | 155 | public Sender getSender() { 156 | return sender; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/main/java/org/fluentd/logger/FluentLoggerFactory.java: -------------------------------------------------------------------------------- 1 | // 2 | // A Structured Logger for Fluent 3 | // 4 | // Copyright (C) 2011 - 2013 OZAWA Tsuyoshi 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | package org.fluentd.logger; 19 | 20 | import java.lang.reflect.Constructor; 21 | import java.lang.reflect.InvocationTargetException; 22 | import java.util.ArrayList; 23 | import java.util.Iterator; 24 | import java.util.Map; 25 | import java.util.Map.Entry; 26 | import java.util.Properties; 27 | import java.util.WeakHashMap; 28 | 29 | import org.fluentd.logger.sender.ExponentialDelayReconnector; 30 | import org.fluentd.logger.sender.RawSocketSender; 31 | import org.fluentd.logger.sender.Reconnector; 32 | import org.fluentd.logger.sender.Sender; 33 | 34 | public class FluentLoggerFactory { 35 | 36 | private final Map loggers; 37 | 38 | public FluentLoggerFactory() { 39 | loggers = new WeakHashMap(); 40 | } 41 | 42 | public FluentLogger getLogger(String tagPrefix) { 43 | return getLogger(tagPrefix, "localhost", 24224); 44 | } 45 | 46 | public FluentLogger getLogger(String tagPrefix, String host, int port) { 47 | return getLogger(tagPrefix, host, port, 3 * 1000, 1 * 1024 * 1024, new ExponentialDelayReconnector()); 48 | } 49 | 50 | public FluentLogger getLogger(String tagPrefix, String host, int port, int timeout, int bufferCapacity) { 51 | return getLogger(tagPrefix, host, port, timeout, bufferCapacity, new ExponentialDelayReconnector()); 52 | } 53 | 54 | public synchronized FluentLogger getLogger(String tagPrefix, String host, int port, int timeout, int bufferCapacity, 55 | Reconnector reconnector) { 56 | String key = String.format("%s_%s_%d_%d_%d", new Object[] { tagPrefix, host, port, timeout, bufferCapacity }); 57 | 58 | for (Map.Entry entry : loggers.entrySet()) { 59 | if (entry.getValue().equals(key)) { 60 | FluentLogger found = entry.getKey(); 61 | if(found != null) { 62 | return found; 63 | } 64 | break; 65 | } 66 | } 67 | 68 | Sender sender = null; 69 | Properties props = System.getProperties(); 70 | if (!props.containsKey(Config.FLUENT_SENDER_CLASS)) { 71 | // create default sender object 72 | sender = new RawSocketSender(host, port, timeout, bufferCapacity, reconnector); 73 | } else { 74 | String senderClassName = props.getProperty(Config.FLUENT_SENDER_CLASS); 75 | try { 76 | sender = createSenderInstance(senderClassName, new Object[] { host, port, timeout, bufferCapacity }); 77 | } catch (Exception e) { 78 | throw new RuntimeException(e); 79 | } 80 | } 81 | FluentLogger logger = new FluentLogger(tagPrefix, sender); 82 | loggers.put(logger, key); 83 | return logger; 84 | } 85 | 86 | /** Purges an invalid logger from the cache. 87 | */ 88 | protected synchronized void purgeLogger(FluentLogger logger) { 89 | Iterator> it = loggers.entrySet().iterator(); 90 | while (it.hasNext()) { 91 | if (it.next().getKey() == logger) { 92 | it.remove(); 93 | return; 94 | } 95 | } 96 | } 97 | 98 | @SuppressWarnings("unchecked") 99 | private Sender createSenderInstance(final String className, final Object[] params) throws ClassNotFoundException, 100 | SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, 101 | IllegalAccessException, InvocationTargetException { 102 | Class cl = (Class) FluentLogger.class.getClassLoader().loadClass(className); 103 | Constructor cons = cl.getDeclaredConstructor(new Class[] { String.class, int.class, int.class, 104 | int.class }); 105 | return (Sender) cons.newInstance(params); 106 | } 107 | 108 | /** 109 | * the method is for testing 110 | */ 111 | Map getLoggers() { 112 | return loggers; 113 | } 114 | 115 | public synchronized void closeAll() { 116 | for (FluentLogger logger : new ArrayList(loggers.keySet())) { 117 | logger.close(); 118 | } 119 | loggers.clear(); 120 | } 121 | 122 | public synchronized void flushAll() { 123 | for (FluentLogger logger : loggers.keySet()) { 124 | logger.flush(); 125 | } 126 | } 127 | 128 | } 129 | -------------------------------------------------------------------------------- /src/main/java/org/fluentd/logger/errorhandler/ErrorHandler.java: -------------------------------------------------------------------------------- 1 | package org.fluentd.logger.errorhandler; 2 | 3 | import java.io.IOException; 4 | 5 | public abstract class ErrorHandler { 6 | public void handleNetworkError(IOException ex) {}; 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/org/fluentd/logger/sender/ConstantDelayReconnector.java: -------------------------------------------------------------------------------- 1 | package org.fluentd.logger.sender; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import java.util.Deque; 7 | import java.util.LinkedList; 8 | 9 | /** 10 | * Handles constant delay for reconnecting. The default delay is 50 ms. 11 | */ 12 | public class ConstantDelayReconnector implements Reconnector { 13 | private static final Logger LOG = LoggerFactory.getLogger(ConstantDelayReconnector.class); 14 | 15 | private double wait = 50; // Default wait to 50 ms 16 | 17 | private static final int MAX_ERROR_HISTORY_SIZE = 100; 18 | 19 | private Deque errorHistory = new LinkedList(); 20 | 21 | public ConstantDelayReconnector() { 22 | errorHistory = new LinkedList(); 23 | } 24 | 25 | public ConstantDelayReconnector(int wait) { 26 | this.wait = wait; 27 | errorHistory = new LinkedList(); 28 | } 29 | 30 | public void addErrorHistory(long timestamp) { 31 | errorHistory.addLast(timestamp); 32 | if (errorHistory.size() > MAX_ERROR_HISTORY_SIZE) { 33 | errorHistory.removeFirst(); 34 | } 35 | } 36 | 37 | public boolean isErrorHistoryEmpty() { 38 | return errorHistory.isEmpty(); 39 | } 40 | 41 | public void clearErrorHistory() { 42 | errorHistory.clear(); 43 | } 44 | 45 | public boolean enableReconnection(long timestamp) { 46 | return errorHistory.isEmpty() || timestamp - errorHistory.getLast() >= wait; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/org/fluentd/logger/sender/Event.java: -------------------------------------------------------------------------------- 1 | // 2 | // A Structured Logger for Fluent 3 | // 4 | // Copyright (C) 2011 - 2013 Muga Nishizawa 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | package org.fluentd.logger.sender; 19 | 20 | import java.io.IOException; 21 | import java.util.Map; 22 | 23 | import org.msgpack.MessageTypeException; 24 | import org.msgpack.packer.Packer; 25 | import org.msgpack.template.AbstractTemplate; 26 | import org.msgpack.template.Templates; 27 | import org.msgpack.unpacker.Unpacker; 28 | 29 | public class Event { 30 | public String tag; 31 | 32 | public long timestamp; 33 | 34 | public Map data; 35 | 36 | public Event() { 37 | } 38 | 39 | public Event(String tag, Map data) { 40 | this(tag, System.currentTimeMillis() / 1000, data); 41 | } 42 | 43 | public Event(String tag, long timestamp, Map data) { 44 | this.tag = tag; 45 | this.timestamp = timestamp; 46 | this.data = data; 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return String.format("Event{tag=%s,timestamp=%d,data=%s}", 52 | tag, timestamp, data.toString()); 53 | } 54 | 55 | public static class EventTemplate extends AbstractTemplate { 56 | public static EventTemplate INSTANCE = new EventTemplate(); 57 | 58 | public void write(Packer pk, Event v, boolean required) throws IOException { 59 | if (v == null) { 60 | if (required) { 61 | throw new MessageTypeException("Attempted to write null"); 62 | } 63 | pk.writeNil(); 64 | return; 65 | } 66 | 67 | pk.writeArrayBegin(3); 68 | { 69 | Templates.TString.write(pk, v.tag, required); 70 | Templates.TLong.write(pk, v.timestamp, required); 71 | pk.writeMapBegin(v.data.size()); 72 | { 73 | for (Map.Entry entry : v.data.entrySet()) { 74 | Templates.TString.write(pk, entry.getKey(), required); 75 | try { 76 | pk.write(entry.getValue()); 77 | } catch (MessageTypeException e) { 78 | String val = entry.getValue().toString(); 79 | Templates.TString.write(pk, val, required); 80 | } 81 | } 82 | } 83 | pk.writeMapEnd(); 84 | } 85 | pk.writeArrayEnd(); 86 | } 87 | 88 | public Event read(Unpacker u, Event to, boolean required) throws IOException { 89 | throw new UnsupportedOperationException("Don't need the operation"); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/org/fluentd/logger/sender/ExponentialDelayReconnector.java: -------------------------------------------------------------------------------- 1 | package org.fluentd.logger.sender; 2 | 3 | import java.util.LinkedList; 4 | 5 | /** 6 | * Calculates exponential delay for reconnecting. The start delay is 50ms and exponentially grows to max 60 seconds in 7 | * function of the number of connection errors. 8 | */ 9 | public class ExponentialDelayReconnector implements Reconnector { 10 | // Visible for test 11 | public static final double WAIT_MILLIS = 500; // Start wait is 500ms 12 | 13 | private static final double WAIT_INCR_RATE = 1.5; 14 | 15 | private static final double WAIT_MAX_MILLIS = 60 * 1000; // Max wait is 1 minute 16 | 17 | private int waitMaxCount; 18 | 19 | private LinkedList errorHistory; 20 | 21 | public ExponentialDelayReconnector() { 22 | waitMaxCount = getWaitMaxCount(); 23 | errorHistory = new LinkedList(); 24 | } 25 | 26 | private int getWaitMaxCount() { 27 | double r = WAIT_MAX_MILLIS / WAIT_MILLIS; 28 | for (int j = 1; j <= 100; j++) { 29 | if (r < WAIT_INCR_RATE) { 30 | return j + 1; 31 | } 32 | r = r / WAIT_INCR_RATE; 33 | } 34 | return 100; 35 | } 36 | 37 | public void addErrorHistory(long timestamp) { 38 | errorHistory.addLast(timestamp); 39 | if (errorHistory.size() > waitMaxCount) { 40 | errorHistory.removeFirst(); 41 | } 42 | } 43 | 44 | public boolean isErrorHistoryEmpty() { 45 | return errorHistory.isEmpty(); 46 | } 47 | 48 | public void clearErrorHistory() { 49 | errorHistory.clear(); 50 | } 51 | 52 | public boolean enableReconnection(long timestamp) { 53 | int size = errorHistory.size(); 54 | if (size == 0) { 55 | return true; 56 | } 57 | 58 | double suppressMillis; 59 | if (size < waitMaxCount) { 60 | suppressMillis = WAIT_MILLIS * Math.pow(WAIT_INCR_RATE, size - 1); 61 | } else { 62 | suppressMillis = WAIT_MAX_MILLIS; 63 | } 64 | 65 | return (timestamp - errorHistory.getLast()) >= suppressMillis; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/org/fluentd/logger/sender/NullSender.java: -------------------------------------------------------------------------------- 1 | // 2 | // A Structured Logger for Fluent 3 | // 4 | // Copyright (C) 2011 - 2013 Muga Nishizawa 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | package org.fluentd.logger.sender; 19 | 20 | import org.fluentd.logger.errorhandler.ErrorHandler; 21 | 22 | import java.util.Map; 23 | 24 | public class NullSender implements Sender { 25 | 26 | public NullSender(String host, int port, int timeout, int bufferCapacity) { 27 | } 28 | 29 | @Override 30 | public boolean emit(String tag, Map data) { 31 | return emit(tag, System.currentTimeMillis() / 1000, data); 32 | } 33 | 34 | @Override 35 | public boolean emit(String tag, long timestamp, Map data) { 36 | return true; 37 | } 38 | 39 | @Override 40 | public void flush() { 41 | } 42 | 43 | @Override 44 | public void close() { 45 | } 46 | 47 | public String getName() { 48 | return "null"; 49 | } 50 | 51 | @Override 52 | public String toString() { 53 | return this.getClass().getName(); 54 | } 55 | 56 | @Override 57 | public boolean isConnected() { 58 | return true; 59 | } 60 | 61 | @Override 62 | public void setErrorHandler(ErrorHandler errorHandler) { 63 | } 64 | 65 | @Override 66 | public void removeErrorHandler() { 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/fluentd/logger/sender/RawSocketSender.java: -------------------------------------------------------------------------------- 1 | // 2 | // A Structured Logger for Fluent 3 | // 4 | // Copyright (C) 2011 - 2013 Muga Nishizawa 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | package org.fluentd.logger.sender; 19 | 20 | import org.fluentd.logger.errorhandler.ErrorHandler; 21 | import org.msgpack.MessagePack; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | 25 | import java.io.BufferedOutputStream; 26 | import java.io.IOException; 27 | import java.net.InetSocketAddress; 28 | import java.net.Socket; 29 | import java.nio.ByteBuffer; 30 | import java.util.Map; 31 | 32 | public class RawSocketSender implements Sender { 33 | 34 | private static final Logger LOG = LoggerFactory.getLogger(RawSocketSender.class); 35 | 36 | private static final ErrorHandler DEFAULT_ERROR_HANDLER = new ErrorHandler() {}; 37 | 38 | private MessagePack msgpack; 39 | 40 | private Socket socket; 41 | 42 | private int timeout; 43 | 44 | private BufferedOutputStream out; 45 | 46 | private ByteBuffer pendings; 47 | 48 | private Reconnector reconnector; 49 | 50 | private String name; 51 | 52 | private final String host; 53 | 54 | private final int port; 55 | 56 | private ErrorHandler errorHandler = DEFAULT_ERROR_HANDLER; 57 | 58 | public RawSocketSender() { 59 | this("localhost", 24224); 60 | } 61 | 62 | public RawSocketSender(String host, int port) { 63 | this(host, port, 3 * 1000, 8 * 1024 * 1024); 64 | } 65 | 66 | public RawSocketSender(String host, int port, int timeout, int bufferCapacity) { 67 | this(host, port, timeout, bufferCapacity, new ExponentialDelayReconnector()); 68 | 69 | } 70 | 71 | public RawSocketSender(String host, int port, int timeout, int bufferCapacity, Reconnector reconnector) { 72 | msgpack = new MessagePack(); 73 | msgpack.register(Event.class, Event.EventTemplate.INSTANCE); 74 | pendings = ByteBuffer.allocate(bufferCapacity); 75 | this.host = host; 76 | this.port = port; 77 | this.reconnector = reconnector; 78 | name = String.format("%s_%d_%d_%d", host, port, timeout, bufferCapacity); 79 | this.timeout = timeout; 80 | } 81 | 82 | private void connect() throws IOException { 83 | try { 84 | socket = new Socket(); 85 | socket.connect(new InetSocketAddress(host, port), timeout); 86 | out = new BufferedOutputStream(socket.getOutputStream()); 87 | } catch (IOException e) { 88 | throw e; 89 | } 90 | } 91 | 92 | private void reconnect() throws IOException { 93 | if (socket == null) { 94 | connect(); 95 | } else if (socket.isClosed() || (!socket.isConnected())) { 96 | close(); 97 | connect(); 98 | } 99 | } 100 | 101 | @Override 102 | public synchronized void close() { 103 | // close output stream 104 | if (out != null) { 105 | try { 106 | out.close(); 107 | } catch (IOException e) { // ignore 108 | } finally { 109 | out = null; 110 | } 111 | } 112 | 113 | // close socket 114 | if (socket != null) { 115 | try { 116 | socket.close(); 117 | } catch (IOException e) { // ignore 118 | } finally { 119 | socket = null; 120 | } 121 | } 122 | } 123 | 124 | @Override 125 | public boolean emit(String tag, Map data) { 126 | return emit(tag, System.currentTimeMillis() / 1000, data); 127 | } 128 | 129 | @Override 130 | public boolean emit(String tag, long timestamp, Map data) { 131 | return emit(new Event(tag, timestamp, data)); 132 | } 133 | 134 | protected boolean emit(Event event) { 135 | if (LOG.isTraceEnabled()) { 136 | LOG.trace(String.format("Created %s", new Object[]{event})); 137 | } 138 | 139 | byte[] bytes = null; 140 | try { 141 | // serialize tag, timestamp and data 142 | bytes = msgpack.write(event); 143 | } catch (IOException e) { 144 | LOG.error("Cannot serialize event: " + event, e); 145 | return false; 146 | } 147 | 148 | // send serialized data 149 | return send(bytes); 150 | } 151 | 152 | private boolean flushBuffer() { 153 | if (reconnector.enableReconnection(System.currentTimeMillis())) { 154 | flush(); 155 | if (pendings.position() == 0) { 156 | return true; 157 | } else { 158 | LOG.error("Cannot send logs to " + socket.getInetAddress().toString()); 159 | } 160 | } 161 | 162 | return false; 163 | } 164 | 165 | private synchronized boolean send(byte[] bytes) { 166 | // buffering 167 | if (pendings.position() + bytes.length > pendings.capacity()) { 168 | if (!flushBuffer()) { 169 | return false; 170 | } 171 | if (bytes.length > pendings.remaining()) { 172 | LOG.error("Log data {} larger than remaining buffer size {}", bytes.length, pendings.remaining()); 173 | return false; 174 | } 175 | } 176 | pendings.put(bytes); 177 | 178 | // suppress reconnection burst 179 | if (!reconnector.enableReconnection(System.currentTimeMillis())) { 180 | return true; 181 | } 182 | 183 | // send pending data 184 | flush(); 185 | 186 | return true; 187 | } 188 | 189 | @Override 190 | public synchronized void flush() { 191 | try { 192 | // check whether connection is established or not 193 | reconnect(); 194 | // write data 195 | out.write(getBuffer()); 196 | out.flush(); 197 | clearBuffer(); 198 | reconnector.clearErrorHistory(); 199 | } catch (IOException e) { 200 | try { 201 | errorHandler.handleNetworkError(e); 202 | } 203 | catch (Exception handlerException) { 204 | LOG.warn("ErrorHandler.handleNetworkError failed", handlerException); 205 | } 206 | LOG.error(this.getClass().getName(), "flush", e); 207 | reconnector.addErrorHistory(System.currentTimeMillis()); 208 | close(); 209 | } 210 | } 211 | 212 | synchronized byte[] getBuffer() { 213 | int len = pendings.position(); 214 | pendings.position(0); 215 | byte[] ret = new byte[len]; 216 | pendings.get(ret, 0, len); 217 | return ret; 218 | } 219 | 220 | private void clearBuffer() { 221 | pendings.clear(); 222 | } 223 | 224 | @Override 225 | public String getName() { 226 | return name; 227 | } 228 | 229 | @Override 230 | public String toString() { 231 | return getName(); 232 | } 233 | 234 | @Override 235 | public boolean isConnected() { 236 | return socket != null && !socket.isClosed() && socket.isConnected() && !socket.isOutputShutdown(); 237 | } 238 | 239 | @Override 240 | public void setErrorHandler(ErrorHandler errorHandler) { 241 | if (errorHandler == null) { 242 | throw new IllegalArgumentException("errorHandler is null"); 243 | } 244 | 245 | this.errorHandler = errorHandler; 246 | } 247 | 248 | @Override 249 | public void removeErrorHandler() { 250 | this.errorHandler = DEFAULT_ERROR_HANDLER; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/main/java/org/fluentd/logger/sender/Reconnector.java: -------------------------------------------------------------------------------- 1 | // 2 | // A Structured Logger for Fluent 3 | // 4 | // Copyright (C) 2011 - 2013 Muga Nishizawa 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | package org.fluentd.logger.sender; 19 | 20 | public interface Reconnector { 21 | void clearErrorHistory(); 22 | 23 | void addErrorHistory(long timestamp); 24 | 25 | boolean isErrorHistoryEmpty(); 26 | 27 | boolean enableReconnection(long timestamp); 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/fluentd/logger/sender/Sender.java: -------------------------------------------------------------------------------- 1 | // 2 | // A Structured Logger for Fluent 3 | // 4 | // Copyright (C) 2011 - 2013 FURUHASHI Sadayuki 5 | // 6 | // Licensed under the Apache License, Version 2.0 (the "License"); 7 | // you may not use this file except in compliance with the License. 8 | // You may obtain a copy of the License at 9 | // 10 | // http://www.apache.org/licenses/LICENSE-2.0 11 | // 12 | // Unless required by applicable law or agreed to in writing, software 13 | // distributed under the License is distributed on an "AS IS" BASIS, 14 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | // See the License for the specific language governing permissions and 16 | // limitations under the License. 17 | // 18 | package org.fluentd.logger.sender; 19 | 20 | import org.fluentd.logger.errorhandler.ErrorHandler; 21 | 22 | import java.util.Map; 23 | 24 | public interface Sender { 25 | boolean emit(String tag, Map data); 26 | 27 | boolean emit(String tag, long timestamp, Map data); 28 | 29 | void flush(); 30 | 31 | void close(); 32 | 33 | String getName(); 34 | 35 | boolean isConnected(); 36 | 37 | void setErrorHandler(ErrorHandler errorHandler); 38 | 39 | void removeErrorHandler(); 40 | } 41 | -------------------------------------------------------------------------------- /src/test/java/org/fluentd/logger/TestBugfixes.java: -------------------------------------------------------------------------------- 1 | package org.fluentd.logger; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.util.Collections; 6 | 7 | import org.junit.Test; 8 | 9 | public class TestBugfixes { 10 | /** Prior to the issue fix, this test fails with an NPE on the last line. 11 | */ 12 | @Test 13 | public void validLoggerReturned_whenOpenThenCloseThenOpenWithSameParameters() { 14 | // use test sender so we don't need to have an actual fluentd running... 15 | System.setProperty(Config.FLUENT_SENDER_CLASS, "org.fluentd.logger.sender.NullSender"); 16 | FluentLogger logger = FluentLogger.getLogger("test"); 17 | 18 | // this works 19 | logger.log("tag", Collections.emptyMap()); 20 | 21 | // now close it; sender is closed and set to null 22 | logger.close(); 23 | assertEquals(null, logger.sender); 24 | 25 | // get another logger with the exact same parameters; we'd expect this to work, yes? 26 | FluentLogger logger2 = FluentLogger.getLogger("test"); 27 | 28 | // let's see if it does 29 | logger2.log("tag", Collections.emptyMap()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/test/java/org/fluentd/logger/TestFluentLogFactory.java: -------------------------------------------------------------------------------- 1 | package org.fluentd.logger; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.util.ArrayList; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | import static org.junit.Assert.assertTrue; 11 | 12 | public class TestFluentLogFactory { 13 | private FluentLoggerFactory loggerFactory; 14 | 15 | @Before 16 | public void setup() { 17 | loggerFactory = new FluentLoggerFactory(); 18 | } 19 | 20 | @Test 21 | public void testGetLogger() { 22 | FluentLogger loggerA0 = loggerFactory.getLogger("tagprefix_a"); 23 | FluentLogger loggerA1 = loggerFactory.getLogger("tagprefix_a"); 24 | FluentLogger loggerB0 = loggerFactory.getLogger("tagprefix_b"); 25 | FluentLogger loggerA_lh0 = loggerFactory.getLogger("tagprefix_a", "localhost", 1234); 26 | FluentLogger loggerA_lh1 = loggerFactory.getLogger("tagprefix_a", "127.0.0.1", 1234); 27 | assertTrue(loggerA0 == loggerA1); 28 | assertTrue(loggerA0 != loggerB0); 29 | assertTrue(loggerA0 != loggerA_lh0); 30 | assertTrue(loggerA_lh0 != loggerA_lh1); 31 | } 32 | 33 | @Test 34 | public void testItHoldsLoggersOverGC() throws InterruptedException { 35 | ArrayList loggers = new ArrayList(); 36 | for(int i=0; i<100; i++) { 37 | loggers.add(loggerFactory.getLogger("testtag" + i, "localhost", 999)); 38 | } 39 | System.gc(); 40 | Thread.sleep(1000); 41 | assertEquals(loggers.size(), loggerFactory.getLoggers().size()); 42 | 43 | FluentLogger head = loggers.get(0); 44 | FluentLogger tail = loggers.get(loggers.size() - 1); 45 | loggers.clear(); 46 | System.gc(); 47 | Thread.sleep(1000); 48 | assertEquals(2, loggerFactory.getLoggers().size()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/org/fluentd/logger/TestFluentLogger.java: -------------------------------------------------------------------------------- 1 | package org.fluentd.logger; 2 | 3 | import org.fluentd.logger.errorhandler.ErrorHandler; 4 | import org.fluentd.logger.sender.Event; 5 | import org.fluentd.logger.sender.ExponentialDelayReconnector; 6 | import org.fluentd.logger.sender.NullSender; 7 | import org.fluentd.logger.sender.Sender; 8 | import org.fluentd.logger.util.MockFluentd; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.msgpack.MessagePack; 12 | import org.msgpack.unpacker.Unpacker; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.io.BufferedInputStream; 17 | import java.io.EOFException; 18 | import java.io.IOException; 19 | import java.net.Socket; 20 | import java.util.*; 21 | import java.util.concurrent.CountDownLatch; 22 | import java.util.concurrent.ExecutorService; 23 | import java.util.concurrent.Executors; 24 | import java.util.concurrent.TimeUnit; 25 | import java.util.concurrent.atomic.AtomicReference; 26 | 27 | import static org.junit.Assert.*; 28 | 29 | public class TestFluentLogger { 30 | private Logger _logger = LoggerFactory.getLogger(TestFluentLogger.class); 31 | 32 | class FixedThreadManager { 33 | private final ExecutorService service; 34 | 35 | public FixedThreadManager(int numThreads) { 36 | service = Executors.newFixedThreadPool(numThreads); 37 | } 38 | 39 | public void submit(Runnable r) { 40 | service.submit(r); 41 | } 42 | 43 | public void join() throws InterruptedException { 44 | service.shutdown(); 45 | while(!service.awaitTermination(1000, TimeUnit.MILLISECONDS)) { 46 | _logger.debug("waiting ..."); 47 | } 48 | _logger.trace("Terminating FixedThreadManager"); 49 | } 50 | } 51 | 52 | @Before 53 | public void setUp() { 54 | // To remove garbage loggers from org.fluentd.logger.FluentLoggerFactory.loggers... 55 | System.gc(); 56 | } 57 | 58 | 59 | @Test 60 | public void testNormal01() throws Exception { 61 | // start mock fluentd 62 | int port = MockFluentd.randomPort(); 63 | String host = "localhost"; 64 | final List elist = new ArrayList(); 65 | MockFluentd fluentd = new MockFluentd(port, new MockFluentd.MockProcess() { 66 | public void process(MessagePack msgpack, Socket socket) throws IOException { 67 | BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); 68 | try { 69 | Unpacker unpacker = msgpack.createUnpacker(in); 70 | while (true) { 71 | Event e = unpacker.read(Event.class); 72 | elist.add(e); 73 | } 74 | //socket.close(); 75 | } catch (EOFException e) { 76 | // ignore 77 | } 78 | } 79 | }); 80 | 81 | FixedThreadManager threadManager = new FixedThreadManager(1); 82 | threadManager.submit(fluentd); 83 | fluentd.waitUntilReady(); 84 | 85 | // start loggers 86 | FluentLogger logger = FluentLogger.getLogger("testtag", host, port); 87 | { 88 | Map data = new HashMap(); 89 | data.put("k1", "v1"); 90 | data.put("k2", "v2"); 91 | logger.log("test01", data); 92 | } 93 | { 94 | Map data = new HashMap(); 95 | data.put("k3", "v3"); 96 | data.put("k4", "v4"); 97 | logger.log("test01", data); 98 | } 99 | 100 | // close loggers 101 | logger.close(); 102 | Thread.sleep(2000); 103 | 104 | // close mock fluentd 105 | fluentd.close(); 106 | 107 | // wait for unpacking event data on fluentd 108 | threadManager.join(); 109 | 110 | // check data 111 | assertEquals(2, elist.size()); 112 | assertEquals("testtag.test01", elist.get(0).tag); 113 | assertEquals("testtag.test01", elist.get(1).tag); 114 | } 115 | 116 | @SuppressWarnings("unchecked") 117 | @Test 118 | public void testNormal02() throws Exception { 119 | int loggerCount = 3; 120 | 121 | // start mock fluentd 122 | int port = MockFluentd.randomPort(); 123 | String host = "localhost"; 124 | final List[] elists = new List[loggerCount]; 125 | elists[0] = new ArrayList(); 126 | elists[1] = new ArrayList(); 127 | elists[2] = new ArrayList(); 128 | MockFluentd fluentd = new MockFluentd(port, new MockFluentd.MockProcess() { 129 | public void process(MessagePack msgpack, Socket socket) throws IOException { 130 | BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); 131 | try { 132 | Unpacker unpacker = msgpack.createUnpacker(in); 133 | while (true) { 134 | Event e = unpacker.read(Event.class); 135 | if (e.tag.startsWith("noprefix")) { 136 | elists[2].add(e); // no tag prefix 137 | } else if (e.tag.startsWith("testtag00")) { 138 | elists[0].add(e); // testtag00 139 | } else { 140 | elists[1].add(e); // testtag01 141 | } 142 | } 143 | //socket.close(); 144 | } catch (EOFException e) { 145 | // ignore 146 | } 147 | } 148 | }); 149 | FixedThreadManager threadManager = new FixedThreadManager(1); 150 | threadManager.submit(fluentd); 151 | fluentd.waitUntilReady(); 152 | 153 | // start loggers 154 | FluentLogger[] loggers = new FluentLogger[loggerCount]; 155 | int[] counts = new int[] { 50, 100, 75 }; 156 | loggers[0] = FluentLogger.getLogger("testtag00", host, port); 157 | { 158 | for (int i = 0; i < counts[0]; i++) { 159 | Map data = new HashMap(); 160 | data.put("k1", "v1"); 161 | data.put("k2", "v2"); 162 | loggers[0].log("test00", data); 163 | } 164 | } 165 | loggers[1] = FluentLogger.getLogger("testtag01", host, port); 166 | { 167 | for (int i = 0; i < counts[1]; i++) { 168 | Map data = new HashMap(); 169 | data.put("k3", "v3"); 170 | data.put("k4", "v4"); 171 | loggers[1].log("test01", data); 172 | } 173 | } 174 | loggers[2] = FluentLogger.getLogger(null, host, port); 175 | { 176 | for (int i = 0; i < counts[2]; i++) { 177 | Map data = new HashMap(); 178 | data.put("k5", 5555); 179 | data.put("k6", 6666); 180 | loggers[2].log("noprefix01", data); 181 | } 182 | } 183 | 184 | // close loggers 185 | FluentLogger.closeAll(); 186 | Thread.sleep(2000); 187 | 188 | // close mock fluentd 189 | fluentd.close(); 190 | 191 | // wait for unpacking event data on fluentd 192 | threadManager.join(); 193 | 194 | // check data 195 | assertEquals(counts[0], elists[0].size()); 196 | for (Object obj : elists[0]) { 197 | Event e = (Event) obj; 198 | assertEquals("testtag00.test00", e.tag); 199 | } 200 | assertEquals(counts[1], elists[1].size()); 201 | for (Object obj : elists[1]) { 202 | Event e = (Event) obj; 203 | assertEquals("testtag01.test01", e.tag); 204 | } 205 | assertEquals(counts[2], elists[2].size()); 206 | for (Object obj : elists[2]) { 207 | Event e = (Event) obj; 208 | assertEquals("noprefix01", e.tag); 209 | } 210 | } 211 | 212 | @Test 213 | public void testReconnection() throws Exception { 214 | // start mock fluentd 215 | int port = MockFluentd.randomPort(); 216 | String host = "localhost"; 217 | final List elist1 = new ArrayList(); 218 | final AtomicReference lastError = new AtomicReference(); 219 | 220 | FixedThreadManager threadManager = new FixedThreadManager(2); 221 | 222 | // run a fluentd 223 | MockFluentd fluentd1 = new MockFluentd(port, new MockFluentd.MockProcess() { 224 | public void process(MessagePack msgpack, Socket socket) throws IOException { 225 | BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); 226 | try { 227 | Unpacker unpacker = msgpack.createUnpacker(in); 228 | while (true) { 229 | Event e = unpacker.read(Event.class); 230 | elist1.add(e); 231 | 232 | if (elist1.size() >= 1) 233 | break; 234 | } 235 | socket.close(); 236 | } catch (EOFException e) { 237 | // ignore 238 | } 239 | } 240 | }); 241 | threadManager.submit(fluentd1); 242 | fluentd1.waitUntilReady(); 243 | 244 | // start a logger 245 | final FluentLogger logger = FluentLogger.getLogger("testtag", host, port); 246 | final ErrorHandler errorHandler = new ErrorHandler() { 247 | @Override 248 | public void handleNetworkError(IOException ex) { 249 | lastError.set(ex); 250 | } 251 | }; 252 | logger.setErrorHandler(errorHandler); 253 | 254 | assertFalse(logger.isConnected()); 255 | { 256 | Map data = new HashMap(); 257 | data.put("k1", "v1"); 258 | data.put("k2", "v2"); 259 | logger.log("test01", data); 260 | } 261 | assertTrue(logger.isConnected()); 262 | 263 | // close the fluentd to make the situation that the fluentd gets down 264 | TimeUnit.MILLISECONDS.sleep(500); 265 | _logger.info("Closing the current fluentd instance"); 266 | fluentd1.closeClientSockets(); 267 | fluentd1.close(); 268 | 269 | // the logger should fail to send an event 270 | TimeUnit.MILLISECONDS.sleep(500); 271 | assertTrue(logger.isConnected()); 272 | for (int i = 0; i < 2; i++) { 273 | // repeat twice to test both behaviors on socket write error and connection error 274 | assertNull(lastError.get()); 275 | { 276 | // writing to the closed socket 277 | Map data = new HashMap(); 278 | data.put("k3", "v3"); 279 | data.put("k4", "v4"); 280 | logger.log("test01", data); 281 | } 282 | assertTrue(lastError.get() instanceof IOException); 283 | lastError.set(null); // Clear the last error 284 | assertFalse(logger.isConnected()); 285 | TimeUnit.MILLISECONDS.sleep((long) (ExponentialDelayReconnector.WAIT_MILLIS * 1.5)); 286 | } 287 | 288 | // the logger shouldn't call the error handler after calling removeErrorHandler() 289 | logger.removeErrorHandler(); 290 | { 291 | // writing to the closed socket 292 | Map data = new HashMap(); 293 | data.put("k3", "v3"); 294 | data.put("k4", "v4"); 295 | logger.log("test01", data); 296 | } 297 | assertNull(lastError.get()); 298 | lastError.set(null); // Clear the last error 299 | 300 | logger.setErrorHandler(errorHandler); 301 | 302 | // run the fluentd again 303 | final List elist2 = new ArrayList(); 304 | MockFluentd fluentd2 = new MockFluentd(port, new MockFluentd.MockProcess() { 305 | public void process(MessagePack msgpack, Socket socket) throws IOException { 306 | BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); 307 | try { 308 | Unpacker unpacker = msgpack.createUnpacker(in); 309 | while (true) { 310 | Event e = unpacker.read(Event.class); 311 | elist2.add(e); 312 | } 313 | // socket.close(); 314 | } catch (EOFException e) { 315 | // ignore 316 | } 317 | } 318 | }); 319 | threadManager.submit(fluentd2); 320 | fluentd2.waitUntilReady(); 321 | 322 | TimeUnit.MILLISECONDS.sleep((long) (ExponentialDelayReconnector.WAIT_MILLIS * 1.5)); 323 | { 324 | Map data = new HashMap(); 325 | data.put("k5", "v5"); 326 | data.put("k6", "v6"); 327 | logger.log("test01", data); 328 | } 329 | assertNull(lastError.get()); 330 | assertTrue(logger.isConnected()); 331 | 332 | // close loggers 333 | FluentLogger.closeAll(); 334 | Thread.sleep(2000); 335 | 336 | fluentd2.close(); 337 | 338 | // wait for unpacking event data on fluentd 339 | TimeUnit.MILLISECONDS.sleep(2000); 340 | threadManager.join(); 341 | assertNull(lastError.get()); 342 | 343 | // check data 344 | assertEquals(1, elist1.size()); 345 | assertEquals("testtag.test01", elist1.get(0).tag); 346 | 347 | assertEquals(4, elist2.size()); 348 | for (int i = 0; i < elist2.size(); i++) { 349 | assertEquals("testtag.test01", elist2.get(i).tag); 350 | } 351 | } 352 | 353 | @Test 354 | public void testClose() throws Exception { 355 | // use NullSender 356 | Properties props = System.getProperties(); 357 | props.setProperty(Config.FLUENT_SENDER_CLASS, NullSender.class.getName()); 358 | 359 | // create logger objects 360 | FluentLogger.getLogger("tag1"); 361 | FluentLogger.getLogger("tag2"); 362 | FluentLogger.getLogger("tag3"); 363 | 364 | Map loggers; 365 | { 366 | loggers = FluentLogger.getLoggers(); 367 | assertEquals(3, loggers.size()); 368 | } 369 | 370 | // close and delete 371 | FluentLogger.closeAll(); 372 | { 373 | loggers = FluentLogger.getLoggers(); 374 | assertEquals(0, loggers.size()); 375 | } 376 | 377 | props.remove(Config.FLUENT_SENDER_CLASS); 378 | } 379 | 380 | @Test 381 | public void testInMultiThreading() throws Exception { 382 | final int N = 15; 383 | final int LOOP = 15000; 384 | final String tag = "foodb.bartbl"; 385 | final ArrayList counters = new ArrayList(N); 386 | for (int i = 0; i < N; i++) 387 | counters.add(0L); 388 | 389 | // start mock fluentd 390 | final int port = MockFluentd.randomPort(); 391 | final String host = "localhost"; 392 | MockFluentd fluentd = new MockFluentd(port, new MockFluentd.MockProcess() { 393 | public void process(MessagePack msgpack, Socket socket) throws IOException { 394 | BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); 395 | try { 396 | while (true) { 397 | Unpacker unpacker = msgpack.createUnpacker(in); 398 | Event e = unpacker.read(Event.class); 399 | if (e.tag.equals(tag)) { 400 | for (Map.Entry entry : e.data.entrySet()) { 401 | Integer index = Integer.valueOf(entry.getKey()); 402 | Long i = counters.get(index); 403 | counters.set(index, i + (Long)entry.getValue()); 404 | } 405 | } 406 | } 407 | } catch (EOFException e) { 408 | } 409 | } 410 | }); 411 | 412 | FixedThreadManager threadManager = new FixedThreadManager(1); 413 | threadManager.submit(fluentd); 414 | fluentd.waitUntilReady(); 415 | 416 | final FluentLogger logger = FluentLogger.getLogger(null, host, port); 417 | ExecutorService executorService = Executors.newFixedThreadPool(N); 418 | /* 419 | * Each thread emits the following events LOOP times 420 | * Thread#0: {'0' => 0} 421 | * Thread#1: {'0' => 0, '1' => 1} 422 | * Thread#2: {'0' => 0, '1' => 1, '2' => 2} 423 | * : 424 | * Thread#(N-1): {'0' => 0, '1' => 1, '2' => 2 ... '(N-1)' => (N-1)} 425 | */ 426 | for (int i = 0; i < N; i++) { 427 | final int ii = i; 428 | executorService.execute(new Runnable() { 429 | @Override 430 | public void run() { 431 | Map event = new HashMap(); 432 | for (int j = 0; j <= ii; j++) { 433 | event.put(String.valueOf(j), j); 434 | } 435 | for (int j = 0; j < LOOP; j++) { 436 | logger.log(tag, event); 437 | 438 | if (j % 500 == ii) 439 | logger.flush(); 440 | } 441 | logger.flush(); 442 | } 443 | }); 444 | } 445 | Thread.sleep(1000); 446 | executorService.shutdown(); 447 | executorService.awaitTermination(300, TimeUnit.SECONDS); 448 | 449 | logger.close(); 450 | 451 | Thread.sleep(2000); 452 | 453 | // close mock fluentd 454 | fluentd.close(); 455 | 456 | // wait for unpacking event data on fluentd 457 | threadManager.join(); 458 | 459 | // check data 460 | for (int i = 0; i < N; i++) { 461 | assertEquals((i * LOOP * (N - i)), (long)counters.get(i)); 462 | } 463 | } 464 | 465 | @Test 466 | public void testFlushOnClose() throws Exception { 467 | // start mock fluentd 468 | int port = MockFluentd.randomPort(); 469 | String host = "localhost"; 470 | final List elist = new ArrayList(); 471 | final CountDownLatch latch = new CountDownLatch(1); 472 | MockFluentd fluentd = new MockFluentd(port, new MockFluentd.MockProcess() { 473 | public void process(MessagePack msgpack, Socket socket) throws IOException { 474 | BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); 475 | try { 476 | Unpacker unpacker = msgpack.createUnpacker(in); 477 | while (true) { 478 | Event e = unpacker.read(Event.class); 479 | elist.add(e); 480 | latch.countDown(); 481 | } 482 | //socket.close(); 483 | } catch (EOFException e) { 484 | // ignore 485 | } 486 | } 487 | }); 488 | 489 | FixedThreadManager threadManager = new FixedThreadManager(1); 490 | 491 | // start loggers 492 | FluentLogger logger = FluentLogger.getLogger("prefix", host, port); 493 | { 494 | Map data = new HashMap(); 495 | data.put("k", "v"); 496 | // Fluentd hasn't started yet and the record will be buffered. 497 | logger.log("tag", data); 498 | } 499 | 500 | threadManager.submit(fluentd); 501 | fluentd.waitUntilReady(); 502 | 503 | // close loggers and it should flush the buffer 504 | logger.close(); 505 | 506 | // wait for fluentd's getting at least one kv pair 507 | latch.await(3, TimeUnit.SECONDS); 508 | 509 | // close mock fluentd 510 | fluentd.close(); 511 | 512 | // wait for unpacking event data on fluentd 513 | threadManager.join(); 514 | 515 | // check data 516 | assertEquals(1, elist.size()); 517 | Event ev = elist.get(0); 518 | assertEquals("prefix.tag", ev.tag); 519 | assertEquals(1, ev.data.size()); 520 | assertTrue(ev.data.containsKey("k")); 521 | assertTrue(ev.data.containsValue("v")); 522 | } 523 | 524 | public void testGetSender() { 525 | FluentLogger logger = FluentLogger.getLogger("prefix"); 526 | Sender sender = logger.getSender(); 527 | assertNotNull(sender); 528 | } 529 | } 530 | -------------------------------------------------------------------------------- /src/test/java/org/fluentd/logger/sender/TestNullSender.java: -------------------------------------------------------------------------------- 1 | package org.fluentd.logger.sender; 2 | 3 | import static org.junit.Assert.assertTrue; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import org.junit.Test; 9 | 10 | public class TestNullSender { 11 | 12 | @Test 13 | public void testNormal() throws Exception { 14 | //public NullSender(String host, int port, int timeout, int bufferCapacity) { 15 | NullSender sender = new NullSender("localhost", 24224, 3 * 1000, 1 * 1024 * 1024); 16 | 17 | // emit 18 | { 19 | Map data = new HashMap(); 20 | assertTrue(sender.emit("test", data)); 21 | } 22 | { 23 | Map data = new HashMap(); 24 | assertTrue(sender.emit("test", System.currentTimeMillis() / 1000, data)); 25 | } 26 | 27 | // flush 28 | sender.flush(); 29 | assertTrue(true); 30 | 31 | // close 32 | sender.close(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/org/fluentd/logger/sender/TestRawSocketSender.java: -------------------------------------------------------------------------------- 1 | package org.fluentd.logger.sender; 2 | 3 | import org.fluentd.logger.util.MockFluentd; 4 | import org.fluentd.logger.util.MockFluentd.MockProcess; 5 | import org.junit.Test; 6 | import org.msgpack.MessagePack; 7 | import org.msgpack.unpacker.Unpacker; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.io.BufferedInputStream; 12 | import java.io.EOFException; 13 | import java.io.IOException; 14 | import java.net.Socket; 15 | import java.util.*; 16 | import java.util.concurrent.ConcurrentLinkedQueue; 17 | import java.util.concurrent.CountDownLatch; 18 | import java.util.concurrent.ExecutorService; 19 | import java.util.concurrent.Executors; 20 | import java.util.concurrent.TimeUnit; 21 | import java.util.concurrent.atomic.AtomicBoolean; 22 | 23 | import static org.junit.Assert.assertEquals; 24 | import static org.junit.Assert.assertFalse; 25 | import static org.junit.Assert.assertTrue; 26 | 27 | 28 | public class TestRawSocketSender { 29 | 30 | @Test 31 | public void testNormal01() throws Exception { 32 | // start mock fluentd 33 | int port = MockFluentd.randomPort(); 34 | final List elist = new ArrayList(); 35 | MockFluentd fluentd = new MockFluentd(port, new MockFluentd.MockProcess() { 36 | public void process(MessagePack msgpack, Socket socket) throws IOException { 37 | BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); 38 | try { 39 | Unpacker unpacker = msgpack.createUnpacker(in); 40 | while (true) { 41 | Event e = unpacker.read(Event.class); 42 | elist.add(e); 43 | } 44 | //socket.close(); 45 | } catch (EOFException e) { 46 | // ignore 47 | } 48 | } 49 | }); 50 | fluentd.start(); 51 | fluentd.waitUntilReady(); 52 | 53 | // start senders 54 | Sender sender = new RawSocketSender("localhost", port); 55 | Map data = new HashMap(); 56 | data.put("t1k1", "t1v1"); 57 | data.put("t1k2", "t1v2"); 58 | sender.emit("tag.label1", data); 59 | 60 | Map data2 = new HashMap(); 61 | data2.put("t2k1", "t2v1"); 62 | data2.put("t2k2", "t2v2"); 63 | sender.emit("tag.label2", data2); 64 | 65 | // close sender sockets 66 | sender.close(); 67 | 68 | // wait for unpacking event data on fluentd 69 | Thread.sleep(2000); 70 | 71 | // close mock server sockets 72 | fluentd.close(); 73 | 74 | 75 | // check data 76 | assertEquals(2, elist.size()); 77 | { 78 | Event e = elist.get(0); 79 | assertEquals("tag.label1", e.tag); 80 | assertEquals("t1v1", e.data.get("t1k1")); 81 | assertEquals("t1v2", e.data.get("t1k2")); 82 | } 83 | { 84 | Event e = elist.get(1); 85 | assertEquals("tag.label2", e.tag); 86 | assertEquals("t2v1", e.data.get("t2k1")); 87 | assertEquals("t2v2", e.data.get("t2k2")); 88 | } 89 | } 90 | 91 | 92 | 93 | @Test 94 | public void testNormal02() throws Exception { 95 | // start mock fluentd 96 | int port = MockFluentd.randomPort(); // Use a random port available 97 | final List elist = new ArrayList(); 98 | MockFluentd fluentd = new MockFluentd(port, new MockFluentd.MockProcess() { 99 | public void process(MessagePack msgpack, Socket socket) throws IOException { 100 | BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); 101 | try { 102 | Unpacker unpacker = msgpack.createUnpacker(in); 103 | while (true) { 104 | Event e = unpacker.read(Event.class); 105 | elist.add(e); 106 | } 107 | //socket.close(); 108 | } catch (EOFException e) { 109 | // ignore 110 | } 111 | } 112 | }); 113 | fluentd.start(); 114 | fluentd.waitUntilReady(); 115 | 116 | // start senders 117 | Sender sender = new RawSocketSender("localhost", port); 118 | int count = 10000; 119 | for (int i = 0; i < count; i++) { 120 | String tag = "tag:i"; 121 | Map record = new HashMap(); 122 | record.put("i", i); 123 | record.put("n", "name:" + i); 124 | sender.emit(tag, record); 125 | } 126 | 127 | // close sender sockets 128 | sender.close(); 129 | 130 | // wait for unpacking event data on fluentd 131 | Thread.sleep(2000); 132 | 133 | // close mock server sockets 134 | fluentd.close(); 135 | 136 | 137 | // check data 138 | assertEquals(count, elist.size()); 139 | } 140 | 141 | @Test 142 | public void testNormal03() throws Exception { 143 | // start mock fluentds 144 | final MockFluentd[] fluentds = new MockFluentd[2]; 145 | final List[] elists = new List[2]; 146 | final int[] ports = new int[2]; 147 | ports[0] = MockFluentd.randomPort(); 148 | RawSocketSender rawSocketSender = new RawSocketSender("localhost", ports[0]); // it should be failed to connect to fluentd 149 | elists[0] = new ArrayList(); 150 | fluentds[0] = new MockFluentd(ports[0], new MockFluentd.MockProcess() { 151 | public void process(MessagePack msgpack, Socket socket) throws IOException { 152 | BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); 153 | try { 154 | Unpacker unpacker = msgpack.createUnpacker(in); 155 | while (true) { 156 | Event e = unpacker.read(Event.class); 157 | elists[0].add(e); 158 | } 159 | //socket.close(); 160 | } catch (EOFException e) { 161 | // ignore 162 | } 163 | } 164 | }); 165 | fluentds[0].start(); 166 | fluentds[0].waitUntilReady(); 167 | 168 | ports[1] = MockFluentd.randomPort(); 169 | elists[1] = new ArrayList(); 170 | fluentds[1] = new MockFluentd(ports[1], new MockFluentd.MockProcess() { 171 | public void process(MessagePack msgpack, Socket socket) throws IOException { 172 | BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); 173 | try { 174 | Unpacker unpacker = msgpack.createUnpacker(in); 175 | while (true) { 176 | Event e = unpacker.read(Event.class); 177 | elists[1].add(e); 178 | } 179 | //socket.close(); 180 | } catch (EOFException e) { 181 | // ignore 182 | } 183 | } 184 | }); 185 | fluentds[1].start(); 186 | fluentds[1].waitUntilReady(); 187 | 188 | // start senders 189 | Sender[] senders = new Sender[2]; 190 | int[] counts = new int[2]; 191 | senders[0] = rawSocketSender; 192 | counts[0] = 10000; 193 | for (int i = 0; i < counts[0]; i++) { 194 | String tag = "tag:i"; 195 | Map record = new HashMap(); 196 | record.put("i", i); 197 | record.put("n", "name:" + i); 198 | senders[0].emit(tag, record); 199 | } 200 | senders[1] = new RawSocketSender("localhost", ports[1]); 201 | counts[1] = 10000; 202 | for (int i = 0; i < counts[1]; i++) { 203 | String tag = "tag:i"; 204 | Map record = new HashMap(); 205 | record.put("i", i); 206 | record.put("n", "name:" + i); 207 | senders[1].emit(tag, record); 208 | } 209 | 210 | // close sender sockets 211 | senders[0].close(); 212 | senders[1].close(); 213 | 214 | // wait for unpacking event data on fluentd 215 | Thread.sleep(2000); 216 | 217 | // close mock server sockets 218 | fluentds[0].close(); 219 | fluentds[1].close(); 220 | 221 | 222 | // check data 223 | assertEquals(counts[0], elists[0].size()); 224 | assertEquals(counts[1], elists[1].size()); 225 | } 226 | 227 | @Test 228 | public void testTimeout() throws InterruptedException { 229 | final AtomicBoolean socketFinished = new AtomicBoolean(false); 230 | ExecutorService executor = Executors.newSingleThreadExecutor(); 231 | 232 | executor.execute(new Runnable() { 233 | @Override 234 | public void run() { 235 | RawSocketSender socketSender = null; 236 | try { 237 | // try to connect to test network 238 | socketSender = new RawSocketSender("192.0.2.1", 24224, 200, 8 * 1024); 239 | } 240 | finally { 241 | if (socketSender != null) { 242 | socketSender.close(); 243 | } 244 | socketFinished.set(true); 245 | } 246 | } 247 | }); 248 | 249 | while(!socketFinished.get()) 250 | Thread.yield(); 251 | 252 | assertTrue(socketFinished.get()); 253 | executor.shutdownNow(); 254 | } 255 | 256 | @Test 257 | public void testBufferingAndResending() throws InterruptedException, IOException { 258 | final ConcurrentLinkedQueue readEvents = new ConcurrentLinkedQueue(); 259 | final CountDownLatch countDownLatch = new CountDownLatch(4); 260 | int port = MockFluentd.randomPort(); 261 | MockProcess mockProcess = new MockFluentd.MockProcess() { 262 | public void process(MessagePack msgpack, Socket socket) throws IOException { 263 | BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); 264 | try { 265 | Unpacker unpacker = msgpack.createUnpacker(in); 266 | while (true) { 267 | Event e = unpacker.read(Event.class); 268 | readEvents.add(e); 269 | countDownLatch.countDown(); 270 | } 271 | } catch (EOFException e) { 272 | // e.printStackTrace(); 273 | } 274 | } 275 | }; 276 | 277 | MockFluentd fluentd = new MockFluentd(port, mockProcess); 278 | fluentd.start(); 279 | fluentd.waitUntilReady(); 280 | 281 | Sender sender = new RawSocketSender("localhost", port); 282 | assertFalse(sender.isConnected()); 283 | Map data = new HashMap(); 284 | data.put("key0", "v0"); 285 | sender.emit("tag0", data); 286 | assertTrue(sender.isConnected()); 287 | 288 | // close fluentd to make the next sending failed 289 | TimeUnit.MILLISECONDS.sleep(500); 290 | 291 | fluentd.closeClientSockets(); 292 | 293 | TimeUnit.MILLISECONDS.sleep(500); 294 | 295 | data = new HashMap(); 296 | data.put("key0", "v1"); 297 | sender.emit("tag0", data); 298 | assertFalse(sender.isConnected()); 299 | 300 | // wait to avoid the suppression of reconnection 301 | TimeUnit.MILLISECONDS.sleep(500); 302 | 303 | data = new HashMap(); 304 | data.put("key0", "v2"); 305 | sender.emit("tag0", data); 306 | 307 | data = new HashMap(); 308 | data.put("key0", "v3"); 309 | sender.emit("tag0", data); 310 | 311 | countDownLatch.await(500, TimeUnit.MILLISECONDS); 312 | 313 | sender.close(); 314 | 315 | fluentd.close(); 316 | 317 | assertEquals(4, readEvents.size()); 318 | 319 | Event event = readEvents.poll(); 320 | assertEquals("tag0", event.tag); 321 | assertEquals(1, event.data.size()); 322 | assertTrue(event.data.keySet().contains("key0")); 323 | assertTrue(event.data.values().contains("v0")); 324 | 325 | event = readEvents.poll(); 326 | assertEquals("tag0", event.tag); 327 | assertEquals(1, event.data.size()); 328 | assertTrue(event.data.keySet().contains("key0")); 329 | assertTrue(event.data.values().contains("v1")); 330 | 331 | event = readEvents.poll(); 332 | assertEquals("tag0", event.tag); 333 | assertEquals(1, event.data.size()); 334 | assertTrue(event.data.keySet().contains("key0")); 335 | assertTrue(event.data.values().contains("v2")); 336 | 337 | event = readEvents.poll(); 338 | assertEquals("tag0", event.tag); 339 | assertEquals(1, event.data.size()); 340 | assertTrue(event.data.keySet().contains("key0")); 341 | assertTrue(event.data.values().contains("v3")); 342 | } 343 | 344 | @Test 345 | public void testReconnectAfterBufferFull() throws Exception { 346 | final CountDownLatch bufferFull = new CountDownLatch(1); 347 | 348 | // start mock fluentd 349 | int port = MockFluentd.randomPort(); // Use a random port available 350 | final List elist = new ArrayList(); 351 | final MockFluentd fluentd = new MockFluentd(port, new MockFluentd.MockProcess() { 352 | public void process(MessagePack msgpack, Socket socket) throws IOException { 353 | try { 354 | BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); 355 | Unpacker unpacker = msgpack.createUnpacker(in); 356 | while (true) { 357 | Event e = unpacker.read(Event.class); 358 | elist.add(e); 359 | } 360 | } catch (EOFException e) { 361 | // ignore 362 | } finally { 363 | socket.close(); 364 | } 365 | } 366 | }); 367 | 368 | ExecutorService executor = Executors.newSingleThreadExecutor(); 369 | executor.execute(new Runnable() { 370 | @Override 371 | public void run() { 372 | try { 373 | bufferFull.await(20, TimeUnit.SECONDS); 374 | fluentd.start(); 375 | } catch (InterruptedException e) { 376 | e.printStackTrace(); 377 | } 378 | } 379 | }); 380 | 381 | // start senders 382 | Sender sender = new RawSocketSender("localhost", port); 383 | String tag = "tag"; 384 | int i; 385 | for (i = 0; i < 1000000; i++) { // Enough to fill the sender's buffer 386 | Map record = new HashMap(); 387 | record.put("num", i); 388 | record.put("str", "name" + i); 389 | 390 | if (bufferFull.getCount() > 0) { 391 | // Fill the sender's buffer 392 | if (!sender.emit(tag, record)) { 393 | // Buffer full. Need to recover the fluentd 394 | bufferFull.countDown(); 395 | Thread.sleep(2000); 396 | } 397 | } 398 | else { 399 | // Flush the sender's buffer after the fluentd starts 400 | sender.emit(tag, record); 401 | break; 402 | } 403 | } 404 | 405 | // close sender sockets 406 | sender.close(); 407 | 408 | // wait for unpacking event data on fluentd 409 | Thread.sleep(2000); 410 | 411 | // close mock server sockets 412 | fluentd.close(); 413 | 414 | // check data 415 | assertEquals(0, bufferFull.getCount()); 416 | assertEquals(i, elist.size()); 417 | } 418 | 419 | @Test 420 | public void testBufferOverflow() throws Exception { 421 | // start mock fluentd 422 | int port = MockFluentd.randomPort(); 423 | MockFluentd fluentd = new MockFluentd(port, new MockFluentd.MockProcess() { 424 | public void process(MessagePack msgpack, Socket socket) throws IOException { 425 | BufferedInputStream in = new BufferedInputStream(socket.getInputStream()); 426 | try { 427 | Unpacker unpacker = msgpack.createUnpacker(in); 428 | while (true) { 429 | unpacker.read(Event.class); 430 | } 431 | //socket.close(); 432 | } catch (EOFException e) { 433 | // ignore 434 | } 435 | } 436 | }); 437 | fluentd.start(); 438 | 439 | // start senders 440 | Sender sender = new RawSocketSender("localhost", port, 3000, 256); 441 | Map data = new HashMap(); 442 | data.put("large", randomString(512)); 443 | boolean success = sender.emit("tag.label1", data); 444 | assertFalse(success); 445 | 446 | // close sender sockets 447 | sender.close(); 448 | // close mock server sockets 449 | fluentd.close(); 450 | } 451 | 452 | private static final String CHARS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ "; 453 | 454 | private String randomString(int len) { 455 | StringBuilder sb = new StringBuilder(len); 456 | 457 | Random rnd = new Random(); 458 | for (int i = 0; i < len; i++) { 459 | if (i != 0 && i % 128 == 0) { 460 | sb.append("\r\n"); 461 | } 462 | 463 | sb.append(CHARS.charAt(rnd.nextInt(CHARS.length()))); 464 | } 465 | 466 | return sb.toString(); 467 | } 468 | } 469 | -------------------------------------------------------------------------------- /src/test/java/org/fluentd/logger/sender/TestSenderFluentdDownOperation.java: -------------------------------------------------------------------------------- 1 | package org.fluentd.logger.sender; 2 | 3 | import static org.junit.Assert.assertArrayEquals; 4 | 5 | import java.io.IOException; 6 | import java.net.Socket; 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import org.fluentd.logger.sender.RawSocketSender; 11 | import org.fluentd.logger.sender.Sender; 12 | import org.fluentd.logger.util.MockFluentd; 13 | import org.fluentd.logger.util.MockFluentd.MockProcess; 14 | import org.junit.Ignore; 15 | import org.junit.Test; 16 | import org.msgpack.MessagePack; 17 | import org.msgpack.packer.BufferPacker; 18 | 19 | public class TestSenderFluentdDownOperation { 20 | 21 | /** 22 | * if Sender object was created when fluentd doesn't work, ... 23 | */ 24 | @Test @Ignore 25 | public void testFluentdDownOperation01() throws Exception { 26 | int port = 25225; 27 | MessagePack msgpack = new MessagePack(); 28 | msgpack.register(Event.class, Event.EventTemplate.INSTANCE); 29 | BufferPacker packer = msgpack.createBufferPacker(); 30 | long timestamp = System.currentTimeMillis() / 1000; 31 | 32 | // start senders 33 | RawSocketSender sender = new RawSocketSender("localhost", port); 34 | Map data = new HashMap(); 35 | data.put("t1k1", "t1v1"); 36 | data.put("t1k2", "t1v2"); 37 | sender.emit("tag.label1", timestamp, data); 38 | 39 | packer.write(new Event("tag.label1", timestamp, data)); 40 | byte[] bytes1 = packer.toByteArray(); 41 | assertArrayEquals(bytes1, sender.getBuffer()); 42 | 43 | Map data2 = new HashMap(); 44 | data2.put("t2k1", "t2v1"); 45 | data2.put("t2k2", "t2v2"); 46 | sender.emit("tag.label2", timestamp, data2); 47 | 48 | packer.write(new Event("tag.label2", timestamp, data2)); 49 | byte[] bytes2 = packer.toByteArray(); 50 | assertArrayEquals(bytes2, sender.getBuffer()); 51 | 52 | // close sender sockets 53 | sender.close(); 54 | } 55 | 56 | /** 57 | * if emit method was invoked when fluentd doesn't work, ... 58 | */ 59 | @Ignore @Test 60 | public void testFluentdDownOperation02()throws Exception { 61 | int port = 25225; 62 | MessagePack msgpack = new MessagePack(); 63 | msgpack.register(Event.class, Event.EventTemplate.INSTANCE); 64 | BufferPacker packer = msgpack.createBufferPacker(); 65 | long timestamp = System.currentTimeMillis(); 66 | 67 | // start mock server 68 | MockFluentd server = new MockFluentd(port, new MockFluentd.MockProcess() { 69 | public void process(MessagePack msgpack, Socket socket) throws IOException { 70 | System.out.println("server closing"); 71 | socket.close(); 72 | System.out.println("server closed"); 73 | } 74 | }); 75 | server.start(); 76 | 77 | // start senders 78 | RawSocketSender sender = new RawSocketSender("localhost", port); 79 | 80 | // server close 81 | server.close(); 82 | 83 | // sleep a little bit 84 | Thread.sleep(1000); 85 | 86 | Map data = new HashMap(); 87 | data.put("t1k1", "t1v1"); 88 | data.put("t1k2", "t1v2"); 89 | for (int i = 0; i < 3; ++i) { 90 | System.out.println("sender emit"); 91 | sender.emit("tag.label1", data); 92 | } 93 | 94 | packer.write(new Event("tag.label1", timestamp, data)); 95 | byte[] bytes1 = packer.toByteArray(); 96 | assertArrayEquals(bytes1, sender.getBuffer()); 97 | 98 | Map data2 = new HashMap(); 99 | data2.put("t2k1", "t2v1"); 100 | data2.put("t2k2", "t2v2"); 101 | sender.emit("tag.label2", data2); 102 | 103 | packer.write(new Event("tag.label2", timestamp, data2)); 104 | byte[] bytes2 = packer.toByteArray(); 105 | assertArrayEquals(bytes2, sender.getBuffer()); 106 | 107 | // close sender sockets 108 | sender.close(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/test/java/org/fluentd/logger/util/MockFluentd.java: -------------------------------------------------------------------------------- 1 | package org.fluentd.logger.util; 2 | 3 | import org.fluentd.logger.sender.Event; 4 | import org.msgpack.MessagePack; 5 | import org.msgpack.packer.Packer; 6 | import org.msgpack.template.Templates; 7 | import org.msgpack.type.Value; 8 | import org.msgpack.unpacker.Unpacker; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.IOException; 13 | import java.net.ServerSocket; 14 | import java.net.Socket; 15 | import java.util.HashMap; 16 | import java.util.List; 17 | import java.util.concurrent.ConcurrentLinkedQueue; 18 | import java.util.concurrent.ExecutorService; 19 | import java.util.concurrent.Executors; 20 | import java.util.concurrent.TimeUnit; 21 | import java.util.concurrent.atomic.AtomicBoolean; 22 | 23 | public class MockFluentd extends Thread { 24 | 25 | private static final Logger _logger = LoggerFactory.getLogger(MockFluentd.class); 26 | 27 | private ConcurrentLinkedQueue clientSockets = new ConcurrentLinkedQueue(); 28 | 29 | public static interface MockProcess { 30 | public void process(MessagePack msgpack, Socket socket) throws IOException; 31 | } 32 | 33 | public static class MockEventTemplate extends Event.EventTemplate { 34 | public static MockEventTemplate INSTANCE = new MockEventTemplate(); 35 | 36 | public void write(Packer pk, Event v, boolean required) throws IOException { 37 | throw new UnsupportedOperationException("don't need operation"); 38 | } 39 | 40 | public Event read(Unpacker u, Event to, boolean required) throws IOException { 41 | if (!required && u.trySkipNil()) { 42 | return null; 43 | } 44 | 45 | to = new Event(); 46 | u.readArrayBegin(); 47 | { 48 | to.tag = Templates.TString.read(u, null, required); 49 | to.timestamp = Templates.TLong.read(u, null, required); 50 | int size = u.readMapBegin(); 51 | to.data = new HashMap(size); 52 | { 53 | for (int i = 0; i < size; i++) { 54 | String key = (String) toObject(u, u.readValue()); 55 | Object value = toObject(u, u.readValue()); 56 | to.data.put(key, value); 57 | } 58 | } 59 | u.readMapEnd(); 60 | } 61 | u.readArrayEnd(); 62 | return to; 63 | } 64 | 65 | private static Object toObject(Unpacker u, Value v) { 66 | if (v.isNilValue()) { 67 | v.asNilValue(); 68 | return null; 69 | } else if (v.isRawValue()) { 70 | return v.asRawValue().getString(); // String only 71 | } else if (v.isBooleanValue()) { 72 | return v.asBooleanValue().getBoolean(); 73 | } else if (v.isFloatValue()) { 74 | return v.asFloatValue().getDouble(); // double only 75 | } else if (v.isIntegerValue()) { 76 | return v.asIntegerValue().getLong(); // long only 77 | } else if (v.isMapValue()) { 78 | throw new UnsupportedOperationException(); 79 | } else if (v.isArrayValue()) { 80 | throw new UnsupportedOperationException(); 81 | } else { 82 | throw new UnsupportedOperationException(); 83 | } 84 | } 85 | } 86 | 87 | private final int port; 88 | private ServerSocket serverSocket; 89 | 90 | private MockProcess process; 91 | 92 | private AtomicBoolean started = new AtomicBoolean(false); 93 | private AtomicBoolean finished = new AtomicBoolean(false); 94 | 95 | public MockFluentd(int port, MockProcess mockProcess) { 96 | this.port = port; 97 | process = mockProcess; 98 | } 99 | 100 | /** 101 | * Return an available port in the system 102 | * @return port number 103 | * @throws IOException 104 | */ 105 | public static int randomPort() throws IOException { 106 | ServerSocket s = new ServerSocket(0); 107 | int port = s.getLocalPort(); 108 | s.close(); 109 | return port; 110 | } 111 | 112 | private ExecutorService service = Executors.newCachedThreadPool(); 113 | 114 | 115 | public void run() { 116 | try { 117 | serverSocket = new ServerSocket(port); 118 | } catch (IOException e) { 119 | e.printStackTrace(); 120 | throw new RuntimeException("Failed to start MockFluentd process", e); 121 | } 122 | 123 | _logger.debug("Started MockFluentd port:" + serverSocket.getLocalPort()); 124 | 125 | while (!finished.get()) { 126 | try { 127 | started.set(true); 128 | final Socket socket = serverSocket.accept(); 129 | socket.setSoLinger(true, 0); 130 | clientSockets.add(socket); 131 | service.submit(new Runnable() { 132 | public void run() { 133 | try { 134 | _logger.trace("received log"); 135 | MessagePack msgpack = new MessagePack(); 136 | msgpack.register(Event.class, MockEventTemplate.INSTANCE); 137 | process.process(msgpack, socket); 138 | _logger.trace("wrote log"); 139 | } catch (IOException e) { 140 | // ignore 141 | } 142 | } 143 | }); 144 | } catch (IOException e) { 145 | // ignore 146 | } 147 | } 148 | _logger.debug("Terminated MockFluentd port:" + serverSocket.getLocalPort()); 149 | } 150 | 151 | public void waitUntilReady() 152 | throws InterruptedException 153 | { 154 | int tick = 200; 155 | while (!started.get()) { 156 | TimeUnit.MILLISECONDS.sleep(tick); 157 | } 158 | // Just in case 159 | TimeUnit.MILLISECONDS.sleep(tick); 160 | } 161 | 162 | public void close() throws IOException { 163 | finished.set(true); 164 | service.shutdown(); 165 | try { 166 | // We need to wait until all log writing threads are finished. 167 | int numTrial = 0; 168 | final int maxTrial = 5; 169 | while(numTrial < 5 && !service.awaitTermination(1, TimeUnit.SECONDS)) { 170 | numTrial++; 171 | } 172 | if(numTrial >= maxTrial) 173 | _logger.error("Timed out"); 174 | } 175 | catch(InterruptedException e) { 176 | _logger.error("interrupted", e); 177 | } 178 | if (serverSocket != null) { 179 | serverSocket.close(); 180 | } 181 | 182 | } 183 | 184 | public void closeClientSockets() { 185 | Socket s = null; 186 | while ((s = clientSockets.poll()) != null) { 187 | try { 188 | s.close(); 189 | } catch (IOException e) { 190 | e.printStackTrace(); 191 | } 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | %d %highlight(%-5level) [%thread] %msg%n%ex{0} 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | --------------------------------------------------------------------------------