logstash-output-kusto 1.0.6-java → 2.0.1-java

Sign up to get free protection for your applications and to get access to all the features.
Files changed (139) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +42 -37
  3. data/CONTRIBUTORS +10 -10
  4. data/Gemfile +11 -22
  5. data/LICENSE +201 -201
  6. data/README.md +103 -94
  7. data/SECURITY.md +41 -41
  8. data/lib/logstash/outputs/kusto/ingestor.rb +166 -138
  9. data/lib/logstash/outputs/kusto/interval.rb +81 -81
  10. data/lib/logstash/outputs/kusto.rb +424 -422
  11. data/lib/logstash-output-kusto_jars.rb +72 -149
  12. data/logstash-output-kusto.gemspec +35 -36
  13. data/spec/outputs/kusto/ingestor_spec.rb +131 -121
  14. data/spec/outputs/kusto_spec.rb +56 -56
  15. data/spec/spec_helpers.rb +21 -21
  16. data/vendor/jar-dependencies/com/azure/azure-core/1.41.0/azure-core-1.41.0.jar +0 -0
  17. data/vendor/jar-dependencies/com/azure/azure-core-http-netty/1.13.5/azure-core-http-netty-1.13.5.jar +0 -0
  18. data/vendor/jar-dependencies/com/azure/azure-data-tables/12.3.13/azure-data-tables-12.3.13.jar +0 -0
  19. data/vendor/jar-dependencies/com/azure/azure-identity/1.9.2/azure-identity-1.9.2.jar +0 -0
  20. data/vendor/jar-dependencies/com/azure/azure-json/1.0.1/azure-json-1.0.1.jar +0 -0
  21. data/vendor/jar-dependencies/com/azure/azure-storage-blob/12.23.0/azure-storage-blob-12.23.0.jar +0 -0
  22. data/vendor/jar-dependencies/com/azure/azure-storage-common/12.22.0/azure-storage-common-12.22.0.jar +0 -0
  23. data/vendor/jar-dependencies/com/azure/azure-storage-internal-avro/12.8.0/azure-storage-internal-avro-12.8.0.jar +0 -0
  24. data/vendor/jar-dependencies/com/azure/azure-storage-queue/12.18.0/azure-storage-queue-12.18.0.jar +0 -0
  25. data/{lib/com/fasterxml/jackson/core/jackson-annotations/2.12.5/jackson-annotations-2.12.5.jar → vendor/jar-dependencies/com/fasterxml/jackson/core/jackson-annotations/2.14.2/jackson-annotations-2.14.2.jar} +0 -0
  26. data/vendor/jar-dependencies/com/fasterxml/jackson/core/jackson-core/2.14.2/jackson-core-2.14.2.jar +0 -0
  27. data/{lib/com/fasterxml/jackson/core/jackson-databind/2.13.0/jackson-databind-2.13.0.jar → vendor/jar-dependencies/com/fasterxml/jackson/core/jackson-databind/2.14.2/jackson-databind-2.14.2.jar} +0 -0
  28. data/vendor/jar-dependencies/com/fasterxml/jackson/dataformat/jackson-dataformat-xml/2.14.2/jackson-dataformat-xml-2.14.2.jar +0 -0
  29. data/vendor/jar-dependencies/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.14.2/jackson-datatype-jsr310-2.14.2.jar +0 -0
  30. data/{lib/com/fasterxml/woodstox/woodstox-core/6.2.4/woodstox-core-6.2.4.jar → vendor/jar-dependencies/com/fasterxml/woodstox/woodstox-core/6.5.0/woodstox-core-6.5.0.jar} +0 -0
  31. data/vendor/jar-dependencies/com/microsoft/azure/kusto/kusto-data/5.0.2/kusto-data-5.0.2.jar +0 -0
  32. data/vendor/jar-dependencies/com/microsoft/azure/kusto/kusto-ingest/5.0.2/kusto-ingest-5.0.2.jar +0 -0
  33. data/vendor/jar-dependencies/com/microsoft/azure/msal4j/1.13.8/msal4j-1.13.8.jar +0 -0
  34. data/{lib/com/microsoft/azure/msal4j-persistence-extension/1.1.0/msal4j-persistence-extension-1.1.0.jar → vendor/jar-dependencies/com/microsoft/azure/msal4j-persistence-extension/1.2.0/msal4j-persistence-extension-1.2.0.jar} +0 -0
  35. data/vendor/jar-dependencies/com/nimbusds/content-type/2.2/content-type-2.2.jar +0 -0
  36. data/vendor/jar-dependencies/com/nimbusds/lang-tag/1.7/lang-tag-1.7.jar +0 -0
  37. data/vendor/jar-dependencies/com/nimbusds/nimbus-jose-jwt/9.30.2/nimbus-jose-jwt-9.30.2.jar +0 -0
  38. data/vendor/jar-dependencies/com/nimbusds/oauth2-oidc-sdk/10.7.1/oauth2-oidc-sdk-10.7.1.jar +0 -0
  39. data/vendor/jar-dependencies/commons-codec/commons-codec/1.13/commons-codec-1.13.jar +0 -0
  40. data/vendor/jar-dependencies/io/netty/netty-buffer/4.1.94.Final/netty-buffer-4.1.94.Final.jar +0 -0
  41. data/{lib/io/netty/netty-codec/4.1.68.Final/netty-codec-4.1.68.Final.jar → vendor/jar-dependencies/io/netty/netty-codec/4.1.94.Final/netty-codec-4.1.94.Final.jar} +0 -0
  42. data/{lib/io/netty/netty-codec-dns/4.1.68.Final/netty-codec-dns-4.1.68.Final.jar → vendor/jar-dependencies/io/netty/netty-codec-dns/4.1.94.Final/netty-codec-dns-4.1.94.Final.jar} +0 -0
  43. data/{lib/io/netty/netty-codec-http/4.1.68.Final/netty-codec-http-4.1.68.Final.jar → vendor/jar-dependencies/io/netty/netty-codec-http/4.1.94.Final/netty-codec-http-4.1.94.Final.jar} +0 -0
  44. data/{lib/io/netty/netty-codec-http2/4.1.68.Final/netty-codec-http2-4.1.68.Final.jar → vendor/jar-dependencies/io/netty/netty-codec-http2/4.1.94.Final/netty-codec-http2-4.1.94.Final.jar} +0 -0
  45. data/vendor/jar-dependencies/io/netty/netty-codec-socks/4.1.94.Final/netty-codec-socks-4.1.94.Final.jar +0 -0
  46. data/vendor/jar-dependencies/io/netty/netty-common/4.1.94.Final/netty-common-4.1.94.Final.jar +0 -0
  47. data/{lib/io/netty/netty-handler/4.1.68.Final/netty-handler-4.1.68.Final.jar → vendor/jar-dependencies/io/netty/netty-handler/4.1.94.Final/netty-handler-4.1.94.Final.jar} +0 -0
  48. data/{lib/io/netty/netty-handler-proxy/4.1.68.Final/netty-handler-proxy-4.1.68.Final.jar → vendor/jar-dependencies/io/netty/netty-handler-proxy/4.1.94.Final/netty-handler-proxy-4.1.94.Final.jar} +0 -0
  49. data/{lib/io/netty/netty-resolver/4.1.68.Final/netty-resolver-4.1.68.Final.jar → vendor/jar-dependencies/io/netty/netty-resolver/4.1.94.Final/netty-resolver-4.1.94.Final.jar} +0 -0
  50. data/vendor/jar-dependencies/io/netty/netty-resolver-dns/4.1.94.Final/netty-resolver-dns-4.1.94.Final.jar +0 -0
  51. data/vendor/jar-dependencies/io/netty/netty-resolver-dns-classes-macos/4.1.94.Final/netty-resolver-dns-classes-macos-4.1.94.Final.jar +0 -0
  52. data/vendor/jar-dependencies/io/netty/netty-resolver-dns-native-macos/4.1.94.Final/netty-resolver-dns-native-macos-4.1.94.Final.jar +0 -0
  53. data/vendor/jar-dependencies/io/netty/netty-tcnative-boringssl-static/2.0.61.Final/netty-tcnative-boringssl-static-2.0.61.Final.jar +0 -0
  54. data/vendor/jar-dependencies/io/netty/netty-tcnative-classes/2.0.61.Final/netty-tcnative-classes-2.0.61.Final.jar +0 -0
  55. data/{lib/io/netty/netty-transport/4.1.68.Final/netty-transport-4.1.68.Final.jar → vendor/jar-dependencies/io/netty/netty-transport/4.1.94.Final/netty-transport-4.1.94.Final.jar} +0 -0
  56. data/vendor/jar-dependencies/io/netty/netty-transport-classes-epoll/4.1.94.Final/netty-transport-classes-epoll-4.1.94.Final.jar +0 -0
  57. data/vendor/jar-dependencies/io/netty/netty-transport-classes-kqueue/4.1.94.Final/netty-transport-classes-kqueue-4.1.94.Final.jar +0 -0
  58. data/vendor/jar-dependencies/io/netty/netty-transport-native-epoll/4.1.94.Final/netty-transport-native-epoll-4.1.94.Final.jar +0 -0
  59. data/vendor/jar-dependencies/io/netty/netty-transport-native-kqueue/4.1.94.Final/netty-transport-native-kqueue-4.1.94.Final.jar +0 -0
  60. data/vendor/jar-dependencies/io/netty/netty-transport-native-unix-common/4.1.94.Final/netty-transport-native-unix-common-4.1.94.Final.jar +0 -0
  61. data/vendor/jar-dependencies/io/projectreactor/netty/reactor-netty-core/1.0.33/reactor-netty-core-1.0.33.jar +0 -0
  62. data/vendor/jar-dependencies/io/projectreactor/netty/reactor-netty-http/1.0.33/reactor-netty-http-1.0.33.jar +0 -0
  63. data/{lib/io/projectreactor/reactor-core/3.4.10/reactor-core-3.4.10.jar → vendor/jar-dependencies/io/projectreactor/reactor-core/3.4.32/reactor-core-3.4.32.jar} +0 -0
  64. data/vendor/jar-dependencies/net/java/dev/jna/jna/5.13.0/jna-5.13.0.jar +0 -0
  65. data/vendor/jar-dependencies/net/java/dev/jna/jna-platform/5.13.0/jna-platform-5.13.0.jar +0 -0
  66. data/{lib/net/minidev/accessors-smart/2.4.7/accessors-smart-2.4.7.jar → vendor/jar-dependencies/net/minidev/accessors-smart/2.4.9/accessors-smart-2.4.9.jar} +0 -0
  67. data/{lib/net/minidev/json-smart/2.4.7/json-smart-2.4.7.jar → vendor/jar-dependencies/net/minidev/json-smart/2.4.10/json-smart-2.4.10.jar} +0 -0
  68. data/vendor/jar-dependencies/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar +0 -0
  69. data/vendor/jar-dependencies/org/apache/httpcomponents/httpclient/4.5.14/httpclient-4.5.14.jar +0 -0
  70. data/vendor/jar-dependencies/org/apache/httpcomponents/httpcore/4.4.16/httpcore-4.4.16.jar +0 -0
  71. data/vendor/jar-dependencies/org/logstash/outputs/logstash-output-kusto/2.0.1/logstash-output-kusto-2.0.1.jar +0 -0
  72. data/vendor/jar-dependencies/org/ow2/asm/asm/9.3/asm-9.3.jar +0 -0
  73. data/vendor/jar-dependencies/org/reactivestreams/reactive-streams/1.0.4/reactive-streams-1.0.4.jar +0 -0
  74. data/version +1 -0
  75. metadata +111 -113
  76. data/lib/com/azure/azure-core/1.21.0/azure-core-1.21.0.jar +0 -0
  77. data/lib/com/azure/azure-core-http-netty/1.11.1/azure-core-http-netty-1.11.1.jar +0 -0
  78. data/lib/com/azure/azure-identity/1.3.7/azure-identity-1.3.7.jar +0 -0
  79. data/lib/com/fasterxml/jackson/core/jackson-annotations/2.13.0/jackson-annotations-2.13.0.jar +0 -0
  80. data/lib/com/fasterxml/jackson/core/jackson-core/2.12.5/jackson-core-2.12.5.jar +0 -0
  81. data/lib/com/fasterxml/jackson/core/jackson-core/2.13.0/jackson-core-2.13.0.jar +0 -0
  82. data/lib/com/fasterxml/jackson/core/jackson-databind/2.12.5/jackson-databind-2.12.5.jar +0 -0
  83. data/lib/com/fasterxml/jackson/dataformat/jackson-dataformat-xml/2.12.5/jackson-dataformat-xml-2.12.5.jar +0 -0
  84. data/lib/com/fasterxml/jackson/datatype/jackson-datatype-jsr310/2.12.5/jackson-datatype-jsr310-2.12.5.jar +0 -0
  85. data/lib/com/fasterxml/jackson/module/jackson-module-jaxb-annotations/2.12.5/jackson-module-jaxb-annotations-2.12.5.jar +0 -0
  86. data/lib/com/google/code/findbugs/jsr305/1.3.9/jsr305-1.3.9.jar +0 -0
  87. data/lib/com/google/errorprone/error_prone_annotations/2.1.3/error_prone_annotations-2.1.3.jar +0 -0
  88. data/lib/com/google/guava/guava/24.1.1-jre/guava-24.1.1-jre.jar +0 -0
  89. data/lib/com/google/j2objc/j2objc-annotations/1.1/j2objc-annotations-1.1.jar +0 -0
  90. data/lib/com/microsoft/azure/azure-keyvault-core/1.2.4/azure-keyvault-core-1.2.4.jar +0 -0
  91. data/lib/com/microsoft/azure/azure-storage/8.6.6/azure-storage-8.6.6.jar +0 -0
  92. data/lib/com/microsoft/azure/kusto/kusto-data/3.1.1/kusto-data-3.1.1.jar +0 -0
  93. data/lib/com/microsoft/azure/kusto/kusto-data/3.1.3/kusto-data-3.1.3.jar +0 -0
  94. data/lib/com/microsoft/azure/kusto/kusto-data/3.2.1/kusto-data-3.2.1.jar +0 -0
  95. data/lib/com/microsoft/azure/kusto/kusto-ingest/3.1.1/kusto-ingest-3.1.1.jar +0 -0
  96. data/lib/com/microsoft/azure/kusto/kusto-ingest/3.1.3/kusto-ingest-3.1.3.jar +0 -0
  97. data/lib/com/microsoft/azure/kusto/kusto-ingest/3.2.1/kusto-ingest-3.2.1.jar +0 -0
  98. data/lib/com/microsoft/azure/msal4j/1.11.0/msal4j-1.11.0.jar +0 -0
  99. data/lib/com/nimbusds/content-type/2.1/content-type-2.1.jar +0 -0
  100. data/lib/com/nimbusds/lang-tag/1.5/lang-tag-1.5.jar +0 -0
  101. data/lib/com/nimbusds/nimbus-jose-jwt/9.9.3/nimbus-jose-jwt-9.9.3.jar +0 -0
  102. data/lib/com/nimbusds/oauth2-oidc-sdk/9.7/oauth2-oidc-sdk-9.7.jar +0 -0
  103. data/lib/commons-codec/commons-codec/1.11/commons-codec-1.11.jar +0 -0
  104. data/lib/io/netty/netty-buffer/4.1.68.Final/netty-buffer-4.1.68.Final.jar +0 -0
  105. data/lib/io/netty/netty-codec-socks/4.1.68.Final/netty-codec-socks-4.1.68.Final.jar +0 -0
  106. data/lib/io/netty/netty-common/4.1.68.Final/netty-common-4.1.68.Final.jar +0 -0
  107. data/lib/io/netty/netty-resolver-dns/4.1.68.Final/netty-resolver-dns-4.1.68.Final.jar +0 -0
  108. data/lib/io/netty/netty-resolver-dns-native-macos/4.1.68.Final/netty-resolver-dns-native-macos-4.1.68.Final-osx-x86_64.jar +0 -0
  109. data/lib/io/netty/netty-tcnative-boringssl-static/2.0.43.Final/netty-tcnative-boringssl-static-2.0.43.Final.jar +0 -0
  110. data/lib/io/netty/netty-transport-native-epoll/4.1.68.Final/netty-transport-native-epoll-4.1.68.Final-linux-x86_64.jar +0 -0
  111. data/lib/io/netty/netty-transport-native-kqueue/4.1.68.Final/netty-transport-native-kqueue-4.1.68.Final-osx-x86_64.jar +0 -0
  112. data/lib/io/netty/netty-transport-native-unix-common/4.1.68.Final/netty-transport-native-unix-common-4.1.68.Final.jar +0 -0
  113. data/lib/io/projectreactor/netty/reactor-netty-core/1.0.11/reactor-netty-core-1.0.11.jar +0 -0
  114. data/lib/io/projectreactor/netty/reactor-netty-http/1.0.11/reactor-netty-http-1.0.11.jar +0 -0
  115. data/lib/jakarta/activation/jakarta.activation-api/1.2.1/jakarta.activation-api-1.2.1.jar +0 -0
  116. data/lib/jakarta/xml/bind/jakarta.xml.bind-api/2.3.2/jakarta.xml.bind-api-2.3.2.jar +0 -0
  117. data/lib/net/java/dev/jna/jna/5.5.0/jna-5.5.0.jar +0 -0
  118. data/lib/net/java/dev/jna/jna-platform/5.6.0/jna-platform-5.6.0.jar +0 -0
  119. data/lib/org/apache/commons/commons-lang3/3.11/commons-lang3-3.11.jar +0 -0
  120. data/lib/org/apache/commons/commons-text/1.9/commons-text-1.9.jar +0 -0
  121. data/lib/org/apache/httpcomponents/httpclient/4.5.13/httpclient-4.5.13.jar +0 -0
  122. data/lib/org/apache/httpcomponents/httpcore/4.4.15/httpcore-4.4.15.jar +0 -0
  123. data/lib/org/checkerframework/checker-compat-qual/2.0.0/checker-compat-qual-2.0.0.jar +0 -0
  124. data/lib/org/codehaus/mojo/animal-sniffer-annotations/1.14/animal-sniffer-annotations-1.14.jar +0 -0
  125. data/lib/org/json/json/20201115/json-20201115.jar +0 -0
  126. data/lib/org/ow2/asm/asm/9.1/asm-9.1.jar +0 -0
  127. data/lib/org/reactivestreams/reactive-streams/1.0.3/reactive-streams-1.0.3.jar +0 -0
  128. /data/{lib → vendor/jar-dependencies}/com/github/stephenc/jcip/jcip-annotations/1.0-1/jcip-annotations-1.0-1.jar +0 -0
  129. /data/{lib → vendor/jar-dependencies}/com/univocity/univocity-parsers/2.9.1/univocity-parsers-2.9.1.jar +0 -0
  130. /data/{lib → vendor/jar-dependencies}/commons-logging/commons-logging/1.2/commons-logging-1.2.jar +0 -0
  131. /data/{lib → vendor/jar-dependencies}/io/github/resilience4j/resilience4j-core/1.7.1/resilience4j-core-1.7.1.jar +0 -0
  132. /data/{lib → vendor/jar-dependencies}/io/github/resilience4j/resilience4j-retry/1.7.1/resilience4j-retry-1.7.1.jar +0 -0
  133. /data/{lib → vendor/jar-dependencies}/io/vavr/vavr/0.10.2/vavr-0.10.2.jar +0 -0
  134. /data/{lib → vendor/jar-dependencies}/io/vavr/vavr-match/0.10.2/vavr-match-0.10.2.jar +0 -0
  135. /data/{lib → vendor/jar-dependencies}/org/apache/commons/commons-text/1.10.0/commons-text-1.10.0.jar +0 -0
  136. /data/{lib → vendor/jar-dependencies}/org/codehaus/woodstox/stax2-api/4.2.1/stax2-api-4.2.1.jar +0 -0
  137. /data/{lib → vendor/jar-dependencies}/org/jetbrains/annotations/22.0.0/annotations-22.0.0.jar +0 -0
  138. /data/{lib → vendor/jar-dependencies}/org/slf4j/slf4j-api/1.8.0-beta4/slf4j-api-1.8.0-beta4.jar +0 -0
  139. /data/{lib → vendor/jar-dependencies}/org/slf4j/slf4j-simple/1.8.0-beta4/slf4j-simple-1.8.0-beta4.jar +0 -0
@@ -1,422 +1,424 @@
1
- # encoding: utf-8
2
-
3
- require 'logstash/outputs/base'
4
- require 'logstash/namespace'
5
- require 'logstash/errors'
6
-
7
- require 'logstash/outputs/kusto/ingestor'
8
- require 'logstash/outputs/kusto/interval'
9
-
10
- ##
11
- # This plugin sends messages to Azure Kusto in batches.
12
- #
13
- class LogStash::Outputs::Kusto < LogStash::Outputs::Base
14
- config_name 'kusto'
15
- concurrency :shared
16
-
17
- FIELD_REF = /%\{[^}]+\}/
18
-
19
- attr_reader :failure_path
20
-
21
- # The path to the file to write. Event fields can be used here,
22
- # like `/var/log/logstash/%{host}/%{application}`
23
- # One may also utilize the path option for date-based log
24
- # rotation via the joda time format. This will use the event
25
- # timestamp.
26
- # E.g.: `path => "./test-%{+YYYY-MM-dd}.txt"` to create
27
- # `./test-2013-05-29.txt`
28
- #
29
- # If you use an absolute path you cannot start with a dynamic string.
30
- # E.g: `/%{myfield}/`, `/test-%{myfield}/` are not valid paths
31
- config :path, validate: :string, required: true
32
-
33
- # Flush interval (in seconds) for flushing writes to files.
34
- # 0 will flush on every message. Increase this value to recude IO calls but keep
35
- # in mind that events buffered before flush can be lost in case of abrupt failure.
36
- config :flush_interval, validate: :number, default: 2
37
-
38
- # If the generated path is invalid, the events will be saved
39
- # into this file and inside the defined path.
40
- config :filename_failure, validate: :string, default: '_filepath_failures'
41
-
42
- # If the configured file is deleted, but an event is handled by the plugin,
43
- # the plugin will recreate the file. Default => true
44
- config :create_if_deleted, validate: :boolean, default: true
45
-
46
- # Dir access mode to use. Note that due to the bug in jruby system umask
47
- # is ignored on linux: https://github.com/jruby/jruby/issues/3426
48
- # Setting it to -1 uses default OS value.
49
- # Example: `"dir_mode" => 0750`
50
- config :dir_mode, validate: :number, default: -1
51
-
52
- # File access mode to use. Note that due to the bug in jruby system umask
53
- # is ignored on linux: https://github.com/jruby/jruby/issues/3426
54
- # Setting it to -1 uses default OS value.
55
- # Example: `"file_mode" => 0640`
56
- config :file_mode, validate: :number, default: -1
57
-
58
- # TODO: fix the interval type...
59
- config :stale_cleanup_interval, validate: :number, default: 10
60
- config :stale_cleanup_type, validate: %w[events interval], default: 'events'
61
-
62
- # Should the plugin recover from failure?
63
- #
64
- # If `true`, the plugin will look for temp files from past runs within the
65
- # path (before any dynamic pattern is added) and try to process them
66
- #
67
- # If `false`, the plugin will disregard temp files found
68
- config :recovery, validate: :boolean, default: true
69
-
70
-
71
- # The Kusto endpoint for ingestion related communication. You can see it on the Azure Portal.
72
- config :ingest_url, validate: :string, required: true
73
-
74
- # The following are the credentails used to connect to the Kusto service
75
- # application id
76
- config :app_id, validate: :string, required: true
77
- # application key (secret)
78
- config :app_key, validate: :password, required: true
79
- # aad tenant id
80
- config :app_tenant, validate: :string, default: nil
81
-
82
- # The following are the data settings that impact where events are written to
83
- # Database name
84
- config :database, validate: :string, required: true
85
- # Target table name
86
- config :table, validate: :string, required: true
87
- # Mapping name - Used by Kusto to map each attribute from incoming event JSON strings to the appropriate column in the table.
88
- # Note that this must be in JSON format, as this is the interface between Logstash and Kusto
89
- config :json_mapping, validate: :string, required: true
90
-
91
- # Mapping name - deprecated, use json_mapping
92
- config :mapping, validate: :string, deprecated: true
93
-
94
-
95
- # Determines if local files used for temporary storage will be deleted
96
- # after upload is successful
97
- config :delete_temp_files, validate: :boolean, default: true
98
-
99
- # TODO: will be used to route events to many tables according to event properties
100
- config :dynamic_event_routing, validate: :boolean, default: false
101
-
102
- # Specify how many files can be uploaded concurrently
103
- config :upload_concurrent_count, validate: :number, default: 3
104
-
105
- # Specify how many files can be kept in the upload queue before the main process
106
- # starts processing them in the main thread (not healthy)
107
- config :upload_queue_size, validate: :number, default: 30
108
-
109
- # Host of the proxy , is an optional field. Can connect directly
110
- config :proxy_host, validate: :string, required: false
111
-
112
- # Port where the proxy runs , defaults to 80. Usually a value like 3128
113
- config :proxy_port, validate: :number, required: false , default: 80
114
-
115
- # Check Proxy URL can be over http or https. Dowe need it this way or ignore this & remove this
116
- config :proxy_protocol, validate: :string, required: false , default: 'http'
117
-
118
- default :codec, 'json_lines'
119
-
120
- def register
121
- require 'fileutils' # For mkdir_p
122
-
123
- @files = {}
124
- @io_mutex = Mutex.new
125
-
126
- final_mapping = json_mapping
127
- if final_mapping.empty?
128
- final_mapping = mapping
129
- end
130
-
131
- # TODO: add id to the tmp path to support multiple outputs of the same type
132
- # add fields from the meta that will note the destination of the events in the file
133
- @path = if dynamic_event_routing
134
- File.expand_path("#{path}.%{[@metadata][database]}.%{[@metadata][table]}.%{[@metadata][final_mapping]}")
135
- else
136
- File.expand_path("#{path}.#{database}.#{table}")
137
- end
138
-
139
- validate_path
140
-
141
- @file_root = if path_with_field_ref?
142
- extract_file_root
143
- else
144
- File.dirname(path)
145
- end
146
- @failure_path = File.join(@file_root, @filename_failure)
147
-
148
- executor = Concurrent::ThreadPoolExecutor.new(min_threads: 1,
149
- max_threads: upload_concurrent_count,
150
- max_queue: upload_queue_size,
151
- fallback_policy: :caller_runs)
152
-
153
- @ingestor = Ingestor.new(ingest_url, app_id, app_key, app_tenant, database, table, final_mapping, delete_temp_files, proxy_host, proxy_port,proxy_protocol, @logger, executor)
154
-
155
- # send existing files
156
- recover_past_files if recovery
157
-
158
- @last_stale_cleanup_cycle = Time.now
159
-
160
- @flush_interval = @flush_interval.to_i
161
- if @flush_interval > 0
162
- @flusher = Interval.start(@flush_interval, -> { flush_pending_files })
163
- end
164
-
165
- if (@stale_cleanup_type == 'interval') && (@stale_cleanup_interval > 0)
166
- @cleaner = Interval.start(stale_cleanup_interval, -> { close_stale_files })
167
- end
168
- end
169
-
170
- private
171
- def validate_path
172
- if (root_directory =~ FIELD_REF) != nil
173
- @logger.error('The starting part of the path should not be dynamic.', path: @path)
174
- raise LogStash::ConfigurationError.new('The starting part of the path should not be dynamic.')
175
- end
176
-
177
- if !path_with_field_ref?
178
- @logger.error('Path should include some time related fields to allow for file rotation.', path: @path)
179
- raise LogStash::ConfigurationError.new('Path should include some time related fields to allow for file rotation.')
180
- end
181
- end
182
-
183
- private
184
- def root_directory
185
- parts = @path.split(File::SEPARATOR).reject(&:empty?)
186
- if Gem.win_platform?
187
- # First part is the drive letter
188
- parts[1]
189
- else
190
- parts.first
191
- end
192
- end
193
-
194
- public
195
- def multi_receive_encoded(events_and_encoded)
196
- encoded_by_path = Hash.new { |h, k| h[k] = [] }
197
-
198
- events_and_encoded.each do |event, encoded|
199
- file_output_path = event_path(event)
200
- encoded_by_path[file_output_path] << encoded
201
- end
202
-
203
- @io_mutex.synchronize do
204
- encoded_by_path.each do |path, chunks|
205
- fd = open(path)
206
- # append to the file
207
- chunks.each { |chunk| fd.write(chunk) }
208
- fd.flush unless @flusher && @flusher.alive?
209
- end
210
-
211
- close_stale_files if @stale_cleanup_type == 'events'
212
- end
213
- end
214
-
215
- def close
216
- @flusher.stop unless @flusher.nil?
217
- @cleaner.stop unless @cleaner.nil?
218
- @io_mutex.synchronize do
219
- @logger.debug('Close: closing files')
220
-
221
- @files.each do |path, fd|
222
- begin
223
- fd.close
224
- @logger.debug("Closed file #{path}", fd: fd)
225
-
226
- kusto_send_file(path)
227
- rescue Exception => e
228
- @logger.error('Exception while flushing and closing files.', exception: e)
229
- end
230
- end
231
- end
232
-
233
- @ingestor.stop unless @ingestor.nil?
234
- end
235
-
236
- private
237
- def inside_file_root?(log_path)
238
- target_file = File.expand_path(log_path)
239
- return target_file.start_with?("#{@file_root}/")
240
- end
241
-
242
- private
243
- def event_path(event)
244
- file_output_path = generate_filepath(event)
245
- if path_with_field_ref? && !inside_file_root?(file_output_path)
246
- @logger.warn('The event tried to write outside the files root, writing the event to the failure file', event: event, filename: @failure_path)
247
- file_output_path = @failure_path
248
- elsif !@create_if_deleted && deleted?(file_output_path)
249
- file_output_path = @failure_path
250
- end
251
- @logger.debug('Writing event to tmp file.', filename: file_output_path)
252
-
253
- file_output_path
254
- end
255
-
256
- private
257
- def generate_filepath(event)
258
- event.sprintf(@path)
259
- end
260
-
261
- private
262
- def path_with_field_ref?
263
- path =~ FIELD_REF
264
- end
265
-
266
- private
267
- def extract_file_root
268
- parts = File.expand_path(path).split(File::SEPARATOR)
269
- parts.take_while { |part| part !~ FIELD_REF }.join(File::SEPARATOR)
270
- end
271
-
272
- # the back-bone of @flusher, our periodic-flushing interval.
273
- private
274
- def flush_pending_files
275
- @io_mutex.synchronize do
276
- @logger.debug('Starting flush cycle')
277
-
278
- @files.each do |path, fd|
279
- @logger.debug('Flushing file', path: path, fd: fd)
280
- fd.flush
281
- end
282
- end
283
- rescue Exception => e
284
- # squash exceptions caught while flushing after logging them
285
- @logger.error('Exception flushing files', exception: e.message, backtrace: e.backtrace)
286
- end
287
-
288
- # every 10 seconds or so (triggered by events, but if there are no events there's no point closing files anyway)
289
- private
290
- def close_stale_files
291
- now = Time.now
292
- return unless now - @last_stale_cleanup_cycle >= @stale_cleanup_interval
293
-
294
- @logger.debug('Starting stale files cleanup cycle', files: @files)
295
- inactive_files = @files.select { |path, fd| not fd.active }
296
- @logger.debug("#{inactive_files.count} stale files found", inactive_files: inactive_files)
297
- inactive_files.each do |path, fd|
298
- @logger.info("Closing file #{path}")
299
- fd.close
300
- @files.delete(path)
301
-
302
- kusto_send_file(path)
303
- end
304
- # mark all files as inactive, a call to write will mark them as active again
305
- @files.each { |path, fd| fd.active = false }
306
- @last_stale_cleanup_cycle = now
307
- end
308
-
309
- private
310
- def cached?(path)
311
- @files.include?(path) && !@files[path].nil?
312
- end
313
-
314
- private
315
- def deleted?(path)
316
- !File.exist?(path)
317
- end
318
-
319
- private
320
- def open(path)
321
- return @files[path] if !deleted?(path) && cached?(path)
322
-
323
- if deleted?(path)
324
- if @create_if_deleted
325
- @logger.debug('Required file does not exist, creating it.', path: path)
326
- @files.delete(path)
327
- else
328
- return @files[path] if cached?(path)
329
- end
330
- end
331
-
332
- @logger.info('Opening file', path: path)
333
-
334
- dir = File.dirname(path)
335
- if !Dir.exist?(dir)
336
- @logger.info('Creating directory', directory: dir)
337
- if @dir_mode != -1
338
- FileUtils.mkdir_p(dir, mode: @dir_mode)
339
- else
340
- FileUtils.mkdir_p(dir)
341
- end
342
- end
343
-
344
- # work around a bug opening fifos (bug JRUBY-6280)
345
- stat = begin
346
- File.stat(path)
347
- rescue
348
- nil
349
- end
350
- fd = if stat && stat.ftype == 'fifo' && LogStash::Environment.jruby?
351
- java.io.FileWriter.new(java.io.File.new(path))
352
- elsif @file_mode != -1
353
- File.new(path, 'a+', @file_mode)
354
- else
355
- File.new(path, 'a+')
356
- end
357
- # fd = if @file_mode != -1
358
- # File.new(path, 'a+', @file_mode)
359
- # else
360
- # File.new(path, 'a+')
361
- # end
362
- # end
363
- @files[path] = IOWriter.new(fd)
364
- end
365
-
366
- private
367
- def kusto_send_file(file_path)
368
- @ingestor.upload_async(file_path, delete_temp_files)
369
- end
370
-
371
- private
372
- def recover_past_files
373
- require 'find'
374
-
375
- # we need to find the last "regular" part in the path before any dynamic vars
376
- path_last_char = @path.length - 1
377
-
378
- pattern_start = @path.index('%') || path_last_char
379
- last_folder_before_pattern = @path.rindex('/', pattern_start) || path_last_char
380
- new_path = path[0..last_folder_before_pattern]
381
-
382
- begin
383
- return unless Dir.exist?(new_path)
384
- @logger.info("Going to recover old files in path #{@new_path}")
385
-
386
- old_files = Find.find(new_path).select { |p| /.*\.#{database}\.#{table}$/ =~ p }
387
- @logger.info("Found #{old_files.length} old file(s), sending them now...")
388
-
389
- old_files.each do |file|
390
- kusto_send_file(file)
391
- end
392
- rescue Errno::ENOENT => e
393
- @logger.warn('No such file or directory', exception: e.class, message: e.message, path: new_path, backtrace: e.backtrace)
394
- end
395
- end
396
- end
397
-
398
- # wrapper class
399
- class IOWriter
400
- def initialize(io)
401
- @io = io
402
- end
403
-
404
- def write(*args)
405
- @io.write(*args)
406
- @active = true
407
- end
408
-
409
- def flush
410
- @io.flush
411
- end
412
-
413
- def method_missing(method_name, *args, &block)
414
- if @io.respond_to?(method_name)
415
-
416
- @io.send(method_name, *args, &block)
417
- else
418
- super
419
- end
420
- end
421
- attr_accessor :active
422
- end
1
+ # encoding: utf-8
2
+
3
+ require 'logstash/outputs/base'
4
+ require 'logstash/namespace'
5
+ require 'logstash/errors'
6
+
7
+ require 'logstash/outputs/kusto/ingestor'
8
+ require 'logstash/outputs/kusto/interval'
9
+
10
+ ##
11
+ # This plugin sends messages to Azure Kusto in batches.
12
+ #
13
+ class LogStash::Outputs::Kusto < LogStash::Outputs::Base
14
+ config_name 'kusto'
15
+ concurrency :shared
16
+
17
+ FIELD_REF = /%\{[^}]+\}/
18
+
19
+ attr_reader :failure_path
20
+
21
+ # The path to the file to write. Event fields can be used here,
22
+ # like `/var/log/logstash/%{host}/%{application}`
23
+ # One may also utilize the path option for date-based log
24
+ # rotation via the joda time format. This will use the event
25
+ # timestamp.
26
+ # E.g.: `path => "./test-%{+YYYY-MM-dd}.txt"` to create
27
+ # `./test-2013-05-29.txt`
28
+ #
29
+ # If you use an absolute path you cannot start with a dynamic string.
30
+ # E.g: `/%{myfield}/`, `/test-%{myfield}/` are not valid paths
31
+ config :path, validate: :string, required: true
32
+
33
+ # Flush interval (in seconds) for flushing writes to files.
34
+ # 0 will flush on every message. Increase this value to recude IO calls but keep
35
+ # in mind that events buffered before flush can be lost in case of abrupt failure.
36
+ config :flush_interval, validate: :number, default: 2
37
+
38
+ # If the generated path is invalid, the events will be saved
39
+ # into this file and inside the defined path.
40
+ config :filename_failure, validate: :string, default: '_filepath_failures'
41
+
42
+ # If the configured file is deleted, but an event is handled by the plugin,
43
+ # the plugin will recreate the file. Default => true
44
+ config :create_if_deleted, validate: :boolean, default: true
45
+
46
+ # Dir access mode to use. Note that due to the bug in jruby system umask
47
+ # is ignored on linux: https://github.com/jruby/jruby/issues/3426
48
+ # Setting it to -1 uses default OS value.
49
+ # Example: `"dir_mode" => 0750`
50
+ config :dir_mode, validate: :number, default: -1
51
+
52
+ # File access mode to use. Note that due to the bug in jruby system umask
53
+ # is ignored on linux: https://github.com/jruby/jruby/issues/3426
54
+ # Setting it to -1 uses default OS value.
55
+ # Example: `"file_mode" => 0640`
56
+ config :file_mode, validate: :number, default: -1
57
+
58
+ # TODO: fix the interval type...
59
+ config :stale_cleanup_interval, validate: :number, default: 10
60
+ config :stale_cleanup_type, validate: %w[events interval], default: 'events'
61
+
62
+ # Should the plugin recover from failure?
63
+ #
64
+ # If `true`, the plugin will look for temp files from past runs within the
65
+ # path (before any dynamic pattern is added) and try to process them
66
+ #
67
+ # If `false`, the plugin will disregard temp files found
68
+ config :recovery, validate: :boolean, default: true
69
+
70
+
71
+ # The Kusto endpoint for ingestion related communication. You can see it on the Azure Portal.
72
+ config :ingest_url, validate: :string, required: true
73
+
74
+ # The following are the credentails used to connect to the Kusto service
75
+ # application id
76
+ config :app_id, validate: :string, required: false
77
+ # application key (secret)
78
+ config :app_key, validate: :password, required: false
79
+ # aad tenant id
80
+ config :app_tenant, validate: :string, default: nil
81
+ # managed identity id
82
+ config :managed_identity, validate: :string, default: nil
83
+
84
+ # The following are the data settings that impact where events are written to
85
+ # Database name
86
+ config :database, validate: :string, required: true
87
+ # Target table name
88
+ config :table, validate: :string, required: true
89
+ # Mapping name - Used by Kusto to map each attribute from incoming event JSON strings to the appropriate column in the table.
90
+ # Note that this must be in JSON format, as this is the interface between Logstash and Kusto
91
+ config :json_mapping, validate: :string, required: true
92
+
93
+ # Mapping name - deprecated, use json_mapping
94
+ config :mapping, validate: :string, deprecated: true
95
+
96
+
97
+ # Determines if local files used for temporary storage will be deleted
98
+ # after upload is successful
99
+ config :delete_temp_files, validate: :boolean, default: true
100
+
101
+ # TODO: will be used to route events to many tables according to event properties
102
+ config :dynamic_event_routing, validate: :boolean, default: false
103
+
104
+ # Specify how many files can be uploaded concurrently
105
+ config :upload_concurrent_count, validate: :number, default: 3
106
+
107
+ # Specify how many files can be kept in the upload queue before the main process
108
+ # starts processing them in the main thread (not healthy)
109
+ config :upload_queue_size, validate: :number, default: 30
110
+
111
+ # Host of the proxy , is an optional field. Can connect directly
112
+ config :proxy_host, validate: :string, required: false
113
+
114
+ # Port where the proxy runs , defaults to 80. Usually a value like 3128
115
+ config :proxy_port, validate: :number, required: false , default: 80
116
+
117
+ # Check Proxy URL can be over http or https. Dowe need it this way or ignore this & remove this
118
+ config :proxy_protocol, validate: :string, required: false , default: 'http'
119
+
120
+ default :codec, 'json_lines'
121
+
122
+ def register
123
+ require 'fileutils' # For mkdir_p
124
+
125
+ @files = {}
126
+ @io_mutex = Mutex.new
127
+
128
+ final_mapping = json_mapping
129
+ if final_mapping.empty?
130
+ final_mapping = mapping
131
+ end
132
+
133
+ # TODO: add id to the tmp path to support multiple outputs of the same type
134
+ # add fields from the meta that will note the destination of the events in the file
135
+ @path = if dynamic_event_routing
136
+ File.expand_path("#{path}.%{[@metadata][database]}.%{[@metadata][table]}.%{[@metadata][final_mapping]}")
137
+ else
138
+ File.expand_path("#{path}.#{database}.#{table}")
139
+ end
140
+
141
+ validate_path
142
+
143
+ @file_root = if path_with_field_ref?
144
+ extract_file_root
145
+ else
146
+ File.dirname(path)
147
+ end
148
+ @failure_path = File.join(@file_root, @filename_failure)
149
+
150
+ executor = Concurrent::ThreadPoolExecutor.new(min_threads: 1,
151
+ max_threads: upload_concurrent_count,
152
+ max_queue: upload_queue_size,
153
+ fallback_policy: :caller_runs)
154
+
155
+ @ingestor = Ingestor.new(ingest_url, app_id, app_key, app_tenant, managed_identity, database, table, final_mapping, delete_temp_files, proxy_host, proxy_port,proxy_protocol, @logger, executor)
156
+
157
+ # send existing files
158
+ recover_past_files if recovery
159
+
160
+ @last_stale_cleanup_cycle = Time.now
161
+
162
+ @flush_interval = @flush_interval.to_i
163
+ if @flush_interval > 0
164
+ @flusher = Interval.start(@flush_interval, -> { flush_pending_files })
165
+ end
166
+
167
+ if (@stale_cleanup_type == 'interval') && (@stale_cleanup_interval > 0)
168
+ @cleaner = Interval.start(stale_cleanup_interval, -> { close_stale_files })
169
+ end
170
+ end
171
+
172
+ private
173
+ def validate_path
174
+ if (root_directory =~ FIELD_REF) != nil
175
+ @logger.error('The starting part of the path should not be dynamic.', path: @path)
176
+ raise LogStash::ConfigurationError.new('The starting part of the path should not be dynamic.')
177
+ end
178
+
179
+ if !path_with_field_ref?
180
+ @logger.error('Path should include some time related fields to allow for file rotation.', path: @path)
181
+ raise LogStash::ConfigurationError.new('Path should include some time related fields to allow for file rotation.')
182
+ end
183
+ end
184
+
185
+ private
186
+ def root_directory
187
+ parts = @path.split(File::SEPARATOR).reject(&:empty?)
188
+ if Gem.win_platform?
189
+ # First part is the drive letter
190
+ parts[1]
191
+ else
192
+ parts.first
193
+ end
194
+ end
195
+
196
+ public
197
+ def multi_receive_encoded(events_and_encoded)
198
+ encoded_by_path = Hash.new { |h, k| h[k] = [] }
199
+
200
+ events_and_encoded.each do |event, encoded|
201
+ file_output_path = event_path(event)
202
+ encoded_by_path[file_output_path] << encoded
203
+ end
204
+
205
+ @io_mutex.synchronize do
206
+ encoded_by_path.each do |path, chunks|
207
+ fd = open(path)
208
+ # append to the file
209
+ chunks.each { |chunk| fd.write(chunk) }
210
+ fd.flush unless @flusher && @flusher.alive?
211
+ end
212
+
213
+ close_stale_files if @stale_cleanup_type == 'events'
214
+ end
215
+ end
216
+
217
+ def close
218
+ @flusher.stop unless @flusher.nil?
219
+ @cleaner.stop unless @cleaner.nil?
220
+ @io_mutex.synchronize do
221
+ @logger.debug('Close: closing files')
222
+
223
+ @files.each do |path, fd|
224
+ begin
225
+ fd.close
226
+ @logger.debug("Closed file #{path}", fd: fd)
227
+
228
+ kusto_send_file(path)
229
+ rescue Exception => e
230
+ @logger.error('Exception while flushing and closing files.', exception: e)
231
+ end
232
+ end
233
+ end
234
+
235
+ @ingestor.stop unless @ingestor.nil?
236
+ end
237
+
238
+ private
239
+ def inside_file_root?(log_path)
240
+ target_file = File.expand_path(log_path)
241
+ return target_file.start_with?("#{@file_root}/")
242
+ end
243
+
244
+ private
245
+ def event_path(event)
246
+ file_output_path = generate_filepath(event)
247
+ if path_with_field_ref? && !inside_file_root?(file_output_path)
248
+ @logger.warn('The event tried to write outside the files root, writing the event to the failure file', event: event, filename: @failure_path)
249
+ file_output_path = @failure_path
250
+ elsif !@create_if_deleted && deleted?(file_output_path)
251
+ file_output_path = @failure_path
252
+ end
253
+ @logger.debug('Writing event to tmp file.', filename: file_output_path)
254
+
255
+ file_output_path
256
+ end
257
+
258
+ private
259
+ def generate_filepath(event)
260
+ event.sprintf(@path)
261
+ end
262
+
263
+ private
264
+ def path_with_field_ref?
265
+ path =~ FIELD_REF
266
+ end
267
+
268
+ private
269
+ def extract_file_root
270
+ parts = File.expand_path(path).split(File::SEPARATOR)
271
+ parts.take_while { |part| part !~ FIELD_REF }.join(File::SEPARATOR)
272
+ end
273
+
274
+ # the back-bone of @flusher, our periodic-flushing interval.
275
+ private
276
+ def flush_pending_files
277
+ @io_mutex.synchronize do
278
+ @logger.debug('Starting flush cycle')
279
+
280
+ @files.each do |path, fd|
281
+ @logger.debug('Flushing file', path: path, fd: fd)
282
+ fd.flush
283
+ end
284
+ end
285
+ rescue Exception => e
286
+ # squash exceptions caught while flushing after logging them
287
+ @logger.error('Exception flushing files', exception: e.message, backtrace: e.backtrace)
288
+ end
289
+
290
+ # every 10 seconds or so (triggered by events, but if there are no events there's no point closing files anyway)
291
+ private
292
+ def close_stale_files
293
+ now = Time.now
294
+ return unless now - @last_stale_cleanup_cycle >= @stale_cleanup_interval
295
+
296
+ @logger.debug('Starting stale files cleanup cycle', files: @files)
297
+ inactive_files = @files.select { |path, fd| not fd.active }
298
+ @logger.debug("#{inactive_files.count} stale files found", inactive_files: inactive_files)
299
+ inactive_files.each do |path, fd|
300
+ @logger.info("Closing file #{path}")
301
+ fd.close
302
+ @files.delete(path)
303
+
304
+ kusto_send_file(path)
305
+ end
306
+ # mark all files as inactive, a call to write will mark them as active again
307
+ @files.each { |path, fd| fd.active = false }
308
+ @last_stale_cleanup_cycle = now
309
+ end
310
+
311
+ private
312
+ def cached?(path)
313
+ @files.include?(path) && !@files[path].nil?
314
+ end
315
+
316
+ private
317
+ def deleted?(path)
318
+ !File.exist?(path)
319
+ end
320
+
321
+ private
322
+ def open(path)
323
+ return @files[path] if !deleted?(path) && cached?(path)
324
+
325
+ if deleted?(path)
326
+ if @create_if_deleted
327
+ @logger.debug('Required file does not exist, creating it.', path: path)
328
+ @files.delete(path)
329
+ else
330
+ return @files[path] if cached?(path)
331
+ end
332
+ end
333
+
334
+ @logger.info('Opening file', path: path)
335
+
336
+ dir = File.dirname(path)
337
+ if !Dir.exist?(dir)
338
+ @logger.info('Creating directory', directory: dir)
339
+ if @dir_mode != -1
340
+ FileUtils.mkdir_p(dir, mode: @dir_mode)
341
+ else
342
+ FileUtils.mkdir_p(dir)
343
+ end
344
+ end
345
+
346
+ # work around a bug opening fifos (bug JRUBY-6280)
347
+ stat = begin
348
+ File.stat(path)
349
+ rescue
350
+ nil
351
+ end
352
+ fd = if stat && stat.ftype == 'fifo' && LogStash::Environment.jruby?
353
+ java.io.FileWriter.new(java.io.File.new(path))
354
+ elsif @file_mode != -1
355
+ File.new(path, 'a+', @file_mode)
356
+ else
357
+ File.new(path, 'a+')
358
+ end
359
+ # fd = if @file_mode != -1
360
+ # File.new(path, 'a+', @file_mode)
361
+ # else
362
+ # File.new(path, 'a+')
363
+ # end
364
+ # end
365
+ @files[path] = IOWriter.new(fd)
366
+ end
367
+
368
+ private
369
+ def kusto_send_file(file_path)
370
+ @ingestor.upload_async(file_path, delete_temp_files)
371
+ end
372
+
373
+ private
374
+ def recover_past_files
375
+ require 'find'
376
+
377
+ # we need to find the last "regular" part in the path before any dynamic vars
378
+ path_last_char = @path.length - 1
379
+
380
+ pattern_start = @path.index('%') || path_last_char
381
+ last_folder_before_pattern = @path.rindex('/', pattern_start) || path_last_char
382
+ new_path = path[0..last_folder_before_pattern]
383
+
384
+ begin
385
+ return unless Dir.exist?(new_path)
386
+ @logger.info("Going to recover old files in path #{@new_path}")
387
+
388
+ old_files = Find.find(new_path).select { |p| /.*\.#{database}\.#{table}$/ =~ p }
389
+ @logger.info("Found #{old_files.length} old file(s), sending them now...")
390
+
391
+ old_files.each do |file|
392
+ kusto_send_file(file)
393
+ end
394
+ rescue Errno::ENOENT => e
395
+ @logger.warn('No such file or directory', exception: e.class, message: e.message, path: new_path, backtrace: e.backtrace)
396
+ end
397
+ end
398
+ end
399
+
400
+ # wrapper class
401
+ class IOWriter
402
+ def initialize(io)
403
+ @io = io
404
+ end
405
+
406
+ def write(*args)
407
+ @io.write(*args)
408
+ @active = true
409
+ end
410
+
411
+ def flush
412
+ @io.flush
413
+ end
414
+
415
+ def method_missing(method_name, *args, &block)
416
+ if @io.respond_to?(method_name)
417
+
418
+ @io.send(method_name, *args, &block)
419
+ else
420
+ super
421
+ end
422
+ end
423
+ attr_accessor :active
424
+ end