fluent-plugin-oci-logging-fluentd-v1.15 1.0.3

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: e274d50ce9b66dc365cc18d13a7cb89c1e036420eb2cb00c6dc9fcd79278829b
4
+ data.tar.gz: c34fe1818586edca220158d5c7723b03656e623f475fed1e316f4ba9fbc2269c
5
+ SHA512:
6
+ metadata.gz: 1374a784e0f06a49029fafa23c1fe539a308e670ea6e866f0c0b987d7c393e0fa079f3ff61406abf8b13d39c344f19ed82f01de0b828aacc29bcda41a585e320
7
+ data.tar.gz: dce98148332b5b8413922a35719c79b718b787dc64e35b4b0d285b436a97c494fd8baa7e3d6bde7f2ee39bb557957f6b75adb0081cf78586dc9cbfcf16a58381
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,290 @@
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
+ PUBLIC_DEFAULT_DEBIAN_CA_PATH = PUBLIC_DEFAULT_UBUNTU_CA_PATH
31
+
32
+ def logger
33
+ @log ||= OS.windows? ? Logger.new(WINDOWS_UPLOADER_OUTPUT_LOG) : Logger.new($stdout)
34
+ end
35
+
36
+ ##
37
+ # Calculate federation endpoints based on metadata and optional inputs
38
+ #
39
+ # @param [String] region the region identifier
40
+ #
41
+ # @return [String] the federation endpoint that will be used
42
+ def get_federation_endpoint(region)
43
+ if region == 'r1'
44
+ endpoint = ENV['FEDERATION_ENDPOINT'] || 'https://auth.r1.oracleiaas.com/v1/x509'
45
+ else
46
+ if @realmDomainComponent.nil?
47
+ logger.warn('realm domain is null, fall back to OCI Regions')
48
+ @realmDomainComponent = OCI::Regions.get_second_level_domain(region) if @realmDomainComponent.nil?
49
+ end
50
+
51
+ endpoint = ENV['FEDERATION_ENDPOINT'] || "https://auth.#{region}.#{@realmDomainComponent}/v1/x509"
52
+ end
53
+
54
+ logger.info("endpoint is #{endpoint} in region #{region}")
55
+ endpoint
56
+ end
57
+
58
+ def get_instance_md_with_retry(retries = RETRIES)
59
+ Retriable.retriable(tries: retries, on: StandardError, timeout: 12) do
60
+ return get_instance_md
61
+ end
62
+ end
63
+
64
+ def get_instance_md
65
+ # v2 of IMDS requires an authorization header
66
+ md = get_instance_md_with_url('http://169.254.169.254/opc/v2/instance/')
67
+ if md.nil?
68
+ logger.info('IMDS v2 is not available, use v1')
69
+ md = get_instance_md_with_url('http://169.254.169.254/opc/v1/instance/')
70
+ end
71
+
72
+ if !md.nil?
73
+ logger.info "Successfully fetch instance metadata for hosts in overlay #{md}"
74
+ return md
75
+ else
76
+ raise StandardError, 'Failure fetching instance metadata, possible '\
77
+ 'reason is network issue or host is not OCI instance'
78
+ end
79
+ end
80
+
81
+ def get_instance_md_with_url(uri_link)
82
+ uri = URI.parse(uri_link)
83
+ http = ::Net::HTTP.new(uri.host, uri.port)
84
+ http.open_timeout = 2 # in seconds
85
+ http.read_timeout = 2 # in seconds
86
+ request = ::Net::HTTP::Get.new(uri.request_uri)
87
+ request.add_field('Authorization', 'Bearer Oracle') if uri_link.include?('v2')
88
+ resp = http.request(request)
89
+ JSON.parse(resp.body)
90
+ rescue StandardError
91
+ logger.warn("failed to get instance metadata with link #{uri_link}")
92
+ nil
93
+ end
94
+
95
+ ##
96
+ # Calculate logging endpoint from environment or metadata.
97
+ # Preference is given to the environment variable 'LOGGING_FRONTEND'.
98
+ #
99
+ # @param [String] region the region identifier
100
+ #
101
+ # @return [String] The logging endpoint that will be used.
102
+ def get_logging_endpoint(region, logging_endpoint_override: nil)
103
+ unless logging_endpoint_override.nil?
104
+ logger.info "using logging endpoint override #{logging_endpoint_override} for testing"
105
+ return logging_endpoint_override
106
+ end
107
+
108
+ if region == 'r1'
109
+ endpoint = ENV['LOGGING_FRONTEND'] || "https://ingestion.logging.#{region}.oci.oracleiaas.com"
110
+ else
111
+ if @realmDomainComponent.nil?
112
+ logger.warn('realm domain is null, fall back to OCI Regions')
113
+ @realmDomainComponent = OCI::Regions.get_second_level_domain(region) if @realmDomainComponent.nil?
114
+ end
115
+
116
+ endpoint = ENV['LOGGING_FRONTEND'] || "https://ingestion.logging.#{region}.oci.#{@realmDomainComponent}"
117
+ end
118
+
119
+ logger.info("endpoint is #{endpoint} in region #{region}")
120
+ endpoint
121
+ end
122
+
123
+ def get_signer_type(principal_override: nil, config_dir: nil)
124
+ config_dir ||= OS.windows? ? WINDOWS_OCI_CONFIG_DIR : LINUX_OCI_CONFIG_DIR
125
+
126
+ if (File.file?(config_dir) && principal_override != AUTO_SIGNER_TYPE) || principal_override == USER_SIGNER_TYPE
127
+ begin
128
+ logger.info("using #{USER_SIGNER_TYPE} signer type with config dir #{config_dir}")
129
+ oci_config = OCI::ConfigFileLoader.load_config(
130
+ config_file_location: config_dir, profile_name: USER_CONFIG_PROFILE_NAME
131
+ )
132
+ signer_type = USER_SIGNER_TYPE
133
+ rescue StandardError => e
134
+ if e.full_message.include?('Profile not found in the given config file.')
135
+ logger.warn("Profile #{USER_CONFIG_PROFILE_NAME} not configured "\
136
+ "in user api-key cli using other principal instead: #{e}")
137
+ signer_type = AUTO_SIGNER_TYPE
138
+ oci_config = OCI::Config.new
139
+ else
140
+ raise "User api-keys not setup correctly: #{e}"
141
+ end
142
+ end
143
+ else # if user api-keys is not setup in the expected format we expect instance principal
144
+ logger.info("using #{AUTO_SIGNER_TYPE} signer type")
145
+ signer_type = AUTO_SIGNER_TYPE
146
+ oci_config = OCI::Config.new
147
+ end
148
+ [oci_config, signer_type]
149
+ end
150
+
151
+ ##
152
+ # Configure the signer for the logging client call
153
+ #
154
+ # @param [String] signer_type the type of signer that should be returned
155
+ #
156
+ # @return [OCI::Signer] a signer that is representative of the signer type
157
+ def get_signer(oci_config, signer_type)
158
+ case signer_type
159
+ when USER_SIGNER_TYPE
160
+ get_host_info_for_non_oci_instance(oci_config)
161
+ set_default_ca_file
162
+ OCI::Signer.new(
163
+ oci_config.user,
164
+ oci_config.fingerprint,
165
+ oci_config.tenancy,
166
+ oci_config.key_file,
167
+ pass_phrase: oci_config.pass_phrase
168
+ )
169
+ when AUTO_SIGNER_TYPE
170
+ logger.info 'signer type is "auto", creating signer based on system setup'
171
+ get_host_info_for_oci_instance
172
+ set_default_ca_file
173
+ signer = create_resource_principal_signer
174
+ if signer.nil?
175
+ logger.info('resource principal is not setup, use instance principal instead')
176
+ signer = create_instance_principal_signer
177
+ else
178
+ logger.info('use resource principal')
179
+ end
180
+
181
+ signer
182
+ else
183
+ raise StandardError, "Principal type #{signer_type} not supported, "\
184
+ "use 'instance', 'resource' or 'user' instead"
185
+ end
186
+ end
187
+
188
+ def get_host_info_for_non_oci_instance(oci_config)
189
+ # set needed properties
190
+ @region = oci_config.region
191
+ # for non-OCI instances we can't get the display_name or hostname from IMDS and the fallback is the ip address
192
+ # of the machine
193
+ begin
194
+ @hostname = Socket.gethostname
195
+ rescue StandardError
196
+ ip = Socket.ip_address_list.detect { |intf| intf.ipv4_private? }
197
+ @hostname = ip ? ip.ip_address : 'UNKNOWN'
198
+ end
199
+
200
+ # No metadata service support for non-OCI instances
201
+ @realmDomainComponent = OCI::Regions.get_second_level_domain(@region)
202
+
203
+ logger.info("in non oci instance, region is #{@region}, "\
204
+ " hostname is #{@hostname}, realm is #{@realmDomainComponent}")
205
+ end
206
+
207
+ def get_host_info_for_oci_instance
208
+ md = @metadata_override || get_instance_md_with_retry
209
+
210
+ @region = md['canonicalRegionName'] == 'us-seattle-1' ? 'r1' : md['canonicalRegionName']
211
+ @hostname = md.key?('displayName') ? md['displayName'] : ''
212
+ @realmDomainComponent = md.fetch('regionInfo', {}).fetch(
213
+ 'realmDomainComponent', OCI::Regions.get_second_level_domain(@region)
214
+ )
215
+ logger.info("in oci instance, region is #{@region}, hostname is"\
216
+ " #{@hostname}, realm is #{@realmDomainComponent}")
217
+ end
218
+
219
+ ##
220
+ # Since r1 overlay has a different default make sure to update this
221
+ #
222
+ def set_default_ca_file
223
+ @ca_file = PUBLIC_DEFAULT_LINUX_CA_PATH if @ca_file.nil?
224
+ if OS.windows?
225
+ @ca_file = @ca_file == PUBLIC_DEFAULT_LINUX_CA_PATH ? PUBLIC_DEFAULT_WINDOWS_CA_PATH : @ca_file
226
+ elsif OS.ubuntu?
227
+ @ca_file = @ca_file == PUBLIC_DEFAULT_LINUX_CA_PATH ? PUBLIC_DEFAULT_UBUNTU_CA_PATH : @ca_file
228
+ elsif OS.debian?
229
+ @ca_file = @ca_file == PUBLIC_DEFAULT_LINUX_CA_PATH ? PUBLIC_DEFAULT_DEBIAN_CA_PATH : @ca_file
230
+ else
231
+ @ca_file = @region == 'r1' && @ca_file == PUBLIC_DEFAULT_LINUX_CA_PATH ? R1_CA_PATH : @ca_file
232
+ end
233
+
234
+ if @ca_file.nil?
235
+ msg = 'ca_file is not specified'
236
+ logger.error msg
237
+ raise StandardError, msg
238
+ end
239
+
240
+ # verify the ssl bundle actually exists
241
+ unless File.file?(@ca_file)
242
+ msg = "Does not exist or cannot open ca file: #{@ca_file}"
243
+ logger.error msg
244
+ raise StandardError, msg
245
+ end
246
+
247
+ # setting the cert_bundle_path
248
+ logger.info "using cert_bundle_path #{@ca_file}"
249
+ end
250
+
251
+ def create_resource_principal_signer
252
+ logger.info 'creating resource principal'
253
+ add_rp_env_override
254
+ OCI::Auth::Signers.resource_principals_signer
255
+ rescue StandardError => e
256
+ logger.info("fail to create resource principal with error #{e}")
257
+ nil
258
+ end
259
+
260
+ def add_rp_env_override
261
+ env_file = ENV['LOCAL_TEST_ENV_FILE'] || PUBLIC_RESOURCE_PRINCIPAL_ENV_FILE
262
+
263
+ resource_principal_env = {}
264
+
265
+ raise 'resource principal env file does not exist' unless File.exist? env_file
266
+
267
+ file = File.readlines(env_file)
268
+ file.each do |env|
269
+ a = env.split('=')
270
+ resource_principal_env[a[0]] = a[1].chomp
271
+ end
272
+
273
+ logger.info("resource principal env is setup with #{resource_principal_env}")
274
+ ENV.update resource_principal_env
275
+ end
276
+
277
+ def create_instance_principal_signer
278
+ endpoint = @federation_endpoint_override || get_federation_endpoint(@region)
279
+
280
+ unless endpoint.nil?
281
+ logger.info "create instance principal with federation_endpoint = #{endpoint}, cert_bundle #{@ca_file}"
282
+ end
283
+
284
+ ::OCI::Auth::Signers::InstancePrincipalsSecurityTokenSigner.new(
285
+ federation_endpoint: endpoint, federation_client_cert_bundle: @ca_file
286
+ )
287
+ end
288
+ end
289
+ end
290
+ 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).utc.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).utc.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,70 @@
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.debian?
31
+ linux? and os_name_debian?
32
+ end
33
+
34
+ def self.os_name_ubuntu?
35
+ os_name = 'not_found'
36
+ file_name = '/etc/os-release'
37
+ if File.exists?(file_name)
38
+ IO.foreach(file_name).each do |line|
39
+ if line.start_with?('ID=')
40
+ os_name = line.split('=')[1].strip
41
+ end
42
+ end
43
+ else
44
+ logger.info('Unknown linux distribution detected')
45
+ end
46
+ os_name == 'ubuntu' ? true : false
47
+ rescue StandardError => e
48
+ log.error "Unable to detect ubuntu platform due to: #{e.message}"
49
+ false
50
+ end
51
+
52
+ def self.os_name_debian?
53
+ os_name = 'not_found'
54
+ file_name = '/etc/os-release'
55
+ if File.exists?(file_name)
56
+ File.foreach(file_name).each do |line|
57
+ if line.start_with?('ID=')
58
+ os_name = line.split('=')[1].strip
59
+ end
60
+ end
61
+ else
62
+ logger.info('Unknown linux distribution detected')
63
+ end
64
+ os_name == 'debian' ? true : false
65
+ rescue StandardError => e
66
+ log.error "Unable to detect ubuntu platform due to: #{e.message}"
67
+ false
68
+ end
69
+
70
+ end
@@ -0,0 +1,105 @@
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
+ PAYLOAD_SIZE = 9*1024*1024 #restricting payload size at 9MB
46
+
47
+ def configure(conf)
48
+ super
49
+ log.debug 'determining the signer type'
50
+
51
+ oci_config, signer_type = get_signer_type(principal_override: @principal_override)
52
+ signer = get_signer(oci_config, signer_type)
53
+ log.info "using authentication principal #{signer_type}"
54
+
55
+ @client = OCI::Loggingingestion::LoggingClient.new(
56
+ config: oci_config,
57
+ endpoint: get_logging_endpoint(@region, logging_endpoint_override: @logging_endpoint_override),
58
+ signer: signer,
59
+ proxy_settings: nil,
60
+ retry_config: nil
61
+ )
62
+
63
+ @client.api_client.request_option_overrides = { ca_file: @ca_file }
64
+ end
65
+
66
+ def start
67
+ super
68
+ log.debug 'start'
69
+ end
70
+
71
+ #### Sync Buffered Output ##############################
72
+ # Implement write() if your plugin uses a normal buffer.
73
+ ########################################################
74
+ def write(chunk)
75
+ log.debug "writing chunk metadata #{chunk.metadata}", \
76
+ dump_unique_id_hex(chunk.unique_id)
77
+ log_batches_map = {}
78
+ # For standard chunk format (without #format() method)
79
+ size = 0
80
+ chunk.each do |time, record|
81
+ begin
82
+ tag = get_modified_tag(chunk.metadata.tag)
83
+ source_identifier = record.key?('tailed_path') ? record['tailed_path'] : ''
84
+ content = flatten_hash(record)
85
+ size += content.to_json.bytesize
86
+ build_request(time, record, tag, log_batches_map, source_identifier)
87
+ if size >= PAYLOAD_SIZE
88
+ log.info "Exceeding payload size. Size : #{size}"
89
+ send_requests(log_batches_map)
90
+ log_batches_map = {}
91
+ size = 0
92
+ end
93
+ rescue StandardError => e
94
+ log.error(e.full_message)
95
+ end
96
+ end
97
+ # flushing data to LJ
98
+ unless log_batches_map.empty?
99
+ log.info "Payload size : #{size}"
100
+ send_requests(log_batches_map)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ 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=3
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-fluentd-v1.15
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.3
5
+ platform: ruby
6
+ authors:
7
+ - OCI Observability Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-02-14 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.15.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.15.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.0.3.1
190
+ signing_key:
191
+ specification_version: 4
192
+ summary: OCI Fluentd Logging output plugin following Unified Schema
193
+ test_files: []