fluent-plugin-oci-logging 1.0.2

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 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: []