fluent-plugin-oci-logging 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ff4390e3b3e77082712da61a486e9bf756a55e849053490e03eb884a260e0d5b
4
+ data.tar.gz: 2e232b1bf8c667db6d4d2a96d9746b1a71a26d056e126a8b79eda83e3abaf5ed
5
+ SHA512:
6
+ metadata.gz: 382946fb56ed0575bc5e7996c96f92abec7c59efaee23fba3a2e76077d280ed6ff22f555732e27ca6959f19f26a6961bf0298277da02d775ac5d48f8623ddc09
7
+ data.tar.gz: 985ec4b0de8339aa19a33e03d6c67399137ca16f248657bc7b8216867f6e4c12577f9b1e9bd2d8114b7fe948f78fa860fe6b48ac342e42b773cc344407a633af
data/README.md ADDED
@@ -0,0 +1,136 @@
1
+ # Oracle Cloud Infrastructure Fluentd Plugin
2
+
3
+ This is the official [fluentd](https://docs.fluentd.org/) plugin for the Oracle
4
+ Cloud Infrastructure (OCI) Logging service. This project is open source, in
5
+ active development and maintained by Oracle.
6
+
7
+ ## Requirements
8
+ To use this fluentd plugin, you must have:
9
+
10
+ * An Oracle Cloud Infrastructure acount.
11
+ * A user created in that account, in a group with a policy that grants the
12
+ desired permissions. This can be a user for yourself, or another person/system
13
+ that needs to call the API. For an example of how to set up a new user, group,
14
+ compartment, and policy, see [Adding Users](https://docs.cloud.oracle.com/Content/GSG/Tasks/addingusers.htm)
15
+ in the Getting Started Guide. For a list of typical policies you may want to
16
+ use, see [Common Policies](https://docs.cloud.oracle.com/Content/Identity/Concepts/commonpolicies.htm)
17
+ in the User Guide.
18
+ * Ruby version 2.2 or later running on Mac, Linux or Windows.
19
+
20
+ ## Installation
21
+
22
+ Add this line to your application's Gemfile:
23
+
24
+ ```ruby
25
+ gem 'fluent-plugin-oci-logging'
26
+ ```
27
+
28
+ And then execute:
29
+
30
+ ```shell
31
+ $ bundle
32
+ ```
33
+
34
+ Or install it yourself as:
35
+
36
+ ```
37
+ gem install fluent-plugin-oci-logging
38
+ ```
39
+
40
+ Besides the plugin, the above commands will also automatically install fluentd,
41
+ as well as the rest of the required ruby dependencies, in your system.
42
+
43
+ ## Configuration
44
+
45
+ For usage with [instance principals](https://docs.oracle.com/en-us/iaas/Content/Identity/Tasks/callingservicesfrominstances.htm):
46
+
47
+ ```xml
48
+ <source>
49
+ @type dummy
50
+ tag test
51
+ dummy {"test":"message"}
52
+ </source>
53
+ <match **>
54
+ @type oci_logging
55
+ log_object_id ocid1.log.oc1.XXX.xxx
56
+ </match>
57
+ ```
58
+
59
+ For usage with an [API signing key]( https://docs.oracle.com/en-us/iaas/Content/API/Concepts/apisigningkey.htm):
60
+
61
+ ```xml
62
+ <source>
63
+ @type dummy
64
+ tag test
65
+ dummy {"test":"message"}
66
+ </source>
67
+ <match **>
68
+ @type oci_logging
69
+ principal_override user
70
+ log_object_id ocid1.log.oc1.XXX.xxx
71
+ </match>
72
+ ```
73
+
74
+ To authenticate as a particular user, you need to [generate an API Signing Key](https://docs.cloud.oracle.com/en-us/iaas/Content/API/SDKDocs/cliconfigure.htm) for that user.
75
+
76
+ ## Logging Setup
77
+
78
+ Detailed instructions, alongside examples, on how you can setup your logging
79
+ environment can be found in the official [OCI docs](https://docs.cloud.oracle.com/en-us/iaas/Content/Logging/Task/managinglogs.htm).
80
+ Also, to find out how to search your logs, you can check the documentation
81
+ for [log search](https://docs.cloud.oracle.com/en-us/iaas/Content/Logging/Concepts/searchinglogs.htm).
82
+
83
+ ## Documentation
84
+
85
+ Full documentation, including prerequisites, installation, and configuration
86
+ instructions can be found [here](https://docs.cloud.oracle.com/en-us/iaas/Content/Logging/Concepts/loggingoverview.htm).
87
+
88
+ API reference can be found [here](https://docs.cloud.oracle.com/en-us/iaas/tools/ruby/latest/index.html).
89
+
90
+ This documentation can be found installed in your system in the gem specific directory. You can find its exact location by running the command:
91
+
92
+ ```shell
93
+ gem contents fluent-plugin-oci-logging
94
+ ```
95
+
96
+ Alternatively, you can also view it via ruby's documentation tool `ri` with the following command:
97
+
98
+ ```shell
99
+ ri -f markdown fluent-plugin-oci-logging:README
100
+ ```
101
+
102
+ Finally, you can view it by extracting the gem contents (the gem file itself is a tar archive).
103
+
104
+
105
+ ## Known Issues
106
+
107
+ You can find information on any known issues with the SDK under the [Issues](https://github.com/oracle/fluent-plugin-oci-logging/issues) tab.
108
+
109
+ ## Questions or Feedback?
110
+
111
+ Please [open an issue for any problems or questions](https://github.com/oracle/fluent-plugin-oci-logging/issues) you may have.
112
+
113
+ Addtional ways to get in touch:
114
+
115
+ * [Stack Overflow](https://stackoverflow.com/): Please use the [oracle-cloud-infrastructure](https://stackoverflow.com/questions/tagged/oracle-cloud-infrastructure) and [oci-ruby-sdk](https://stackoverflow.com/questions/tagged/oci-ruby-sdk) tags in your post
116
+ * [Developer Tools section](https://community.oracle.com/community/cloud_computing/bare-metal/content?filterID=contentstatus%5Bpublished%5D~category%5Bdeveloper-tools%5D&filterID=contentstatus%5Bpublished%5D~objecttype~objecttype%5Bthread%5D) of the Oracle Cloud forums
117
+ * [My Oracle Support](https://support.oracle.com)
118
+
119
+ ## Contributing
120
+
121
+ This project welcomes contributions from the community. Before submitting a pull
122
+ request, please [review our contribution guide](./CONTRIBUTING.md).
123
+
124
+ ## Security
125
+
126
+ Please consult the [security guide](./SECURITY.md) for our responsible security
127
+ vulnerability disclosure process.
128
+
129
+ ## License
130
+
131
+ Copyright (c) 2021, Oracle and/or its affiliates.
132
+
133
+ This software is dual-licensed to you under the Universal Permissive License (UPL) 1.0 as shown at <https://oss.oracle.com/licenses/upl>
134
+ or Apache License 2.0 as shown at <http://www.apache.org/licenses/LICENSE-2.0>. You may choose either license.
135
+
136
+ See [LICENSE](./LICENSE.txt) for more details.
@@ -0,0 +1,286 @@
1
+ # fluent-plugin-oci-logging version 1.0.
2
+ #
3
+ # Copyright (c) 2021, Oracle and/or its affiliates.
4
+ # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
5
+
6
+ # frozen_string_literal: true
7
+
8
+ require 'retriable'
9
+ require 'oci'
10
+
11
+ require_relative 'os'
12
+ require_relative 'logging_utils'
13
+
14
+ module Fluent
15
+ module Plugin
16
+ # Setup code for OCI Logging
17
+ module PublicLoggingSetup
18
+ RETRIES = 3
19
+ USER_SIGNER_TYPE = 'user'
20
+ AUTO_SIGNER_TYPE = 'auto'
21
+ LINUX_OCI_CONFIG_DIR = Fluent::Plugin::PublicLoggingUtils.determine_linux_config_path
22
+ WINDOWS_OCI_CONFIG_DIR = Fluent::Plugin::PublicLoggingUtils.determine_windows_config_path
23
+ USER_CONFIG_PROFILE_NAME = Fluent::Plugin::PublicLoggingUtils.determine_config_profile_name(LINUX_OCI_CONFIG_DIR, WINDOWS_OCI_CONFIG_DIR)
24
+ PUBLIC_RESOURCE_PRINCIPAL_ENV_FILE = '/etc/resource_principal_env'
25
+
26
+ R1_CA_PATH = '/etc/pki/tls/certs/ca-bundle.crt'
27
+ PUBLIC_DEFAULT_LINUX_CA_PATH = '/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem'
28
+ PUBLIC_DEFAULT_WINDOWS_CA_PATH = 'C:\\oracle_unified_agent\\unified-monitoring-agent\\embedded\\ssl\\certs\\cacert.pem'
29
+ PUBLIC_DEFAULT_UBUNTU_CA_PATH = '/etc/ssl/certs/ca-certificates.crt'
30
+
31
+ def logger
32
+ @log ||= OS.windows? ? Logger.new(WINDOWS_UPLOADER_OUTPUT_LOG) : Logger.new($stdout)
33
+ end
34
+
35
+ ##
36
+ # Calculate federation endpoints based on metadata and optional inputs
37
+ #
38
+ # @param [String] region the region identifier
39
+ #
40
+ # @return [String] the federation endpoint that will be used
41
+ def get_federation_endpoint(region)
42
+ if region == 'r1'
43
+ endpoint = ENV['FEDERATION_ENDPOINT'] || 'https://auth.r1.oracleiaas.com/v1/x509'
44
+ else
45
+ if @realmDomainComponent.nil?
46
+ logger.warn('realm domain is null, fall back to OCI Regions')
47
+ @realmDomainComponent = OCI::Regions.get_second_level_domain(region) if @realmDomainComponent.nil?
48
+ end
49
+
50
+ endpoint = ENV['FEDERATION_ENDPOINT'] || "https://auth.#{region}.#{@realmDomainComponent}/v1/x509"
51
+ end
52
+
53
+ logger.info("endpoint is #{endpoint} in region #{region}")
54
+ endpoint
55
+ end
56
+
57
+ def get_instance_md_with_retry(retries = RETRIES)
58
+ Retriable.retriable(tries: retries, on: StandardError, timeout: 12) do
59
+ return get_instance_md
60
+ end
61
+ end
62
+
63
+ def get_instance_md
64
+ # v2 of IMDS requires an authorization header
65
+ md = get_instance_md_with_url('http://169.254.169.254/opc/v2/instance/')
66
+ if md.nil?
67
+ logger.info('IMDS v2 is not available, use v1')
68
+ md = get_instance_md_with_url('http://169.254.169.254/opc/v1/instance/')
69
+ end
70
+
71
+ if !md.nil?
72
+ logger.info "Successfully fetch instance metadata for hosts in overlay #{md}"
73
+ return md
74
+ else
75
+ raise StandardError, 'Failure fetching instance metadata, possible '\
76
+ 'reason is network issue or host is not OCI instance'
77
+ end
78
+ end
79
+
80
+ def get_instance_md_with_url(uri_link)
81
+ uri = URI.parse(uri_link)
82
+ http = ::Net::HTTP.new(uri.host, uri.port)
83
+ http.open_timeout = 2 # in seconds
84
+ http.read_timeout = 2 # in seconds
85
+ request = ::Net::HTTP::Get.new(uri.request_uri)
86
+ request.add_field('Authorization', 'Bearer Oracle') if uri_link.include?('v2')
87
+ resp = http.request(request)
88
+ JSON.parse(resp.body)
89
+ rescue StandardError
90
+ logger.warn("failed to get instance metadata with link #{uri_link}")
91
+ nil
92
+ end
93
+
94
+ ##
95
+ # Calculate logging endpoint from environment or metadata.
96
+ # Preference is given to the environment variable 'LOGGING_FRONTEND'.
97
+ #
98
+ # @param [String] region the region identifier
99
+ #
100
+ # @return [String] The logging endpoint that will be used.
101
+ def get_logging_endpoint(region, logging_endpoint_override: nil)
102
+ unless logging_endpoint_override.nil?
103
+ logger.info "using logging endpoint override #{logging_endpoint_override} for testing"
104
+ return logging_endpoint_override
105
+ end
106
+
107
+ if region == 'r1'
108
+ endpoint = ENV['LOGGING_FRONTEND'] || "https://ingestion.logging.#{region}.oci.oracleiaas.com"
109
+ else
110
+ if @realmDomainComponent.nil?
111
+ logger.warn('realm domain is null, fall back to OCI Regions')
112
+ @realmDomainComponent = OCI::Regions.get_second_level_domain(region) if @realmDomainComponent.nil?
113
+ end
114
+
115
+ endpoint = ENV['LOGGING_FRONTEND'] || "https://ingestion.logging.#{region}.oci.#{@realmDomainComponent}"
116
+ end
117
+
118
+ logger.info("endpoint is #{endpoint} in region #{region}")
119
+ endpoint
120
+ end
121
+
122
+ def get_signer_type(principal_override: nil, config_dir: nil)
123
+ config_dir ||= OS.windows? ? WINDOWS_OCI_CONFIG_DIR : LINUX_OCI_CONFIG_DIR
124
+
125
+ if (File.file?(config_dir) && principal_override != AUTO_SIGNER_TYPE) || principal_override == USER_SIGNER_TYPE
126
+ begin
127
+ logger.info("using #{USER_SIGNER_TYPE} signer type with config dir #{config_dir}")
128
+ oci_config = OCI::ConfigFileLoader.load_config(
129
+ config_file_location: config_dir, profile_name: USER_CONFIG_PROFILE_NAME
130
+ )
131
+ signer_type = USER_SIGNER_TYPE
132
+ rescue StandardError => e
133
+ if e.full_message.include?('Profile not found in the given config file.')
134
+ logger.warn("Profile #{USER_CONFIG_PROFILE_NAME} not configured "\
135
+ "in user api-key cli using other principal instead: #{e}")
136
+ signer_type = AUTO_SIGNER_TYPE
137
+ oci_config = OCI::Config.new
138
+ else
139
+ raise "User api-keys not setup correctly: #{e}"
140
+ end
141
+ end
142
+ else # if user api-keys is not setup in the expected format we expect instance principal
143
+ logger.info("using #{AUTO_SIGNER_TYPE} signer type")
144
+ signer_type = AUTO_SIGNER_TYPE
145
+ oci_config = OCI::Config.new
146
+ end
147
+ [oci_config, signer_type]
148
+ end
149
+
150
+ ##
151
+ # Configure the signer for the logging client call
152
+ #
153
+ # @param [String] signer_type the type of signer that should be returned
154
+ #
155
+ # @return [OCI::Signer] a signer that is representative of the signer type
156
+ def get_signer(oci_config, signer_type)
157
+ case signer_type
158
+ when USER_SIGNER_TYPE
159
+ get_host_info_for_non_oci_instance(oci_config)
160
+ set_default_ca_file
161
+ OCI::Signer.new(
162
+ oci_config.user,
163
+ oci_config.fingerprint,
164
+ oci_config.tenancy,
165
+ oci_config.key_file,
166
+ pass_phrase: oci_config.pass_phrase
167
+ )
168
+ when AUTO_SIGNER_TYPE
169
+ logger.info 'signer type is "auto", creating signer based on system setup'
170
+ get_host_info_for_oci_instance
171
+ set_default_ca_file
172
+ signer = create_resource_principal_signer
173
+ if signer.nil?
174
+ logger.info('resource principal is not setup, use instance principal instead')
175
+ signer = create_instance_principal_signer
176
+ else
177
+ logger.info('use resource principal')
178
+ end
179
+
180
+ signer
181
+ else
182
+ raise StandardError, "Principal type #{signer_type} not supported, "\
183
+ "use 'instance', 'resource' or 'user' instead"
184
+ end
185
+ end
186
+
187
+ def get_host_info_for_non_oci_instance(oci_config)
188
+ # set needed properties
189
+ @region = oci_config.region
190
+ # for non-OCI instances we can't get the display_name or hostname from IMDS and the fallback is the ip address
191
+ # of the machine
192
+ begin
193
+ @hostname = Socket.gethostname
194
+ rescue StandardError
195
+ ip = Socket.ip_address_list.detect { |intf| intf.ipv4_private? }
196
+ @hostname = ip ? ip.ip_address : 'UNKNOWN'
197
+ end
198
+
199
+ # No metadata service support for non-OCI instances
200
+ @realmDomainComponent = OCI::Regions.get_second_level_domain(@region)
201
+
202
+ logger.info("in non oci instance, region is #{@region}, "\
203
+ " hostname is #{@hostname}, realm is #{@realmDomainComponent}")
204
+ end
205
+
206
+ def get_host_info_for_oci_instance
207
+ md = @metadata_override || get_instance_md_with_retry
208
+
209
+ @region = md['canonicalRegionName'] == 'us-seattle-1' ? 'r1' : md['canonicalRegionName']
210
+ @hostname = md.key?('displayName') ? md['displayName'] : ''
211
+ @realmDomainComponent = md.fetch('regionInfo', {}).fetch(
212
+ 'realmDomainComponent', OCI::Regions.get_second_level_domain(@region)
213
+ )
214
+ logger.info("in oci instance, region is #{@region}, hostname is"\
215
+ " #{@hostname}, realm is #{@realmDomainComponent}")
216
+ end
217
+
218
+ ##
219
+ # Since r1 overlay has a different default make sure to update this
220
+ #
221
+ def set_default_ca_file
222
+ if OS.windows?
223
+ @ca_file = @ca_file == PUBLIC_DEFAULT_LINUX_CA_PATH ? PUBLIC_DEFAULT_WINDOWS_CA_PATH : @ca_file
224
+ elsif OS.ubuntu?
225
+ @ca_file = @ca_file == PUBLIC_DEFAULT_LINUX_CA_PATH ? PUBLIC_DEFAULT_UBUNTU_CA_PATH : @ca_file
226
+ else
227
+ @ca_file = @region == 'r1' && @ca_file == PUBLIC_DEFAULT_LINUX_CA_PATH ? R1_CA_PATH : @ca_file
228
+ end
229
+
230
+ if @ca_file.nil?
231
+ msg = 'ca_file is not specified'
232
+ logger.error msg
233
+ raise StandardError, msg
234
+ end
235
+
236
+ # verify the ssl bundle actually exists
237
+ unless File.file?(@ca_file)
238
+ msg = "Does not exist or cannot open ca file: #{@ca_file}"
239
+ logger.error msg
240
+ raise StandardError, msg
241
+ end
242
+
243
+ # setting the cert_bundle_path
244
+ logger.info "using cert_bundle_path #{@ca_file}"
245
+ end
246
+
247
+ def create_resource_principal_signer
248
+ logger.info 'creating resource principal'
249
+ add_rp_env_override
250
+ OCI::Auth::Signers.resource_principals_signer
251
+ rescue StandardError => e
252
+ logger.info("fail to create resource principal with error #{e}")
253
+ nil
254
+ end
255
+
256
+ def add_rp_env_override
257
+ env_file = ENV['LOCAL_TEST_ENV_FILE'] || PUBLIC_RESOURCE_PRINCIPAL_ENV_FILE
258
+
259
+ resource_principal_env = {}
260
+
261
+ raise 'resource principal env file does not exist' unless File.exist? env_file
262
+
263
+ file = File.readlines(env_file)
264
+ file.each do |env|
265
+ a = env.split('=')
266
+ resource_principal_env[a[0]] = a[1].chomp
267
+ end
268
+
269
+ logger.info("resource principal env is setup with #{resource_principal_env}")
270
+ ENV.update resource_principal_env
271
+ end
272
+
273
+ def create_instance_principal_signer
274
+ endpoint = @federation_endpoint_override || get_federation_endpoint(@region)
275
+
276
+ unless endpoint.nil?
277
+ logger.info "create instance principal with federation_endpoint = #{endpoint}, cert_bundle #{@ca_file}"
278
+ end
279
+
280
+ ::OCI::Auth::Signers::InstancePrincipalsSecurityTokenSigner.new(
281
+ federation_endpoint: endpoint, federation_client_cert_bundle: @ca_file
282
+ )
283
+ end
284
+ end
285
+ end
286
+ end
@@ -0,0 +1,207 @@
1
+ # fluent-plugin-oci-logging version 1.0.
2
+ #
3
+ # Copyright (c) 2021, Oracle and/or its affiliates.
4
+ # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
5
+
6
+ # frozen_string_literal: true
7
+
8
+ require 'etc'
9
+ require 'securerandom'
10
+ require 'oci'
11
+
12
+ module Fluent
13
+ module Plugin
14
+ # Common utility methods
15
+ module PublicLoggingUtils
16
+ PUBLIC_CLIENT_SPEC_VERSION = '1.0'
17
+ PUBLIC_LOGGING_PREFIX = 'com.oraclecloud.logging.custom.'
18
+
19
+ ##
20
+ # Build the wrapper for all the log entries to be sent to lumberjack.
21
+ #
22
+ # @return [OCI::LoggingClient::Models::PutLogsDetails] PutLogsDetails wrapper to be filled with log entries
23
+ def get_put_logs_details_request
24
+ request = OCI::Loggingingestion::Models::PutLogsDetails.new
25
+ request.specversion = PUBLIC_CLIENT_SPEC_VERSION
26
+ request
27
+ end
28
+
29
+ ##
30
+ # Build the requests to be sent to the Lumberjack endpoint.
31
+ #
32
+ # @param [Time] time Fluentd event time (may be different from event's
33
+ # timestamp)
34
+ # @param [Hash] record The fluentd record.
35
+ # @param [String] tagpath The fluentd path if populated.
36
+ # @param [Hash] log_batches_map List of pre-existing log batch.
37
+ # @param [String] sourceidentifier The fluentd contianing the source path.
38
+ #
39
+ # @return [Array] requests List of requests for Lumberjack's backend.
40
+ def build_request(time, record, tagpath, log_batches_map, sourceidentifier)
41
+ log = @log || Logger.new($stdout)
42
+ content = flatten_hash(record)
43
+
44
+ # create lumberjack request records
45
+ logentry = ::OCI::Loggingingestion::Models::LogEntry.new
46
+ logentry.time = Time.at(time).strftime('%FT%T.%LZ')
47
+ begin
48
+ logentry.data = content.to_json
49
+ rescue StandardError
50
+ begin
51
+ log.warn('ruby "to_json" expected UTF-8 encoding will retry parsing with forced encoding')
52
+ # json requires UTF-8 encoding some logs may not follow that format so we need to fix that
53
+ content = encode_to_utf8(content)
54
+ logentry.data = content.to_json
55
+ rescue StandardError
56
+ log.warn('unexpected encoding in the log request, will send log as a string instead of json')
57
+ # instead of loosing data because of an unknown encoding issue send the data as a string
58
+ logentry.data = content.to_s
59
+ end
60
+ end
61
+ logentry.id = SecureRandom.uuid
62
+
63
+ requestkey = tagpath + sourceidentifier
64
+
65
+ unless log_batches_map.key?(requestkey)
66
+ log_entry_batch = OCI::Loggingingestion::Models::LogEntryBatch.new
67
+ log_entry_batch.source = @hostname
68
+ log_entry_batch.type = PUBLIC_LOGGING_PREFIX + tagpath
69
+ log_entry_batch.subject = sourceidentifier
70
+ log_entry_batch.defaultlogentrytime = Time.at(time).strftime('%FT%T.%LZ')
71
+ log_entry_batch.entries = []
72
+
73
+ log_batches_map[requestkey] = log_entry_batch
74
+ end
75
+
76
+ log_batches_map[requestkey].entries << logentry
77
+ end
78
+
79
+ ##
80
+ # Send prebuilt requests to the logging endpoint.
81
+ #
82
+ # @param [Hash] log_batches_map
83
+ def send_requests(log_batches_map)
84
+ log = @log || Logger.new($stdout) # for testing
85
+
86
+ request = get_put_logs_details_request
87
+
88
+ request.log_entry_batches = log_batches_map.values
89
+ begin
90
+ resp = @client.put_logs(@log_object_id, request)
91
+ rescue OCI::Errors::ServiceError => e
92
+ log.error "Service Error received sending request: #{e}"
93
+ if e.status_code == 400
94
+ log.info 'Eating service error as it is caused by Bad Request[400 HTTP code]'
95
+ else
96
+ log.error "Throwing service error for status code:#{e.status_code} as we want fluentd to re-try"
97
+ raise
98
+ end
99
+ rescue OCI::Errors::NetworkError => e
100
+ log.error "Network Error received sending request: #{e}"
101
+ if e.code == 400
102
+ log.info 'Eating network error as it is caused by Bad Request[400 HTTP code]'
103
+ else
104
+ log.error "Throwing network error for code:#{e.code} as we want fluentd to re-try"
105
+ raise
106
+ end
107
+ rescue StandardError => e
108
+ log.error "Standard Error received sending request: #{e}"
109
+ raise
110
+ end
111
+ request.log_entry_batches.each do |batch|
112
+ log.info "log batch type #{batch.type}, log batch subject #{batch.subject}, size #{batch.entries.size}"
113
+ end
114
+
115
+ log.info "response #{resp.status} id: #{resp.request_id}"
116
+ end
117
+
118
+ ##
119
+ # Flatten the keys of a hash.
120
+ #
121
+ # @param [Hash] record The hash object to flatten.
122
+ #
123
+ # @return [Hash] The updated, flattened, hash.
124
+ def flatten_hash(record)
125
+ record.each_with_object({}) do |(k, v), h|
126
+ if v.is_a? Hash
127
+ flatten_hash(v).map { |h_k, h_v| h["#{k}.#{h_k}"] = h_v }
128
+ elsif k == 'log'
129
+ h['msg'] = v
130
+ else
131
+ h[k] = v
132
+ end
133
+ end
134
+ end
135
+
136
+ ##
137
+ # Force all the string values in the hash to be encoded to UTF-8.
138
+ #
139
+ # @param [Hash] record the flattened hash needing to have the encoding changes
140
+ #
141
+ # @return [Hash] The updated hash.
142
+ def encode_to_utf8(record)
143
+ # the reason for using ISO-8859-1 is that it covers most of the out
144
+ # of band characters other encoding's don't have this way we can
145
+ # encode it to a known value and then encode it back into UTF-8
146
+ record.transform_values { |v| v.to_s.force_encoding('ISO-8859-1').encode('UTF-8') }
147
+ end
148
+
149
+ ##
150
+ # Parse out the log_type from the chunk metadata tag
151
+ #
152
+ # @param [String] rawtag the string of the chunk metadata tag that needs to be parsed
153
+ #
154
+ # @return [String] take out the tag after the first '.' character or return the whole tag if there is no '.'
155
+ def get_modified_tag(rawtag)
156
+ tag = rawtag || 'empty'
157
+ tag.split('.').length > 1 ? tag.partition('.')[2] : tag
158
+ end
159
+
160
+ ##
161
+ # Return the correct oci configuration file path for linux platforms.
162
+ #
163
+ # @return [String] path to the configuration file
164
+ def self.determine_linux_config_path
165
+ managed_agent = '/etc/unified-monitoring-agent/.oci/config'
166
+ unmanaged_agent = File.join(Dir.home, '.oci/config')
167
+ config = File.file?(managed_agent) ? managed_agent : unmanage_agent
168
+ return config
169
+ rescue StandardError
170
+ return unmanaged_agent
171
+ end
172
+
173
+ ##
174
+ # Return the correct oci configuration file path for windows platforms.
175
+ #
176
+ # @return [String] path to the configuration file
177
+ def self.determine_windows_config_path
178
+ managed_agent = 'C:\\oracle_unified_agent\\.oci\\config'
179
+ unmanaged_agent = File.join("C:\\Users\\#{Etc.getlogin}\\.oci\\config")
180
+ config = File.file?(managed_agent) ? managed_agent : unmanage_agent
181
+ return config
182
+ rescue StandardError
183
+ return unmanaged_agent
184
+ end
185
+
186
+ ##
187
+ # Return the correct oci configuration user profile for auth
188
+ #
189
+ # @param [String] linux_cfg Path to linux configuration file
190
+ # @param [String] windows_cfg Path to windows configuration file
191
+ #
192
+ # @return [String] username to use
193
+ def self.determine_config_profile_name(linux_cfg, windows_cfg)
194
+ managed_agent='UNIFIED_MONITORING_AGENT'
195
+ unmanaged_agent='DEFAULT'
196
+ location = OS.windows? ? windows_cfg : linux_cfg
197
+ if location.start_with?('/etc/unified-monitoring-agent', 'C:\\oracle_unified_agent')
198
+ return managed_agent
199
+ end
200
+ oci_config = OCI::ConfigFileLoader.load_config(config_file_location: location)
201
+ oci_config.profile
202
+ rescue StandardError
203
+ unmanaged_agent
204
+ end
205
+ end
206
+ end
207
+ end
@@ -0,0 +1,48 @@
1
+ # fluent-plugin-oci-logging version 1.0.
2
+ #
3
+ # Copyright (c) 2021, Oracle and/or its affiliates.
4
+ # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
5
+
6
+ # frozen_string_literal: true
7
+
8
+ # Utility class to determine the underlying operating system.
9
+ module OS
10
+ def self.windows?
11
+ (/cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM) != nil
12
+ end
13
+
14
+ def self.mac?
15
+ (/darwin/ =~ RUBY_PLATFORM) != nil
16
+ end
17
+
18
+ def self.unix?
19
+ !OS.windows?
20
+ end
21
+
22
+ def self.linux?
23
+ unix? and !mac?
24
+ end
25
+
26
+ def self.ubuntu?
27
+ linux? and os_name_ubuntu?
28
+ end
29
+
30
+ def self.os_name_ubuntu?
31
+ os_name = 'not_found'
32
+ file_name = '/etc/os-release'
33
+ if File.exists?(file_name)
34
+ IO.foreach(file_name).each do |line|
35
+ if line.start_with?('ID=')
36
+ os_name = line.split('=')[1].strip
37
+ end
38
+ end
39
+ else
40
+ logger.info('Unknown linux distribution detected')
41
+ end
42
+ os_name == 'ubuntu' ? true : false
43
+ rescue StandardError => e
44
+ log.error "Unable to detect ubuntu platform due to: #{e.message}"
45
+ false
46
+ end
47
+
48
+ end
@@ -0,0 +1,92 @@
1
+ # fluent-plugin-oci-logging version 1.0.
2
+ #
3
+ # Copyright (c) 2021, Oracle and/or its affiliates.
4
+ # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
5
+
6
+ # frozen_string_literal: true
7
+
8
+ require_relative 'logging_utils'
9
+ require_relative 'logging_setup'
10
+ require 'socket'
11
+
12
+ module Fluent
13
+ module Plugin
14
+ # OCI Logging Fluentd Output plugin
15
+ class OCILoggingOutput < Fluent::Plugin::Output
16
+ include Fluent::Plugin::PublicLoggingUtils
17
+ include Fluent::Plugin::PublicLoggingSetup
18
+
19
+ Fluent::Plugin.register_output('oci_logging', self)
20
+
21
+ # allow instance metadata to be configurable (for testing)
22
+ config_param :metadata_override, :hash, default: nil
23
+
24
+ # Path to the PEM CA certificate file for TLS. Can contain several CA certs.
25
+ # We are defaulting to '/etc/oci-pki/ca-bundle.pem'
26
+ # This can be overriden for testing.
27
+ config_param :ca_file, :string, default: PUBLIC_DEFAULT_LINUX_CA_PATH
28
+
29
+ # Override to manually provide the host endpoint
30
+ config_param :logging_endpoint_override, :string, default: nil
31
+
32
+ # Override to manually provide the federation endpoint
33
+ config_param :federation_endpoint_override, :string, default: nil
34
+
35
+ # The only required parameter used to identify where we are sending logs for LJ
36
+ config_param :log_object_id, :string
37
+
38
+ # optional forced override for debugging, testing, and potential custom configurations
39
+ config_param :principal_override, :string, default: nil
40
+
41
+ attr_accessor :client, :hostname
42
+
43
+ helpers :event_emitter
44
+
45
+ def configure(conf)
46
+ super
47
+ log.debug 'determining the signer type'
48
+
49
+ oci_config, signer_type = get_signer_type(principal_override: @principal_override)
50
+ signer = get_signer(oci_config, signer_type)
51
+ log.info "using authentication principal #{signer_type}"
52
+
53
+ @client = OCI::Loggingingestion::LoggingClient.new(
54
+ config: oci_config,
55
+ endpoint: get_logging_endpoint(@region, logging_endpoint_override: @logging_endpoint_override),
56
+ signer: signer,
57
+ proxy_settings: nil,
58
+ retry_config: nil
59
+ )
60
+
61
+ @client.api_client.request_option_overrides = { ca_file: @ca_file }
62
+ end
63
+
64
+ def start
65
+ super
66
+ log.debug 'start'
67
+ end
68
+
69
+ #### Sync Buffered Output ##############################
70
+ # Implement write() if your plugin uses a normal buffer.
71
+ ########################################################
72
+ def write(chunk)
73
+ log.debug "writing chunk metadata #{chunk.metadata}", \
74
+ dump_unique_id_hex(chunk.unique_id)
75
+ log_batches_map = {}
76
+
77
+ # For standard chunk format (without #format() method)
78
+ chunk.each do |time, record|
79
+ begin
80
+ tag = get_modified_tag(chunk.metadata.tag)
81
+ source_identifier = record.key?('tailed_path') ? record['tailed_path'] : ''
82
+ build_request(time, record, tag, log_batches_map, source_identifier)
83
+ rescue StandardError => e
84
+ log.error(e.full_message)
85
+ end
86
+ end
87
+
88
+ send_requests(log_batches_map) # flush data
89
+ end
90
+ end
91
+ end
92
+ end
data/lib/version.rb ADDED
@@ -0,0 +1,22 @@
1
+ # fluent-plugin-oci-logging version 1.0.
2
+ #
3
+ # Copyright (c) 2021, Oracle and/or its affiliates.
4
+ # Licensed under the Universal Permissive License v 1.0 as shown at https://oss.oracle.com/licenses/upl/
5
+
6
+ # frozen_string_literal: true
7
+
8
+ require 'open3'
9
+
10
+ def number_of_commits
11
+ tstamp = Time.now.utc.strftime('%Y%m%d%H%M%S').to_s
12
+ cmd = 'git log --pretty=oneline | wc -l'
13
+ stdout_str, stderr_str, status = Open3.capture3(cmd)
14
+ status.success? ? stdout_str.strip() : "rc-#{tstamp}"
15
+ end
16
+
17
+ module OCILogging
18
+ MAJOR=1
19
+ MINOR=0
20
+ PATCH=number_of_commits
21
+ VERSION = ENV['OCI_LOGGING_GEM_VERSION'] || "#{MAJOR}.#{MINOR}.#{PATCH}"
22
+ end
metadata ADDED
@@ -0,0 +1,193 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-oci-logging
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.2
5
+ platform: ruby
6
+ authors:
7
+ - OCI Observability Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-06-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fluentd
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 1.12.3
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 1.12.3
27
+ - !ruby/object:Gem::Dependency
28
+ name: oci
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.12'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.12'
41
+ - !ruby/object:Gem::Dependency
42
+ name: retriable
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.1.2
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.1.2
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: mocha
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.9'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.9'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '13.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '13.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-rake
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '0.5'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '0.5'
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.17'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.17'
125
+ - !ruby/object:Gem::Dependency
126
+ name: test-unit
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: webmock
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.0'
153
+ description: 'Oracle Observability FluentD Plugins : Logging output plugin for OCI
154
+ logging'
155
+ email:
156
+ - no-reply@support.oracle.com
157
+ executables: []
158
+ extensions: []
159
+ extra_rdoc_files:
160
+ - README.md
161
+ files:
162
+ - README.md
163
+ - lib/fluent/plugin/logging_setup.rb
164
+ - lib/fluent/plugin/logging_utils.rb
165
+ - lib/fluent/plugin/os.rb
166
+ - lib/fluent/plugin/out_oci_logging.rb
167
+ - lib/version.rb
168
+ homepage: https://docs.cloud.oracle.com/en-us/iaas/Content/Logging/Concepts/loggingoverview.htm
169
+ licenses:
170
+ - UPL-1.0
171
+ - Apache-2.0
172
+ metadata:
173
+ source_code_uri: https://github.com/oracle/fluent-plugin-oci-logging
174
+ post_install_message:
175
+ rdoc_options: []
176
+ require_paths:
177
+ - lib
178
+ required_ruby_version: !ruby/object:Gem::Requirement
179
+ requirements:
180
+ - - ">="
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
183
+ required_rubygems_version: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - ">="
186
+ - !ruby/object:Gem::Version
187
+ version: '0'
188
+ requirements: []
189
+ rubygems_version: 3.2.15
190
+ signing_key:
191
+ specification_version: 4
192
+ summary: OCI Fluentd Logging output plugin following Unified Schema
193
+ test_files: []