connectors_service 8.5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +93 -0
- data/NOTICE.txt +2 -0
- data/bin/connectors_service +4 -0
- data/bin/list_connectors +4 -0
- data/config/connectors.yml +25 -0
- data/lib/app/app.rb +25 -0
- data/lib/app/config.rb +132 -0
- data/lib/app/console_app.rb +278 -0
- data/lib/app/dispatcher.rb +121 -0
- data/lib/app/menu.rb +104 -0
- data/lib/app/preflight_check.rb +134 -0
- data/lib/app/version.rb +10 -0
- data/lib/connectors/base/adapter.rb +119 -0
- data/lib/connectors/base/connector.rb +57 -0
- data/lib/connectors/base/custom_client.rb +111 -0
- data/lib/connectors/connector_status.rb +31 -0
- data/lib/connectors/crawler/scheduler.rb +32 -0
- data/lib/connectors/example/connector.rb +57 -0
- data/lib/connectors/example/example_attachments/first_attachment.txt +1 -0
- data/lib/connectors/example/example_attachments/second_attachment.txt +1 -0
- data/lib/connectors/example/example_attachments/third_attachment.txt +1 -0
- data/lib/connectors/gitlab/adapter.rb +50 -0
- data/lib/connectors/gitlab/connector.rb +67 -0
- data/lib/connectors/gitlab/custom_client.rb +44 -0
- data/lib/connectors/gitlab/extractor.rb +69 -0
- data/lib/connectors/mongodb/connector.rb +138 -0
- data/lib/connectors/registry.rb +52 -0
- data/lib/connectors/sync_status.rb +21 -0
- data/lib/connectors.rb +16 -0
- data/lib/connectors_app/// +13 -0
- data/lib/connectors_service.rb +24 -0
- data/lib/connectors_utility.rb +16 -0
- data/lib/core/configuration.rb +48 -0
- data/lib/core/connector_settings.rb +142 -0
- data/lib/core/elastic_connector_actions.rb +269 -0
- data/lib/core/heartbeat.rb +32 -0
- data/lib/core/native_scheduler.rb +24 -0
- data/lib/core/output_sink/base_sink.rb +33 -0
- data/lib/core/output_sink/combined_sink.rb +38 -0
- data/lib/core/output_sink/console_sink.rb +51 -0
- data/lib/core/output_sink/es_sink.rb +74 -0
- data/lib/core/output_sink.rb +13 -0
- data/lib/core/scheduler.rb +158 -0
- data/lib/core/single_scheduler.rb +29 -0
- data/lib/core/sync_job_runner.rb +111 -0
- data/lib/core.rb +16 -0
- data/lib/list_connectors.rb +22 -0
- data/lib/stubs/app_config.rb +35 -0
- data/lib/stubs/connectors/stats.rb +35 -0
- data/lib/stubs/service_type.rb +13 -0
- data/lib/utility/constants.rb +20 -0
- data/lib/utility/cron.rb +81 -0
- data/lib/utility/elasticsearch/index/language_data.yml +111 -0
- data/lib/utility/elasticsearch/index/mappings.rb +104 -0
- data/lib/utility/elasticsearch/index/text_analysis_settings.rb +226 -0
- data/lib/utility/environment.rb +33 -0
- data/lib/utility/errors.rb +132 -0
- data/lib/utility/es_client.rb +84 -0
- data/lib/utility/exception_tracking.rb +64 -0
- data/lib/utility/extension_mapping_util.rb +123 -0
- data/lib/utility/logger.rb +84 -0
- data/lib/utility/middleware/basic_auth.rb +27 -0
- data/lib/utility/middleware/bearer_auth.rb +27 -0
- data/lib/utility/middleware/restrict_hostnames.rb +73 -0
- data/lib/utility.rb +16 -0
- metadata +487 -0
@@ -0,0 +1,104 @@
|
|
1
|
+
#
|
2
|
+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
3
|
+
# or more contributor license agreements. Licensed under the Elastic License;
|
4
|
+
# you may not use this file except in compliance with the Elastic License.
|
5
|
+
#
|
6
|
+
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
module Utility
|
10
|
+
module Elasticsearch
|
11
|
+
module Index
|
12
|
+
module Mappings
|
13
|
+
ENUM_IGNORE_ABOVE = 2048
|
14
|
+
|
15
|
+
DATE_FIELD_MAPPING = {
|
16
|
+
type: 'date'
|
17
|
+
}
|
18
|
+
|
19
|
+
KEYWORD_FIELD_MAPPING = {
|
20
|
+
type: 'keyword'
|
21
|
+
}
|
22
|
+
|
23
|
+
TEXT_FIELD_MAPPING = {
|
24
|
+
type: 'text',
|
25
|
+
analyzer: 'iq_text_base',
|
26
|
+
index_options: 'freqs',
|
27
|
+
fields: {
|
28
|
+
'stem': {
|
29
|
+
type: 'text',
|
30
|
+
analyzer: 'iq_text_stem'
|
31
|
+
},
|
32
|
+
'prefix' => {
|
33
|
+
type: 'text',
|
34
|
+
analyzer: 'i_prefix',
|
35
|
+
search_analyzer: 'q_prefix',
|
36
|
+
index_options: 'docs'
|
37
|
+
},
|
38
|
+
'delimiter' => {
|
39
|
+
type: 'text',
|
40
|
+
analyzer: 'iq_text_delimiter',
|
41
|
+
index_options: 'freqs'
|
42
|
+
},
|
43
|
+
'joined': {
|
44
|
+
type: 'text',
|
45
|
+
analyzer: 'i_text_bigram',
|
46
|
+
search_analyzer: 'q_text_bigram',
|
47
|
+
index_options: 'freqs'
|
48
|
+
},
|
49
|
+
'enum': {
|
50
|
+
type: 'keyword',
|
51
|
+
ignore_above: ENUM_IGNORE_ABOVE
|
52
|
+
}
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
WORKPLACE_SEARCH_SUBEXTRACTION_STAMP_FIELD_MAPPINGS = {
|
57
|
+
_subextracted_as_of: DATE_FIELD_MAPPING,
|
58
|
+
_subextracted_version: KEYWORD_FIELD_MAPPING
|
59
|
+
}.freeze
|
60
|
+
|
61
|
+
CRAWLER_FIELD_MAPPINGS = {
|
62
|
+
additional_urls: KEYWORD_FIELD_MAPPING,
|
63
|
+
body_content: TEXT_FIELD_MAPPING,
|
64
|
+
domains: KEYWORD_FIELD_MAPPING,
|
65
|
+
headings: TEXT_FIELD_MAPPING,
|
66
|
+
last_crawled_at: DATE_FIELD_MAPPING,
|
67
|
+
links: KEYWORD_FIELD_MAPPING,
|
68
|
+
meta_description: TEXT_FIELD_MAPPING,
|
69
|
+
meta_keywords: KEYWORD_FIELD_MAPPING,
|
70
|
+
title: TEXT_FIELD_MAPPING,
|
71
|
+
url: KEYWORD_FIELD_MAPPING,
|
72
|
+
url_host: KEYWORD_FIELD_MAPPING,
|
73
|
+
url_path: KEYWORD_FIELD_MAPPING,
|
74
|
+
url_path_dir1: KEYWORD_FIELD_MAPPING,
|
75
|
+
url_path_dir2: KEYWORD_FIELD_MAPPING,
|
76
|
+
url_path_dir3: KEYWORD_FIELD_MAPPING,
|
77
|
+
url_port: KEYWORD_FIELD_MAPPING,
|
78
|
+
url_scheme: KEYWORD_FIELD_MAPPING
|
79
|
+
}.freeze
|
80
|
+
|
81
|
+
def self.default_text_fields_mappings(connectors_index:, crawler_index: false)
|
82
|
+
{
|
83
|
+
dynamic: true,
|
84
|
+
dynamic_templates: [
|
85
|
+
{
|
86
|
+
data: {
|
87
|
+
match_mapping_type: 'string',
|
88
|
+
mapping: TEXT_FIELD_MAPPING
|
89
|
+
}
|
90
|
+
}
|
91
|
+
],
|
92
|
+
properties: {
|
93
|
+
id: KEYWORD_FIELD_MAPPING
|
94
|
+
}.tap do |properties|
|
95
|
+
properties.merge!(WORKPLACE_SEARCH_SUBEXTRACTION_STAMP_FIELD_MAPPINGS) if connectors_index
|
96
|
+
end.tap do |properties|
|
97
|
+
properties.merge!(CRAWLER_FIELD_MAPPINGS) if crawler_index
|
98
|
+
end
|
99
|
+
}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
#
|
2
|
+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
3
|
+
# or more contributor license agreements. Licensed under the Elastic License;
|
4
|
+
# you may not use this file except in compliance with the Elastic License.
|
5
|
+
#
|
6
|
+
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require 'yaml'
|
10
|
+
|
11
|
+
module Utility
|
12
|
+
module Elasticsearch
|
13
|
+
module Index
|
14
|
+
class TextAnalysisSettings
|
15
|
+
class UnsupportedLanguageCode < StandardError; end
|
16
|
+
|
17
|
+
DEFAULT_LANGUAGE = :en
|
18
|
+
FRONT_NGRAM_MAX_GRAM = 12
|
19
|
+
LANGUAGE_DATA_FILE_PATH = File.join(File.dirname(__FILE__), 'language_data.yml')
|
20
|
+
|
21
|
+
GENERIC_FILTERS = {
|
22
|
+
front_ngram: {
|
23
|
+
type: 'edge_ngram',
|
24
|
+
min_gram: 1,
|
25
|
+
max_gram: FRONT_NGRAM_MAX_GRAM
|
26
|
+
},
|
27
|
+
delimiter: {
|
28
|
+
type: 'word_delimiter_graph',
|
29
|
+
generate_word_parts: true,
|
30
|
+
generate_number_parts: true,
|
31
|
+
catenate_words: true,
|
32
|
+
catenate_numbers: true,
|
33
|
+
catenate_all: true,
|
34
|
+
preserve_original: false,
|
35
|
+
split_on_case_change: true,
|
36
|
+
split_on_numerics: true,
|
37
|
+
stem_english_possessive: true
|
38
|
+
},
|
39
|
+
bigram_joiner: {
|
40
|
+
type: 'shingle',
|
41
|
+
token_separator: '',
|
42
|
+
max_shingle_size: 2,
|
43
|
+
output_unigrams: false
|
44
|
+
},
|
45
|
+
bigram_joiner_unigrams: {
|
46
|
+
type: 'shingle',
|
47
|
+
token_separator: '',
|
48
|
+
max_shingle_size: 2,
|
49
|
+
output_unigrams: true
|
50
|
+
},
|
51
|
+
bigram_max_size: {
|
52
|
+
type: 'length',
|
53
|
+
min: 0,
|
54
|
+
max: 16
|
55
|
+
}
|
56
|
+
}.freeze
|
57
|
+
|
58
|
+
NON_ICU_ANALYSIS_SETTINGS = {
|
59
|
+
tokenizer_name: 'standard', folding_filters: %w(cjk_width lowercase asciifolding)
|
60
|
+
}.freeze
|
61
|
+
|
62
|
+
ICU_ANALYSIS_SETTINGS = {
|
63
|
+
tokenizer_name: 'icu_tokenizer', folding_filters: %w(icu_folding)
|
64
|
+
}.freeze
|
65
|
+
|
66
|
+
def initialize(language_code: nil, analysis_icu: false)
|
67
|
+
@language_code = (language_code || DEFAULT_LANGUAGE).to_sym
|
68
|
+
|
69
|
+
raise UnsupportedLanguageCode, "Language '#{language_code}' is not supported" unless language_data[@language_code]
|
70
|
+
|
71
|
+
@analysis_icu = analysis_icu
|
72
|
+
@analysis_settings = icu_settings(analysis_icu)
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_h
|
76
|
+
{
|
77
|
+
analysis: {
|
78
|
+
analyzer: analyzer_definitions,
|
79
|
+
filter: filter_definitions
|
80
|
+
},
|
81
|
+
index: {
|
82
|
+
similarity: {
|
83
|
+
default: {
|
84
|
+
type: 'BM25'
|
85
|
+
}
|
86
|
+
}
|
87
|
+
}
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
attr_reader :language_code, :analysis_settings
|
94
|
+
|
95
|
+
def icu_settings(analysis_settings)
|
96
|
+
return ICU_ANALYSIS_SETTINGS if analysis_settings
|
97
|
+
|
98
|
+
NON_ICU_ANALYSIS_SETTINGS
|
99
|
+
end
|
100
|
+
|
101
|
+
def stemmer_name
|
102
|
+
language_data[language_code][:stemmer]
|
103
|
+
end
|
104
|
+
|
105
|
+
def stop_words_name_or_list
|
106
|
+
language_data[language_code][:stop_words]
|
107
|
+
end
|
108
|
+
|
109
|
+
def custom_filter_definitions
|
110
|
+
language_data[language_code][:custom_filter_definitions] || {}
|
111
|
+
end
|
112
|
+
|
113
|
+
def prepended_filters
|
114
|
+
language_data[language_code][:prepended_filters] || []
|
115
|
+
end
|
116
|
+
|
117
|
+
def postpended_filters
|
118
|
+
language_data[language_code][:postpended_filters] || []
|
119
|
+
end
|
120
|
+
|
121
|
+
def stem_filter_name
|
122
|
+
"#{language_code}-stem-filter".to_sym
|
123
|
+
end
|
124
|
+
|
125
|
+
def stop_words_filter_name
|
126
|
+
"#{language_code}-stop-words-filter".to_sym
|
127
|
+
end
|
128
|
+
|
129
|
+
def filter_definitions
|
130
|
+
definitions = GENERIC_FILTERS.dup
|
131
|
+
|
132
|
+
definitions[stem_filter_name] = {
|
133
|
+
type: 'stemmer',
|
134
|
+
name: stemmer_name
|
135
|
+
}
|
136
|
+
|
137
|
+
definitions[stop_words_filter_name] = {
|
138
|
+
type: 'stop',
|
139
|
+
stopwords: stop_words_name_or_list
|
140
|
+
}
|
141
|
+
|
142
|
+
definitions.merge(custom_filter_definitions)
|
143
|
+
end
|
144
|
+
|
145
|
+
def analyzer_definitions
|
146
|
+
definitions = {}
|
147
|
+
|
148
|
+
definitions[:i_prefix] = {
|
149
|
+
tokenizer: analysis_settings[:tokenizer_name],
|
150
|
+
filter: [
|
151
|
+
*analysis_settings[:folding_filters],
|
152
|
+
'front_ngram'
|
153
|
+
]
|
154
|
+
}
|
155
|
+
|
156
|
+
definitions[:q_prefix] = {
|
157
|
+
tokenizer: analysis_settings[:tokenizer_name],
|
158
|
+
filter: [
|
159
|
+
*analysis_settings[:folding_filters]
|
160
|
+
]
|
161
|
+
}
|
162
|
+
|
163
|
+
definitions[:iq_text_base] = {
|
164
|
+
tokenizer: analysis_settings[:tokenizer_name],
|
165
|
+
filter: [
|
166
|
+
*analysis_settings[:folding_filters],
|
167
|
+
stop_words_filter_name
|
168
|
+
]
|
169
|
+
}
|
170
|
+
|
171
|
+
definitions[:iq_text_stem] = {
|
172
|
+
tokenizer: analysis_settings[:tokenizer_name],
|
173
|
+
filter: [
|
174
|
+
*prepended_filters,
|
175
|
+
*analysis_settings[:folding_filters],
|
176
|
+
stop_words_filter_name,
|
177
|
+
stem_filter_name,
|
178
|
+
*postpended_filters
|
179
|
+
]
|
180
|
+
}
|
181
|
+
|
182
|
+
definitions[:iq_text_delimiter] = {
|
183
|
+
tokenizer: 'whitespace',
|
184
|
+
filter: [
|
185
|
+
*prepended_filters,
|
186
|
+
'delimiter',
|
187
|
+
*analysis_settings[:folding_filters],
|
188
|
+
stop_words_filter_name,
|
189
|
+
stem_filter_name,
|
190
|
+
*postpended_filters
|
191
|
+
]
|
192
|
+
}
|
193
|
+
|
194
|
+
definitions[:i_text_bigram] = {
|
195
|
+
tokenizer: analysis_settings[:tokenizer_name],
|
196
|
+
filter: [
|
197
|
+
*analysis_settings[:folding_filters],
|
198
|
+
stem_filter_name,
|
199
|
+
'bigram_joiner',
|
200
|
+
'bigram_max_size'
|
201
|
+
]
|
202
|
+
}
|
203
|
+
|
204
|
+
definitions[:q_text_bigram] = {
|
205
|
+
tokenizer: analysis_settings[:tokenizer_name],
|
206
|
+
filter: [
|
207
|
+
*analysis_settings[:folding_filters],
|
208
|
+
stem_filter_name,
|
209
|
+
'bigram_joiner_unigrams',
|
210
|
+
'bigram_max_size'
|
211
|
+
]
|
212
|
+
}
|
213
|
+
|
214
|
+
definitions
|
215
|
+
end
|
216
|
+
|
217
|
+
def language_data
|
218
|
+
@language_data ||= YAML.safe_load(
|
219
|
+
File.read(LANGUAGE_DATA_FILE_PATH),
|
220
|
+
symbolize_names: true
|
221
|
+
)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
#
|
2
|
+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
3
|
+
# or more contributor license agreements. Licensed under the Elastic License;
|
4
|
+
# you may not use this file except in compliance with the Elastic License.
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'logger'
|
8
|
+
require 'utility/logger'
|
9
|
+
require 'active_support/core_ext/module'
|
10
|
+
|
11
|
+
module Utility
|
12
|
+
module Environment
|
13
|
+
def self.set_execution_environment(config, &block)
|
14
|
+
# Set UTC as the timezone
|
15
|
+
ENV['TZ'] = 'UTC'
|
16
|
+
Logger.level = config[:log_level]
|
17
|
+
es_config = config[:elasticsearch]
|
18
|
+
disable_warnings = if es_config.has_key?(:disable_warnings)
|
19
|
+
es_config[:disable_warnings]
|
20
|
+
else
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
if disable_warnings
|
25
|
+
Logger.info('Disabling warnings')
|
26
|
+
Kernel.silence_warnings(&block)
|
27
|
+
else
|
28
|
+
Logger.info('Enabling warnings')
|
29
|
+
Kernel.enable_warnings(&block)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
#
|
2
|
+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
3
|
+
# or more contributor license agreements. Licensed under the Elastic License;
|
4
|
+
# you may not use this file except in compliance with the Elastic License.
|
5
|
+
#
|
6
|
+
|
7
|
+
require 'active_support/core_ext/string'
|
8
|
+
|
9
|
+
module Utility
|
10
|
+
class DocumentError
|
11
|
+
attr_accessor :error_class, :error_message, :stack_trace, :error_id
|
12
|
+
|
13
|
+
def initialize(error_class, error_message, stack_trace, error_id)
|
14
|
+
@error_class = error_class
|
15
|
+
@error_message = error_message
|
16
|
+
@error_id = error_id
|
17
|
+
|
18
|
+
# keywords must be < 32kb, UTF-8 chars can be up to 3 bytes, thus 32k/3 ~= 10k
|
19
|
+
# See https://github.com/elastic/workplace-search-team/issues/1723
|
20
|
+
@stack_trace = stack_trace.truncate(10_000)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_h
|
24
|
+
{
|
25
|
+
'error_class' => error_class,
|
26
|
+
'error_message' => error_message,
|
27
|
+
'stack_trace' => stack_trace,
|
28
|
+
'error_id' => error_id
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class ClientError < StandardError; end
|
34
|
+
class EvictionWithNoProgressError < StandardError; end
|
35
|
+
class EvictionError < StandardError
|
36
|
+
attr_accessor :cursors
|
37
|
+
|
38
|
+
def initialize(message = nil, cursors: nil)
|
39
|
+
super(message)
|
40
|
+
@cursors = cursors
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
class SuspendedJobError < StandardError
|
45
|
+
attr_accessor :suspend_until, :cursors
|
46
|
+
|
47
|
+
def initialize(message = nil, suspend_until:, cursors: nil)
|
48
|
+
super(message)
|
49
|
+
@suspend_until = suspend_until
|
50
|
+
@cursors = cursors
|
51
|
+
end
|
52
|
+
end
|
53
|
+
class ThrottlingError < SuspendedJobError; end
|
54
|
+
class TransientServerError < SuspendedJobError; end
|
55
|
+
class UnrecoverableServerError < StandardError; end
|
56
|
+
class TransientSubextractorError < StandardError; end
|
57
|
+
class JobDocumentLimitError < StandardError; end
|
58
|
+
class JobClaimingError < StandardError; end
|
59
|
+
|
60
|
+
class MonitoringError < StandardError
|
61
|
+
attr_accessor :tripped_by
|
62
|
+
|
63
|
+
def initialize(message = nil, tripped_by: nil)
|
64
|
+
super("#{message}#{tripped_by.present? ? " Tripped by - #{tripped_by.class}: #{tripped_by.message}" : ''}")
|
65
|
+
@tripped_by = tripped_by
|
66
|
+
end
|
67
|
+
end
|
68
|
+
class MaxSuccessiveErrorsExceededError < MonitoringError; end
|
69
|
+
class MaxErrorsExceededError < MonitoringError; end
|
70
|
+
class MaxErrorsInWindowExceededError < MonitoringError; end
|
71
|
+
|
72
|
+
class JobSyncNotPossibleYetError < StandardError
|
73
|
+
attr_accessor :sync_will_be_possible_at
|
74
|
+
|
75
|
+
def initialize(message = nil, sync_will_be_possible_at: nil)
|
76
|
+
human_readable_errors = []
|
77
|
+
|
78
|
+
human_readable_errors.push(message) unless message.nil?
|
79
|
+
human_readable_errors.push("Content source was created too recently to schedule jobs, next job scheduling is possible at #{sync_will_be_possible_at}.") unless sync_will_be_possible_at.nil?
|
80
|
+
|
81
|
+
super(human_readable_errors.join(' '))
|
82
|
+
end
|
83
|
+
end
|
84
|
+
class PlatinumLicenseRequiredError < StandardError; end
|
85
|
+
class JobInterruptedError < StandardError; end
|
86
|
+
class JobCannotBeUpdatedError < StandardError; end
|
87
|
+
class SecretInvalidError < StandardError; end
|
88
|
+
class InvalidIndexingConfigurationError < StandardError; end
|
89
|
+
class InvalidTokenError < StandardError; end
|
90
|
+
class TokenRefreshFailedError < StandardError; end
|
91
|
+
class ConnectorNotAvailableError < StandardError; end
|
92
|
+
|
93
|
+
# For when we want to explicitly set a #cause but can't
|
94
|
+
class ExplicitlyCausedError < StandardError
|
95
|
+
attr_reader :reason
|
96
|
+
|
97
|
+
def initialize(reason)
|
98
|
+
@reason = reason
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class PublishingFailedError < ExplicitlyCausedError; end
|
103
|
+
|
104
|
+
class Error
|
105
|
+
attr_reader :status_code, :code, :message
|
106
|
+
|
107
|
+
def initialize(status_code, code, message)
|
108
|
+
@status_code = status_code
|
109
|
+
@code = code
|
110
|
+
@message = message
|
111
|
+
end
|
112
|
+
|
113
|
+
def to_h
|
114
|
+
{
|
115
|
+
'code' => @code,
|
116
|
+
'message' => @message
|
117
|
+
}
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class HealthCheckFailedError < StandardError
|
122
|
+
def initialize(msg = nil)
|
123
|
+
super("Health check failed for 3rd-party service: #{msg}")
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
INTERNAL_SERVER_ERROR = Utility::Error.new(500, 'INTERNAL_SERVER_ERROR', 'Internal server error')
|
128
|
+
INVALID_API_KEY = Utility::Error.new(401, 'INVALID_API_KEY', 'Invalid API key')
|
129
|
+
UNSUPPORTED_AUTH_SCHEME = Utility::Error.new(401, 'UNSUPPORTED_AUTH_SCHEME', 'Unsupported authorization scheme')
|
130
|
+
INVALID_ACCESS_TOKEN = Utility::Error.new(401, 'INVALID_ACCESS_TOKEN', 'Invalid/expired access token, please refresh the token')
|
131
|
+
TOKEN_REFRESH_ERROR = Utility::Error.new(401, 'TOKEN_REFRESH_ERROR', 'Failed to refresh token, please re-authenticate the application')
|
132
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
#
|
2
|
+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
3
|
+
# or more contributor license agreements. Licensed under the Elastic License;
|
4
|
+
# you may not use this file except in compliance with the Elastic License.
|
5
|
+
#
|
6
|
+
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require 'logger'
|
10
|
+
require 'elasticsearch'
|
11
|
+
|
12
|
+
module Utility
|
13
|
+
class EsClient < ::Elasticsearch::Client
|
14
|
+
class IndexingFailedError < StandardError
|
15
|
+
def initialize(message, error = nil)
|
16
|
+
super(message)
|
17
|
+
@cause = error
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :cause
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(es_config)
|
24
|
+
super(connection_configs(es_config))
|
25
|
+
end
|
26
|
+
|
27
|
+
def connection_configs(es_config)
|
28
|
+
configs = {}
|
29
|
+
configs[:api_key] = es_config[:api_key] if es_config[:api_key]
|
30
|
+
if es_config[:cloud_id]
|
31
|
+
configs[:cloud_id] = es_config[:cloud_id]
|
32
|
+
elsif es_config[:hosts]
|
33
|
+
configs[:hosts] = es_config[:hosts]
|
34
|
+
else
|
35
|
+
raise 'Either elasticsearch.cloud_id or elasticsearch.hosts should be configured.'
|
36
|
+
end
|
37
|
+
configs[:retry_on_failure] = es_config[:retry_on_failure] || false
|
38
|
+
configs[:request_timeout] = es_config[:request_timeout] || nil
|
39
|
+
configs[:log] = es_config[:log] || false
|
40
|
+
configs[:trace] = es_config[:trace] || false
|
41
|
+
|
42
|
+
# if log or trace is activated, we use the application logger
|
43
|
+
configs[:logger] = if configs[:log] || configs[:trace]
|
44
|
+
Utility::Logger.logger
|
45
|
+
else
|
46
|
+
# silence!
|
47
|
+
::Logger.new(IO::NULL)
|
48
|
+
end
|
49
|
+
configs
|
50
|
+
end
|
51
|
+
|
52
|
+
def bulk(arguments = {})
|
53
|
+
raise_if_necessary(super(arguments))
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def raise_if_necessary(response)
|
59
|
+
if response['errors']
|
60
|
+
first_error = nil
|
61
|
+
|
62
|
+
response['items'].each do |item|
|
63
|
+
%w[index delete].each do |op|
|
64
|
+
if item.has_key?(op) && item[op].has_key?('error')
|
65
|
+
first_error = item
|
66
|
+
|
67
|
+
break
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if first_error
|
73
|
+
trace_id = Utility::Logger.generate_trace_id
|
74
|
+
Utility::Logger.error("Failed to index documents into Elasticsearch. First error in response is: #{first_error.to_json}")
|
75
|
+
short_message = Utility::Logger.abbreviated_message(first_error.to_json)
|
76
|
+
raise IndexingFailedError.new("Failed to index documents into Elasticsearch with an error '#{short_message}'. Look up the error ID [#{trace_id}] in the application logs to see the full error message.")
|
77
|
+
else
|
78
|
+
raise IndexingFailedError.new('Failed to index documents into Elasticsearch due to unknown error. Try enabling tracing for Elasticsearch and checking the logs.')
|
79
|
+
end
|
80
|
+
end
|
81
|
+
response
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
#
|
2
|
+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
3
|
+
# or more contributor license agreements. Licensed under the Elastic License;
|
4
|
+
# you may not use this file except in compliance with the Elastic License.
|
5
|
+
#
|
6
|
+
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require 'bson'
|
10
|
+
require 'utility/logger'
|
11
|
+
|
12
|
+
module Utility
|
13
|
+
class ExceptionTracking
|
14
|
+
class << self
|
15
|
+
def capture_message(message, context = {})
|
16
|
+
Utility::Logger.error("Error: #{message}. Context: #{context.inspect}")
|
17
|
+
|
18
|
+
# When the method is called from a rescue block, our return value may leak outside of its
|
19
|
+
# intended scope, so let's explicitly return nil here to be safe.
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def capture_exception(exception, context = {})
|
24
|
+
Utility::Logger.log_stacktrace(generate_stack_trace(exception))
|
25
|
+
Utility::Logger.error("Context: #{context.inspect}") if context
|
26
|
+
end
|
27
|
+
|
28
|
+
def log_exception(exception, message = nil)
|
29
|
+
Utility::Logger.error(message) if message
|
30
|
+
Utility::Logger.log_stacktrace(generate_stack_trace(exception))
|
31
|
+
end
|
32
|
+
|
33
|
+
def augment_exception(exception)
|
34
|
+
unless exception.respond_to?(:id)
|
35
|
+
exception.instance_eval do
|
36
|
+
def id
|
37
|
+
@error_id ||= BSON::ObjectId.new.to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def generate_error_message(exception, message, context)
|
44
|
+
context = { :message_id => exception.id }.merge(context || {}) if exception.respond_to?(:id)
|
45
|
+
context_message = context && "Context: #{context.inspect}"
|
46
|
+
['Exception', message, exception.class.to_s, exception.message, context_message]
|
47
|
+
.compact
|
48
|
+
.map { |part| part.to_s.dup.force_encoding('UTF-8') }
|
49
|
+
.join(': ')
|
50
|
+
end
|
51
|
+
|
52
|
+
def generate_stack_trace(exception)
|
53
|
+
full_message = exception.full_message
|
54
|
+
|
55
|
+
cause = exception
|
56
|
+
while cause.cause != cause && (cause = cause.cause)
|
57
|
+
full_message << "Cause:\n#{cause.full_message}"
|
58
|
+
end
|
59
|
+
|
60
|
+
full_message.dup.force_encoding('UTF-8')
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|