├── .github ├── dco.yml └── workflows │ └── test.yaml ├── .gitignore ├── Build.PL ├── Changes ├── LICENSE ├── META.json ├── README.md ├── cpanfile ├── lib └── Fluent │ ├── Logger.pm │ └── Logger │ └── UDP.pm ├── minil.toml ├── t ├── 00_compile.t ├── 01_new.t ├── 02_unix_socket.t ├── 03_tcp.t ├── 04_live.t ├── 05_prefer_integer.t ├── 06_fork.t ├── 07_str_bin.t ├── 07_str_utf8.t ├── 08_udp.t ├── 09_buffer_overflow_handler.t ├── 10_truncate_buffet_at_overflow.t ├── 11_retry_immediately.t └── Util.pm └── xt ├── 02_perlcritic.t ├── 04_pod-coverage.t ├── 05_dependencies.t ├── 06_benchmark.t ├── 07_dummy_benchmark.t ├── 08_benchmark_ack.t └── perlcriticrc /.github/dco.yml: -------------------------------------------------------------------------------- 1 | require: 2 | members: false 3 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: [push] 3 | jobs: 4 | test: 5 | runs-on: ubuntu-latest 6 | strategy: 7 | matrix: 8 | perl: 9 | - "5.16" 10 | - "5.24" 11 | - "5.30" 12 | - "5.38" 13 | - "5.40" 14 | name: Perl ${{ matrix.perl }} 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Setup perl 18 | uses: shogo82148/actions-setup-perl@v1 19 | with: 20 | perl-version: ${{ matrix.perl }} 21 | - name: Setup ruby 22 | uses: ruby/setup-ruby@v1 23 | with: 24 | ruby-version: 3.3 25 | - name: prepare 26 | run: | 27 | cpanm -nf Proc::Guard Number::Format 28 | gem install fluentd --no-document -v '~> 1.0' 29 | - name: test 30 | run: | 31 | cpanm --installdeps . 32 | perl Build.PL 33 | ./Build 34 | ./Build test 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | META.yml 2 | MYMETA* 3 | Makefile 4 | blib/ 5 | inc/ 6 | pm_to_blib 7 | *~ 8 | /Fluent-Logger-* 9 | /.build 10 | /_build_params 11 | /Build 12 | !Build/ 13 | !META.json 14 | !LICENSE 15 | -------------------------------------------------------------------------------- /Build.PL: -------------------------------------------------------------------------------- 1 | # ========================================================================= 2 | # THIS FILE IS AUTOMATICALLY GENERATED BY MINILLA. 3 | # DO NOT EDIT DIRECTLY. 4 | # ========================================================================= 5 | 6 | use 5.008_001; 7 | use strict; 8 | 9 | use Module::Build::Tiny 0.035; 10 | 11 | Build_PL(); 12 | 13 | -------------------------------------------------------------------------------- /Changes: -------------------------------------------------------------------------------- 1 | Revision history for Fluent::Logger 2 | 3 | {{$NEXT}} 4 | 5 | 0.29 2024-08-16T03:52:39Z 6 | * Set Data::MessagePack->utf8(1) by default (#33,#32 fujiwara, allanoepping) 7 | 8 | 0.28 2020-04-24T02:55:44Z 9 | * Switch to Path::Tiny from Path:Class (#30 jjatria) 10 | 11 | 0.27 2018-06-19T07:52:56Z 12 | * Add retry_immediately option (#28) 13 | 14 | 0.25 2017-06-02T01:41:24Z 15 | * Fix tests failure on Perl 5.26 (#27) 16 | 17 | 0.24 2017-05-22T02:10:23Z 18 | * Switch to UUID::Tiny from Data::UUID (#25 #26) 19 | 20 | 0.23 2017-05-16T04:12:25Z 21 | * Data::MessagePack::Stream(for ack option) is optional (#23 chorny #24) 22 | 23 | 0.22 2017-05-11T03:16:33Z 24 | * Add ack option (#22) 25 | 26 | 0.21 2017-03-23T01:33:19Z 27 | * Call buffer_overflow_handler at close() (#21 yoheimuta) 28 | 29 | 0.20 2017-03-01T04:36:41Z 30 | * Fix tests only which broken with Fluentd 0.14 (#20 #21) 31 | 32 | 0.19 2016-12-21T08:44:55Z 33 | * Added fluent-logger-ruby compatible buffer_overflow_handler (#17 tokuhirom) 34 | * Added truncate_buffer_at_overflow option (#18) 35 | 36 | 0.18 2016-06-02T05:52:34Z 37 | * Support Sub-second time (for Fluentd 0.14 or later) 38 | 39 | 0.17 2016-05-26T07:57:21Z 40 | * Fix error handling (Fluent::Logger::UDP) 41 | 42 | 0.16 2016-05-25T09:10:32Z 43 | * Add Fluent::Logger::UDP 44 | 45 | 0.15 2016-05-22T01:40:48Z 46 | * Switch to Class::Tiny (#13 miyagawa) 47 | 48 | 0.14 2016-04-19T13:57:18Z 49 | * Remove dependency for Proc::Guard (#11) 50 | 51 | 0.13 2015-02-02T02:50:31Z 52 | * Suppress warning (Use of uninitialized value) (shoichikaji) 53 | * Fix document (kiyoto) 54 | 55 | 0.12 2014-08-06T14:07:32Z 56 | * Handle perls built without locale support (Hugmeir) 57 | 58 | 0.11 2013-10-15 59 | * No code changes 60 | * Migrate to Minilla 61 | 62 | 0.10 2013-09-09 63 | * fix failed test on FreeBSD (bokutin) 64 | 65 | 0.09 2013-06-06 66 | * No code changes 67 | * fix failed tests with Test::TCP >= 1.27 (reported by hisaichi5518) 68 | 69 | 0.08 2012-12-20 70 | * fork safe (fujiwara) 71 | 72 | 0.07 2012-06-14 73 | * Tell fluentd flush log forcely on t/04_live.t (cho45) 74 | 75 | 0.06 2012-04-23 76 | * wait child process on t/02_unix_socket.t (tokuhirom) 77 | 78 | 0.05 2012-04-10 79 | * avoid checking RECONNECT_WAIT for testing reconnect (koji_magi) 80 | 81 | 0.04 2012-01-31 82 | * shouldn't need to set prefer_integer for every post (lestrrat) 83 | * update README.md 84 | 85 | 0.03 2012-01-30 86 | * added buffering / pending feature 87 | 88 | 0.02 2011-11-29 89 | * added prefer_integer flag 90 | 91 | 0.01 2011-11-28 92 | * First release 93 | 94 | 0.01_01 2011-11-18 95 | * Initial version 96 | 97 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This software is copyright (c) 2013 by FUJIWARA Shunichiro . 2 | 3 | This is free software; you can redistribute it and/or modify it under 4 | the same terms as the Perl 5 programming language system itself. 5 | 6 | Terms of the Perl programming language system itself 7 | 8 | a) the GNU General Public License as published by the Free 9 | Software Foundation; either version 1, or (at your option) any 10 | later version, or 11 | b) the "Artistic License" 12 | 13 | --- The GNU General Public License, Version 1, February 1989 --- 14 | 15 | This software is Copyright (c) 2013 by HIROSE Masaaki . 16 | 17 | This is free software, licensed under: 18 | 19 | The GNU General Public License, Version 1, February 1989 20 | 21 | GNU GENERAL PUBLIC LICENSE 22 | Version 1, February 1989 23 | 24 | Copyright (C) 1989 Free Software Foundation, Inc. 25 | 51 Franklin St, Suite 500, Boston, MA 02110-1335 USA 26 | 27 | Everyone is permitted to copy and distribute verbatim copies 28 | of this license document, but changing it is not allowed. 29 | 30 | Preamble 31 | 32 | The license agreements of most software companies try to keep users 33 | at the mercy of those companies. By contrast, our General Public 34 | License is intended to guarantee your freedom to share and change free 35 | software--to make sure the software is free for all its users. The 36 | General Public License applies to the Free Software Foundation's 37 | software and to any other program whose authors commit to using it. 38 | You can use it for your programs, too. 39 | 40 | When we speak of free software, we are referring to freedom, not 41 | price. Specifically, the General Public License is designed to make 42 | sure that you have the freedom to give away or sell copies of free 43 | software, that you receive source code or can get it if you want it, 44 | that you can change the software or use pieces of it in new free 45 | programs; and that you know you can do these things. 46 | 47 | To protect your rights, we need to make restrictions that forbid 48 | anyone to deny you these rights or to ask you to surrender the rights. 49 | These restrictions translate to certain responsibilities for you if you 50 | distribute copies of the software, or if you modify it. 51 | 52 | For example, if you distribute copies of a such a program, whether 53 | gratis or for a fee, you must give the recipients all the rights that 54 | you have. You must make sure that they, too, receive or can get the 55 | source code. And you must tell them their rights. 56 | 57 | We protect your rights with two steps: (1) copyright the software, and 58 | (2) offer you this license which gives you legal permission to copy, 59 | distribute and/or modify the software. 60 | 61 | Also, for each author's protection and ours, we want to make certain 62 | that everyone understands that there is no warranty for this free 63 | software. If the software is modified by someone else and passed on, we 64 | want its recipients to know that what they have is not the original, so 65 | that any problems introduced by others will not reflect on the original 66 | authors' reputations. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | GNU GENERAL PUBLIC LICENSE 72 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 73 | 74 | 0. This License Agreement applies to any program or other work which 75 | contains a notice placed by the copyright holder saying it may be 76 | distributed under the terms of this General Public License. The 77 | "Program", below, refers to any such program or work, and a "work based 78 | on the Program" means either the Program or any work containing the 79 | Program or a portion of it, either verbatim or with modifications. Each 80 | licensee is addressed as "you". 81 | 82 | 1. You may copy and distribute verbatim copies of the Program's source 83 | code as you receive it, in any medium, provided that you conspicuously and 84 | appropriately publish on each copy an appropriate copyright notice and 85 | disclaimer of warranty; keep intact all the notices that refer to this 86 | General Public License and to the absence of any warranty; and give any 87 | other recipients of the Program a copy of this General Public License 88 | along with the Program. You may charge a fee for the physical act of 89 | transferring a copy. 90 | 91 | 2. You may modify your copy or copies of the Program or any portion of 92 | it, and copy and distribute such modifications under the terms of Paragraph 93 | 1 above, provided that you also do the following: 94 | 95 | a) cause the modified files to carry prominent notices stating that 96 | you changed the files and the date of any change; and 97 | 98 | b) cause the whole of any work that you distribute or publish, that 99 | in whole or in part contains the Program or any part thereof, either 100 | with or without modifications, to be licensed at no charge to all 101 | third parties under the terms of this General Public License (except 102 | that you may choose to grant warranty protection to some or all 103 | third parties, at your option). 104 | 105 | c) If the modified program normally reads commands interactively when 106 | run, you must cause it, when started running for such interactive use 107 | in the simplest and most usual way, to print or display an 108 | announcement including an appropriate copyright notice and a notice 109 | that there is no warranty (or else, saying that you provide a 110 | warranty) and that users may redistribute the program under these 111 | conditions, and telling the user how to view a copy of this General 112 | Public License. 113 | 114 | d) You may charge a fee for the physical act of transferring a 115 | copy, and you may at your option offer warranty protection in 116 | exchange for a fee. 117 | 118 | Mere aggregation of another independent work with the Program (or its 119 | derivative) on a volume of a storage or distribution medium does not bring 120 | the other work under the scope of these terms. 121 | 122 | 3. You may copy and distribute the Program (or a portion or derivative of 123 | it, under Paragraph 2) in object code or executable form under the terms of 124 | Paragraphs 1 and 2 above provided that you also do one of the following: 125 | 126 | a) accompany it with the complete corresponding machine-readable 127 | source code, which must be distributed under the terms of 128 | Paragraphs 1 and 2 above; or, 129 | 130 | b) accompany it with a written offer, valid for at least three 131 | years, to give any third party free (except for a nominal charge 132 | for the cost of distribution) a complete machine-readable copy of the 133 | corresponding source code, to be distributed under the terms of 134 | Paragraphs 1 and 2 above; or, 135 | 136 | c) accompany it with the information you received as to where the 137 | corresponding source code may be obtained. (This alternative is 138 | allowed only for noncommercial distribution and only if you 139 | received the program in object code or executable form alone.) 140 | 141 | Source code for a work means the preferred form of the work for making 142 | modifications to it. For an executable file, complete source code means 143 | all the source code for all modules it contains; but, as a special 144 | exception, it need not include source code for modules which are standard 145 | libraries that accompany the operating system on which the executable 146 | file runs, or for standard header files or definitions files that 147 | accompany that operating system. 148 | 149 | 4. You may not copy, modify, sublicense, distribute or transfer the 150 | Program except as expressly provided under this General Public License. 151 | Any attempt otherwise to copy, modify, sublicense, distribute or transfer 152 | the Program is void, and will automatically terminate your rights to use 153 | the Program under this License. However, parties who have received 154 | copies, or rights to use copies, from you under this General Public 155 | License will not have their licenses terminated so long as such parties 156 | remain in full compliance. 157 | 158 | 5. By copying, distributing or modifying the Program (or any work based 159 | on the Program) you indicate your acceptance of this license to do so, 160 | and all its terms and conditions. 161 | 162 | 6. Each time you redistribute the Program (or any work based on the 163 | Program), the recipient automatically receives a license from the original 164 | licensor to copy, distribute or modify the Program subject to these 165 | terms and conditions. You may not impose any further restrictions on the 166 | recipients' exercise of the rights granted herein. 167 | 168 | 7. The Free Software Foundation may publish revised and/or new versions 169 | of the General Public License from time to time. Such new versions will 170 | be similar in spirit to the present version, but may differ in detail to 171 | address new problems or concerns. 172 | 173 | Each version is given a distinguishing version number. If the Program 174 | specifies a version number of the license which applies to it and "any 175 | later version", you have the option of following the terms and conditions 176 | either of that version or of any later version published by the Free 177 | Software Foundation. If the Program does not specify a version number of 178 | the license, you may choose any version ever published by the Free Software 179 | Foundation. 180 | 181 | 8. If you wish to incorporate parts of the Program into other free 182 | programs whose distribution conditions are different, write to the author 183 | to ask for permission. For software which is copyrighted by the Free 184 | Software Foundation, write to the Free Software Foundation; we sometimes 185 | make exceptions for this. Our decision will be guided by the two goals 186 | of preserving the free status of all derivatives of our free software and 187 | of promoting the sharing and reuse of software generally. 188 | 189 | NO WARRANTY 190 | 191 | 9. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 192 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 193 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 194 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 195 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 196 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 197 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 198 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 199 | REPAIR OR CORRECTION. 200 | 201 | 10. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 202 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 203 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 204 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 205 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 206 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 207 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 208 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 209 | POSSIBILITY OF SUCH DAMAGES. 210 | 211 | END OF TERMS AND CONDITIONS 212 | 213 | Appendix: How to Apply These Terms to Your New Programs 214 | 215 | If you develop a new program, and you want it to be of the greatest 216 | possible use to humanity, the best way to achieve this is to make it 217 | free software which everyone can redistribute and change under these 218 | terms. 219 | 220 | To do so, attach the following notices to the program. It is safest to 221 | attach them to the start of each source file to most effectively convey 222 | the exclusion of warranty; and each file should have at least the 223 | "copyright" line and a pointer to where the full notice is found. 224 | 225 | 226 | Copyright (C) 19yy 227 | 228 | This program is free software; you can redistribute it and/or modify 229 | it under the terms of the GNU General Public License as published by 230 | the Free Software Foundation; either version 1, or (at your option) 231 | any later version. 232 | 233 | This program is distributed in the hope that it will be useful, 234 | but WITHOUT ANY WARRANTY; without even the implied warranty of 235 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 236 | GNU General Public License for more details. 237 | 238 | You should have received a copy of the GNU General Public License 239 | along with this program; if not, write to the Free Software 240 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301 USA 241 | 242 | 243 | Also add information on how to contact you by electronic and paper mail. 244 | 245 | If the program is interactive, make it output a short notice like this 246 | when it starts in an interactive mode: 247 | 248 | Gnomovision version 69, Copyright (C) 19xx name of author 249 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 250 | This is free software, and you are welcome to redistribute it 251 | under certain conditions; type `show c' for details. 252 | 253 | The hypothetical commands `show w' and `show c' should show the 254 | appropriate parts of the General Public License. Of course, the 255 | commands you use may be called something other than `show w' and `show 256 | c'; they could even be mouse-clicks or menu items--whatever suits your 257 | program. 258 | 259 | You should also get your employer (if you work as a programmer) or your 260 | school, if any, to sign a "copyright disclaimer" for the program, if 261 | necessary. Here a sample; alter the names: 262 | 263 | Yoyodyne, Inc., hereby disclaims all copyright interest in the 264 | program `Gnomovision' (a program to direct compilers to make passes 265 | at assemblers) written by James Hacker. 266 | 267 | , 1 April 1989 268 | Ty Coon, President of Vice 269 | 270 | That's all there is to it! 271 | 272 | 273 | --- The Artistic License 1.0 --- 274 | 275 | This software is Copyright (c) 2013 by HIROSE Masaaki . 276 | 277 | This is free software, licensed under: 278 | 279 | The Artistic License 1.0 280 | 281 | The Artistic License 282 | 283 | Preamble 284 | 285 | The intent of this document is to state the conditions under which a Package 286 | may be copied, such that the Copyright Holder maintains some semblance of 287 | artistic control over the development of the package, while giving the users of 288 | the package the right to use and distribute the Package in a more-or-less 289 | customary fashion, plus the right to make reasonable modifications. 290 | 291 | Definitions: 292 | 293 | - "Package" refers to the collection of files distributed by the Copyright 294 | Holder, and derivatives of that collection of files created through 295 | textual modification. 296 | - "Standard Version" refers to such a Package if it has not been modified, 297 | or has been modified in accordance with the wishes of the Copyright 298 | Holder. 299 | - "Copyright Holder" is whoever is named in the copyright or copyrights for 300 | the package. 301 | - "You" is you, if you're thinking about copying or distributing this Package. 302 | - "Reasonable copying fee" is whatever you can justify on the basis of media 303 | cost, duplication charges, time of people involved, and so on. (You will 304 | not be required to justify it to the Copyright Holder, but only to the 305 | computing community at large as a market that must bear the fee.) 306 | - "Freely Available" means that no fee is charged for the item itself, though 307 | there may be fees involved in handling the item. It also means that 308 | recipients of the item may redistribute it under the same conditions they 309 | received it. 310 | 311 | 1. You may make and give away verbatim copies of the source form of the 312 | Standard Version of this Package without restriction, provided that you 313 | duplicate all of the original copyright notices and associated disclaimers. 314 | 315 | 2. You may apply bug fixes, portability fixes and other modifications derived 316 | from the Public Domain or from the Copyright Holder. A Package modified in such 317 | a way shall still be considered the Standard Version. 318 | 319 | 3. You may otherwise modify your copy of this Package in any way, provided that 320 | you insert a prominent notice in each changed file stating how and when you 321 | changed that file, and provided that you do at least ONE of the following: 322 | 323 | a) place your modifications in the Public Domain or otherwise make them 324 | Freely Available, such as by posting said modifications to Usenet or an 325 | equivalent medium, or placing the modifications on a major archive site 326 | such as ftp.uu.net, or by allowing the Copyright Holder to include your 327 | modifications in the Standard Version of the Package. 328 | 329 | b) use the modified Package only within your corporation or organization. 330 | 331 | c) rename any non-standard executables so the names do not conflict with 332 | standard executables, which must also be provided, and provide a separate 333 | manual page for each non-standard executable that clearly documents how it 334 | differs from the Standard Version. 335 | 336 | d) make other distribution arrangements with the Copyright Holder. 337 | 338 | 4. You may distribute the programs of this Package in object code or executable 339 | form, provided that you do at least ONE of the following: 340 | 341 | a) distribute a Standard Version of the executables and library files, 342 | together with instructions (in the manual page or equivalent) on where to 343 | get the Standard Version. 344 | 345 | b) accompany the distribution with the machine-readable source of the Package 346 | with your modifications. 347 | 348 | c) accompany any non-standard executables with their corresponding Standard 349 | Version executables, giving the non-standard executables non-standard 350 | names, and clearly documenting the differences in manual pages (or 351 | equivalent), together with instructions on where to get the Standard 352 | Version. 353 | 354 | d) make other distribution arrangements with the Copyright Holder. 355 | 356 | 5. You may charge a reasonable copying fee for any distribution of this 357 | Package. You may charge any fee you choose for support of this Package. You 358 | may not charge a fee for this Package itself. However, you may distribute this 359 | Package in aggregate with other (possibly commercial) programs as part of a 360 | larger (possibly commercial) software distribution provided that you do not 361 | advertise this Package as a product of your own. 362 | 363 | 6. The scripts and library files supplied as input to or produced as output 364 | from the programs of this Package do not automatically fall under the copyright 365 | of this Package, but belong to whomever generated them, and may be sold 366 | commercially, and may be aggregated with this Package. 367 | 368 | 7. C or perl subroutines supplied by you and linked into this Package shall not 369 | be considered part of this Package. 370 | 371 | 8. The name of the Copyright Holder may not be used to endorse or promote 372 | products derived from this software without specific prior written permission. 373 | 374 | 9. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED 375 | WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF 376 | MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. 377 | 378 | The End 379 | -------------------------------------------------------------------------------- /META.json: -------------------------------------------------------------------------------- 1 | { 2 | "abstract" : "A structured event logger for Fluent", 3 | "author" : [ 4 | "HIROSE Masaaki " 5 | ], 6 | "dynamic_config" : 0, 7 | "generated_by" : "Minilla/v3.1.18, CPAN::Meta::Converter version 2.150010", 8 | "license" : [ 9 | "perl_5" 10 | ], 11 | "meta-spec" : { 12 | "url" : "http://search.cpan.org/perldoc?CPAN::Meta::Spec", 13 | "version" : 2 14 | }, 15 | "name" : "Fluent-Logger", 16 | "no_index" : { 17 | "directory" : [ 18 | "t", 19 | "xt", 20 | "inc", 21 | "share", 22 | "eg", 23 | "examples", 24 | "author", 25 | "builder" 26 | ] 27 | }, 28 | "prereqs" : { 29 | "build" : { 30 | "requires" : { 31 | "Capture::Tiny" : "0", 32 | "ExtUtils::MakeMaker" : "6.36", 33 | "Path::Tiny" : "0.028", 34 | "Test::More" : "0.88", 35 | "Test::SharedFork" : "0", 36 | "Test::TCP" : "1.3", 37 | "version" : "0.77" 38 | } 39 | }, 40 | "configure" : { 41 | "requires" : { 42 | "Module::Build::Tiny" : "0.035" 43 | } 44 | }, 45 | "develop" : { 46 | "requires" : { 47 | "Number::Format" : "0", 48 | "Test::CPAN::Meta" : "0", 49 | "Test::MinimumVersion::Fast" : "0.04", 50 | "Test::PAUSE::Permissions" : "0.07", 51 | "Test::Pod" : "1.41", 52 | "Test::Spellunker" : "v0.2.7" 53 | } 54 | }, 55 | "runtime" : { 56 | "requires" : { 57 | "Class::Tiny" : "0", 58 | "Data::MessagePack" : "0.35_01", 59 | "Data::MessagePack::Stream" : "0", 60 | "Time::Piece" : "0", 61 | "UUID::Tiny" : "0" 62 | } 63 | } 64 | }, 65 | "release_status" : "unstable", 66 | "resources" : { 67 | "bugtracker" : { 68 | "web" : "https://github.com/fluent/fluent-logger-perl/issues" 69 | }, 70 | "homepage" : "https://github.com/fluent/fluent-logger-perl", 71 | "repository" : { 72 | "type" : "git", 73 | "url" : "https://github.com/fluent/fluent-logger-perl.git", 74 | "web" : "https://github.com/fluent/fluent-logger-perl" 75 | } 76 | }, 77 | "version" : "0.29", 78 | "x_contributors" : [ 79 | "Alexandr Ciornii ", 80 | "Brian Fraser ", 81 | "DQNEO ", 82 | "FUJIWARA Shunichiro <67804+fujiwara@users.noreply.github.com>", 83 | "HIROSE Masaaki ", 84 | "José Joaquín Atria ", 85 | "Kiyoto Tamura ", 86 | "Masahiro Nakagawa ", 87 | "Shinichiro Sei ", 88 | "Shoichi Kaji ", 89 | "Tatsuhiko Miyagawa ", 90 | "Tokuhiro Matsuno ", 91 | "Tomohiro Hosaka ", 92 | "cho45 ", 93 | "fujiwara ", 94 | "koji_magi ", 95 | "lestrrat ", 96 | "yoheimuta " 97 | ], 98 | "x_serialization_backend" : "JSON::PP version 4.08", 99 | "x_static_install" : 1 100 | } 101 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Actions Status](https://github.com/fluent/fluent-logger-perl/workflows/test/badge.svg)](https://github.com/fluent/fluent-logger-perl/actions) 2 | # NAME 3 | 4 | Fluent::Logger - A structured event logger for Fluent 5 | 6 | # SYNOPSIS 7 | 8 | use Fluent::Logger; 9 | 10 | my $logger = Fluent::Logger->new( 11 | host => '127.0.0.1', 12 | port => 24224, 13 | ); 14 | $logger->post("myapp.access", { "agent" => "foo" }); 15 | # output: myapp.access {"agent":"foo"} 16 | 17 | my $logger = Fluent::Logger->new( 18 | tag_prefix => 'myapp', 19 | host => '127.0.0.1', 20 | port => 24224, 21 | ); 22 | $logger->post("access", { "agent" => "foo" }); 23 | # output: myapp.access {"agent":"foo"} 24 | 25 | # DESCRIPTION 26 | 27 | Fluent::Logger is a structured event logger for Fluentd. 28 | 29 | # METHODS 30 | 31 | - **new**(%args) 32 | 33 | create new logger instance. 34 | 35 | %args: 36 | 37 | tag_prefix => 'Str': optional 38 | host => 'Str': default is '127.0.0.1' 39 | port => 'Int': default is 24224 40 | timeout => 'Num': default is 3.0 41 | socket => 'Str': default undef (e.g. "/var/run/fluent/fluent.sock") 42 | prefer_integer => 'Bool': default 1 (set to Data::MessagePack->prefer_integer) 43 | utf8 => 'Bool': default 1 (set to Data::MessagePack->utf8) 44 | event_time => 'Bool': default 0 (timestamp includes nanoseconds, supported by fluentd >= 0.14.0) 45 | buffer_limit => 'Int': defualt 8388608 (8MB) 46 | buffer_overflow_handler => 'Code': optional 47 | truncate_buffer_at_overflow => 'Bool': default 0 48 | ack => 'Bool': default 0 (not works on MSWin32) 49 | retry_immediately => 'Int': default 0 (retries immediately N times on error occured) 50 | 51 | - buffer\_overflow\_handler 52 | 53 | You can inject your own custom coderef to handle buffer overflow in the event of connection failure. 54 | This will mitigate the loss of data instead of simply throwing data away. 55 | 56 | Your proc should accept a single argument, which will be the internal buffer of messages from the logger. 57 | This coderef is also called when logger.close() failed to flush the remaining internal buffer of messages. 58 | A typical use-case for this would be writing to disk or possibly writing to Redis. 59 | 60 | - truncate\_buffer\_at\_overflow 61 | 62 | When truncate\_buffer\_at\_overflow is true and pending buffer size is larger than buffer\_limit, post() returns undef. 63 | 64 | Pending buffer still be kept, but last message passed to post() is not sent and not appended to buffer. You may handle the message by other method. 65 | 66 | - ack 67 | 68 | post() waits ack response from server for each messages. 69 | 70 | An exception will raise if ack is miss match or timed out. 71 | 72 | This option does not work on MSWin32 platform currently, because Data::MessagePack::Stream does not work. 73 | 74 | - retry\_immediately 75 | 76 | By default, Fluent::Logger will retry to send the buffer at next post() called when an error occured in post(). 77 | 78 | If retry\_immediately(N) is set, retries immediately max N times. 79 | 80 | - **post**($tag:Str, $msg:HashRef) 81 | 82 | Send message to fluent server with tag. 83 | 84 | Return bytes length of written messages. 85 | 86 | If event\_time is set to true, log's timestamp includes nanoseconds. 87 | 88 | - **post\_with\_time**($tag:Str, $msg:HashRef, $time:Int|Float) 89 | 90 | Send message to fluent server with tag and time. 91 | 92 | If event\_time is set to true, $time argument accepts Float value (such as Time::HiRes::time()). 93 | 94 | - **close**() 95 | 96 | close connection. 97 | 98 | If the logger has pending data, flushing it to server on close. 99 | 100 | - **errstr** 101 | 102 | return error message. 103 | 104 | $logger->post( info => { "msg": "test" } ) 105 | or die $logger->errstr; 106 | 107 | # AUTHOR 108 | 109 | HIROSE Masaaki <hirose31 \_at\_ gmail.com> 110 | 111 | Shinichiro Sei <sei \_at\_ kayac.com> 112 | 113 | FUJIWARA Shunichiro <fujiwara \_at\_ cpan.org> 114 | 115 | # THANKS TO 116 | 117 | Kazuki Ohta 118 | 119 | FURUHASHI Sadayuki 120 | 121 | lestrrat 122 | 123 | # REPOSITORY 124 | 125 | [https://github.com/fluent/fluent-logger-perl](https://github.com/fluent/fluent-logger-perl) 126 | 127 | git clone git://github.com/fluent/fluent-logger-perl.git 128 | 129 | patches and collaborators are welcome. 130 | 131 | # SEE ALSO 132 | 133 | [http://fluent.github.com/](http://fluent.github.com/) 134 | 135 | # COPYRIGHT & LICENSE 136 | 137 | Copyright FUJIWARA Shunichiro 138 | 139 | This library is free software; you can redistribute it and/or modify 140 | it under the same terms as Perl itself. 141 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires 'Data::MessagePack', '0.35_01'; 2 | requires 'Class::Tiny'; 3 | requires 'Time::Piece'; 4 | requires 'UUID::Tiny'; 5 | 6 | if ($^O ne 'MSWin32') { 7 | requires 'Data::MessagePack::Stream'; 8 | } 9 | 10 | on build => sub { 11 | requires 'Capture::Tiny'; 12 | requires 'ExtUtils::MakeMaker', '6.36'; 13 | requires 'Path::Tiny', '0.028'; 14 | requires 'Test::More', '0.88'; 15 | requires 'Test::SharedFork'; 16 | requires 'Test::TCP', '1.3'; 17 | requires 'version', '0.77'; 18 | }; 19 | 20 | on develop => sub { 21 | requires 'Number::Format'; 22 | }; 23 | -------------------------------------------------------------------------------- /lib/Fluent/Logger.pm: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8; -*- 2 | package Fluent::Logger; 3 | 4 | use strict; 5 | use warnings; 6 | 7 | our $VERSION = '0.29'; 8 | 9 | use IO::Select; 10 | use IO::Socket::INET; 11 | use IO::Socket::UNIX; 12 | use Data::MessagePack; 13 | use Time::Piece; 14 | use Carp; 15 | use Scalar::Util qw/ refaddr /; 16 | use Time::HiRes qw/ time /; 17 | use UUID::Tiny qw/ create_uuid UUID_V4 /; 18 | use MIME::Base64 qw/ encode_base64 /; 19 | 20 | use constant RECONNECT_WAIT => 0.5; 21 | use constant RECONNECT_WAIT_INCR_RATE => 1.5; 22 | use constant RECONNECT_WAIT_MAX => 60; 23 | use constant RECONNECT_WAIT_MAX_COUNT => 12; 24 | 25 | use constant MP_HEADER_3ELM_ARRAY => "\x93"; 26 | use constant MP_HEADER_4ELM_ARRAY => "\x94"; 27 | use constant MP_HEADER_EVENT_TIME => "\xd7\x00"; 28 | 29 | use subs 'prefer_integer'; 30 | 31 | use Class::Tiny +{ 32 | tag_prefix => sub {}, 33 | host => sub { "127.0.0.1" }, 34 | port => sub { 24224 }, 35 | socket => sub {}, 36 | timeout => sub { 3.0 }, 37 | buffer_limit => sub { 8 * 1024 * 1024 }, # fixme 38 | buffer_overflow_handler => sub { undef }, 39 | truncate_buffer_at_overflow => sub { 0 }, 40 | max_write_retry => sub { 5 }, 41 | write_length => sub { 8 * 1024 * 1024 }, 42 | socket_io => sub {}, 43 | errors => sub { [] }, 44 | prefer_integer => sub { 1 }, 45 | utf8 => sub { 1 }, 46 | packer => sub { 47 | my $self = shift; 48 | my $mp = Data::MessagePack->new; 49 | $mp->utf8( $self->utf8 ); 50 | $mp->prefer_integer( $self->prefer_integer ); 51 | $mp; 52 | }, 53 | pending => sub { "" }, 54 | connect_error_history => sub { +[] }, 55 | owner_pid => sub {}, 56 | event_time => sub { 0 }, 57 | ack => sub { 0 }, 58 | pending_acks => sub { +[] }, 59 | unpacker => sub { 60 | require Data::MessagePack::Stream; 61 | Data::MessagePack::Stream->new; 62 | }, 63 | selector => sub { }, 64 | retry_immediately => sub { 0 }, 65 | }; 66 | 67 | sub BUILD { 68 | my $self = shift; 69 | $self->_connect; 70 | } 71 | 72 | sub prefer_integer { 73 | my $self = shift; 74 | 75 | if (@_) { 76 | $self->{prefer_integer} = shift; 77 | $self->packer->prefer_integer( $self->prefer_integer ); 78 | } elsif ( exists $self->{prefer_integer} ) { 79 | return $self->{prefer_integer}; 80 | } else { 81 | my $defaults = Class::Tiny->get_all_attribute_defaults_for( ref $self ); 82 | return $self->{prefer_integer} = $defaults->{prefer_integer}->(); 83 | } 84 | } 85 | 86 | sub _carp { 87 | my $self = shift; 88 | my $msg = shift; 89 | chomp $msg; 90 | carp( 91 | sprintf "%s %s[%s](%s): %s", 92 | localtime->strftime("%Y-%m-%dT%H:%M:%S%z"), 93 | ref $self, 94 | refaddr $self, 95 | $self->_connect_info, 96 | $msg, 97 | ); 98 | } 99 | 100 | sub _add_error { 101 | my $self = shift; 102 | my $msg = shift; 103 | $self->_carp($msg); 104 | push @{ $self->errors }, $msg; 105 | } 106 | 107 | sub errstr { 108 | my $self = shift; 109 | return join ("\n", @{ $self->errors }); 110 | } 111 | 112 | sub _connect_info { 113 | my $self = shift; 114 | $self->socket || sprintf "%s:%d", $self->host, $self->port; 115 | } 116 | 117 | sub _connect { 118 | my $self = shift; 119 | my $force = shift; 120 | 121 | return if $self->socket_io && !$force; 122 | 123 | my $sock = defined $self->socket 124 | ? IO::Socket::UNIX->new( Peer => $self->socket ) 125 | : IO::Socket::INET->new( 126 | PeerAddr => $self->host, 127 | PeerPort => $self->port, 128 | Proto => 'tcp', 129 | Timeout => $self->timeout, 130 | ReuseAddr => 1, 131 | ); 132 | if (!$sock) { 133 | $self->_add_error("Can't connect: $!"); 134 | push @{ $self->connect_error_history }, time; 135 | if (@{ $self->connect_error_history } > RECONNECT_WAIT_MAX_COUNT) { 136 | shift @{ $self->connect_error_history }; 137 | } 138 | return; 139 | } 140 | $self->connect_error_history([]); 141 | $self->owner_pid($$); 142 | $self->selector(IO::Select->new($sock)); 143 | $self->socket_io($sock); 144 | } 145 | 146 | sub close { 147 | my $self = shift; 148 | 149 | if ( length $self->{pending} ) { 150 | $self->_carp("flushing pending data on close"); 151 | $self->_connect unless $self->socket_io; 152 | my $written = eval { 153 | $self->_write( $self->{pending} ); 154 | }; 155 | if ($@ || !$written) { 156 | my $size = length $self->{pending}; 157 | $self->_carp("Can't send pending data. LOST $size bytes.: $@"); 158 | $self->_call_buffer_overflow_handler(); 159 | } else { 160 | $self->_carp("pending data was flushed successfully"); 161 | } 162 | }; 163 | $self->{pending} = ""; 164 | $self->{pending_acks} = []; 165 | delete $self->{selector}; 166 | my $socket = delete $self->{socket_io}; 167 | $socket->close if $socket; 168 | } 169 | 170 | sub post { 171 | my($self, $tag, $msg) = @_; 172 | 173 | $self->_post( $tag || "", $msg, time() ); 174 | } 175 | 176 | sub post_with_time { 177 | my ($self, $tag, $msg, $time) = @_; 178 | 179 | $self->_post( $tag || "", $msg, $time ); 180 | } 181 | 182 | sub _pack_time { 183 | my ($self, $time) = @_; 184 | 185 | if ($self->event_time) { 186 | my $time_i = int $time; 187 | my $nanosec = int(($time - $time_i) * 10 ** 9); 188 | return MP_HEADER_EVENT_TIME . pack("NN", $time_i, $nanosec); 189 | } else { 190 | return $self->packer->pack(int $time); 191 | } 192 | } 193 | 194 | sub _post { 195 | my ($self, $tag, $msg, $time) = @_; 196 | 197 | if (ref $msg ne "HASH") { 198 | $self->_add_error("message '$msg' must be a HashRef"); 199 | return; 200 | } 201 | 202 | $tag = join('.', $self->tag_prefix, $tag) if $self->tag_prefix; 203 | my $p = $self->packer; 204 | $self->_send( 205 | $p->pack($tag), 206 | $self->_pack_time($time), 207 | $p->pack($msg), 208 | ); 209 | } 210 | 211 | sub _send { 212 | my ($self, @args) = @_; 213 | 214 | my ($data, $unique_key); 215 | if ( $self->ack ) { 216 | $unique_key = encode_base64(create_uuid(UUID_V4)); 217 | $data = join('', MP_HEADER_4ELM_ARRAY, @args, $self->{packer}->pack({ chunk => $unique_key })); 218 | push @{$self->{pending_acks}}, $unique_key; 219 | } else { 220 | $data = join('', MP_HEADER_3ELM_ARRAY, @args); 221 | } 222 | 223 | my $prev_size = length($self->{pending}); 224 | my $current_size = length($data); 225 | $self->{pending} .= $data; 226 | 227 | my $errors = @{ $self->connect_error_history }; 228 | if ( $errors && length $self->pending <= $self->buffer_limit ) 229 | { 230 | my $suppress_sec; 231 | if ( $errors < RECONNECT_WAIT_MAX_COUNT ) { 232 | $suppress_sec = RECONNECT_WAIT * (RECONNECT_WAIT_INCR_RATE ** ($errors - 1)); 233 | } else { 234 | $suppress_sec = RECONNECT_WAIT_MAX; 235 | } 236 | if ( time - $self->connect_error_history->[-1] < $suppress_sec ) { 237 | return; 238 | } 239 | } 240 | 241 | my ($written, $error); 242 | for ( 0 .. $self->retry_immediately ) { 243 | # check owner pid for fork safe 244 | if (!$self->socket_io || $self->owner_pid != $$) { 245 | $self->_connect(1); 246 | } 247 | eval { 248 | $written = $self->_write( $self->{pending} ); 249 | my $acked = $self->ack 250 | ? $self->_wait_ack(@{ $self->{pending_acks} }) 251 | : 1; 252 | if ($written && $acked) { 253 | $self->{pending} = ""; 254 | $self->{pending_acks} = []; 255 | } 256 | }; 257 | if (!$@) { 258 | return $written; 259 | } 260 | my $e = $@; 261 | $error = "Cannot send data: $e"; 262 | my $sock = delete $self->{socket_io}; 263 | $sock->close if $sock; 264 | delete $self->{selector}; 265 | 266 | if ( length($self->{pending}) > $self->buffer_limit ) { 267 | if ( defined $self->buffer_overflow_handler ) { 268 | $self->_call_buffer_overflow_handler(); 269 | $self->{pending} = ""; 270 | $self->{pending_acks} = [] if $self->ack; 271 | } elsif ( $self->truncate_buffer_at_overflow ) { 272 | substr($self->{pending}, $prev_size, $current_size, ""); 273 | pop @{$self->{pending_acks}} if $self->ack; 274 | } 275 | } 276 | } 277 | 278 | $self->_add_error($error) if defined $error; 279 | return $written; 280 | } 281 | 282 | sub _wait_ack { 283 | my $self = shift; 284 | my @acks = @_; 285 | 286 | my $up = $self->unpacker; 287 | local $SIG{"PIPE"} = sub { die $! }; 288 | READ: 289 | while (1) { 290 | my ($s) = $self->selector->can_read($self->timeout); 291 | if (!$s) { 292 | die "ack read timed out"; 293 | } 294 | $s->sysread(my $buf, 1024); 295 | return if @acks > 0 && length($buf) == 0; 296 | $up->feed($buf); 297 | while ($up->next) { 298 | my $ack = $up->data; 299 | my $unique_key = shift @acks; 300 | if ($unique_key && ref $ack eq "HASH") { 301 | if ($ack->{ack} ne $unique_key) { 302 | die "ack is not expected: " . $ack->{ack}; 303 | } 304 | } else { 305 | unshift @{ $self->{pending_acks} }, $unique_key; 306 | die "Can't send data. ack is not expected. $@"; 307 | } 308 | last READ if @acks == 0; 309 | } 310 | } 311 | return 1; 312 | } 313 | 314 | sub _call_buffer_overflow_handler { 315 | my $self = shift; 316 | if (my $handler = $self->buffer_overflow_handler) { 317 | eval { 318 | $handler->($self->{pending}); 319 | }; 320 | if (my $error = $@) { 321 | $self->_add_error("Can't call buffer overflow handler: $error"); 322 | } 323 | } 324 | } 325 | 326 | sub _write { 327 | my $self = shift; 328 | my $data = shift; 329 | my $length = length($data); 330 | my $retry = my $written = 0; 331 | die "Connection is not available" unless $self->socket_io; 332 | 333 | local $SIG{"PIPE"} = sub { die $! }; 334 | 335 | while ($written < $length) { 336 | my ($s) = $self->selector->can_write($self->timeout); 337 | die "send write timed out" unless $s; 338 | my $nwrite 339 | = $s->syswrite($data, $self->write_length, $written); 340 | 341 | if (!$nwrite) { 342 | if ($retry > $self->max_write_retry) { 343 | die "failed write retry; max write retry count. $!"; 344 | } 345 | $retry++; 346 | } else { 347 | $written += $nwrite; 348 | } 349 | } 350 | $written; 351 | } 352 | 353 | sub DEMOLISH { 354 | my $self = shift; 355 | $self->close; 356 | } 357 | 358 | 359 | 1; 360 | __END__ 361 | 362 | =encoding utf-8 363 | 364 | =head1 NAME 365 | 366 | Fluent::Logger - A structured event logger for Fluent 367 | 368 | =head1 SYNOPSIS 369 | 370 | use Fluent::Logger; 371 | 372 | my $logger = Fluent::Logger->new( 373 | host => '127.0.0.1', 374 | port => 24224, 375 | ); 376 | $logger->post("myapp.access", { "agent" => "foo" }); 377 | # output: myapp.access {"agent":"foo"} 378 | 379 | my $logger = Fluent::Logger->new( 380 | tag_prefix => 'myapp', 381 | host => '127.0.0.1', 382 | port => 24224, 383 | ); 384 | $logger->post("access", { "agent" => "foo" }); 385 | # output: myapp.access {"agent":"foo"} 386 | 387 | =head1 DESCRIPTION 388 | 389 | Fluent::Logger is a structured event logger for Fluentd. 390 | 391 | =head1 METHODS 392 | 393 | =over 4 394 | 395 | =item B(%args) 396 | 397 | create new logger instance. 398 | 399 | %args: 400 | 401 | tag_prefix => 'Str': optional 402 | host => 'Str': default is '127.0.0.1' 403 | port => 'Int': default is 24224 404 | timeout => 'Num': default is 3.0 405 | socket => 'Str': default undef (e.g. "/var/run/fluent/fluent.sock") 406 | prefer_integer => 'Bool': default 1 (set to Data::MessagePack->prefer_integer) 407 | utf8 => 'Bool': default 1 (set to Data::MessagePack->utf8) 408 | event_time => 'Bool': default 0 (timestamp includes nanoseconds, supported by fluentd >= 0.14.0) 409 | buffer_limit => 'Int': defualt 8388608 (8MB) 410 | buffer_overflow_handler => 'Code': optional 411 | truncate_buffer_at_overflow => 'Bool': default 0 412 | ack => 'Bool': default 0 (not works on MSWin32) 413 | retry_immediately => 'Int': default 0 (retries immediately N times on error occured) 414 | 415 | =over 4 416 | 417 | =item buffer_overflow_handler 418 | 419 | You can inject your own custom coderef to handle buffer overflow in the event of connection failure. 420 | This will mitigate the loss of data instead of simply throwing data away. 421 | 422 | Your proc should accept a single argument, which will be the internal buffer of messages from the logger. 423 | This coderef is also called when logger.close() failed to flush the remaining internal buffer of messages. 424 | A typical use-case for this would be writing to disk or possibly writing to Redis. 425 | 426 | =item truncate_buffer_at_overflow 427 | 428 | When truncate_buffer_at_overflow is true and pending buffer size is larger than buffer_limit, post() returns undef. 429 | 430 | Pending buffer still be kept, but last message passed to post() is not sent and not appended to buffer. You may handle the message by other method. 431 | 432 | =item ack 433 | 434 | post() waits ack response from server for each messages. 435 | 436 | An exception will raise if ack is miss match or timed out. 437 | 438 | This option does not work on MSWin32 platform currently, because Data::MessagePack::Stream does not work. 439 | 440 | =item retry_immediately 441 | 442 | By default, Fluent::Logger will retry to send the buffer at next post() called when an error occured in post(). 443 | 444 | If retry_immediately(N) is set, retries immediately max N times. 445 | 446 | =back 447 | 448 | =item B($tag:Str, $msg:HashRef) 449 | 450 | Send message to fluent server with tag. 451 | 452 | Return bytes length of written messages. 453 | 454 | If event_time is set to true, log's timestamp includes nanoseconds. 455 | 456 | =item B($tag:Str, $msg:HashRef, $time:Int|Float) 457 | 458 | Send message to fluent server with tag and time. 459 | 460 | If event_time is set to true, $time argument accepts Float value (such as Time::HiRes::time()). 461 | 462 | =item B() 463 | 464 | close connection. 465 | 466 | If the logger has pending data, flushing it to server on close. 467 | 468 | =item B 469 | 470 | return error message. 471 | 472 | $logger->post( info => { "msg": "test" } ) 473 | or die $logger->errstr; 474 | 475 | =back 476 | 477 | =head1 AUTHOR 478 | 479 | HIROSE Masaaki Ehirose31 _at_ gmail.comE 480 | 481 | Shinichiro Sei Esei _at_ kayac.comE 482 | 483 | FUJIWARA Shunichiro Efujiwara _at_ cpan.orgE 484 | 485 | =head1 THANKS TO 486 | 487 | Kazuki Ohta 488 | 489 | FURUHASHI Sadayuki 490 | 491 | lestrrat 492 | 493 | =head1 REPOSITORY 494 | 495 | L 496 | 497 | git clone git://github.com/fluent/fluent-logger-perl.git 498 | 499 | patches and collaborators are welcome. 500 | 501 | =head1 SEE ALSO 502 | 503 | L 504 | 505 | =head1 COPYRIGHT & LICENSE 506 | 507 | Copyright FUJIWARA Shunichiro 508 | 509 | This library is free software; you can redistribute it and/or modify 510 | it under the same terms as Perl itself. 511 | 512 | =cut 513 | -------------------------------------------------------------------------------- /lib/Fluent/Logger/UDP.pm: -------------------------------------------------------------------------------- 1 | package Fluent::Logger::UDP; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use IO::Socket::INET; 7 | use Time::Piece; 8 | use Carp; 9 | use Scalar::Util qw/ refaddr /; 10 | use Time::HiRes qw/ time /; 11 | 12 | use Class::Tiny +{ 13 | host => sub { "127.0.0.1" }, 14 | port => sub { 5160 }, 15 | socket_io => sub {}, 16 | owner_pid => sub {}, 17 | }; 18 | 19 | sub BUILD { 20 | my $self = shift; 21 | $self->_connect; 22 | } 23 | 24 | sub _connect { 25 | my $self = shift; 26 | my $force = shift; 27 | 28 | return if $self->{socket_io} && !$force; 29 | 30 | my $sock = IO::Socket::INET->new( 31 | PeerAddr => $self->host, 32 | PeerPort => $self->port, 33 | Proto => "udp", 34 | ); 35 | if (!$sock) { 36 | $self->_carp("Can't create socket: $!"); 37 | return; 38 | } 39 | $self->{owner_pid} = $$; 40 | $self->{socket_io} = $sock; 41 | } 42 | 43 | sub _carp { 44 | my $self = shift; 45 | my $msg = shift; 46 | chomp $msg; 47 | carp ( 48 | sprintf "%s %s[%s](%s): %s", 49 | localtime->strftime("%Y-%m-%dT%H:%M:%S%z"), 50 | ref $self, 51 | refaddr $self, 52 | $self->_connect_info, 53 | $msg, 54 | ); 55 | } 56 | 57 | sub _connect_info { 58 | my $self = shift; 59 | $self->{socket} || sprintf "%s:%d", $self->host, $self->port; 60 | } 61 | 62 | sub post { 63 | my ($self, $msg) = @_; 64 | if (ref $msg) { 65 | $self->_carp("message '$msg' must be a Scalar"); 66 | return; 67 | } 68 | 69 | # fork safe 70 | if (!$self->{socket_io} || $self->{owner_pid} != $$) { 71 | $self->_connect(1); 72 | } 73 | my $written = 0; 74 | eval { 75 | local $SIG{"PIPE"} = sub { die $! }; 76 | my $length = length($msg); 77 | while ($written < $length) { 78 | my $nwrite 79 | = $self->socket_io->syswrite($msg, $length, $written); 80 | if (!$nwrite) { 81 | die "failed to write. $!"; 82 | } else { 83 | $written += $nwrite; 84 | } 85 | } 86 | }; 87 | if ($@ || !$written) { 88 | my $error = $@; 89 | $self->_carp("Cannot send data: $error"); 90 | delete $self->{socket_io}; 91 | } 92 | $written; 93 | } 94 | 95 | 1; 96 | 97 | __END__ 98 | 99 | =encoding utf-8 100 | 101 | =head1 NAME 102 | 103 | Fluent::Logger::UDP - A event logger for Fluentd in_udp 104 | 105 | =head1 SYNOPSIS 106 | 107 | use Fluent::Logger::UDP; 108 | 109 | my $logger = Fluent::Logger::UDP->new( 110 | host => '127.0.0.1', 111 | port => 5160, 112 | ); 113 | $logger->post('{"foo":"bar"}'); # must be a scalar 114 | 115 | =head1 DESCRIPTION 116 | 117 | Fluent::Logger::UDP is a event logger for Fluentd in_udp. 118 | 119 | =head1 METHODS 120 | 121 | =over 4 122 | 123 | =item B(%args) 124 | 125 | create a new logger instance. 126 | 127 | %args: 128 | 129 | host => 'Str': default is '127.0.0.1' 130 | port => 'Int': default is 5160 131 | 132 | =item B($msg:Str) 133 | 134 | Send a message to in_udp. 135 | 136 | Return bytes length of written messages. 137 | 138 | =back 139 | 140 | =head1 AUTHOR 141 | 142 | FUJIWARA Shunichiro Efujiwara _at_ cpan.orgE 143 | 144 | =head1 REPOSITORY 145 | 146 | L 147 | 148 | git clone git://github.com/fluent/fluent-logger-perl.git 149 | 150 | patches and collaborators are welcome. 151 | 152 | =head1 SEE ALSO 153 | 154 | L 155 | 156 | =head1 COPYRIGHT & LICENSE 157 | 158 | Copyright FUJIWARA Shunichiro 159 | 160 | This library is free software; you can redistribute it and/or modify 161 | it under the same terms as Perl itself. 162 | 163 | =cut 164 | -------------------------------------------------------------------------------- /minil.toml: -------------------------------------------------------------------------------- 1 | badges = ["github-actions"] 2 | name = "Fluent-Logger" 3 | -------------------------------------------------------------------------------- /t/00_compile.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | use ExtUtils::MakeMaker (); 4 | 5 | BEGIN { use_ok 'Fluent::Logger' or BAIL_OUT('will not work with compilation problems'); } 6 | 7 | done_testing; 8 | -------------------------------------------------------------------------------- /t/01_new.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use Test::More; 4 | 5 | require Fluent::Logger; 6 | use Test::TCP; 7 | 8 | subtest 'new' => sub { 9 | test_tcp( 10 | client => sub { 11 | my $port = shift; 12 | 13 | my $obj = Fluent::Logger->new({ 14 | port => $port, 15 | }); 16 | ok $obj; 17 | }, 18 | server => sub { 19 | my $port = shift; 20 | 21 | my $sock = IO::Socket::INET->new( 22 | LocalPort => $port, 23 | LocalAddr => '127.0.0.1', 24 | Proto => 'tcp', 25 | Listen => 5, 26 | ) or die "Cannot open server socket: $!"; 27 | 28 | $sock->listen or die $!; 29 | while (my $c = $sock->accept) { 30 | print $c; 31 | } 32 | }, 33 | ); 34 | }; 35 | 36 | done_testing; 37 | -------------------------------------------------------------------------------- /t/02_unix_socket.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use FindBin; 4 | use lib "$FindBin::Bin/../"; 5 | use Test::More; 6 | use Test::SharedFork; 7 | use File::Temp qw/ tempdir /; 8 | use t::Util qw/ streaming_decode_mp /; 9 | use IO::Socket::UNIX; 10 | 11 | use_ok "Fluent::Logger"; 12 | 13 | my $dir = tempdir( CLEANUP => 1 ); 14 | my $sock = "$dir/test.sock"; 15 | note "socket: $sock"; 16 | 17 | my $pid = fork(); 18 | if ($pid == 0) { 19 | Test::SharedFork->child; 20 | sleep 1; 21 | my $logger = Fluent::Logger->new( socket => $sock ); 22 | isa_ok $logger, "Fluent::Logger"; 23 | ok $logger->post("test.debug" => { foo => "bar" }); 24 | ok $logger->close; 25 | } 26 | elsif (defined $pid) { 27 | Test::SharedFork->parent; 28 | my $sock = IO::Socket::UNIX->new( 29 | Local => $sock, 30 | Listen => 5, 31 | ) or die "Cannot open server socket: $!"; 32 | 33 | while (my $cs = $sock->accept) { 34 | my $data = streaming_decode_mp($cs); 35 | note explain $data; 36 | isa_ok $data => "ARRAY"; 37 | is $data->[0] => "test.debug"; 38 | is_deeply $data->[2] => { foo => "bar" }; 39 | last; 40 | } 41 | waitpid $pid, 0; 42 | done_testing; 43 | } else { 44 | die "Cannot fork: $!"; 45 | }; 46 | -------------------------------------------------------------------------------- /t/03_tcp.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use FindBin; 3 | use lib "$FindBin::Bin/../"; 4 | use Test::More; 5 | use Test::SharedFork; 6 | use File::Temp qw/ tempdir /; 7 | use t::Util qw/ streaming_decode_mp /; 8 | use Test::TCP(); 9 | use IO::Socket::INET; 10 | 11 | use_ok "Fluent::Logger"; 12 | 13 | my $port = Test::TCP::empty_port; 14 | note "port: $port"; 15 | 16 | my $pid = fork(); 17 | if ($pid == 0) { 18 | Test::SharedFork->child; 19 | sleep 1; 20 | my $logger = Fluent::Logger->new( 21 | host => "127.0.0.1", 22 | port => $port, 23 | ); 24 | isa_ok $logger, "Fluent::Logger"; 25 | ok $logger->post("test.debug" => { foo => "bar" }); 26 | ok $logger->close; 27 | } 28 | elsif (defined $pid) { 29 | Test::SharedFork->parent; 30 | my $sock = IO::Socket::INET->new( 31 | LocalPort => $port, 32 | LocalAddr => "127.0.0.1", 33 | Listen => 5, 34 | ) or die "Cannot open server socket: $!"; 35 | 36 | while (my $cs = $sock->accept) { 37 | my $data = streaming_decode_mp($cs); 38 | note explain $data; 39 | isa_ok $data => "ARRAY"; 40 | is $data->[0] => "test.debug"; 41 | is_deeply $data->[2] => { foo => "bar" }; 42 | last; 43 | } 44 | sleep 1; 45 | done_testing; 46 | }; 47 | -------------------------------------------------------------------------------- /t/04_live.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use FindBin; 4 | use lib "$FindBin::Bin/../"; 5 | use Test::More; 6 | use Test::TCP; 7 | use Time::Piece; 8 | use t::Util qw/ run_fluentd slurp_log $ENABLE_TEST_EVENT_TIME /; 9 | use POSIX qw/ setlocale LC_ALL /; 10 | use Capture::Tiny qw/ capture /; 11 | 12 | use Config; 13 | if ( $Config{d_setlocale} ) { 14 | setlocale(LC_ALL, "C"); 15 | } 16 | 17 | my ($server, $dir) = run_fluentd(); 18 | my $port = $server->port; 19 | 20 | use_ok "Fluent::Logger"; 21 | 22 | subtest tcp => sub { 23 | my $logger = Fluent::Logger->new( port => $port ); 24 | 25 | isa_ok $logger, "Fluent::Logger"; 26 | my $tag = "test.tcp"; 27 | ok $logger->post( $tag, { "foo" => "bar" }), "post ok"; 28 | 29 | my $time = time - int rand(3600); 30 | my $time_str = localtime($time)->strftime("%Y-%m-%dT%H:%M:%S.000000000%z"); 31 | 32 | ok $logger->post_with_time( $tag, { "FOO" => "BAR" }, $time ), "post_with_time ok"; 33 | sleep 1; 34 | my $log = slurp_log $dir; 35 | note $log; 36 | like $log => qr/"foo":"bar","tag":"$tag"/, "match post log"; 37 | like $log => qr/"FOO":"BAR","tag":"$tag","time":"\Q$time_str\E"/, "match post_with_time log"; 38 | }; 39 | 40 | subtest tcp_event_time => sub { 41 | plan skip_all => "installed fluentd not supports event_time" 42 | unless $ENABLE_TEST_EVENT_TIME; 43 | 44 | my $logger = Fluent::Logger->new( port => $port, event_time => 1 ); 45 | isa_ok $logger, "Fluent::Logger"; 46 | my $tag = "test.tcp"; 47 | ok $logger->post( $tag, { "event_time" => "foo" }), "post ok"; 48 | 49 | my $time = Time::HiRes::time; 50 | my $time_i = int($time); 51 | my $nanosec = sprintf("%09d", int(($time - $time_i) * 10 ** 9)); 52 | my $time_str = localtime($time)->strftime("%Y-%m-%dT%H:%M:%S.${nanosec}%z"); 53 | 54 | ok $logger->post_with_time( $tag, { "event_time" => "bar" }, $time ), "post_with_time ok"; 55 | sleep 1; 56 | my $log = slurp_log $dir; 57 | note $log; 58 | like $log => qr/"event_time":"foo","tag":"$tag","time":"\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{9}[+-]\d{4}"/, "match post log"; 59 | like $log => qr/"event_time":"bar","tag":"$tag","time":"\Q$time_str\E"/, "match post_with_time log"; 60 | }; 61 | 62 | subtest ack => sub { 63 | my $logger = Fluent::Logger->new( port => $port, ack => 1 ); 64 | 65 | isa_ok $logger, "Fluent::Logger"; 66 | my $tag = "test.tcp"; 67 | ok $logger->post( $tag, { "foo" => "bar" }), "post ok"; 68 | 69 | my $time = time - int rand(3600); 70 | my $time_str = localtime($time)->strftime("%Y-%m-%dT%H:%M:%S.000000000%z"); 71 | 72 | ok $logger->post_with_time( $tag, { "FOO" => "BAR" }, $time ), "post_with_time ok"; 73 | sleep 1; 74 | my $log = slurp_log $dir; 75 | note $log; 76 | like $log => qr/"foo":"bar","tag":"$tag"/, "match post log"; 77 | like $log => qr/"FOO":"BAR","tag":"$tag","time":"\Q$time_str\E"/, "match post_with_time log"; 78 | }; 79 | 80 | subtest error => sub { 81 | my $logger = Fluent::Logger->new( port => $port ); 82 | ok $logger->post( "test.error" => { foo => "ok" } ); 83 | 84 | undef $server; # shutdown 85 | 86 | my $r; 87 | $r = $logger->post( "test.error" => "not hashref?" ); 88 | is $r => undef, "not hash ref"; 89 | like $logger->errstr => qr/HashRef/i; 90 | 91 | $r = $logger->post( "test.error" => { "foo" => "broken pipe?" } ); 92 | is $r => undef, "broken pipe"; 93 | like $logger->errstr => qr/Broken pipe/i; 94 | 95 | $r = $logger->post( "test.error" => { "foo" => "connection refused?" } ); 96 | is $r => undef, "connection refused"; 97 | like $logger->errstr => qr/Can't connect: (?:Connection refused|Invalid argument)/i; 98 | 99 | sleep 1; 100 | 101 | # restart server on the same port 102 | ($server, $dir) = run_fluentd($port); 103 | ok $logger->post( "test.error" => { foo => "reconnected?" } ), "reconnected"; 104 | 105 | undef $server; 106 | sleep 1; 107 | ($server, $dir) = run_fluentd($port); 108 | 109 | ok !$logger->post( "test.error" => { foo => "retried?" } ); 110 | my ($stdout, $stderr) = capture { 111 | undef $logger; 112 | }; 113 | like $stderr, qr{flushed success}i, "flushed success logged"; 114 | note $stderr; 115 | 116 | kill USR1 => $server->pid; 117 | undef $server; 118 | 119 | my $log = slurp_log $dir; 120 | like $log => qr{"foo":"retried\?"}, "retried sent"; 121 | }; 122 | 123 | subtest lost => sub { 124 | my $logger = Fluent::Logger->new( port => $port ); 125 | ok !$logger->post( "test.lost" => { foo => "to be lost first" } ); 126 | ok !$logger->post( "test.lost" => { foo => "to be lost second" } ); 127 | 128 | my ($stdout, $stderr) = capture { 129 | undef $logger; 130 | }; 131 | like $stderr, qr{LOST}i, "lost logged"; 132 | note $stderr; 133 | }; 134 | 135 | done_testing; 136 | -------------------------------------------------------------------------------- /t/05_prefer_integer.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use FindBin; 4 | use lib "$FindBin::Bin/../"; 5 | use Test::More; 6 | use Test::TCP; 7 | use Time::Piece; 8 | use t::Util qw/ run_fluentd slurp_log /; 9 | use POSIX qw/ setlocale LC_ALL /; 10 | 11 | use Config; 12 | if ( $Config{d_setlocale} ) { 13 | setlocale(LC_ALL, "C"); 14 | } 15 | 16 | my ($server, $dir) = run_fluentd(); 17 | my $port = $server->port; 18 | 19 | use_ok "Fluent::Logger"; 20 | 21 | subtest 'prefer_integer trigger' => sub { 22 | my $logger = Fluent::Logger->new( 23 | prefer_integer => 0 24 | ); 25 | 26 | is $logger->packer->get_prefer_integer, 0, "prefer_integer = 0"; 27 | $logger->prefer_integer( 1 ); 28 | is $logger->packer->get_prefer_integer, 1, "prefer_integer = 1"; 29 | }; 30 | 31 | subtest as_int => sub { 32 | my $logger = Fluent::Logger->new( 33 | port => $port, 34 | prefer_integer => 1, 35 | ); 36 | my $tag = "test.integer"; 37 | ok $logger->post( $tag, { "as_int" => "123" }); 38 | sleep 1; 39 | my $log = slurp_log $dir; 40 | like $log => qr{"as_int":123}; 41 | }; 42 | 43 | subtest as_str => sub { 44 | my $logger = Fluent::Logger->new( 45 | port => $port, 46 | prefer_integer => 0, 47 | ); 48 | my $tag = "test.integer"; 49 | ok $logger->post( $tag, { "as_str" => "123" }); 50 | sleep 1; 51 | my $log = slurp_log $dir; 52 | like $log => qr{"as_str":"123"}; 53 | }; 54 | 55 | subtest change_flag => sub { 56 | my $logger = Fluent::Logger->new( 57 | port => $port, 58 | prefer_integer => 1, 59 | ); 60 | my $tag = "test.integer"; 61 | ok $logger->post( $tag, { "change_as_int" => "123" }); 62 | sleep 1; 63 | my $log = slurp_log $dir; 64 | like $log => qr{"change_as_int":123}; 65 | 66 | $logger->prefer_integer(0); 67 | ok $logger->post( $tag, { "change_as_str" => "123" }); 68 | sleep 1; 69 | $log = slurp_log $dir; 70 | like $log => qr{"change_as_str":"123"}; 71 | note $log; 72 | }; 73 | 74 | done_testing; 75 | -------------------------------------------------------------------------------- /t/06_fork.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use FindBin; 4 | use lib "$FindBin::Bin/../"; 5 | use Test::More; 6 | use t::Util qw/ run_fluentd /; 7 | use POSIX qw/ setlocale LC_ALL /; 8 | use Test::SharedFork; 9 | 10 | use Config; 11 | if ( $Config{d_setlocale} ) { 12 | setlocale(LC_ALL, "C"); 13 | } 14 | 15 | my ($server, $dir) = run_fluentd(); 16 | my $port = $server->port; 17 | 18 | use_ok "Fluent::Logger"; 19 | 20 | my $logger = Fluent::Logger->new( port => $port ); 21 | my $local_port = $logger->socket_io->sockport; 22 | 23 | ok $local_port, "local port: $local_port"; 24 | ok $logger->post("test.parent" => { foo => "bar" }); 25 | 26 | my $pid = fork(); 27 | if ($pid == 0) { 28 | # child 29 | note "child pid: $$"; 30 | ok $logger->post("test.child" => { foo => "bar" }); 31 | ok $logger->socket_io->sockport != $local_port, "different port on child"; 32 | exit; 33 | } 34 | elsif ($pid) { 35 | # parent 36 | note "parent pid: $$"; 37 | ok $logger->post("test.parent" => { foo => "bar" }); 38 | is $logger->socket_io->sockport => $local_port, "same port on parent"; 39 | waitpid($pid, 0); 40 | } 41 | else { 42 | die $!; 43 | } 44 | 45 | done_testing; 46 | -------------------------------------------------------------------------------- /t/07_str_bin.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use FindBin; 4 | use lib "$FindBin::Bin/../"; 5 | use Test::More; 6 | use Test::TCP; 7 | use Time::Piece; 8 | use Encode; 9 | use t::Util qw/ run_fluentd slurp_log /; 10 | use POSIX qw/ setlocale LC_ALL /; 11 | use Capture::Tiny qw/ capture /; 12 | 13 | use Config; 14 | if ( $Config{d_setlocale} ) { 15 | setlocale(LC_ALL, "C"); 16 | } 17 | 18 | my ($server, $dir) = run_fluentd(); 19 | my $port = $server->port; 20 | 21 | use_ok "Fluent::Logger"; 22 | 23 | subtest str_bin => sub { 24 | my $logger = Fluent::Logger->new( port => $port, utf8 => 0 ); 25 | 26 | isa_ok $logger, "Fluent::Logger"; 27 | is $logger->packer->get_utf8, 0, "packer utf8 is off"; 28 | my $tag = "test.tcp"; 29 | ok $logger->post( $tag, { "foo" => decode_utf8("内部文字列") }), "post str ok"; 30 | ok $logger->post( $tag, { "bar" => "バイナリ列" }), "post bin ok"; 31 | ok $logger->post( $tag, { "broken" => "\xE0\x80\xAF" }), "post broken utf8 ok"; 32 | sleep 1; 33 | my $log = slurp_log $dir; 34 | note $log; 35 | like $log => qr/"foo":"内部文字列","tag":"$tag"/, "match post str log"; 36 | like $log => qr/"bar":"バイナリ列","tag":"$tag"/, "match post bin log"; 37 | like $log => qr/"broken":"\xE0\x80\xAF","tag":"$tag"/, "match post broken utf8 log"; 38 | }; 39 | 40 | done_testing; 41 | -------------------------------------------------------------------------------- /t/07_str_utf8.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use FindBin; 4 | use lib "$FindBin::Bin/../"; 5 | use Test::More; 6 | use Test::TCP; 7 | use Time::Piece; 8 | use Encode; 9 | use t::Util qw/ run_fluentd slurp_log /; 10 | use POSIX qw/ setlocale LC_ALL /; 11 | use Capture::Tiny qw/ capture /; 12 | 13 | use Config; 14 | if ( $Config{d_setlocale} ) { 15 | setlocale(LC_ALL, "C"); 16 | } 17 | 18 | my ($server, $dir) = run_fluentd(); 19 | my $port = $server->port; 20 | 21 | use_ok "Fluent::Logger"; 22 | 23 | subtest str_bin => sub { 24 | my $logger = Fluent::Logger->new( port => $port, utf8 => 1 ); 25 | 26 | isa_ok $logger, "Fluent::Logger"; 27 | is $logger->packer->get_utf8, 1, "packer utf8 is on"; 28 | my $tag = "test.tcp"; 29 | ok $logger->post( $tag, { "foo" => decode_utf8("内部文字列") }), "post str ok"; 30 | ok $logger->post( $tag, { "bar" => "バイナリ列" }), "post bin ok"; 31 | ok $logger->post( $tag, { "broken" => "\xE0\x80\xAF" }), "post broken utf8 ok"; 32 | sleep 1; 33 | my $log = slurp_log $dir; 34 | note $log; 35 | like $log => qr/"foo":"内部文字列","tag":"$tag"/, "match post str log"; 36 | like $log => qr/"bar":"バイナリ列","tag":"$tag"/, "match post bin log"; 37 | like $log => qr/"broken":"\xE0\x80\xAF","tag":"$tag"/, "match post broken utf8 log"; 38 | }; 39 | 40 | done_testing; 41 | -------------------------------------------------------------------------------- /t/08_udp.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use FindBin; 4 | use lib "$FindBin::Bin/../"; 5 | use Test::More; 6 | use Test::TCP; 7 | use Time::Piece; 8 | use t::Util qw/ run_fluentd slurp_log /; 9 | use POSIX qw/ setlocale LC_ALL /; 10 | use Capture::Tiny qw/ capture /; 11 | 12 | use Config; 13 | if ( $Config{d_setlocale} ) { 14 | setlocale(LC_ALL, "C"); 15 | } 16 | 17 | my ($server, $dir) = run_fluentd(undef, "udp"); 18 | my $port = $server->port; 19 | 20 | use_ok "Fluent::Logger::UDP"; 21 | 22 | subtest udp => sub { 23 | my $logger = Fluent::Logger::UDP->new( port => $port ); 24 | 25 | isa_ok $logger, "Fluent::Logger::UDP"; 26 | ok $logger->post('{"foo":"bar"}'), "post ok"; 27 | 28 | sleep 1; 29 | my $log = slurp_log $dir; 30 | note $log; 31 | like $log => qr/"foo":"bar","tag":"test\.udp"/, "match post log"; 32 | }; 33 | 34 | done_testing; 35 | -------------------------------------------------------------------------------- /t/09_buffer_overflow_handler.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use Test::TCP; 6 | use Fluent::Logger; 7 | 8 | my $port = Test::TCP::empty_port(); 9 | 10 | my $handler_called; 11 | 12 | my $no_such_logger = Fluent::Logger->new( 13 | host => '127.0.0.1', 14 | port => $port, 15 | buffer_overflow_handler => sub { $handler_called++ }, 16 | buffer_limit => 8*1024*1024, 17 | ); 18 | for (1..10) { 19 | $no_such_logger->post("test", { "k" => "v" x (1024 * 1024) }); 20 | } 21 | is $handler_called, 1, 'called once at buffer_overflow'; 22 | 23 | undef $no_such_logger; 24 | is $handler_called, 2, 'called once at close'; 25 | 26 | done_testing; 27 | 28 | -------------------------------------------------------------------------------- /t/10_truncate_buffet_at_overflow.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use utf8; 4 | use Test::More; 5 | use Test::TCP; 6 | use Fluent::Logger; 7 | 8 | my $port = Test::TCP::empty_port(); 9 | 10 | my $handler_called; 11 | 12 | my $no_such_logger = Fluent::Logger->new( 13 | host => '127.0.0.1', 14 | port => $port, 15 | buffer_limit => 8*1024*1024, 16 | truncate_buffer_at_overflow => 1, 17 | ); 18 | for (1..8) { 19 | ok ! $no_such_logger->post("test", { "k" => "v" x (1024 * 1024) }); 20 | } 21 | 22 | for (1..8) { 23 | ok ! $no_such_logger->post("test", { "k" => "v" x (1024 * 1024) }); 24 | } 25 | ok length($no_such_logger->pending) <= 8*1024*1024, "pending buffer size > 8*1024*1024"; 26 | 27 | undef $no_such_logger; 28 | 29 | done_testing; 30 | 31 | -------------------------------------------------------------------------------- /t/11_retry_immediately.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use FindBin; 4 | use lib "$FindBin::Bin/../"; 5 | use Test::More; 6 | use t::Util qw/ run_fluentd /; 7 | use POSIX qw/ setlocale LC_ALL /; 8 | use Test::SharedFork; 9 | 10 | use Config; 11 | if ( $Config{d_setlocale} ) { 12 | setlocale(LC_ALL, "C"); 13 | } 14 | 15 | my ($server, $dir) = run_fluentd(); 16 | my $port = $server->port; 17 | 18 | use_ok "Fluent::Logger"; 19 | 20 | my $logger = Fluent::Logger->new( 21 | port => $port, 22 | retry_immediately => 1, 23 | ); 24 | 25 | ok $logger->post( "test.error" => { foo => "ok" } ); 26 | 27 | undef $server; # shutdown 28 | sleep 1; 29 | 30 | ($server, $dir) = run_fluentd($port); # start fluentd on the same port 31 | 32 | ok $logger->post( "test.error" => { foo => "retried?" } ); 33 | 34 | done_testing; 35 | -------------------------------------------------------------------------------- /t/Util.pm: -------------------------------------------------------------------------------- 1 | package t::Util; 2 | use strict; 3 | use warnings; 4 | use File::Temp qw/ tempdir /; 5 | use Path::Tiny qw/ path /; 6 | use Test::TCP; 7 | use version; 8 | 9 | use Exporter 'import'; 10 | our $ENABLE_TEST_EVENT_TIME; 11 | our @EXPORT_OK = qw/ streaming_decode_mp run_fluentd slurp_log $ENABLE_TEST_EVENT_TIME /; 12 | 13 | sub streaming_decode_mp { 14 | my $sock = shift; 15 | my $offset = 0; 16 | my $up = Data::MessagePack::Unpacker->new; 17 | while( read($sock, my $buf, 1024) ) { 18 | $offset = $up->execute($buf, $offset); 19 | if ($up->is_finished) { 20 | return $up->data; 21 | } 22 | } 23 | } 24 | 25 | sub slurp_log($) { 26 | my $dir = shift; 27 | my @file = grep { !/\.meta$/ } path($dir)->children( qr/test\.log/ ); 28 | return join("", map { $_->slurp } @file); 29 | } 30 | 31 | sub run_fluentd { 32 | my $fixed_port = shift; 33 | my $input = shift || "forward"; 34 | my ($v) = ( `fluentd --version` =~ /^fluentd ([0-9.]+)/ ); 35 | if (!$v) { 36 | Test::More::plan skip_all => "fluentd is not installed."; 37 | } 38 | if (version->parse($v) >= version->parse("0.14.0")) { 39 | Test::More::note "fluentd version $v: enabling tests for event time."; 40 | $ENABLE_TEST_EVENT_TIME = 1; 41 | } else { 42 | Test::More::note "fluentd version < 0.14.0: disabling tests for event time."; 43 | } 44 | my $dir = tempdir( CLEANUP => 1 ); 45 | my $code = sub { 46 | my $port = shift; 47 | open my $conf, ">", "$dir/fluent.conf" or die $!; 48 | if ( $input eq "forward" ) { 49 | print $conf <<"_END_"; 50 | 51 | \@type forward 52 | port ${port} 53 | 54 | _END_ 55 | } elsif ($input eq "tcp" || $input eq "udp") { 56 | print $conf <<"_END_"; 57 | 58 | \@type tcp 59 | tag test.tcp 60 | port ${port} 61 | format json 62 | 63 | 64 | \@type udp 65 | tag test.udp 66 | port ${port} 67 | format json 68 | 69 | _END_ 70 | } 71 | print $conf <<"_END_"; 72 | 73 | \@type stdout 74 | time_format %Y-%m-%dT%H:%M:%S.%N%z 75 | include_time_key true 76 | include_tag_key true 77 | format json 78 | 79 | _END_ 80 | exec "fluentd", 81 | "-c", "$dir/fluent.conf", 82 | "--log", "$dir/test.log", 83 | ; 84 | die $!; 85 | }; 86 | my $server = Test::TCP->new( 87 | code => $code, 88 | wait_port_sleep => 0.1, 89 | wait_port_retry => 100, 90 | $fixed_port ? ( port => $fixed_port ) : (), 91 | ); 92 | return ($server, $dir); 93 | } 94 | 95 | 1; 96 | -------------------------------------------------------------------------------- /xt/02_perlcritic.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use Test::More; 3 | eval { 4 | require Test::Perl::Critic; 5 | Test::Perl::Critic->import( -profile => 'xt/perlcriticrc'); 6 | }; 7 | plan skip_all => "Test::Perl::Critic is not installed." if $@; 8 | all_critic_ok('lib'); 9 | -------------------------------------------------------------------------------- /xt/04_pod-coverage.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | eval "use Test::Pod::Coverage 1.04"; 3 | plan skip_all => "Test::Pod::Coverage 1.04 required for testing POD coverage" 4 | if $@; 5 | all_pod_coverage_ok( 6 | { 7 | also_private => [ 8 | qr/^[A-Z_]+$/, 9 | # attributes 10 | qw/ tag_prefix host port socket timeout buffer_limit 11 | max_write_retry write_length socket_io 12 | errors prefer_integer packer pending 13 | connect_error_history owner_pid 14 | event_time selector 15 | / 16 | ], 17 | }, 18 | ); 19 | -------------------------------------------------------------------------------- /xt/05_dependencies.t: -------------------------------------------------------------------------------- 1 | # -*- mode: cperl; -*- 2 | use FindBin; 3 | use lib "$FindBin::Bin/../"; 4 | use Test::More; 5 | eval { 6 | require Test::Dependencies; 7 | import Test::Dependencies 8 | exclude => [qw(Test::Dependencies Test::Base Test::Perl::Critic 9 | Fluent::Logger t::Util )], 10 | style => 'light'; 11 | }; 12 | plan skip_all => "Test::Dependencies required for testing dependencies" 13 | if $@; 14 | 15 | ok_dependencies(); 16 | -------------------------------------------------------------------------------- /xt/06_benchmark.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use FindBin; 4 | use lib "$FindBin::Bin/../"; 5 | use Test::More; 6 | use Test::TCP; 7 | use Time::HiRes qw/ time /; 8 | use t::Util qw/ run_fluentd /; 9 | 10 | Test::More::plan skip_all => "skip < 5.10" if $] < 5.010; 11 | 12 | my ($server, $dir) = run_fluentd(); 13 | my $port = $server->port; 14 | 15 | require Number::Format; 16 | 17 | use_ok "Fluent::Logger"; 18 | diag "starting benchmark..."; 19 | for my $size ( 10, 100, 1000 ) { 20 | my $n = 50000; 21 | my $msg = "x" x $size; 22 | my $start = time; 23 | my $logger = Fluent::Logger->new( port => $port ); 24 | my $w = 0; 25 | for ( 1 .. $n ) { 26 | $w += $logger->post( "test.benchmark", { "msg" => $msg } ); 27 | } 28 | my $elapsed = time - $start; 29 | diag sprintf "%.2f sec / %d msgs (%d bytes) = %.2fqps (%sbps)", 30 | $elapsed, $n, $w / $n, $n / $elapsed, 31 | Number::Format::format_bytes($w * 8 / $elapsed); 32 | } 33 | 34 | done_testing; 35 | -------------------------------------------------------------------------------- /xt/07_dummy_benchmark.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use FindBin; 4 | use lib "$FindBin::Bin/../"; 5 | use Test::More; 6 | use Test::TCP; 7 | use Time::HiRes qw/ time sleep /; 8 | use t::Util qw/ run_fluentd /; 9 | use File::Temp qw/ tempdir /; 10 | 11 | Test::More::plan skip_all => "skip < 5.10" if $] < 5.010; 12 | 13 | require Proc::Guard; 14 | require Number::Format; 15 | 16 | use_ok "Fluent::Logger"; 17 | diag "starting benchmark..."; 18 | 19 | for my $type ( "tcp", "unix" ) { 20 | for my $size ( 10, 100, 1000 ) { 21 | my ($server, $dir) = dummy_server($type); 22 | my $n = 100_000; 23 | my $msg = "x" x $size; 24 | my @args = $type eq "tcp" 25 | ? ( port => $server->port ) 26 | : ( socket => "$dir/server.sock" ); 27 | my $start = time; 28 | my $logger = Fluent::Logger->new(@args); 29 | my $w = 0; 30 | for ( 1 .. $n ) { 31 | $w += $logger->post( "test.benchmark", { "msg" => $msg } ); 32 | } 33 | my $elapsed = time - $start; 34 | diag sprintf "%s: %.2f sec / %d msgs (%d bytes) = %.2fqps (%sbps)", 35 | $type, $elapsed, $n, $w / $n, $n / $elapsed, 36 | Number::Format::format_bytes($w * 8 / $elapsed); 37 | } 38 | } 39 | 40 | done_testing; 41 | 42 | sub dummy_server { 43 | my ($type) = @_; 44 | my $dir = tempdir( CLEANUP => 1 ); 45 | my @nc = qw/ nc -k -l /; 46 | my $server; 47 | if ($type eq "tcp") { 48 | $server = Test::TCP->new( 49 | code => sub { 50 | my $port = shift; 51 | open STDOUT, ">", "/dev/null"; 52 | exec @nc, $port; 53 | }, 54 | ); 55 | } 56 | else { 57 | my $socket = "$dir/server.sock"; 58 | $server = Proc::Guard->new( 59 | code => sub { 60 | open STDOUT, ">", "/dev/null"; 61 | exec @nc, "-U", $socket; 62 | }, 63 | ); 64 | sleep 0.1 while !-e $socket; # wait for socket created 65 | } 66 | ($server, $dir); 67 | } 68 | 69 | -------------------------------------------------------------------------------- /xt/08_benchmark_ack.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use FindBin; 4 | use lib "$FindBin::Bin/../"; 5 | use Test::More; 6 | use Test::TCP; 7 | use Time::HiRes qw/ time /; 8 | use t::Util qw/ run_fluentd /; 9 | 10 | Test::More::plan skip_all => "skip < 5.10" if $] < 5.010; 11 | 12 | my ($server, $dir) = run_fluentd(); 13 | my $port = $server->port; 14 | 15 | require Number::Format; 16 | 17 | use_ok "Fluent::Logger"; 18 | diag "starting benchmark..."; 19 | for my $size ( 10, 100, 1000 ) { 20 | my $n = 50000; 21 | my $msg = "x" x $size; 22 | my $start = time; 23 | my $logger = Fluent::Logger->new( port => $port, ack => 1 ); 24 | my $w = 0; 25 | for ( 1 .. $n ) { 26 | $w += $logger->post( "test.benchmark", { "msg" => $msg } ); 27 | } 28 | my $elapsed = time - $start; 29 | diag sprintf "%.2f sec / %d msgs (%d bytes) = %.2fqps (%sbps)", 30 | $elapsed, $n, $w / $n, $n / $elapsed, 31 | Number::Format::format_bytes($w * 8 / $elapsed); 32 | } 33 | 34 | done_testing; 35 | -------------------------------------------------------------------------------- /xt/perlcriticrc: -------------------------------------------------------------------------------- 1 | [TestingAndDebugging::ProhibitNoStrict] 2 | allow=refs 3 | --------------------------------------------------------------------------------