fluentd 0.12.40 → 1.6.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of fluentd might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/.github/ISSUE_TEMPLATE/bug_report.md +39 -0
- data/.github/ISSUE_TEMPLATE/feature_request.md +23 -0
- data/.github/ISSUE_TEMPLATE.md +17 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +13 -0
- data/.gitignore +5 -0
- data/.gitlab/cicd-template.yaml +10 -0
- data/.gitlab-ci.yml +147 -0
- data/.travis.yml +56 -20
- data/ADOPTERS.md +5 -0
- data/CHANGELOG.md +1369 -0
- data/CONTRIBUTING.md +16 -5
- data/GOVERNANCE.md +55 -0
- data/Gemfile +5 -0
- data/GithubWorkflow.md +78 -0
- data/LICENSE +202 -0
- data/MAINTAINERS.md +7 -0
- data/README.md +23 -11
- data/Rakefile +48 -2
- data/Vagrantfile +17 -0
- data/appveyor.yml +37 -0
- data/bin/fluent-binlog-reader +7 -0
- data/bin/fluent-ca-generate +6 -0
- data/bin/fluent-plugin-config-format +5 -0
- data/bin/fluent-plugin-generate +5 -0
- data/bin/fluentd +3 -0
- data/code-of-conduct.md +3 -0
- data/example/copy_roundrobin.conf +39 -0
- data/example/counter.conf +18 -0
- data/example/in_dummy_blocks.conf +17 -0
- data/example/in_dummy_with_compression.conf +23 -0
- data/example/in_forward.conf +7 -0
- data/example/in_forward_client.conf +37 -0
- data/example/in_forward_shared_key.conf +15 -0
- data/example/in_forward_tls.conf +14 -0
- data/example/in_forward_users.conf +24 -0
- data/example/in_forward_workers.conf +21 -0
- data/example/in_http.conf +3 -1
- data/example/in_out_forward.conf +17 -0
- data/example/logevents.conf +25 -0
- data/example/multi_filters.conf +61 -0
- data/example/out_exec_filter.conf +42 -0
- data/example/out_forward.conf +13 -13
- data/example/out_forward_buf_file.conf +23 -0
- data/example/out_forward_client.conf +109 -0
- data/example/out_forward_heartbeat_none.conf +16 -0
- data/example/out_forward_shared_key.conf +36 -0
- data/example/out_forward_tls.conf +18 -0
- data/example/out_forward_users.conf +65 -0
- data/example/out_null.conf +36 -0
- data/example/secondary_file.conf +42 -0
- data/example/suppress_config_dump.conf +7 -0
- data/example/worker_section.conf +36 -0
- data/fluent.conf +29 -0
- data/fluentd.gemspec +21 -11
- data/lib/fluent/agent.rb +67 -90
- data/lib/fluent/clock.rb +62 -0
- data/lib/fluent/command/binlog_reader.rb +244 -0
- data/lib/fluent/command/ca_generate.rb +181 -0
- data/lib/fluent/command/cat.rb +42 -18
- data/lib/fluent/command/debug.rb +12 -10
- data/lib/fluent/command/fluentd.rb +153 -5
- data/lib/fluent/command/plugin_config_formatter.rb +292 -0
- data/lib/fluent/command/plugin_generator.rb +324 -0
- data/lib/fluent/compat/call_super_mixin.rb +67 -0
- data/lib/fluent/compat/detach_process_mixin.rb +33 -0
- data/lib/fluent/compat/exec_util.rb +129 -0
- data/lib/fluent/compat/file_util.rb +54 -0
- data/lib/fluent/compat/filter.rb +68 -0
- data/lib/fluent/compat/formatter.rb +111 -0
- data/lib/fluent/compat/formatter_utils.rb +85 -0
- data/lib/fluent/compat/handle_tag_and_time_mixin.rb +62 -0
- data/lib/fluent/compat/handle_tag_name_mixin.rb +53 -0
- data/lib/fluent/compat/input.rb +49 -0
- data/lib/fluent/compat/output.rb +718 -0
- data/lib/fluent/compat/output_chain.rb +60 -0
- data/lib/fluent/compat/parser.rb +310 -0
- data/lib/fluent/compat/parser_utils.rb +40 -0
- data/lib/fluent/compat/propagate_default.rb +62 -0
- data/lib/fluent/compat/record_filter_mixin.rb +34 -0
- data/lib/fluent/compat/set_tag_key_mixin.rb +50 -0
- data/lib/fluent/compat/set_time_key_mixin.rb +69 -0
- data/lib/fluent/compat/socket_util.rb +165 -0
- data/lib/fluent/compat/string_util.rb +34 -0
- data/lib/fluent/compat/structured_format_mixin.rb +26 -0
- data/lib/fluent/compat/type_converter.rb +90 -0
- data/lib/fluent/config/configure_proxy.rb +210 -62
- data/lib/fluent/config/dsl.rb +12 -5
- data/lib/fluent/config/element.rb +107 -9
- data/lib/fluent/config/literal_parser.rb +9 -3
- data/lib/fluent/config/parser.rb +4 -4
- data/lib/fluent/config/section.rb +51 -14
- data/lib/fluent/config/types.rb +28 -13
- data/lib/fluent/config/v1_parser.rb +3 -5
- data/lib/fluent/config.rb +23 -20
- data/lib/fluent/configurable.rb +79 -21
- data/lib/fluent/counter/base_socket.rb +46 -0
- data/lib/fluent/counter/client.rb +297 -0
- data/lib/fluent/counter/error.rb +86 -0
- data/lib/fluent/counter/mutex_hash.rb +163 -0
- data/lib/fluent/counter/server.rb +273 -0
- data/lib/fluent/counter/store.rb +205 -0
- data/lib/fluent/counter/validator.rb +145 -0
- data/lib/fluent/counter.rb +23 -0
- data/lib/fluent/daemon.rb +15 -0
- data/lib/fluent/engine.rb +102 -65
- data/lib/fluent/env.rb +7 -3
- data/lib/fluent/error.rb +30 -0
- data/lib/fluent/event.rb +197 -21
- data/lib/fluent/event_router.rb +93 -10
- data/lib/fluent/filter.rb +2 -50
- data/lib/fluent/formatter.rb +4 -293
- data/lib/fluent/input.rb +2 -32
- data/lib/fluent/label.rb +10 -2
- data/lib/fluent/load.rb +3 -3
- data/lib/fluent/log.rb +348 -81
- data/lib/fluent/match.rb +37 -36
- data/lib/fluent/mixin.rb +12 -176
- data/lib/fluent/msgpack_factory.rb +62 -0
- data/lib/fluent/output.rb +10 -612
- data/lib/fluent/output_chain.rb +23 -0
- data/lib/fluent/parser.rb +4 -800
- data/lib/fluent/plugin/bare_output.rb +63 -0
- data/lib/fluent/plugin/base.rb +192 -0
- data/lib/fluent/plugin/buf_file.rb +128 -174
- data/lib/fluent/plugin/buf_memory.rb +9 -92
- data/lib/fluent/plugin/buffer/chunk.rb +221 -0
- data/lib/fluent/plugin/buffer/file_chunk.rb +383 -0
- data/lib/fluent/plugin/buffer/memory_chunk.rb +90 -0
- data/lib/fluent/plugin/buffer.rb +779 -0
- data/lib/fluent/plugin/compressable.rb +92 -0
- data/lib/fluent/plugin/exec_util.rb +3 -108
- data/lib/fluent/plugin/file_util.rb +4 -34
- data/lib/fluent/plugin/file_wrapper.rb +120 -0
- data/lib/fluent/plugin/filter.rb +93 -0
- data/lib/fluent/plugin/filter_grep.rb +117 -34
- data/lib/fluent/plugin/filter_parser.rb +85 -62
- data/lib/fluent/plugin/filter_record_transformer.rb +27 -39
- data/lib/fluent/plugin/filter_stdout.rb +15 -12
- data/lib/fluent/plugin/formatter.rb +50 -0
- data/lib/fluent/plugin/formatter_csv.rb +52 -0
- data/lib/fluent/plugin/formatter_hash.rb +33 -0
- data/lib/fluent/plugin/formatter_json.rb +55 -0
- data/lib/fluent/plugin/formatter_ltsv.rb +42 -0
- data/lib/fluent/plugin/formatter_msgpack.rb +33 -0
- data/lib/fluent/plugin/formatter_out_file.rb +51 -0
- data/lib/fluent/plugin/formatter_single_value.rb +34 -0
- data/lib/fluent/plugin/formatter_stdout.rb +76 -0
- data/lib/fluent/plugin/formatter_tsv.rb +38 -0
- data/lib/fluent/plugin/in_debug_agent.rb +17 -6
- data/lib/fluent/plugin/in_dummy.rb +47 -20
- data/lib/fluent/plugin/in_exec.rb +55 -123
- data/lib/fluent/plugin/in_forward.rb +299 -216
- data/lib/fluent/plugin/in_gc_stat.rb +14 -36
- data/lib/fluent/plugin/in_http.rb +204 -91
- data/lib/fluent/plugin/in_monitor_agent.rb +186 -258
- data/lib/fluent/plugin/in_object_space.rb +13 -41
- data/lib/fluent/plugin/in_syslog.rb +112 -134
- data/lib/fluent/plugin/in_tail.rb +408 -745
- data/lib/fluent/plugin/in_tcp.rb +66 -9
- data/lib/fluent/plugin/in_udp.rb +60 -11
- data/lib/fluent/plugin/{in_stream.rb → in_unix.rb} +8 -4
- data/lib/fluent/plugin/input.rb +37 -0
- data/lib/fluent/plugin/multi_output.rb +158 -0
- data/lib/fluent/plugin/out_copy.rb +23 -35
- data/lib/fluent/plugin/out_exec.rb +67 -70
- data/lib/fluent/plugin/out_exec_filter.rb +204 -271
- data/lib/fluent/plugin/out_file.rb +267 -73
- data/lib/fluent/plugin/out_forward.rb +854 -325
- data/lib/fluent/plugin/out_null.rb +42 -9
- data/lib/fluent/plugin/out_relabel.rb +9 -5
- data/lib/fluent/plugin/out_roundrobin.rb +18 -37
- data/lib/fluent/plugin/out_secondary_file.rb +133 -0
- data/lib/fluent/plugin/out_stdout.rb +43 -10
- data/lib/fluent/plugin/out_stream.rb +7 -2
- data/lib/fluent/plugin/output.rb +1498 -0
- data/lib/fluent/plugin/owned_by_mixin.rb +42 -0
- data/lib/fluent/plugin/parser.rb +191 -0
- data/lib/fluent/plugin/parser_apache.rb +28 -0
- data/lib/fluent/plugin/parser_apache2.rb +88 -0
- data/lib/fluent/plugin/parser_apache_error.rb +26 -0
- data/lib/fluent/plugin/parser_csv.rb +39 -0
- data/lib/fluent/plugin/parser_json.rb +94 -0
- data/lib/fluent/plugin/parser_ltsv.rb +49 -0
- data/lib/fluent/plugin/parser_msgpack.rb +50 -0
- data/lib/fluent/plugin/parser_multiline.rb +106 -0
- data/lib/fluent/plugin/parser_nginx.rb +28 -0
- data/lib/fluent/plugin/parser_none.rb +36 -0
- data/lib/fluent/plugin/parser_regexp.rb +68 -0
- data/lib/fluent/plugin/parser_syslog.rb +142 -0
- data/lib/fluent/plugin/parser_tsv.rb +42 -0
- data/lib/fluent/plugin/socket_util.rb +3 -143
- data/lib/fluent/plugin/storage.rb +84 -0
- data/lib/fluent/plugin/storage_local.rb +164 -0
- data/lib/fluent/plugin/string_util.rb +3 -15
- data/lib/fluent/plugin.rb +122 -121
- data/lib/fluent/plugin_helper/cert_option.rb +178 -0
- data/lib/fluent/plugin_helper/child_process.rb +364 -0
- data/lib/fluent/plugin_helper/compat_parameters.rb +333 -0
- data/lib/fluent/plugin_helper/counter.rb +51 -0
- data/lib/fluent/plugin_helper/event_emitter.rb +93 -0
- data/lib/fluent/plugin_helper/event_loop.rb +170 -0
- data/lib/fluent/plugin_helper/extract.rb +104 -0
- data/lib/fluent/plugin_helper/formatter.rb +147 -0
- data/lib/fluent/plugin_helper/http_server/app.rb +79 -0
- data/lib/fluent/plugin_helper/http_server/compat/server.rb +81 -0
- data/lib/fluent/plugin_helper/http_server/compat/webrick_handler.rb +58 -0
- data/lib/fluent/plugin_helper/http_server/methods.rb +35 -0
- data/lib/fluent/plugin_helper/http_server/request.rb +42 -0
- data/lib/fluent/plugin_helper/http_server/router.rb +54 -0
- data/lib/fluent/plugin_helper/http_server/server.rb +87 -0
- data/lib/fluent/plugin_helper/http_server.rb +76 -0
- data/lib/fluent/plugin_helper/inject.rb +151 -0
- data/lib/fluent/plugin_helper/parser.rb +147 -0
- data/lib/fluent/plugin_helper/record_accessor.rb +210 -0
- data/lib/fluent/plugin_helper/retry_state.rb +205 -0
- data/lib/fluent/plugin_helper/server.rb +807 -0
- data/lib/fluent/plugin_helper/socket.rb +250 -0
- data/lib/fluent/plugin_helper/socket_option.rb +80 -0
- data/lib/fluent/plugin_helper/storage.rb +349 -0
- data/lib/fluent/plugin_helper/thread.rb +179 -0
- data/lib/fluent/plugin_helper/timer.rb +92 -0
- data/lib/fluent/plugin_helper.rb +73 -0
- data/lib/fluent/plugin_id.rb +80 -0
- data/lib/fluent/process.rb +3 -489
- data/lib/fluent/registry.rb +52 -10
- data/lib/fluent/root_agent.rb +204 -42
- data/lib/fluent/supervisor.rb +597 -359
- data/lib/fluent/system_config.rb +131 -42
- data/lib/fluent/test/base.rb +6 -54
- data/lib/fluent/test/driver/base.rb +224 -0
- data/lib/fluent/test/driver/base_owned.rb +70 -0
- data/lib/fluent/test/driver/base_owner.rb +135 -0
- data/lib/fluent/test/driver/event_feeder.rb +98 -0
- data/lib/fluent/test/driver/filter.rb +57 -0
- data/lib/fluent/test/driver/formatter.rb +30 -0
- data/lib/fluent/test/driver/input.rb +31 -0
- data/lib/fluent/test/driver/multi_output.rb +53 -0
- data/lib/fluent/test/driver/output.rb +102 -0
- data/lib/fluent/test/driver/parser.rb +30 -0
- data/lib/fluent/test/driver/test_event_router.rb +45 -0
- data/lib/fluent/test/filter_test.rb +0 -1
- data/lib/fluent/test/formatter_test.rb +4 -1
- data/lib/fluent/test/helpers.rb +58 -10
- data/lib/fluent/test/input_test.rb +27 -19
- data/lib/fluent/test/log.rb +79 -0
- data/lib/fluent/test/output_test.rb +28 -39
- data/lib/fluent/test/parser_test.rb +3 -1
- data/lib/fluent/test/startup_shutdown.rb +46 -0
- data/lib/fluent/test.rb +33 -1
- data/lib/fluent/time.rb +450 -1
- data/lib/fluent/timezone.rb +27 -3
- data/lib/fluent/{status.rb → unique_id.rb} +15 -24
- data/lib/fluent/version.rb +1 -1
- data/lib/fluent/winsvc.rb +85 -0
- data/templates/new_gem/Gemfile +3 -0
- data/templates/new_gem/README.md.erb +43 -0
- data/templates/new_gem/Rakefile +13 -0
- data/templates/new_gem/fluent-plugin.gemspec.erb +27 -0
- data/templates/new_gem/lib/fluent/plugin/filter.rb.erb +14 -0
- data/templates/new_gem/lib/fluent/plugin/formatter.rb.erb +14 -0
- data/templates/new_gem/lib/fluent/plugin/input.rb.erb +11 -0
- data/templates/new_gem/lib/fluent/plugin/output.rb.erb +11 -0
- data/templates/new_gem/lib/fluent/plugin/parser.rb.erb +15 -0
- data/templates/new_gem/test/helper.rb.erb +8 -0
- data/templates/new_gem/test/plugin/test_filter.rb.erb +18 -0
- data/templates/new_gem/test/plugin/test_formatter.rb.erb +18 -0
- data/templates/new_gem/test/plugin/test_input.rb.erb +18 -0
- data/templates/new_gem/test/plugin/test_output.rb.erb +18 -0
- data/templates/new_gem/test/plugin/test_parser.rb.erb +18 -0
- data/templates/plugin_config_formatter/param.md-compact.erb +25 -0
- data/templates/plugin_config_formatter/param.md.erb +34 -0
- data/templates/plugin_config_formatter/section.md.erb +12 -0
- data/test/command/test_binlog_reader.rb +346 -0
- data/test/command/test_ca_generate.rb +70 -0
- data/test/command/test_fluentd.rb +901 -0
- data/test/command/test_plugin_config_formatter.rb +276 -0
- data/test/command/test_plugin_generator.rb +92 -0
- data/test/compat/test_calls_super.rb +166 -0
- data/test/compat/test_parser.rb +92 -0
- data/test/config/test_config_parser.rb +126 -2
- data/test/config/test_configurable.rb +946 -187
- data/test/config/test_configure_proxy.rb +424 -74
- data/test/config/test_dsl.rb +11 -11
- data/test/config/test_element.rb +500 -0
- data/test/config/test_literal_parser.rb +8 -0
- data/test/config/test_plugin_configuration.rb +56 -0
- data/test/config/test_section.rb +79 -7
- data/test/config/test_system_config.rb +122 -35
- data/test/config/test_types.rb +38 -0
- data/test/counter/test_client.rb +559 -0
- data/test/counter/test_error.rb +44 -0
- data/test/counter/test_mutex_hash.rb +179 -0
- data/test/counter/test_server.rb +589 -0
- data/test/counter/test_store.rb +258 -0
- data/test/counter/test_validator.rb +137 -0
- data/test/helper.rb +89 -6
- data/test/helpers/fuzzy_assert.rb +89 -0
- data/test/plugin/test_bare_output.rb +118 -0
- data/test/plugin/test_base.rb +115 -0
- data/test/plugin/test_buf_file.rb +823 -460
- data/test/plugin/test_buf_memory.rb +32 -194
- data/test/plugin/test_buffer.rb +1233 -0
- data/test/plugin/test_buffer_chunk.rb +198 -0
- data/test/plugin/test_buffer_file_chunk.rb +844 -0
- data/test/plugin/test_buffer_memory_chunk.rb +338 -0
- data/test/plugin/test_compressable.rb +84 -0
- data/test/plugin/test_filter.rb +357 -0
- data/test/plugin/test_filter_grep.rb +540 -29
- data/test/plugin/test_filter_parser.rb +439 -452
- data/test/plugin/test_filter_record_transformer.rb +123 -166
- data/test/plugin/test_filter_stdout.rb +160 -72
- data/test/plugin/test_formatter_csv.rb +111 -0
- data/test/plugin/test_formatter_hash.rb +35 -0
- data/test/plugin/test_formatter_json.rb +51 -0
- data/test/plugin/test_formatter_ltsv.rb +62 -0
- data/test/plugin/test_formatter_msgpack.rb +28 -0
- data/test/plugin/test_formatter_out_file.rb +95 -0
- data/test/plugin/test_formatter_single_value.rb +38 -0
- data/test/plugin/test_formatter_tsv.rb +68 -0
- data/test/plugin/test_in_debug_agent.rb +24 -1
- data/test/plugin/test_in_dummy.rb +111 -18
- data/test/plugin/test_in_exec.rb +200 -113
- data/test/plugin/test_in_forward.rb +990 -387
- data/test/plugin/test_in_gc_stat.rb +10 -8
- data/test/plugin/test_in_http.rb +600 -224
- data/test/plugin/test_in_monitor_agent.rb +690 -0
- data/test/plugin/test_in_object_space.rb +24 -8
- data/test/plugin/test_in_syslog.rb +154 -215
- data/test/plugin/test_in_tail.rb +1006 -707
- data/test/plugin/test_in_tcp.rb +125 -48
- data/test/plugin/test_in_udp.rb +204 -63
- data/test/plugin/{test_in_stream.rb → test_in_unix.rb} +14 -13
- data/test/plugin/test_input.rb +126 -0
- data/test/plugin/test_metadata.rb +89 -0
- data/test/plugin/test_multi_output.rb +180 -0
- data/test/plugin/test_out_copy.rb +117 -112
- data/test/plugin/test_out_exec.rb +258 -53
- data/test/plugin/test_out_exec_filter.rb +538 -115
- data/test/plugin/test_out_file.rb +865 -178
- data/test/plugin/test_out_forward.rb +998 -210
- data/test/plugin/test_out_null.rb +105 -0
- data/test/plugin/test_out_relabel.rb +28 -0
- data/test/plugin/test_out_roundrobin.rb +36 -29
- data/test/plugin/test_out_secondary_file.rb +458 -0
- data/test/plugin/test_out_stdout.rb +135 -37
- data/test/plugin/test_out_stream.rb +18 -0
- data/test/plugin/test_output.rb +984 -0
- data/test/plugin/test_output_as_buffered.rb +2021 -0
- data/test/plugin/test_output_as_buffered_backup.rb +312 -0
- data/test/plugin/test_output_as_buffered_compress.rb +165 -0
- data/test/plugin/test_output_as_buffered_overflow.rb +250 -0
- data/test/plugin/test_output_as_buffered_retries.rb +911 -0
- data/test/plugin/test_output_as_buffered_secondary.rb +874 -0
- data/test/plugin/test_output_as_standard.rb +374 -0
- data/test/plugin/test_owned_by.rb +35 -0
- data/test/plugin/test_parser.rb +359 -0
- data/test/plugin/test_parser_apache.rb +42 -0
- data/test/plugin/test_parser_apache2.rb +47 -0
- data/test/plugin/test_parser_apache_error.rb +45 -0
- data/test/plugin/test_parser_csv.rb +103 -0
- data/test/plugin/test_parser_json.rb +138 -0
- data/test/plugin/test_parser_labeled_tsv.rb +145 -0
- data/test/plugin/test_parser_multiline.rb +100 -0
- data/test/plugin/test_parser_nginx.rb +88 -0
- data/test/plugin/test_parser_none.rb +52 -0
- data/test/plugin/test_parser_regexp.rb +289 -0
- data/test/plugin/test_parser_syslog.rb +441 -0
- data/test/plugin/test_parser_tsv.rb +122 -0
- data/test/plugin/test_storage.rb +167 -0
- data/test/plugin/test_storage_local.rb +335 -0
- data/test/plugin_helper/data/cert/cert-key.pem +27 -0
- data/test/plugin_helper/data/cert/cert-with-no-newline.pem +19 -0
- data/test/plugin_helper/data/cert/cert.pem +19 -0
- data/test/plugin_helper/http_server/test_app.rb +65 -0
- data/test/plugin_helper/http_server/test_route.rb +32 -0
- data/test/plugin_helper/test_cert_option.rb +16 -0
- data/test/plugin_helper/test_child_process.rb +794 -0
- data/test/plugin_helper/test_compat_parameters.rb +353 -0
- data/test/plugin_helper/test_event_emitter.rb +51 -0
- data/test/plugin_helper/test_event_loop.rb +52 -0
- data/test/plugin_helper/test_extract.rb +194 -0
- data/test/plugin_helper/test_formatter.rb +255 -0
- data/test/plugin_helper/test_http_server_helper.rb +205 -0
- data/test/plugin_helper/test_inject.rb +519 -0
- data/test/plugin_helper/test_parser.rb +264 -0
- data/test/plugin_helper/test_record_accessor.rb +197 -0
- data/test/plugin_helper/test_retry_state.rb +442 -0
- data/test/plugin_helper/test_server.rb +1714 -0
- data/test/plugin_helper/test_storage.rb +542 -0
- data/test/plugin_helper/test_thread.rb +164 -0
- data/test/plugin_helper/test_timer.rb +132 -0
- data/test/scripts/exec_script.rb +0 -6
- data/test/scripts/fluent/plugin/formatter1/formatter_test1.rb +7 -0
- data/test/scripts/fluent/plugin/formatter2/formatter_test2.rb +7 -0
- data/test/scripts/fluent/plugin/out_test.rb +23 -15
- data/test/scripts/fluent/plugin/out_test2.rb +80 -0
- data/test/test_clock.rb +164 -0
- data/test/test_config.rb +16 -7
- data/test/test_configdsl.rb +2 -2
- data/test/test_event.rb +360 -13
- data/test/test_event_router.rb +108 -11
- data/test/test_event_time.rb +199 -0
- data/test/test_filter.rb +48 -6
- data/test/test_formatter.rb +11 -391
- data/test/test_input.rb +1 -1
- data/test/test_log.rb +591 -31
- data/test/test_mixin.rb +1 -1
- data/test/test_output.rb +121 -185
- data/test/test_plugin.rb +251 -0
- data/test/test_plugin_classes.rb +177 -10
- data/test/test_plugin_helper.rb +81 -0
- data/test/test_plugin_id.rb +101 -0
- data/test/test_process.rb +8 -42
- data/test/test_root_agent.rb +766 -21
- data/test/test_supervisor.rb +481 -0
- data/test/test_test_drivers.rb +135 -0
- data/test/test_time_formatter.rb +282 -0
- data/test/test_time_parser.rb +231 -0
- data/test/test_unique_id.rb +47 -0
- metadata +454 -60
- data/COPYING +0 -14
- data/ChangeLog +0 -666
- data/lib/fluent/buffer.rb +0 -365
- data/lib/fluent/plugin/in_status.rb +0 -76
- data/test/plugin/test_in_status.rb +0 -38
- data/test/test_buffer.rb +0 -624
- data/test/test_parser.rb +0 -1305
@@ -16,18 +16,42 @@
|
|
16
16
|
|
17
17
|
require 'cool.io'
|
18
18
|
|
19
|
-
require 'fluent/input'
|
19
|
+
require 'fluent/plugin/input'
|
20
20
|
require 'fluent/config/error'
|
21
21
|
require 'fluent/event'
|
22
|
+
require 'fluent/plugin/buffer'
|
23
|
+
require 'fluent/plugin/parser_multiline'
|
22
24
|
|
23
|
-
|
24
|
-
|
25
|
-
|
25
|
+
if Fluent.windows?
|
26
|
+
require_relative 'file_wrapper'
|
27
|
+
else
|
28
|
+
Fluent::FileWrapper = File
|
29
|
+
end
|
30
|
+
|
31
|
+
module Fluent::Plugin
|
32
|
+
class TailInput < Fluent::Plugin::Input
|
33
|
+
Fluent::Plugin.register_input('tail', self)
|
34
|
+
|
35
|
+
helpers :timer, :event_loop, :parser, :compat_parameters
|
36
|
+
|
37
|
+
class WatcherSetupError < StandardError
|
38
|
+
def initialize(msg)
|
39
|
+
@message = msg
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_s
|
43
|
+
@message
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
FILE_PERMISSION = 0644
|
26
48
|
|
27
49
|
def initialize
|
28
50
|
super
|
29
51
|
@paths = []
|
30
52
|
@tails = {}
|
53
|
+
@pf_file = nil
|
54
|
+
@pf = nil
|
31
55
|
@ignore_list = []
|
32
56
|
end
|
33
57
|
|
@@ -43,6 +67,9 @@ module Fluent
|
|
43
67
|
config_param :pos_file, :string, default: nil
|
44
68
|
desc 'Start to read the logs from the head of file, not bottom.'
|
45
69
|
config_param :read_from_head, :bool, default: false
|
70
|
+
# When the program deletes log file and re-creates log file with same filename after passed refresh_interval,
|
71
|
+
# in_tail may raise a pos_file related error. This is a known issue but there is no such program on production.
|
72
|
+
# If we find such program / application, we will fix the problem.
|
46
73
|
desc 'The interval of refreshing the list of watch file.'
|
47
74
|
config_param :refresh_interval, :time, default: 60
|
48
75
|
desc 'The number of reading lines at each IO.'
|
@@ -53,12 +80,16 @@ module Fluent
|
|
53
80
|
config_param :emit_unmatched_lines, :bool, default: false
|
54
81
|
desc 'Enable the additional watch timer.'
|
55
82
|
config_param :enable_watch_timer, :bool, default: true
|
83
|
+
desc 'Enable the stat watcher based on inotify.'
|
84
|
+
config_param :enable_stat_watcher, :bool, default: true
|
56
85
|
desc 'The encoding after conversion of the input.'
|
57
86
|
config_param :encoding, :string, default: nil
|
58
87
|
desc 'The encoding of the input.'
|
59
88
|
config_param :from_encoding, :string, default: nil
|
60
89
|
desc 'Add the log path being tailed to records. Specify the field name to be used.'
|
61
90
|
config_param :path_key, :string, default: nil
|
91
|
+
desc 'Open and close the file on every update instead of leaving it open until it gets rotated.'
|
92
|
+
config_param :open_on_every_update, :bool, default: false
|
62
93
|
desc 'Limit the watching files that the modification time is within the specified time range (when use \'*\' in path).'
|
63
94
|
config_param :limit_recently_modified, :time, default: nil
|
64
95
|
desc 'Enable the option to skip the refresh of watching list on startup.'
|
@@ -68,34 +99,56 @@ module Fluent
|
|
68
99
|
|
69
100
|
attr_reader :paths
|
70
101
|
|
102
|
+
@@pos_file_paths = {}
|
103
|
+
|
71
104
|
def configure(conf)
|
105
|
+
compat_parameters_convert(conf, :parser)
|
106
|
+
parser_config = conf.elements('parse').first
|
107
|
+
unless parser_config
|
108
|
+
raise Fluent::ConfigError, "<parse> section is required."
|
109
|
+
end
|
110
|
+
unless parser_config["@type"]
|
111
|
+
raise Fluent::ConfigError, "parse/@type is required."
|
112
|
+
end
|
113
|
+
|
114
|
+
(1..Fluent::Plugin::MultilineParser::FORMAT_MAX_NUM).each do |n|
|
115
|
+
parser_config["format#{n}"] = conf["format#{n}"] if conf["format#{n}"]
|
116
|
+
end
|
117
|
+
|
72
118
|
super
|
73
119
|
|
120
|
+
if !@enable_watch_timer && !@enable_stat_watcher
|
121
|
+
raise Fluent::ConfigError, "either of enable_watch_timer or enable_stat_watcher must be true"
|
122
|
+
end
|
123
|
+
|
74
124
|
@paths = @path.split(',').map {|path| path.strip }
|
75
125
|
if @paths.empty?
|
76
|
-
raise ConfigError, "tail: 'path' parameter is required on tail input"
|
126
|
+
raise Fluent::ConfigError, "tail: 'path' parameter is required on tail input"
|
77
127
|
end
|
78
128
|
|
79
|
-
|
129
|
+
# TODO: Use plugin_root_dir and storage plugin to store positions if available
|
130
|
+
if @pos_file
|
131
|
+
if @@pos_file_paths.has_key?(@pos_file) && !called_in_test?
|
132
|
+
plugin_id_using_this_path = @@pos_file_paths[@pos_file]
|
133
|
+
raise Fluent::ConfigError, "Other 'in_tail' plugin already use same pos_file path: plugin_id = #{plugin_id_using_this_path}, pos_file path = #{@pos_file}"
|
134
|
+
end
|
135
|
+
@@pos_file_paths[@pos_file] = self.plugin_id
|
136
|
+
else
|
80
137
|
$log.warn "'pos_file PATH' parameter is not set to a 'tail' source."
|
81
138
|
$log.warn "this parameter is highly recommended to save the position to resume tailing."
|
82
139
|
end
|
83
140
|
|
84
|
-
configure_parser(conf)
|
85
141
|
configure_tag
|
86
142
|
configure_encoding
|
87
143
|
|
88
|
-
@multiline_mode =
|
144
|
+
@multiline_mode = parser_config["@type"] =~ /multiline/
|
89
145
|
@receive_handler = if @multiline_mode
|
90
146
|
method(:parse_multilines)
|
91
147
|
else
|
92
148
|
method(:parse_singleline)
|
93
149
|
end
|
94
|
-
|
95
|
-
|
96
|
-
def configure_parser(conf)
|
97
|
-
@parser = Plugin.new_parser(conf['format'])
|
98
|
-
@parser.configure(conf)
|
150
|
+
@file_perm = system_config.file_permission || FILE_PERMISSION
|
151
|
+
@parser = parser_create(conf: parser_config)
|
99
152
|
end
|
100
153
|
|
101
154
|
def configure_tag
|
@@ -112,44 +165,52 @@ module Fluent
|
|
112
165
|
def configure_encoding
|
113
166
|
unless @encoding
|
114
167
|
if @from_encoding
|
115
|
-
raise ConfigError, "tail: 'from_encoding' parameter must be specified with 'encoding' parameter."
|
168
|
+
raise Fluent::ConfigError, "tail: 'from_encoding' parameter must be specified with 'encoding' parameter."
|
116
169
|
end
|
117
170
|
end
|
118
171
|
|
119
172
|
@encoding = parse_encoding_param(@encoding) if @encoding
|
120
173
|
@from_encoding = parse_encoding_param(@from_encoding) if @from_encoding
|
174
|
+
if @encoding && (@encoding == @from_encoding)
|
175
|
+
log.warn "'encoding' and 'from_encoding' are same encoding. No effect"
|
176
|
+
end
|
121
177
|
end
|
122
178
|
|
123
179
|
def parse_encoding_param(encoding_name)
|
124
180
|
begin
|
125
181
|
Encoding.find(encoding_name) if encoding_name
|
126
182
|
rescue ArgumentError => e
|
127
|
-
raise ConfigError, e.message
|
183
|
+
raise Fluent::ConfigError, e.message
|
128
184
|
end
|
129
185
|
end
|
130
186
|
|
131
187
|
def start
|
188
|
+
super
|
189
|
+
|
132
190
|
if @pos_file
|
133
|
-
|
191
|
+
pos_file_dir = File.dirname(@pos_file)
|
192
|
+
FileUtils.mkdir_p(pos_file_dir) unless Dir.exist?(pos_file_dir)
|
193
|
+
@pf_file = File.open(@pos_file, File::RDWR|File::CREAT|File::BINARY, @file_perm)
|
134
194
|
@pf_file.sync = true
|
135
195
|
@pf = PositionFile.parse(@pf_file)
|
136
196
|
end
|
137
197
|
|
138
|
-
@loop = Coolio::Loop.new
|
139
198
|
refresh_watchers unless @skip_refresh_on_startup
|
140
|
-
|
141
|
-
@refresh_trigger = TailWatcher::TimerWatcher.new(@refresh_interval, true, log, &method(:refresh_watchers))
|
142
|
-
@refresh_trigger.attach(@loop)
|
143
|
-
@thread = Thread.new(&method(:run))
|
199
|
+
timer_execute(:in_tail_refresh_watchers, @refresh_interval, &method(:refresh_watchers))
|
144
200
|
end
|
145
201
|
|
146
202
|
def shutdown
|
147
|
-
|
148
|
-
|
149
|
-
stop_watchers(@tails.keys, true)
|
150
|
-
@loop.stop rescue nil # when all watchers are detached, `stop` raises RuntimeError. We can ignore this exception.
|
151
|
-
@thread.join
|
203
|
+
# during shutdown phase, don't close io. It should be done in close after all threads are stopped. See close.
|
204
|
+
stop_watchers(@tails.keys, immediate: true, remove_watcher: false)
|
152
205
|
@pf_file.close if @pf_file
|
206
|
+
|
207
|
+
super
|
208
|
+
end
|
209
|
+
|
210
|
+
def close
|
211
|
+
super
|
212
|
+
# close file handles after all threads stopped (in #close of thread plugin helper)
|
213
|
+
close_watcher_handles
|
153
214
|
end
|
154
215
|
|
155
216
|
def expand_paths
|
@@ -160,20 +221,25 @@ module Fluent
|
|
160
221
|
path = date.strftime(path)
|
161
222
|
if path.include?('*')
|
162
223
|
paths += Dir.glob(path).select { |p|
|
163
|
-
|
164
|
-
|
165
|
-
if
|
166
|
-
|
224
|
+
begin
|
225
|
+
is_file = !File.directory?(p)
|
226
|
+
if File.readable?(p) && is_file
|
227
|
+
if @limit_recently_modified && File.mtime(p) < (date - @limit_recently_modified)
|
228
|
+
false
|
229
|
+
else
|
230
|
+
true
|
231
|
+
end
|
167
232
|
else
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
log.warn "#{p} unreadable. It is excluded and would be examined next time."
|
174
|
-
@ignore_list << path if @ignore_repeated_permission_error
|
233
|
+
if is_file
|
234
|
+
unless @ignore_list.include?(path)
|
235
|
+
log.warn "#{p} unreadable. It is excluded and would be examined next time."
|
236
|
+
@ignore_list << path if @ignore_repeated_permission_error
|
237
|
+
end
|
175
238
|
end
|
239
|
+
false
|
176
240
|
end
|
241
|
+
rescue Errno::ENOENT
|
242
|
+
log.debug("#{p} is missing after refresh file list")
|
177
243
|
false
|
178
244
|
end
|
179
245
|
}
|
@@ -195,18 +261,32 @@ module Fluent
|
|
195
261
|
target_paths = expand_paths
|
196
262
|
existence_paths = @tails.keys
|
197
263
|
|
264
|
+
log.debug { "tailing paths: target = #{target_paths.join(",")} | existing = #{existence_paths.join(",")}" }
|
265
|
+
|
198
266
|
unwatched = existence_paths - target_paths
|
199
267
|
added = target_paths - existence_paths
|
200
268
|
|
201
|
-
stop_watchers(unwatched, false, true) unless unwatched.empty?
|
269
|
+
stop_watchers(unwatched, immediate: false, unwatched: true) unless unwatched.empty?
|
202
270
|
start_watchers(added) unless added.empty?
|
203
271
|
end
|
204
272
|
|
205
273
|
def setup_watcher(path, pe)
|
206
274
|
line_buffer_timer_flusher = (@multiline_mode && @multiline_flush_interval) ? TailWatcher::LineBufferTimerFlusher.new(log, @multiline_flush_interval, &method(:flush_buffer)) : nil
|
207
|
-
tw = TailWatcher.new(path, @rotate_wait, pe, log, @read_from_head, @enable_watch_timer, @read_lines_limit, method(:update_watcher), line_buffer_timer_flusher,
|
208
|
-
tw.attach
|
275
|
+
tw = TailWatcher.new(path, @rotate_wait, pe, log, @read_from_head, @enable_watch_timer, @enable_stat_watcher, @read_lines_limit, method(:update_watcher), line_buffer_timer_flusher, @from_encoding, @encoding, open_on_every_update, &method(:receive_lines))
|
276
|
+
tw.attach do |watcher|
|
277
|
+
event_loop_attach(watcher.timer_trigger) if watcher.timer_trigger
|
278
|
+
event_loop_attach(watcher.stat_trigger) if watcher.stat_trigger
|
279
|
+
end
|
209
280
|
tw
|
281
|
+
rescue => e
|
282
|
+
if tw
|
283
|
+
tw.detach { |watcher|
|
284
|
+
event_loop_detach(watcher.timer_trigger) if watcher.timer_trigger
|
285
|
+
event_loop_detach(watcher.stat_trigger) if watcher.stat_trigger
|
286
|
+
}
|
287
|
+
tw.close
|
288
|
+
end
|
289
|
+
raise e
|
210
290
|
end
|
211
291
|
|
212
292
|
def start_watchers(paths)
|
@@ -216,65 +296,86 @@ module Fluent
|
|
216
296
|
pe = @pf[path]
|
217
297
|
if @read_from_head && pe.read_inode.zero?
|
218
298
|
begin
|
219
|
-
pe.update(
|
299
|
+
pe.update(Fluent::FileWrapper.stat(path).ino, 0)
|
220
300
|
rescue Errno::ENOENT
|
221
301
|
$log.warn "#{path} not found. Continuing without tailing it."
|
222
302
|
end
|
223
303
|
end
|
224
304
|
end
|
225
305
|
|
226
|
-
|
306
|
+
begin
|
307
|
+
tw = setup_watcher(path, pe)
|
308
|
+
rescue WatcherSetupError => e
|
309
|
+
log.warn "Skip #{path} because unexpected setup error happens: #{e}"
|
310
|
+
next
|
311
|
+
end
|
312
|
+
@tails[path] = tw
|
227
313
|
}
|
228
314
|
end
|
229
315
|
|
230
|
-
def stop_watchers(paths, immediate
|
316
|
+
def stop_watchers(paths, immediate: false, unwatched: false, remove_watcher: true)
|
231
317
|
paths.each { |path|
|
232
|
-
tw = @tails.delete(path)
|
318
|
+
tw = remove_watcher ? @tails.delete(path) : @tails[path]
|
233
319
|
if tw
|
234
320
|
tw.unwatched = unwatched
|
235
321
|
if immediate
|
236
|
-
|
322
|
+
detach_watcher(tw, false)
|
237
323
|
else
|
238
|
-
|
324
|
+
detach_watcher_after_rotate_wait(tw)
|
239
325
|
end
|
240
326
|
end
|
241
327
|
}
|
242
328
|
end
|
243
329
|
|
330
|
+
def close_watcher_handles
|
331
|
+
@tails.keys.each do |path|
|
332
|
+
tw = @tails.delete(path)
|
333
|
+
if tw
|
334
|
+
tw.close
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
244
339
|
# refresh_watchers calls @tails.keys so we don't use stop_watcher -> start_watcher sequence for safety.
|
245
340
|
def update_watcher(path, pe)
|
341
|
+
if @pf
|
342
|
+
unless pe.read_inode == @pf[path].read_inode
|
343
|
+
log.debug "Skip update_watcher because watcher has been already updated by other inotify event"
|
344
|
+
return
|
345
|
+
end
|
346
|
+
end
|
246
347
|
rotated_tw = @tails[path]
|
247
348
|
@tails[path] = setup_watcher(path, pe)
|
248
|
-
|
349
|
+
detach_watcher_after_rotate_wait(rotated_tw) if rotated_tw
|
249
350
|
end
|
250
351
|
|
251
352
|
# TailWatcher#close is called by another thread at shutdown phase.
|
252
353
|
# It causes 'can't modify string; temporarily locked' error in IOHandler
|
253
354
|
# so adding close_io argument to avoid this problem.
|
254
355
|
# At shutdown, IOHandler's io will be released automatically after detached the event loop
|
255
|
-
def
|
256
|
-
tw.
|
356
|
+
def detach_watcher(tw, close_io = true)
|
357
|
+
tw.detach { |watcher|
|
358
|
+
event_loop_detach(watcher.timer_trigger) if watcher.timer_trigger
|
359
|
+
event_loop_detach(watcher.stat_trigger) if watcher.stat_trigger
|
360
|
+
}
|
361
|
+
tw.close if close_io
|
257
362
|
flush_buffer(tw)
|
258
363
|
if tw.unwatched && @pf
|
259
364
|
@pf[tw.path].update_pos(PositionFile::UNWATCHED_POSITION)
|
260
365
|
end
|
261
366
|
end
|
262
367
|
|
263
|
-
def
|
264
|
-
|
265
|
-
|
368
|
+
def detach_watcher_after_rotate_wait(tw)
|
369
|
+
# Call event_loop_attach/event_loop_detach is high-cost for short-live object.
|
370
|
+
# If this has a problem with large number of files, use @_event_loop directly instead of timer_execute.
|
371
|
+
timer_execute(:in_tail_close_watcher, @rotate_wait, repeat: false) do
|
372
|
+
detach_watcher(tw)
|
373
|
+
end
|
266
374
|
end
|
267
375
|
|
268
376
|
def flush_buffer(tw)
|
269
377
|
if lb = tw.line_buffer
|
270
378
|
lb.chomp!
|
271
|
-
if @encoding
|
272
|
-
if @from_encoding
|
273
|
-
lb.encode!(@encoding, @from_encoding)
|
274
|
-
else
|
275
|
-
lb.force_encoding(@encoding)
|
276
|
-
end
|
277
|
-
end
|
278
379
|
@parser.parse(lb) { |time, record|
|
279
380
|
if time && record
|
280
381
|
tag = if @tag_prefix || @tag_suffix
|
@@ -291,14 +392,7 @@ module Fluent
|
|
291
392
|
end
|
292
393
|
end
|
293
394
|
|
294
|
-
|
295
|
-
@loop.run
|
296
|
-
rescue
|
297
|
-
log.error "unexpected error", error: $!.to_s
|
298
|
-
log.error_backtrace
|
299
|
-
end
|
300
|
-
|
301
|
-
# @return true if no error or unrecoverable error happens in emit action. false if got BufferQueueLimitError
|
395
|
+
# @return true if no error or unrecoverable error happens in emit action. false if got BufferOverflowError
|
302
396
|
def receive_lines(lines, tail_watcher)
|
303
397
|
es = @receive_handler.call(lines, tail_watcher)
|
304
398
|
unless es.empty?
|
@@ -309,7 +403,7 @@ module Fluent
|
|
309
403
|
end
|
310
404
|
begin
|
311
405
|
router.emit_stream(tag, es)
|
312
|
-
rescue
|
406
|
+
rescue Fluent::Plugin::Buffer::BufferOverflowError
|
313
407
|
return false
|
314
408
|
rescue
|
315
409
|
# ignore non BufferQueueLimitError errors because in_tail can't recover. Engine shows logs and backtraces.
|
@@ -323,13 +417,6 @@ module Fluent
|
|
323
417
|
def convert_line_to_event(line, es, tail_watcher)
|
324
418
|
begin
|
325
419
|
line.chomp! # remove \n
|
326
|
-
if @encoding
|
327
|
-
if @from_encoding
|
328
|
-
line.encode!(@encoding, @from_encoding)
|
329
|
-
else
|
330
|
-
line.force_encoding(@encoding)
|
331
|
-
end
|
332
|
-
end
|
333
420
|
@parser.parse(line) { |time, record|
|
334
421
|
if time && record
|
335
422
|
record[@path_key] ||= tail_watcher.path unless @path_key.nil?
|
@@ -338,9 +425,9 @@ module Fluent
|
|
338
425
|
if @emit_unmatched_lines
|
339
426
|
record = {'unmatched_line' => line}
|
340
427
|
record[@path_key] ||= tail_watcher.path unless @path_key.nil?
|
341
|
-
es.add(
|
428
|
+
es.add(Fluent::EventTime.now, record)
|
342
429
|
end
|
343
|
-
log.warn "pattern not
|
430
|
+
log.warn "pattern not matched: #{line.inspect}"
|
344
431
|
end
|
345
432
|
}
|
346
433
|
rescue => e
|
@@ -350,7 +437,7 @@ module Fluent
|
|
350
437
|
end
|
351
438
|
|
352
439
|
def parse_singleline(lines, tail_watcher)
|
353
|
-
es = MultiEventStream.new
|
440
|
+
es = Fluent::MultiEventStream.new
|
354
441
|
lines.each { |line|
|
355
442
|
convert_line_to_event(line, es, tail_watcher)
|
356
443
|
}
|
@@ -359,7 +446,7 @@ module Fluent
|
|
359
446
|
|
360
447
|
def parse_multilines(lines, tail_watcher)
|
361
448
|
lb = tail_watcher.line_buffer
|
362
|
-
es = MultiEventStream.new
|
449
|
+
es = Fluent::MultiEventStream.new
|
363
450
|
if @parser.has_firstline?
|
364
451
|
tail_watcher.line_buffer_timer_flusher.reset_timer if tail_watcher.line_buffer_timer_flusher
|
365
452
|
lines.each { |line|
|
@@ -396,28 +483,35 @@ module Fluent
|
|
396
483
|
end
|
397
484
|
|
398
485
|
class TailWatcher
|
399
|
-
def initialize(path, rotate_wait, pe, log, read_from_head, enable_watch_timer, read_lines_limit, update_watcher, line_buffer_timer_flusher, &receive_lines)
|
486
|
+
def initialize(path, rotate_wait, pe, log, read_from_head, enable_watch_timer, enable_stat_watcher, read_lines_limit, update_watcher, line_buffer_timer_flusher, from_encoding, encoding, open_on_every_update, &receive_lines)
|
400
487
|
@path = path
|
401
488
|
@rotate_wait = rotate_wait
|
402
489
|
@pe = pe || MemoryPositionEntry.new
|
403
490
|
@read_from_head = read_from_head
|
404
491
|
@enable_watch_timer = enable_watch_timer
|
492
|
+
@enable_stat_watcher = enable_stat_watcher
|
405
493
|
@read_lines_limit = read_lines_limit
|
406
494
|
@receive_lines = receive_lines
|
407
495
|
@update_watcher = update_watcher
|
408
496
|
|
409
|
-
@
|
497
|
+
@stat_trigger = @enable_stat_watcher ? StatWatcher.new(self, &method(:on_notify)) : nil
|
498
|
+
@timer_trigger = @enable_watch_timer ? TimerTrigger.new(1, log, &method(:on_notify)) : nil
|
410
499
|
|
411
|
-
@
|
412
|
-
|
413
|
-
@rotate_handler = RotateHandler.new(path, log, &method(:on_rotate))
|
500
|
+
@rotate_handler = RotateHandler.new(self, &method(:on_rotate))
|
414
501
|
@io_handler = nil
|
415
502
|
@log = log
|
416
503
|
|
417
504
|
@line_buffer_timer_flusher = line_buffer_timer_flusher
|
505
|
+
@from_encoding = from_encoding
|
506
|
+
@encoding = encoding
|
507
|
+
@open_on_every_update = open_on_every_update
|
418
508
|
end
|
419
509
|
|
420
510
|
attr_reader :path
|
511
|
+
attr_reader :log, :pe, :read_lines_limit, :open_on_every_update
|
512
|
+
attr_reader :from_encoding, :encoding
|
513
|
+
attr_reader :stat_trigger, :enable_watch_timer, :enable_stat_watcher
|
514
|
+
attr_accessor :timer_trigger
|
421
515
|
attr_accessor :line_buffer, :line_buffer_timer_flusher
|
422
516
|
attr_accessor :unwatched # This is used for removing position entry from PositionFile
|
423
517
|
|
@@ -429,37 +523,40 @@ module Fluent
|
|
429
523
|
@receive_lines.call(lines, self)
|
430
524
|
end
|
431
525
|
|
432
|
-
def attach
|
433
|
-
@timer_trigger.attach(loop) if @enable_watch_timer
|
434
|
-
@stat_trigger.attach(loop)
|
526
|
+
def attach
|
435
527
|
on_notify
|
528
|
+
yield self
|
436
529
|
end
|
437
530
|
|
438
531
|
def detach
|
439
|
-
|
440
|
-
@
|
532
|
+
yield self
|
533
|
+
@io_handler.on_notify if @io_handler
|
441
534
|
end
|
442
535
|
|
443
|
-
def close
|
444
|
-
if
|
445
|
-
@io_handler.on_notify
|
536
|
+
def close
|
537
|
+
if @io_handler
|
446
538
|
@io_handler.close
|
539
|
+
@io_handler = nil
|
447
540
|
end
|
448
|
-
detach
|
449
541
|
end
|
450
542
|
|
451
543
|
def on_notify
|
452
|
-
|
544
|
+
begin
|
545
|
+
stat = Fluent::FileWrapper.stat(@path)
|
546
|
+
rescue Errno::ENOENT
|
547
|
+
# moved or deleted
|
548
|
+
stat = nil
|
549
|
+
end
|
550
|
+
|
551
|
+
@rotate_handler.on_notify(stat) if @rotate_handler
|
453
552
|
@line_buffer_timer_flusher.on_notify(self) if @line_buffer_timer_flusher
|
454
|
-
|
455
|
-
@io_handler.on_notify
|
553
|
+
@io_handler.on_notify if @io_handler
|
456
554
|
end
|
457
555
|
|
458
|
-
def on_rotate(
|
459
|
-
if @io_handler
|
460
|
-
if
|
556
|
+
def on_rotate(stat)
|
557
|
+
if @io_handler.nil?
|
558
|
+
if stat
|
461
559
|
# first time
|
462
|
-
stat = io.stat
|
463
560
|
fsize = stat.size
|
464
561
|
inode = stat.ino
|
465
562
|
|
@@ -469,14 +566,15 @@ module Fluent
|
|
469
566
|
# assuming following situation:
|
470
567
|
# a) file was once renamed and backed, or
|
471
568
|
# b) symlink or hardlink to the same file is recreated
|
472
|
-
# in either case, seek to the saved position
|
473
|
-
|
569
|
+
# in either case of a and b, seek to the saved position
|
570
|
+
# c) file was once renamed, truncated and then backed
|
571
|
+
# in this case, consider it truncated
|
572
|
+
@pe.update(inode, 0) if fsize < @pe.read_pos
|
474
573
|
elsif last_inode != 0
|
475
574
|
# this is FilePositionEntry and fluentd once started.
|
476
575
|
# read data from the head of the rotated file.
|
477
576
|
# logs never duplicate because this file is a rotated new file.
|
478
|
-
|
479
|
-
@pe.update(inode, pos)
|
577
|
+
@pe.update(inode, 0)
|
480
578
|
else
|
481
579
|
# this is MemoryPositionEntry or this is the first time fluentd started.
|
482
580
|
# seek to the end of the any files.
|
@@ -485,161 +583,224 @@ module Fluent
|
|
485
583
|
pos = @read_from_head ? 0 : fsize
|
486
584
|
@pe.update(inode, pos)
|
487
585
|
end
|
488
|
-
|
489
|
-
|
490
|
-
@io_handler = IOHandler.new(io, @pe, @log, @read_lines_limit, &method(:wrap_receive_lines))
|
586
|
+
@io_handler = IOHandler.new(self, &method(:wrap_receive_lines))
|
491
587
|
else
|
492
588
|
@io_handler = NullIOHandler.new
|
493
589
|
end
|
494
590
|
else
|
495
|
-
|
496
|
-
log_msg << "; waiting #{@rotate_wait} seconds" if @io_handler.io # wait rotate_time if previous file is exist
|
497
|
-
@log.info log_msg
|
591
|
+
watcher_needs_update = false
|
498
592
|
|
499
|
-
if
|
500
|
-
stat = io.stat
|
593
|
+
if stat
|
501
594
|
inode = stat.ino
|
502
595
|
if inode == @pe.read_inode # truncated
|
503
|
-
@pe.update_pos(
|
504
|
-
io_handler = IOHandler.new(io, @pe, @log, @read_lines_limit, &method(:wrap_receive_lines))
|
596
|
+
@pe.update_pos(0)
|
505
597
|
@io_handler.close
|
506
|
-
|
507
|
-
|
508
|
-
@pe.update(inode, io.pos)
|
509
|
-
io_handler = IOHandler.new(io, @pe, @log, @read_lines_limit, &method(:wrap_receive_lines))
|
510
|
-
@io_handler = io_handler
|
598
|
+
elsif !@io_handler.opened? # There is no previous file. Reuse TailWatcher
|
599
|
+
@pe.update(inode, 0)
|
511
600
|
else # file is rotated and new file found
|
512
|
-
|
513
|
-
|
601
|
+
watcher_needs_update = true
|
602
|
+
# Handle the old log file before renewing TailWatcher [fluentd#1055]
|
603
|
+
@io_handler.on_notify
|
514
604
|
end
|
515
605
|
else # file is rotated and new file not found
|
516
606
|
# Clear RotateHandler to avoid duplicated file watch in same path.
|
517
607
|
@rotate_handler = nil
|
518
|
-
|
519
|
-
@update_watcher.call(@path, swap_state(@pe))
|
608
|
+
watcher_needs_update = true
|
520
609
|
end
|
521
|
-
end
|
522
610
|
|
523
|
-
|
524
|
-
#
|
525
|
-
|
526
|
-
mpe.update(pe.read_inode, pe.read_pos)
|
527
|
-
@pe = mpe
|
528
|
-
@io_handler.pe = mpe # Don't re-create IOHandler because IOHandler has an internal buffer.
|
611
|
+
log_msg = "detected rotation of #{@path}"
|
612
|
+
log_msg << "; waiting #{@rotate_wait} seconds" if watcher_needs_update # wait rotate_time if previous file exists
|
613
|
+
@log.info log_msg
|
529
614
|
|
530
|
-
|
615
|
+
if watcher_needs_update
|
616
|
+
@update_watcher.call(@path, swap_state(@pe))
|
617
|
+
else
|
618
|
+
@io_handler = IOHandler.new(self, &method(:wrap_receive_lines))
|
619
|
+
end
|
531
620
|
end
|
532
621
|
end
|
533
622
|
|
534
|
-
|
535
|
-
|
623
|
+
def swap_state(pe)
|
624
|
+
# Use MemoryPositionEntry for rotated file temporary
|
625
|
+
mpe = MemoryPositionEntry.new
|
626
|
+
mpe.update(pe.read_inode, pe.read_pos)
|
627
|
+
@pe = mpe
|
628
|
+
pe # This pe will be updated in on_rotate after TailWatcher is initialized
|
629
|
+
end
|
630
|
+
|
631
|
+
class TimerTrigger < Coolio::TimerWatcher
|
632
|
+
def initialize(interval, log, &callback)
|
536
633
|
@callback = callback
|
537
634
|
@log = log
|
538
|
-
super(interval,
|
635
|
+
super(interval, true)
|
539
636
|
end
|
540
637
|
|
541
638
|
def on_timer
|
542
639
|
@callback.call
|
543
|
-
rescue
|
544
|
-
|
545
|
-
@log.error $!.to_s
|
640
|
+
rescue => e
|
641
|
+
@log.error e.to_s
|
546
642
|
@log.error_backtrace
|
547
643
|
end
|
548
644
|
end
|
549
645
|
|
550
646
|
class StatWatcher < Coolio::StatWatcher
|
551
|
-
def initialize(
|
647
|
+
def initialize(watcher, &callback)
|
648
|
+
@watcher = watcher
|
552
649
|
@callback = callback
|
553
|
-
|
554
|
-
super(path)
|
650
|
+
super(watcher.path)
|
555
651
|
end
|
556
652
|
|
557
653
|
def on_change(prev, cur)
|
558
654
|
@callback.call
|
559
655
|
rescue
|
560
656
|
# TODO log?
|
561
|
-
@log.error $!.to_s
|
562
|
-
@log.error_backtrace
|
657
|
+
@watcher.log.error $!.to_s
|
658
|
+
@watcher.log.error_backtrace
|
659
|
+
end
|
660
|
+
end
|
661
|
+
|
662
|
+
class FIFO
|
663
|
+
def initialize(from_encoding, encoding)
|
664
|
+
@from_encoding = from_encoding
|
665
|
+
@encoding = encoding
|
666
|
+
@need_enc = from_encoding != encoding
|
667
|
+
@buffer = ''.force_encoding(from_encoding)
|
668
|
+
@eol = "\n".encode(from_encoding).freeze
|
669
|
+
end
|
670
|
+
|
671
|
+
attr_reader :from_encoding, :encoding, :buffer
|
672
|
+
|
673
|
+
def <<(chunk)
|
674
|
+
# Although "chunk" is most likely transient besides String#force_encoding itself
|
675
|
+
# won't affect the actual content of it, it is also probable that "chunk" is
|
676
|
+
# a reused buffer and changing its encoding causes some problems on the caller side.
|
677
|
+
#
|
678
|
+
# Actually, the caller here is specific and "chunk" comes from IO#partial with
|
679
|
+
# the second argument, which the function always returns as a return value.
|
680
|
+
#
|
681
|
+
# Feeding a string that has its encoding attribute set to any double-byte or
|
682
|
+
# quad-byte encoding to IO#readpartial as the second arguments results in an
|
683
|
+
# assertion failure on Ruby < 2.4.0 for unknown reasons.
|
684
|
+
orig_encoding = chunk.encoding
|
685
|
+
chunk.force_encoding(from_encoding)
|
686
|
+
@buffer << chunk
|
687
|
+
# Thus the encoding needs to be reverted back here
|
688
|
+
chunk.force_encoding(orig_encoding)
|
689
|
+
end
|
690
|
+
|
691
|
+
def convert(s)
|
692
|
+
if @need_enc
|
693
|
+
s.encode!(@encoding, @from_encoding)
|
694
|
+
else
|
695
|
+
s
|
696
|
+
end
|
697
|
+
rescue
|
698
|
+
s.encode!(@encoding, @from_encoding, :invalid => :replace, :undef => :replace)
|
563
699
|
end
|
564
|
-
end
|
565
700
|
|
566
|
-
|
567
|
-
|
568
|
-
@
|
569
|
-
@tw = tw
|
570
|
-
@log = log
|
571
|
-
super(interval, false)
|
701
|
+
def next_line
|
702
|
+
idx = @buffer.index(@eol)
|
703
|
+
convert(@buffer.slice!(0, idx + 1)) unless idx.nil?
|
572
704
|
end
|
573
705
|
|
574
|
-
def
|
575
|
-
@
|
576
|
-
rescue => e
|
577
|
-
@log.error e.to_s
|
578
|
-
@log.error_backtrace(e.backtrace)
|
579
|
-
ensure
|
580
|
-
detach
|
706
|
+
def bytesize
|
707
|
+
@buffer.bytesize
|
581
708
|
end
|
582
709
|
end
|
583
710
|
|
584
711
|
class IOHandler
|
585
|
-
def initialize(
|
586
|
-
@
|
587
|
-
@log.info "following tail of #{io.path}" if first
|
588
|
-
@io = io
|
589
|
-
@pe = pe
|
590
|
-
@read_lines_limit = read_lines_limit
|
712
|
+
def initialize(watcher, &receive_lines)
|
713
|
+
@watcher = watcher
|
591
714
|
@receive_lines = receive_lines
|
592
|
-
@
|
715
|
+
@fifo = FIFO.new(@watcher.from_encoding || Encoding::ASCII_8BIT, @watcher.encoding || Encoding::ASCII_8BIT)
|
593
716
|
@iobuf = ''.force_encoding('ASCII-8BIT')
|
594
717
|
@lines = []
|
718
|
+
@io = nil
|
719
|
+
@notify_mutex = Mutex.new
|
720
|
+
@watcher.log.info "following tail of #{@watcher.path}"
|
595
721
|
end
|
596
722
|
|
597
|
-
attr_reader :io
|
598
|
-
attr_accessor :pe
|
599
|
-
|
600
723
|
def on_notify
|
601
|
-
|
602
|
-
|
724
|
+
@notify_mutex.synchronize { handle_notify }
|
725
|
+
end
|
603
726
|
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
727
|
+
def handle_notify
|
728
|
+
with_io do |io|
|
729
|
+
begin
|
730
|
+
read_more = false
|
731
|
+
|
732
|
+
if !io.nil? && @lines.empty?
|
733
|
+
begin
|
734
|
+
while true
|
735
|
+
@fifo << io.readpartial(8192, @iobuf)
|
736
|
+
while (line = @fifo.next_line)
|
737
|
+
@lines << line
|
738
|
+
end
|
739
|
+
if @lines.size >= @watcher.read_lines_limit
|
740
|
+
# not to use too much memory in case the file is very large
|
741
|
+
read_more = true
|
742
|
+
break
|
743
|
+
end
|
619
744
|
end
|
745
|
+
rescue EOFError
|
620
746
|
end
|
621
|
-
rescue EOFError
|
622
747
|
end
|
623
|
-
end
|
624
748
|
|
625
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
749
|
+
unless @lines.empty?
|
750
|
+
if @receive_lines.call(@lines)
|
751
|
+
@watcher.pe.update_pos(io.pos - @fifo.bytesize)
|
752
|
+
@lines.clear
|
753
|
+
else
|
754
|
+
read_more = false
|
755
|
+
end
|
631
756
|
end
|
632
|
-
end
|
633
|
-
end
|
634
|
-
|
635
|
-
rescue
|
636
|
-
@log.error $!.to_s
|
637
|
-
@log.error_backtrace
|
638
|
-
close
|
757
|
+
end while read_more
|
758
|
+
end
|
639
759
|
end
|
640
760
|
|
641
761
|
def close
|
642
|
-
@io
|
762
|
+
if @io && !@io.closed?
|
763
|
+
@io.close
|
764
|
+
@io = nil
|
765
|
+
end
|
766
|
+
end
|
767
|
+
|
768
|
+
def opened?
|
769
|
+
!!@io
|
770
|
+
end
|
771
|
+
|
772
|
+
def open
|
773
|
+
io = Fluent::FileWrapper.open(@watcher.path)
|
774
|
+
io.seek(@watcher.pe.read_pos + @fifo.bytesize)
|
775
|
+
io
|
776
|
+
rescue RangeError
|
777
|
+
io.close if io
|
778
|
+
raise WatcherSetupError, "seek error with #{@watcher.path}: file position = #{@watcher.pe.read_pos.to_s(16)}, reading bytesize = #{@fifo.bytesize.to_s(16)}"
|
779
|
+
rescue Errno::ENOENT
|
780
|
+
nil
|
781
|
+
end
|
782
|
+
|
783
|
+
def with_io
|
784
|
+
begin
|
785
|
+
if @watcher.open_on_every_update
|
786
|
+
io = open
|
787
|
+
begin
|
788
|
+
yield io
|
789
|
+
ensure
|
790
|
+
io.close unless io.nil?
|
791
|
+
end
|
792
|
+
else
|
793
|
+
@io ||= open
|
794
|
+
yield @io
|
795
|
+
end
|
796
|
+
rescue WatcherSetupError => e
|
797
|
+
close
|
798
|
+
raise e
|
799
|
+
rescue
|
800
|
+
@watcher.log.error $!.to_s
|
801
|
+
@watcher.log.error_backtrace
|
802
|
+
close
|
803
|
+
end
|
643
804
|
end
|
644
805
|
end
|
645
806
|
|
@@ -655,48 +816,43 @@ module Fluent
|
|
655
816
|
|
656
817
|
def close
|
657
818
|
end
|
819
|
+
|
820
|
+
def opened?
|
821
|
+
false
|
822
|
+
end
|
658
823
|
end
|
659
824
|
|
660
825
|
class RotateHandler
|
661
|
-
def initialize(
|
662
|
-
@
|
826
|
+
def initialize(watcher, &on_rotate)
|
827
|
+
@watcher = watcher
|
663
828
|
@inode = nil
|
664
829
|
@fsize = -1 # first
|
665
830
|
@on_rotate = on_rotate
|
666
|
-
@log = log
|
667
831
|
end
|
668
832
|
|
669
|
-
def on_notify
|
670
|
-
|
671
|
-
stat = File.stat(@path)
|
672
|
-
inode = stat.ino
|
673
|
-
fsize = stat.size
|
674
|
-
rescue Errno::ENOENT
|
675
|
-
# moved or deleted
|
833
|
+
def on_notify(stat)
|
834
|
+
if stat.nil?
|
676
835
|
inode = nil
|
677
836
|
fsize = 0
|
837
|
+
else
|
838
|
+
inode = stat.ino
|
839
|
+
fsize = stat.size
|
678
840
|
end
|
679
841
|
|
680
842
|
begin
|
681
843
|
if @inode != inode || fsize < @fsize
|
682
|
-
|
683
|
-
begin
|
684
|
-
io = File.open(@path)
|
685
|
-
rescue Errno::ENOENT
|
686
|
-
end
|
687
|
-
@on_rotate.call(io)
|
844
|
+
@on_rotate.call(stat)
|
688
845
|
end
|
689
846
|
@inode = inode
|
690
847
|
@fsize = fsize
|
691
848
|
end
|
692
849
|
|
693
850
|
rescue
|
694
|
-
@log.error $!.to_s
|
695
|
-
@log.error_backtrace
|
851
|
+
@watcher.log.error $!.to_s
|
852
|
+
@watcher.log.error_backtrace
|
696
853
|
end
|
697
854
|
end
|
698
855
|
|
699
|
-
|
700
856
|
class LineBufferTimerFlusher
|
701
857
|
def initialize(log, flush_interval, &flush_method)
|
702
858
|
@log = log
|
@@ -721,12 +877,12 @@ module Fluent
|
|
721
877
|
end
|
722
878
|
end
|
723
879
|
|
724
|
-
|
725
880
|
class PositionFile
|
726
881
|
UNWATCHED_POSITION = 0xffffffffffffffff
|
727
882
|
|
728
|
-
def initialize(file, map, last_pos)
|
883
|
+
def initialize(file, file_mutex, map, last_pos)
|
729
884
|
@file = file
|
885
|
+
@file_mutex = file_mutex
|
730
886
|
@map = map
|
731
887
|
@last_pos = last_pos
|
732
888
|
end
|
@@ -736,31 +892,34 @@ module Fluent
|
|
736
892
|
return m
|
737
893
|
end
|
738
894
|
|
739
|
-
@
|
740
|
-
|
741
|
-
|
742
|
-
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
@map[path] = FilePositionEntry.new(@file, seek)
|
895
|
+
@file_mutex.synchronize {
|
896
|
+
@file.pos = @last_pos
|
897
|
+
@file.write "#{path}\t0000000000000000\t0000000000000000\n"
|
898
|
+
seek = @last_pos + path.bytesize + 1
|
899
|
+
@last_pos = @file.pos
|
900
|
+
@map[path] = FilePositionEntry.new(@file, @file_mutex, seek, 0, 0)
|
901
|
+
}
|
747
902
|
end
|
748
903
|
|
749
904
|
def self.parse(file)
|
750
905
|
compact(file)
|
751
906
|
|
907
|
+
file_mutex = Mutex.new
|
752
908
|
map = {}
|
753
909
|
file.pos = 0
|
754
910
|
file.each_line {|line|
|
755
911
|
m = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(line)
|
756
|
-
|
912
|
+
unless m
|
913
|
+
$log.warn "Unparsable line in pos_file: #{line}"
|
914
|
+
next
|
915
|
+
end
|
757
916
|
path = m[1]
|
758
917
|
pos = m[2].to_i(16)
|
759
918
|
ino = m[3].to_i(16)
|
760
919
|
seek = file.pos - line.bytesize + path.bytesize + 1
|
761
|
-
map[path] = FilePositionEntry.new(file, seek)
|
920
|
+
map[path] = FilePositionEntry.new(file, file_mutex, seek, pos, ino)
|
762
921
|
}
|
763
|
-
new(file, map, file.pos)
|
922
|
+
new(file, file_mutex, map, file.pos)
|
764
923
|
end
|
765
924
|
|
766
925
|
# Clean up unwatched file entries
|
@@ -768,7 +927,10 @@ module Fluent
|
|
768
927
|
file.pos = 0
|
769
928
|
existent_entries = file.each_line.map { |line|
|
770
929
|
m = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(line)
|
771
|
-
|
930
|
+
unless m
|
931
|
+
$log.warn "Unparsable line in pos_file: #{line}"
|
932
|
+
next
|
933
|
+
end
|
772
934
|
path = m[1]
|
773
935
|
pos = m[2].to_i(16)
|
774
936
|
ino = m[3].to_i(16)
|
@@ -791,536 +953,37 @@ module Fluent
|
|
791
953
|
LN_OFFSET = 33
|
792
954
|
SIZE = 34
|
793
955
|
|
794
|
-
def initialize(file, seek)
|
956
|
+
def initialize(file, file_mutex, seek, pos, inode)
|
795
957
|
@file = file
|
958
|
+
@file_mutex = file_mutex
|
796
959
|
@seek = seek
|
960
|
+
@pos = pos
|
961
|
+
@inode = inode
|
797
962
|
end
|
798
963
|
|
799
964
|
def update(ino, pos)
|
800
|
-
@
|
801
|
-
|
802
|
-
|
803
|
-
|
804
|
-
def update_pos(pos)
|
805
|
-
@file.pos = @seek
|
806
|
-
@file.write "%016x" % pos
|
807
|
-
end
|
808
|
-
|
809
|
-
def read_inode
|
810
|
-
@file.pos = @seek + INO_OFFSET
|
811
|
-
raw = @file.read(16)
|
812
|
-
raw ? raw.to_i(16) : 0
|
813
|
-
end
|
814
|
-
|
815
|
-
def read_pos
|
816
|
-
@file.pos = @seek
|
817
|
-
raw = @file.read(16)
|
818
|
-
raw ? raw.to_i(16) : 0
|
819
|
-
end
|
820
|
-
end
|
821
|
-
|
822
|
-
class MemoryPositionEntry
|
823
|
-
def initialize
|
824
|
-
@pos = 0
|
825
|
-
@inode = 0
|
826
|
-
end
|
827
|
-
|
828
|
-
def update(ino, pos)
|
829
|
-
@inode = ino
|
965
|
+
@file_mutex.synchronize {
|
966
|
+
@file.pos = @seek
|
967
|
+
@file.write "%016x\t%016x" % [pos, ino]
|
968
|
+
}
|
830
969
|
@pos = pos
|
970
|
+
@inode = ino
|
831
971
|
end
|
832
972
|
|
833
973
|
def update_pos(pos)
|
974
|
+
@file_mutex.synchronize {
|
975
|
+
@file.pos = @seek
|
976
|
+
@file.write "%016x" % pos
|
977
|
+
}
|
834
978
|
@pos = pos
|
835
979
|
end
|
836
980
|
|
837
|
-
def read_pos
|
838
|
-
@pos
|
839
|
-
end
|
840
|
-
|
841
981
|
def read_inode
|
842
982
|
@inode
|
843
983
|
end
|
844
|
-
end
|
845
|
-
end
|
846
|
-
|
847
|
-
# This TailInput is for existence plugins which extends old in_tail
|
848
|
-
# This class will be removed after release v1.
|
849
|
-
class TailInput < Input
|
850
|
-
def initialize
|
851
|
-
super
|
852
|
-
@paths = []
|
853
|
-
end
|
854
|
-
|
855
|
-
config_param :path, :string
|
856
|
-
config_param :tag, :string
|
857
|
-
config_param :rotate_wait, :time, :default => 5
|
858
|
-
config_param :pos_file, :string, :default => nil
|
859
|
-
|
860
|
-
attr_reader :paths
|
861
|
-
|
862
|
-
def configure(conf)
|
863
|
-
super
|
864
|
-
|
865
|
-
@paths = @path.split(',').map {|path| path.strip }
|
866
|
-
if @paths.empty?
|
867
|
-
raise ConfigError, "tail: 'path' parameter is required on tail input"
|
868
|
-
end
|
869
|
-
|
870
|
-
unless @pos_file
|
871
|
-
$log.warn "'pos_file PATH' parameter is not set to a 'tail' source."
|
872
|
-
$log.warn "this parameter is highly recommended to save the position to resume tailing."
|
873
|
-
end
|
874
|
-
|
875
|
-
configure_parser(conf)
|
876
|
-
end
|
877
|
-
|
878
|
-
def configure_parser(conf)
|
879
|
-
@parser = TextParser.new
|
880
|
-
@parser.configure(conf)
|
881
|
-
end
|
882
|
-
|
883
|
-
def start
|
884
|
-
if @pos_file
|
885
|
-
@pf_file = File.open(@pos_file, File::RDWR|File::CREAT, DEFAULT_FILE_PERMISSION)
|
886
|
-
@pf_file.sync = true
|
887
|
-
@pf = PositionFile.parse(@pf_file)
|
888
|
-
end
|
889
|
-
|
890
|
-
@loop = Coolio::Loop.new
|
891
|
-
@tails = @paths.map {|path|
|
892
|
-
pe = @pf ? @pf[path] : MemoryPositionEntry.new
|
893
|
-
tw = TailWatcher.new(path, @rotate_wait, pe, &method(:receive_lines))
|
894
|
-
tw.log = log
|
895
|
-
tw
|
896
|
-
}
|
897
|
-
@tails.each {|tail|
|
898
|
-
tail.attach(@loop)
|
899
|
-
}
|
900
|
-
@thread = Thread.new(&method(:run))
|
901
|
-
end
|
902
|
-
|
903
|
-
def shutdown
|
904
|
-
@tails.each {|tail|
|
905
|
-
tail.close
|
906
|
-
}
|
907
|
-
@loop.stop
|
908
|
-
@thread.join
|
909
|
-
@pf_file.close if @pf_file
|
910
|
-
end
|
911
|
-
|
912
|
-
def run
|
913
|
-
@loop.run
|
914
|
-
rescue
|
915
|
-
log.error "unexpected error", :error=>$!.to_s
|
916
|
-
log.error_backtrace
|
917
|
-
end
|
918
|
-
|
919
|
-
def receive_lines(lines)
|
920
|
-
es = MultiEventStream.new
|
921
|
-
lines.each {|line|
|
922
|
-
begin
|
923
|
-
line.chomp! # remove \n
|
924
|
-
time, record = parse_line(line)
|
925
|
-
if time && record
|
926
|
-
es.add(time, record)
|
927
|
-
else
|
928
|
-
log.warn "pattern not match: #{line.inspect}"
|
929
|
-
end
|
930
|
-
rescue
|
931
|
-
log.warn line.dump, :error=>$!.to_s
|
932
|
-
log.debug_backtrace
|
933
|
-
end
|
934
|
-
}
|
935
|
-
|
936
|
-
unless es.empty?
|
937
|
-
begin
|
938
|
-
router.emit_stream(@tag, es)
|
939
|
-
rescue
|
940
|
-
# ignore errors. Engine shows logs and backtraces.
|
941
|
-
end
|
942
|
-
end
|
943
|
-
end
|
944
|
-
|
945
|
-
def parse_line(line)
|
946
|
-
return @parser.parse(line)
|
947
|
-
end
|
948
|
-
|
949
|
-
class TailWatcher
|
950
|
-
def initialize(path, rotate_wait, pe, &receive_lines)
|
951
|
-
@path = path
|
952
|
-
@rotate_wait = rotate_wait
|
953
|
-
@pe = pe || MemoryPositionEntry.new
|
954
|
-
@receive_lines = receive_lines
|
955
|
-
|
956
|
-
@rotate_queue = []
|
957
|
-
|
958
|
-
@timer_trigger = TimerWatcher.new(1, true, &method(:on_notify))
|
959
|
-
@stat_trigger = StatWatcher.new(path, &method(:on_notify))
|
960
|
-
|
961
|
-
@rotate_handler = RotateHandler.new(path, &method(:on_rotate))
|
962
|
-
@io_handler = nil
|
963
|
-
@log = $log
|
964
|
-
end
|
965
|
-
|
966
|
-
# We use accessor approach to assign each logger, not passing log object at initialization,
|
967
|
-
# because several plugins depend on these internal classes.
|
968
|
-
# This approach avoids breaking plugins with new log_level option.
|
969
|
-
attr_accessor :log
|
970
|
-
|
971
|
-
def log=(logger)
|
972
|
-
@log = logger
|
973
|
-
@timer_trigger.log = logger
|
974
|
-
@stat_trigger.log = logger
|
975
|
-
@rotate_handler.log = logger
|
976
|
-
end
|
977
|
-
|
978
|
-
def attach(loop)
|
979
|
-
@timer_trigger.attach(loop)
|
980
|
-
@stat_trigger.attach(loop)
|
981
|
-
on_notify
|
982
|
-
end
|
983
|
-
|
984
|
-
def detach
|
985
|
-
@timer_trigger.detach if @timer_trigger.attached?
|
986
|
-
@stat_trigger.detach if @stat_trigger.attached?
|
987
|
-
end
|
988
|
-
|
989
|
-
def close
|
990
|
-
@rotate_queue.reject! {|req|
|
991
|
-
req.io.close
|
992
|
-
true
|
993
|
-
}
|
994
|
-
detach
|
995
|
-
end
|
996
|
-
|
997
|
-
def on_notify
|
998
|
-
@rotate_handler.on_notify
|
999
|
-
return unless @io_handler
|
1000
|
-
@io_handler.on_notify
|
1001
|
-
|
1002
|
-
# proceeds rotate queue
|
1003
|
-
return if @rotate_queue.empty?
|
1004
|
-
@rotate_queue.first.tick
|
1005
|
-
|
1006
|
-
while @rotate_queue.first.ready?
|
1007
|
-
if io = @rotate_queue.first.io
|
1008
|
-
stat = io.stat
|
1009
|
-
inode = stat.ino
|
1010
|
-
if inode == @pe.read_inode
|
1011
|
-
# rotated file has the same inode number with the last file.
|
1012
|
-
# assuming following situation:
|
1013
|
-
# a) file was once renamed and backed, or
|
1014
|
-
# b) symlink or hardlink to the same file is recreated
|
1015
|
-
# in either case, seek to the saved position
|
1016
|
-
pos = @pe.read_pos
|
1017
|
-
else
|
1018
|
-
pos = io.pos
|
1019
|
-
end
|
1020
|
-
@pe.update(inode, pos)
|
1021
|
-
io_handler = IOHandler.new(io, @pe, log, &@receive_lines)
|
1022
|
-
else
|
1023
|
-
io_handler = NullIOHandler.new
|
1024
|
-
end
|
1025
|
-
@io_handler.close
|
1026
|
-
@io_handler = io_handler
|
1027
|
-
@rotate_queue.shift
|
1028
|
-
break if @rotate_queue.empty?
|
1029
|
-
end
|
1030
|
-
end
|
1031
|
-
|
1032
|
-
def on_rotate(io)
|
1033
|
-
if @io_handler == nil
|
1034
|
-
if io
|
1035
|
-
# first time
|
1036
|
-
stat = io.stat
|
1037
|
-
fsize = stat.size
|
1038
|
-
inode = stat.ino
|
1039
|
-
|
1040
|
-
last_inode = @pe.read_inode
|
1041
|
-
if inode == last_inode
|
1042
|
-
# seek to the saved position
|
1043
|
-
pos = @pe.read_pos
|
1044
|
-
elsif last_inode != 0
|
1045
|
-
# this is FilePositionEntry and fluentd once started.
|
1046
|
-
# read data from the head of the rotated file.
|
1047
|
-
# logs never duplicate because this file is a rotated new file.
|
1048
|
-
pos = 0
|
1049
|
-
@pe.update(inode, pos)
|
1050
|
-
else
|
1051
|
-
# this is MemoryPositionEntry or this is the first time fluentd started.
|
1052
|
-
# seek to the end of the any files.
|
1053
|
-
# logs may duplicate without this seek because it's not sure the file is
|
1054
|
-
# existent file or rotated new file.
|
1055
|
-
pos = fsize
|
1056
|
-
@pe.update(inode, pos)
|
1057
|
-
end
|
1058
|
-
io.seek(pos)
|
1059
|
-
|
1060
|
-
@io_handler = IOHandler.new(io, @pe, log, &@receive_lines)
|
1061
|
-
else
|
1062
|
-
@io_handler = NullIOHandler.new
|
1063
|
-
end
|
1064
|
-
|
1065
|
-
else
|
1066
|
-
if io && @rotate_queue.find {|req| req.io == io }
|
1067
|
-
return
|
1068
|
-
end
|
1069
|
-
last_io = @rotate_queue.empty? ? @io_handler.io : @rotate_queue.last.io
|
1070
|
-
if last_io == nil
|
1071
|
-
log.info "detected rotation of #{@path}"
|
1072
|
-
# rotate imeediately if previous file is nil
|
1073
|
-
wait = 0
|
1074
|
-
else
|
1075
|
-
log.info "detected rotation of #{@path}; waiting #{@rotate_wait} seconds"
|
1076
|
-
wait = @rotate_wait
|
1077
|
-
wait -= @rotate_queue.first.wait unless @rotate_queue.empty?
|
1078
|
-
end
|
1079
|
-
@rotate_queue << RotationRequest.new(io, wait)
|
1080
|
-
end
|
1081
|
-
end
|
1082
|
-
|
1083
|
-
class TimerWatcher < Coolio::TimerWatcher
|
1084
|
-
def initialize(interval, repeat, &callback)
|
1085
|
-
@callback = callback
|
1086
|
-
@log = $log
|
1087
|
-
super(interval, repeat)
|
1088
|
-
end
|
1089
|
-
|
1090
|
-
attr_accessor :log
|
1091
|
-
|
1092
|
-
def on_timer
|
1093
|
-
@callback.call
|
1094
|
-
rescue
|
1095
|
-
# TODO log?
|
1096
|
-
@log.error $!.to_s
|
1097
|
-
@log.error_backtrace
|
1098
|
-
end
|
1099
|
-
end
|
1100
|
-
|
1101
|
-
class StatWatcher < Coolio::StatWatcher
|
1102
|
-
def initialize(path, &callback)
|
1103
|
-
@callback = callback
|
1104
|
-
@log = $log
|
1105
|
-
super(path)
|
1106
|
-
end
|
1107
|
-
|
1108
|
-
attr_accessor :log
|
1109
|
-
|
1110
|
-
def on_change(prev, cur)
|
1111
|
-
@callback.call
|
1112
|
-
rescue
|
1113
|
-
# TODO log?
|
1114
|
-
@log.error $!.to_s
|
1115
|
-
@log.error_backtrace
|
1116
|
-
end
|
1117
|
-
end
|
1118
|
-
|
1119
|
-
class RotationRequest
|
1120
|
-
def initialize(io, wait)
|
1121
|
-
@io = io
|
1122
|
-
@wait = wait
|
1123
|
-
end
|
1124
|
-
|
1125
|
-
attr_reader :io, :wait
|
1126
|
-
|
1127
|
-
def tick
|
1128
|
-
@wait -= 1
|
1129
|
-
end
|
1130
|
-
|
1131
|
-
def ready?
|
1132
|
-
@wait <= 0
|
1133
|
-
end
|
1134
|
-
end
|
1135
|
-
|
1136
|
-
MAX_LINES_AT_ONCE = 1000
|
1137
|
-
|
1138
|
-
class IOHandler
|
1139
|
-
def initialize(io, pe, log, &receive_lines)
|
1140
|
-
@log = log
|
1141
|
-
@log.info "following tail of #{io.path}"
|
1142
|
-
@io = io
|
1143
|
-
@pe = pe
|
1144
|
-
@receive_lines = receive_lines
|
1145
|
-
@buffer = ''.force_encoding('ASCII-8BIT')
|
1146
|
-
@iobuf = ''.force_encoding('ASCII-8BIT')
|
1147
|
-
end
|
1148
|
-
|
1149
|
-
attr_reader :io
|
1150
|
-
|
1151
|
-
def on_notify
|
1152
|
-
begin
|
1153
|
-
lines = []
|
1154
|
-
read_more = false
|
1155
|
-
|
1156
|
-
begin
|
1157
|
-
while true
|
1158
|
-
if @buffer.empty?
|
1159
|
-
@io.read_nonblock(2048, @buffer)
|
1160
|
-
else
|
1161
|
-
@buffer << @io.read_nonblock(2048, @iobuf)
|
1162
|
-
end
|
1163
|
-
while line = @buffer.slice!(/.*?\n/m)
|
1164
|
-
lines << line
|
1165
|
-
end
|
1166
|
-
if lines.size >= MAX_LINES_AT_ONCE
|
1167
|
-
# not to use too much memory in case the file is very large
|
1168
|
-
read_more = true
|
1169
|
-
break
|
1170
|
-
end
|
1171
|
-
end
|
1172
|
-
rescue EOFError
|
1173
|
-
end
|
1174
|
-
|
1175
|
-
unless lines.empty?
|
1176
|
-
@receive_lines.call(lines)
|
1177
|
-
@pe.update_pos(@io.pos - @buffer.bytesize)
|
1178
|
-
end
|
1179
|
-
|
1180
|
-
end while read_more
|
1181
|
-
|
1182
|
-
rescue
|
1183
|
-
@log.error $!.to_s
|
1184
|
-
@log.error_backtrace
|
1185
|
-
close
|
1186
|
-
end
|
1187
|
-
|
1188
|
-
def close
|
1189
|
-
@io.close unless @io.closed?
|
1190
|
-
end
|
1191
|
-
end
|
1192
|
-
|
1193
|
-
class NullIOHandler
|
1194
|
-
def initialize
|
1195
|
-
end
|
1196
|
-
|
1197
|
-
def io
|
1198
|
-
end
|
1199
|
-
|
1200
|
-
def on_notify
|
1201
|
-
end
|
1202
|
-
|
1203
|
-
def close
|
1204
|
-
end
|
1205
|
-
end
|
1206
|
-
|
1207
|
-
class RotateHandler
|
1208
|
-
def initialize(path, &on_rotate)
|
1209
|
-
@path = path
|
1210
|
-
@inode = nil
|
1211
|
-
@fsize = -1 # first
|
1212
|
-
@on_rotate = on_rotate
|
1213
|
-
@log = $log
|
1214
|
-
end
|
1215
|
-
|
1216
|
-
attr_accessor :log
|
1217
|
-
|
1218
|
-
def on_notify
|
1219
|
-
begin
|
1220
|
-
io = File.open(@path)
|
1221
|
-
stat = io.stat
|
1222
|
-
inode = stat.ino
|
1223
|
-
fsize = stat.size
|
1224
|
-
rescue Errno::ENOENT
|
1225
|
-
# moved or deleted
|
1226
|
-
inode = nil
|
1227
|
-
fsize = 0
|
1228
|
-
end
|
1229
|
-
|
1230
|
-
begin
|
1231
|
-
if @inode != inode || fsize < @fsize
|
1232
|
-
# rotated or truncated
|
1233
|
-
@on_rotate.call(io)
|
1234
|
-
io = nil
|
1235
|
-
end
|
1236
|
-
|
1237
|
-
@inode = inode
|
1238
|
-
@fsize = fsize
|
1239
|
-
ensure
|
1240
|
-
io.close if io
|
1241
|
-
end
|
1242
|
-
|
1243
|
-
rescue
|
1244
|
-
@log.error $!.to_s
|
1245
|
-
@log.error_backtrace
|
1246
|
-
end
|
1247
|
-
end
|
1248
|
-
end
|
1249
|
-
|
1250
|
-
|
1251
|
-
class PositionFile
|
1252
|
-
def initialize(file, map, last_pos)
|
1253
|
-
@file = file
|
1254
|
-
@map = map
|
1255
|
-
@last_pos = last_pos
|
1256
|
-
end
|
1257
|
-
|
1258
|
-
def [](path)
|
1259
|
-
if m = @map[path]
|
1260
|
-
return m
|
1261
|
-
end
|
1262
|
-
|
1263
|
-
@file.pos = @last_pos
|
1264
|
-
@file.write path
|
1265
|
-
@file.write "\t"
|
1266
|
-
seek = @file.pos
|
1267
|
-
@file.write "0000000000000000\t00000000\n"
|
1268
|
-
@last_pos = @file.pos
|
1269
|
-
|
1270
|
-
@map[path] = FilePositionEntry.new(@file, seek)
|
1271
|
-
end
|
1272
|
-
|
1273
|
-
def self.parse(file)
|
1274
|
-
map = {}
|
1275
|
-
file.pos = 0
|
1276
|
-
file.each_line {|line|
|
1277
|
-
m = /^([^\t]+)\t([0-9a-fA-F]+)\t([0-9a-fA-F]+)/.match(line)
|
1278
|
-
next unless m
|
1279
|
-
path = m[1]
|
1280
|
-
pos = m[2].to_i(16)
|
1281
|
-
ino = m[3].to_i(16)
|
1282
|
-
seek = file.pos - line.bytesize + path.bytesize + 1
|
1283
|
-
map[path] = FilePositionEntry.new(file, seek)
|
1284
|
-
}
|
1285
|
-
new(file, map, file.pos)
|
1286
|
-
end
|
1287
|
-
end
|
1288
|
-
|
1289
|
-
# pos inode
|
1290
|
-
# ffffffffffffffff\tffffffff\n
|
1291
|
-
class FilePositionEntry
|
1292
|
-
POS_SIZE = 16
|
1293
|
-
INO_OFFSET = 17
|
1294
|
-
INO_SIZE = 8
|
1295
|
-
LN_OFFSET = 25
|
1296
|
-
SIZE = 26
|
1297
|
-
|
1298
|
-
def initialize(file, seek)
|
1299
|
-
@file = file
|
1300
|
-
@seek = seek
|
1301
|
-
end
|
1302
|
-
|
1303
|
-
def update(ino, pos)
|
1304
|
-
@file.pos = @seek
|
1305
|
-
@file.write "%016x\t%08x" % [pos, ino]
|
1306
|
-
@inode = ino
|
1307
|
-
end
|
1308
|
-
|
1309
|
-
def update_pos(pos)
|
1310
|
-
@file.pos = @seek
|
1311
|
-
@file.write "%016x" % pos
|
1312
|
-
end
|
1313
|
-
|
1314
|
-
def read_inode
|
1315
|
-
@file.pos = @seek + INO_OFFSET
|
1316
|
-
raw = @file.read(8)
|
1317
|
-
raw ? raw.to_i(16) : 0
|
1318
|
-
end
|
1319
984
|
|
1320
985
|
def read_pos
|
1321
|
-
@
|
1322
|
-
raw = @file.read(16)
|
1323
|
-
raw ? raw.to_i(16) : 0
|
986
|
+
@pos
|
1324
987
|
end
|
1325
988
|
end
|
1326
989
|
|