apptrail-application-events-sdk 0.0.1 → 0.0.5
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 +4 -4
- data/README.md +4 -2
- data/lib/apptrail-application-events-sdk.rb +99 -66
- data/lib/raw-event-schema.json +19 -13
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 033ced91d5d347d03c478bfd99656e901920d6eadb8823b19fd0b5751ac3b736
|
4
|
+
data.tar.gz: 38f63305471ef6aa3ea25392df9c5b72676b3410ab4d6ec5055c8d1597f655e2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: efe5076ba194024da52d8b9305fcaf7319717243eab7f2bba2320cfdb31eb353b194d4ee7fd350295e43520dfaedfa8cf5b4c881a3c2aecccc602527e82e31e9
|
7
|
+
data.tar.gz: 288c4f04e2508aaa7636d685dc14c873382f597ab96a7a83db99f0e2097580671cff16ff6c7bf7ade18e457757d0145c7593a60d10982a9176ce457091accf1c
|
data/README.md
CHANGED
@@ -62,8 +62,10 @@ event = {
|
|
62
62
|
},
|
63
63
|
},
|
64
64
|
],
|
65
|
-
|
66
|
-
|
65
|
+
context: {
|
66
|
+
sourceIpAddress: "103.6.179.245",
|
67
|
+
userAgent: "Apache-HttpClient/4.5.3 (Java/11.0.11)"
|
68
|
+
},
|
67
69
|
tags: {
|
68
70
|
severity: "LOW",
|
69
71
|
},
|
@@ -1,13 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'logger'
|
2
4
|
require 'stringio'
|
3
|
-
require
|
4
|
-
require
|
5
|
+
require 'base64'
|
6
|
+
require 'json'
|
5
7
|
require 'securerandom'
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
|
10
|
-
#HTTP.default_options = HTTP::Options.new(features: {logging: { logger: Logger.new(STDOUT)}})
|
8
|
+
require 'http'
|
9
|
+
require 'json-schema'
|
10
|
+
require 'retriable'
|
11
11
|
|
12
12
|
module Apptrail
|
13
13
|
class << self
|
@@ -15,15 +15,13 @@ module Apptrail
|
|
15
15
|
# For example, log to STDOUT by setting this to Logger.new(STDOUT).
|
16
16
|
#
|
17
17
|
# @return [Logger]
|
18
|
-
|
18
|
+
attr_writer :logger
|
19
19
|
|
20
20
|
def logger
|
21
21
|
@logger ||= Logger.new($stdout).tap do |log|
|
22
|
-
log.progname =
|
22
|
+
log.progname = name
|
23
23
|
end
|
24
24
|
end
|
25
|
-
|
26
|
-
|
27
25
|
end
|
28
26
|
|
29
27
|
class ApptrailError < StandardError; end
|
@@ -34,10 +32,19 @@ module Apptrail
|
|
34
32
|
tries: 3,
|
35
33
|
base_interval: 0.04,
|
36
34
|
on: Apptrail::ApptrailRetryableError,
|
37
|
-
on_retry:
|
35
|
+
on_retry: proc { Apptrail.logger.debug('Retrying upload.') },
|
36
|
+
max_interval: 0.12,
|
37
|
+
max_elapsed_time: 4,
|
38
|
+
rand_factor: 0.2
|
39
|
+
}
|
40
|
+
c.contexts[:session] = {
|
41
|
+
tries: 3,
|
42
|
+
base_interval: 0.04,
|
43
|
+
on: Apptrail::ApptrailRetryableError,
|
44
|
+
on_retry: proc { Apptrail.logger.debug('Retrying refresh session.') },
|
38
45
|
max_interval: 0.12,
|
39
46
|
max_elapsed_time: 4,
|
40
|
-
rand_factor: 0.2
|
47
|
+
rand_factor: 0.2
|
41
48
|
}
|
42
49
|
end
|
43
50
|
|
@@ -46,12 +53,9 @@ module Apptrail
|
|
46
53
|
#
|
47
54
|
# See [Sending events](https://apptrail.com/docs/applications/guide/working-with-events/overview).
|
48
55
|
class ApptrailEventsClient
|
49
|
-
@_form_data = nil
|
50
|
-
@_upload_url = nil
|
51
|
-
|
52
56
|
SCHEMA = JSON.parse(File.read(
|
53
|
-
|
54
|
-
|
57
|
+
File.expand_path('raw-event-schema.json', __dir__)
|
58
|
+
)).freeze
|
55
59
|
private_constant :SCHEMA
|
56
60
|
|
57
61
|
def inspect
|
@@ -59,21 +63,29 @@ module Apptrail
|
|
59
63
|
end
|
60
64
|
|
61
65
|
def to_s
|
62
|
-
|
66
|
+
'ApptrailEventsClient{}'
|
63
67
|
end
|
64
68
|
|
65
69
|
def initialize(region:, api_key:)
|
66
70
|
@_region = region
|
67
71
|
begin
|
68
72
|
parsed_key = Base64.urlsafe_decode64(api_key)
|
69
|
-
application_id,
|
73
|
+
application_id, = parsed_key.split(',', 3)
|
70
74
|
@_application_id = application_id
|
71
|
-
rescue
|
72
|
-
raise ArgumentError,
|
75
|
+
rescue StandardError
|
76
|
+
raise ArgumentError, 'Invalid API Key.'
|
73
77
|
end
|
74
78
|
|
75
79
|
@_base_api_url = "https://events.#{region}.apptrail.com/applications/session"
|
76
80
|
@_api_key = api_key
|
81
|
+
@_session_client = HTTP.persistent @_base_api_url
|
82
|
+
|
83
|
+
@_form_data = nil
|
84
|
+
@_upload_url = nil
|
85
|
+
@_upload_client = nil
|
86
|
+
|
87
|
+
@_finalize_id = SecureRandom.uuid
|
88
|
+
ObjectSpace.define_finalizer(@_finalize_id, proc { close })
|
77
89
|
end
|
78
90
|
|
79
91
|
# Send a single audit event to log to Apptrail.
|
@@ -83,88 +95,109 @@ module Apptrail
|
|
83
95
|
put_events([event])
|
84
96
|
end
|
85
97
|
|
86
|
-
|
87
98
|
# Send a list of up to 1000 audit events to log to Apptrail.
|
88
99
|
#
|
89
100
|
# @param events [Array<ApptrailEvent>]] An array of events to log. See [Apptrail Event Format](https://apptrail.com/docs/applications/guide/event-format) for a full description of the event format.
|
90
101
|
def put_events(events)
|
91
|
-
unless events.is_a?(Array)
|
92
|
-
|
93
|
-
end
|
94
|
-
unless events.length <= 1000
|
95
|
-
raise ArgumentError, "Can not send more than 1000 events in a single PutEvents call."
|
96
|
-
end
|
102
|
+
raise TypeError, 'Must pass in array of events.' unless events.is_a?(Array)
|
103
|
+
raise ArgumentError, 'Can not send more than 1000 events in a single PutEvents call.' unless events.length <= 1000
|
97
104
|
|
98
105
|
begin
|
99
|
-
JSON::Validator.validate!(SCHEMA, events, :
|
106
|
+
JSON::Validator.validate!(SCHEMA, events, list: true, parse_data: false)
|
100
107
|
rescue JSON::Schema::ValidationError => e
|
101
|
-
raise Apptrail::ApptrailError
|
108
|
+
raise Apptrail::ApptrailError, e.message
|
102
109
|
end
|
103
110
|
|
104
|
-
if @_upload_url.nil? || @_form_data.nil?
|
105
|
-
_refresh_post_policy()
|
106
|
-
end
|
111
|
+
_refresh_post_policy() if @_upload_url.nil? || @_form_data.nil? || @_upload_client.nil?
|
107
112
|
|
108
|
-
content =
|
113
|
+
content = StringIO.new
|
109
114
|
events.each do |evt|
|
110
|
-
content << JSON.
|
115
|
+
content << JSON.fast_generate(evt)
|
111
116
|
content << "\n"
|
112
117
|
end
|
113
118
|
|
114
|
-
filename = SecureRandom.uuid
|
119
|
+
filename = "#{SecureRandom.uuid}.jsonl"
|
115
120
|
s3_key = File.join(@_application_id, filename)
|
116
121
|
|
117
|
-
form_file = HTTP::FormData::File.new(StringIO.new(content), :
|
122
|
+
form_file = HTTP::FormData::File.new(StringIO.new(content.string), filename: filename)
|
118
123
|
|
119
124
|
new_form_opts = {
|
120
125
|
**@_form_data,
|
121
|
-
:
|
122
|
-
:
|
126
|
+
key: s3_key,
|
127
|
+
file: form_file
|
123
128
|
}
|
124
129
|
|
130
|
+
resp = nil
|
125
131
|
begin
|
126
132
|
Retriable.with_context(:s3) do
|
127
|
-
resp =
|
128
|
-
|
129
|
-
if
|
130
|
-
raise ApptrailRetryableError
|
131
|
-
elsif
|
132
|
-
if
|
133
|
-
_refresh_post_policy
|
134
|
-
raise ApptrailRetryableError
|
133
|
+
resp = @_upload_client.post(@_upload_url, form: new_form_opts)
|
134
|
+
unless resp.status.success?
|
135
|
+
if resp.status.server_error?
|
136
|
+
raise ApptrailRetryableError, 'Server Error while putting events.'
|
137
|
+
elsif resp.status.client_error?
|
138
|
+
if resp.status.code == 403 && resp.body.to_s.include?('Policy expired')
|
139
|
+
_refresh_post_policy
|
140
|
+
raise ApptrailRetryableError, 'Session expired.'
|
135
141
|
else
|
136
|
-
|
137
|
-
raise ApptrailError.new("Error while putting events.")
|
142
|
+
raise ApptrailError, 'Error while putting events.'
|
138
143
|
end
|
139
144
|
else
|
140
|
-
raise ApptrailError
|
145
|
+
raise ApptrailError, 'Error while putting events.'
|
141
146
|
end
|
142
147
|
end
|
143
148
|
end
|
144
|
-
rescue
|
145
|
-
raise ApptrailError
|
149
|
+
rescue StandardError
|
150
|
+
raise ApptrailError, "Failed to put #{events.length} events. Encountered error."
|
146
151
|
else
|
147
|
-
Apptrail
|
152
|
+
Apptrail.logger.info("Successfully wrote #{events.length} events.")
|
153
|
+
ensure
|
154
|
+
resp.flush if resp
|
148
155
|
end
|
156
|
+
end
|
149
157
|
|
158
|
+
def close
|
159
|
+
# https://github.com/appsignal/rdkafka-ruby/pull/160/files
|
160
|
+
ObjectSpace.undefine_finalizer(@_finalize_id)
|
161
|
+
@_session_client.close if @_session_client
|
162
|
+
@_upload_client.close if @_upload_client
|
150
163
|
end
|
151
164
|
|
152
165
|
private
|
153
166
|
|
154
167
|
def _refresh_post_policy
|
155
|
-
|
156
|
-
resp =
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
168
|
+
fail_default = -> { raise ApptrailError, "Error refreshing credentials." }
|
169
|
+
resp = nil
|
170
|
+
|
171
|
+
begin
|
172
|
+
Retriable.with_context(:session) do
|
173
|
+
resp = @_session_client
|
174
|
+
.auth("Bearer #{@_api_key}")
|
175
|
+
.get(@_base_api_url)
|
176
|
+
|
177
|
+
unless resp.status.success?
|
178
|
+
if resp.status.server_error?
|
179
|
+
raise ApptrailRetryableError, 'Server Error refreshing session.'
|
180
|
+
elsif resp.status.client_error?
|
181
|
+
if resp.status.code == 429
|
182
|
+
raise ApptrailRetryableError, 'Throttling'
|
183
|
+
else
|
184
|
+
fail_default.call
|
185
|
+
end
|
186
|
+
else
|
187
|
+
fail_default.call
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
result = JSON.parse(resp.body.to_s)
|
192
|
+
@_upload_url = result['uploadUrl']
|
193
|
+
@_form_data = result['form']
|
194
|
+
@_upload_client = HTTP.persistent @_upload_url
|
195
|
+
end
|
196
|
+
rescue StandardError => e
|
197
|
+
fail_default.call
|
198
|
+
ensure
|
199
|
+
resp.flush if resp
|
161
200
|
end
|
162
|
-
result = JSON.parse(resp.body.to_s)
|
163
|
-
@_upload_url = result["uploadUrl"]
|
164
|
-
@_form_data = result["form"]
|
165
201
|
end
|
166
|
-
|
167
|
-
|
168
202
|
end
|
169
|
-
|
170
203
|
end
|
data/lib/raw-event-schema.json
CHANGED
@@ -53,21 +53,27 @@
|
|
53
53
|
}
|
54
54
|
}
|
55
55
|
},
|
56
|
-
"
|
57
|
-
"type": "
|
58
|
-
"description": "The
|
59
|
-
"
|
60
|
-
{
|
61
|
-
"
|
56
|
+
"context": {
|
57
|
+
"type": "object",
|
58
|
+
"description": "The context object contains fields that give customers additional information about the environment in and source from which the activity recorded occurred",
|
59
|
+
"properties": {
|
60
|
+
"sourceIpAddress": {
|
61
|
+
"type": "string",
|
62
|
+
"description": "The IP address the activity/request being recorded was made from.",
|
63
|
+
"anyOf": [
|
64
|
+
{
|
65
|
+
"format": "ipv4"
|
66
|
+
},
|
67
|
+
{
|
68
|
+
"format": "ipv6"
|
69
|
+
}
|
70
|
+
]
|
62
71
|
},
|
63
|
-
{
|
64
|
-
"
|
72
|
+
"userAgent": {
|
73
|
+
"type": "string",
|
74
|
+
"description": "The agent through which the request was made, e.g. `Mozilla/5.0` or `python-requests/2.20.1`."
|
65
75
|
}
|
66
|
-
|
67
|
-
},
|
68
|
-
"userAgent": {
|
69
|
-
"type": "string",
|
70
|
-
"description": "The agent through which the request was made, e.g. `Mozilla/5.0` or `python-requests/2.20.1`."
|
76
|
+
}
|
71
77
|
},
|
72
78
|
"eventDetails": {
|
73
79
|
"type": "object",
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: apptrail-application-events-sdk
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Apptrail Team
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-02-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: http
|