api_analytics 1.1.1 → 1.1.3

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 168ec8494abbf47ad60453d1a21f836f9b860b65caaa7816ade1bea3627d72da
4
- data.tar.gz: 4e88773a2b4cab22e0e39a256ca737817d761e0ff47275f43fb26950bb4a042b
3
+ metadata.gz: 2a3b2edfab2680e7e92177b870647955279defdd2aee964ced254d762750fa28
4
+ data.tar.gz: 8465d6e4fd6a22dcf10e1edc3869c31c664d7c4d9cfa54cf87740dd3451db125
5
5
  SHA512:
6
- metadata.gz: efd0cdc91ebcdc11db6a7a7fc4135e9c336a5a71441b9a5be8324738451efcace71d4685c16ad999dea36cb1d88670640f768d3fab0ef5adf8658d55d7b04f0d
7
- data.tar.gz: f4ea4b6320d3e47fc9634bdded2cf3c4f4b61914721f1dc28c913565c9bee6cafdf9cd838b5dd8c3502c40e34816b7bba3c1aca4e345c3f4c010eb2f04bc2a55
6
+ metadata.gz: 6c464823fb30154be3f1e88244ada4237a3f0f15030522478b43df1fd54e3f680df8c627a0107f397c9cc5d0a4c5ed5015903e187bedf438ec55bbc31f023975
7
+ data.tar.gz: 78101f26bd2cb507f04a71f5e30c1ddb45d7d0a1ef0c268ed26fd96851451dd936e794430e83374feee07b5fd51a3394785e252b47bd585e643b32061cef34af
data/.gitignore CHANGED
@@ -1,10 +1,10 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
10
  *.gem
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rubocop.yml CHANGED
@@ -1,13 +1,13 @@
1
- AllCops:
2
- TargetRubyVersion: 2.4
3
-
4
- Style/StringLiterals:
5
- Enabled: true
6
- EnforcedStyle: double_quotes
7
-
8
- Style/StringLiteralsInInterpolation:
9
- Enabled: true
10
- EnforcedStyle: double_quotes
11
-
12
- Layout/LineLength:
13
- Max: 120
1
+ AllCops:
2
+ TargetRubyVersion: 2.4
3
+
4
+ Style/StringLiterals:
5
+ Enabled: true
6
+ EnforcedStyle: double_quotes
7
+
8
+ Style/StringLiteralsInInterpolation:
9
+ Enabled: true
10
+ EnforcedStyle: double_quotes
11
+
12
+ Layout/LineLength:
13
+ Max: 120
data/CHANGELOG.md CHANGED
@@ -1,5 +1,5 @@
1
- ## [Unreleased]
2
-
3
- ## [0.1.0] - 2022-12-05
4
-
5
- - Initial release
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2022-12-05
4
+
5
+ - Initial release
data/CODE_OF_CONDUCT.md CHANGED
@@ -1,84 +1,84 @@
1
- # Contributor Covenant Code of Conduct
2
-
3
- ## Our Pledge
4
-
5
- We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
-
7
- We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
-
9
- ## Our Standards
10
-
11
- Examples of behavior that contributes to a positive environment for our community include:
12
-
13
- * Demonstrating empathy and kindness toward other people
14
- * Being respectful of differing opinions, viewpoints, and experiences
15
- * Giving and gracefully accepting constructive feedback
16
- * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
- * Focusing on what is best not just for us as individuals, but for the overall community
18
-
19
- Examples of unacceptable behavior include:
20
-
21
- * The use of sexualized language or imagery, and sexual attention or
22
- advances of any kind
23
- * Trolling, insulting or derogatory comments, and personal or political attacks
24
- * Public or private harassment
25
- * Publishing others' private information, such as a physical or email
26
- address, without their explicit permission
27
- * Other conduct which could reasonably be considered inappropriate in a
28
- professional setting
29
-
30
- ## Enforcement Responsibilities
31
-
32
- Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
-
34
- Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
-
36
- ## Scope
37
-
38
- This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
-
40
- ## Enforcement
41
-
42
- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at tomjdraper1@gmail.com. All complaints will be reviewed and investigated promptly and fairly.
43
-
44
- All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
-
46
- ## Enforcement Guidelines
47
-
48
- Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
-
50
- ### 1. Correction
51
-
52
- **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
-
54
- **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
-
56
- ### 2. Warning
57
-
58
- **Community Impact**: A violation through a single incident or series of actions.
59
-
60
- **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
-
62
- ### 3. Temporary Ban
63
-
64
- **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
-
66
- **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
-
68
- ### 4. Permanent Ban
69
-
70
- **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
-
72
- **Consequence**: A permanent ban from any sort of public interaction within the community.
73
-
74
- ## Attribution
75
-
76
- This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
- available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
-
79
- Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
-
81
- [homepage]: https://www.contributor-covenant.org
82
-
83
- For answers to common questions about this code of conduct, see the FAQ at
84
- https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation.
6
+
7
+ We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
8
+
9
+ ## Our Standards
10
+
11
+ Examples of behavior that contributes to a positive environment for our community include:
12
+
13
+ * Demonstrating empathy and kindness toward other people
14
+ * Being respectful of differing opinions, viewpoints, and experiences
15
+ * Giving and gracefully accepting constructive feedback
16
+ * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
17
+ * Focusing on what is best not just for us as individuals, but for the overall community
18
+
19
+ Examples of unacceptable behavior include:
20
+
21
+ * The use of sexualized language or imagery, and sexual attention or
22
+ advances of any kind
23
+ * Trolling, insulting or derogatory comments, and personal or political attacks
24
+ * Public or private harassment
25
+ * Publishing others' private information, such as a physical or email
26
+ address, without their explicit permission
27
+ * Other conduct which could reasonably be considered inappropriate in a
28
+ professional setting
29
+
30
+ ## Enforcement Responsibilities
31
+
32
+ Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
33
+
34
+ Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
35
+
36
+ ## Scope
37
+
38
+ This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
39
+
40
+ ## Enforcement
41
+
42
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at tomjdraper1@gmail.com. All complaints will be reviewed and investigated promptly and fairly.
43
+
44
+ All community leaders are obligated to respect the privacy and security of the reporter of any incident.
45
+
46
+ ## Enforcement Guidelines
47
+
48
+ Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
49
+
50
+ ### 1. Correction
51
+
52
+ **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
53
+
54
+ **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
55
+
56
+ ### 2. Warning
57
+
58
+ **Community Impact**: A violation through a single incident or series of actions.
59
+
60
+ **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
61
+
62
+ ### 3. Temporary Ban
63
+
64
+ **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
65
+
66
+ **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
67
+
68
+ ### 4. Permanent Ban
69
+
70
+ **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
71
+
72
+ **Consequence**: A permanent ban from any sort of public interaction within the community.
73
+
74
+ ## Attribution
75
+
76
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0,
77
+ available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
78
+
79
+ Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity).
80
+
81
+ [homepage]: https://www.contributor-covenant.org
82
+
83
+ For answers to common questions about this code of conduct, see the FAQ at
84
+ https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations.
data/Gemfile CHANGED
@@ -1,9 +1,9 @@
1
- # frozen_string_literal: true
2
-
3
- source "https://rubygems.org"
4
-
5
- # Specify your gem's dependencies in api_analytics.gemspec
6
- gemspec
7
-
8
- gem "rake", "~> 13.0"
9
- gem "rubocop", "~> 1.7"
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in api_analytics.gemspec
6
+ gemspec
7
+
8
+ gem "rake", "~> 13.0"
9
+ gem "rubocop", "~> 1.7"
data/LICENSE.txt CHANGED
@@ -1,21 +1,21 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2022 Tom Draper
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
- THE SOFTWARE.
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Tom Draper
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md CHANGED
@@ -1,111 +1,248 @@
1
- # API Analytics
2
-
3
- A lightweight API analytics solution, complete with a dashboard.
4
-
5
- ## Getting Started
6
-
7
- ### 1. Generate a new API key
8
-
9
- Head to https://my-api-analytics.vercel.app/generate to generate your unique API key with a single click. This key is used to monitor your specific API, so keep it secret! It's also required in order to view your APIs analytics dashboard.
10
-
11
- ### 2. Add middleware to your API
12
-
13
- Add our lightweight middleware to your API. Almost all processing is handled by our servers so there should be virtually no impact on your APIs performance.
14
-
15
- ```bash
16
- gem install api_analytics
17
- ```
18
-
19
- #### Rails
20
-
21
- Add the analytics middleware to your rails application in `config/application.rb`.
22
-
23
- ```ruby
24
- require 'rails'
25
- require 'api_analytics'
26
-
27
- Bundler.require(*Rails.groups)
28
-
29
- module RailsMiddleware
30
- class Application < Rails::Application
31
- config.load_defaults 6.1
32
- config.api_only = true
33
-
34
- config.middleware.use ::Analytics::Rails, <API-KEY> # Add middleware
35
- end
36
- end
37
- ```
38
-
39
- #### Sinatra
40
-
41
- ```ruby
42
- require 'sinatra'
43
- require 'api_analytics'
44
-
45
- use Analytics::Sinatra, <API-KEY> # Add middleware
46
-
47
- before do
48
- content_type 'application/json'
49
- end
50
-
51
- get '/' do
52
- {message: 'Hello World!'}.to_json
53
- end
54
- ```
55
-
56
- ### 3. View your analytics
57
-
58
- Your API will now log and store incoming request data on all valid routes. Your logged data can be viewed using two methods: through visualizations and stats on our dashboard, or accessed directly via our data API.
59
-
60
- You can use the same API key across multiple APIs, but all your data will appear in the same dashboard. We recommend generating a new API key for each additional API you want analytics for.
61
-
62
- #### Dashboard
63
-
64
- Head to https://my-api-analytics.vercel.app/dashboard and paste in your API key to access your dashboard.
65
-
66
- Demo: https://my-api-analytics.vercel.app/dashboard/demo
67
-
68
- ![Dashboard](https://user-images.githubusercontent.com/41476809/208440202-966a6930-3d2e-40c5-afc7-2fd0107d6b4f.png)
69
-
70
- #### Data API
71
-
72
- Logged data for all requests can be accessed via our API. Simply send a GET request to `https://api-analytics-server.vercel.app/api/data` with your API key set as `API-Key` in headers.
73
-
74
- ```py
75
- import requests
76
-
77
- headers = {
78
- "API-Key": <API-KEY>
79
- }
80
-
81
- response = requests.get("https://api-analytics-server.vercel.app/api/data", headers=headers)
82
- print(response.json())
83
- ```
84
-
85
- ## Monitoring (coming soon)
86
-
87
- Opt-in active API monitoring is coming soon. Our servers will regularly ping your API endpoints to monitor uptime and response time. Optional email alerts to notify you when your endpoints are down can be subscribed to.
88
-
89
- ![Monitoring](https://user-images.githubusercontent.com/41476809/208298759-f937b668-2d86-43a2-b615-6b7f0b2bc20c.png)
90
-
91
- ## Data and Security
92
-
93
- All data is stored securely in compliance with The EU General Data Protection Regulation (GDPR).
94
-
95
- For any given request to your API, data recorded is limited to:
96
- - Path requested by client
97
- - Client IP address
98
- - Client operating system
99
- - Client browser
100
- - Request method (GET, POST, PUT, etc.)
101
- - Time of request
102
- - Status code
103
- - Response time
104
- - API hostname
105
- - API framework (FastAPI, Flask, Express etc.)
106
-
107
- Data collected is only ever used to populate your analytics dashboard. Your data is anonymous, with the API key the only link between you and you API's analytics. Should you lose your API key, you will have no method to access your API analytics. Inactive API keys (> 1 year) and its associated API request data may be deleted.
108
-
109
- ### Delete Data
110
-
111
- At any time, you can delete all stored data associated with your API key by going to https://my-api-analytics.vercel.app/delete and entering your API key.
1
+ # API Analytics
2
+
3
+ A free and lightweight API analytics solution, complete with a dashboard.
4
+
5
+ ## Getting Started
6
+
7
+ ### 1. Generate an API key
8
+
9
+ Head to [apianalytics.dev/generate](https://apianalytics.dev/generate) to generate your unique API key with a single click. This key is used to monitor your specific API and should be stored privately. It's also required in order to access your API analytics dashboard and data.
10
+
11
+ ### 2. Add middleware to your API
12
+
13
+ Add our lightweight middleware to your API. Almost all processing is handled by our servers so there is minimal impact on the performance of your API.
14
+
15
+ [![Gem version](https://img.shields.io/gem/v/api_analytics)](https://rubygems.org/gems/api_analytics)
16
+
17
+ ```bash
18
+ gem install api_analytics
19
+ ```
20
+
21
+ #### Rails
22
+
23
+ Add the analytics middleware to your rails application in `config/application.rb`.
24
+
25
+ ```ruby
26
+ require 'rails'
27
+ require 'api_analytics'
28
+
29
+ Bundler.require(*Rails.groups)
30
+
31
+ module RailsMiddleware
32
+ class Application < Rails::Application
33
+ config.load_defaults 6.1
34
+ config.api_only = true
35
+
36
+ config.middleware.use ::Analytics::Rails, "YOUR-API-KEY" # Add middleware
37
+ end
38
+ end
39
+ ```
40
+
41
+ #### Sinatra
42
+
43
+ ```ruby
44
+ require 'sinatra'
45
+ require 'api_analytics'
46
+
47
+ use Analytics::Sinatra, "YOUR-API-KEY" # Add middleware
48
+
49
+ before do
50
+ content_type 'application/json'
51
+ end
52
+
53
+ get '/' do
54
+ {message: 'Hello World!'}.to_json
55
+ end
56
+ ```
57
+
58
+ ### 3. View your analytics
59
+
60
+ Your API will now log and store incoming request data on all routes. Your logged data can be viewed using two methods:
61
+
62
+ 1. Through visualizations and statistics on the dashboard
63
+ 2. Accessed directly via the data API
64
+
65
+ You can use the same API key across multiple APIs, but all of your data will appear in the same dashboard. We recommend generating a new API key for each additional API server you want analytics for.
66
+
67
+ #### Dashboard
68
+
69
+ Head to [apianalytics.dev/dashboard](https://apianalytics.dev/dashboard) and paste in your API key to access your dashboard.
70
+
71
+ Demo: [apianalytics.dev/dashboard/demo](https://apianalytics.dev/dashboard/demo)
72
+
73
+ ![dashboard](https://user-images.githubusercontent.com/41476809/272061832-74ba4146-f4b3-4c05-b759-3946f4deb9de.png)
74
+
75
+ #### Data API
76
+
77
+ Logged data for all requests can be accessed via our REST API. Simply send a GET request to `https://apianalytics-server.com/api/data` with your API key set as `X-AUTH-TOKEN` in the headers.
78
+
79
+ ##### Python
80
+
81
+ ```py
82
+ import requests
83
+
84
+ headers = {
85
+ "X-AUTH-TOKEN": "YOUR-API-KEY"
86
+ }
87
+
88
+ response = requests.get("https://apianalytics-server.com/api/data", headers=headers)
89
+ print(response.json())
90
+ ```
91
+
92
+ ##### Node.js
93
+
94
+ ```js
95
+ fetch("https://apianalytics-server.com/api/data", {
96
+ headers: { "X-AUTH-TOKEN": "YOUR-API-KEY" },
97
+ })
98
+ .then((response) => {
99
+ return response.json();
100
+ })
101
+ .then((data) => {
102
+ console.log(data);
103
+ });
104
+ ```
105
+
106
+ ##### cURL
107
+
108
+ ```bash
109
+ curl --header "X-AUTH-TOKEN: YOUR-API-KEY" https://apianalytics-server.com/api/data
110
+ ```
111
+
112
+ ##### Parameters
113
+
114
+ You can filter your data by providing URL parameters in your request.
115
+
116
+ - `page` - the page number, with a max page size of 50,000 (defaults to 1)
117
+ - `date` - the exact day the requests occurred on (`YYYY-MM-DD`)
118
+ - `dateFrom` - a lower bound of a date range the requests occurred in (`YYYY-MM-DD`)
119
+ - `dateTo` - a upper bound of a date range the requests occurred in (`YYYY-MM-DD`)
120
+ - `hostname` - the hostname of your service
121
+ - `ipAddress` - the IP address of the client
122
+ - `status` - the status code of the response
123
+ - `location` - a two-character location code of the client
124
+ - `userId` - a custom user identifier (only relevant if a `get_user_id` mapper function has been set)
125
+
126
+ Example:
127
+
128
+ ```bash
129
+ curl --header "X-AUTH-TOKEN: YOUR-API-KEY" https://apianalytics-server.com/api/data?page=3&dateFrom=2022-01-01&hostname=apianalytics.dev&status=200&userId=b56cbd92-1168-4d7b-8d94-0418da207908
130
+ ```
131
+
132
+ ## Customisation
133
+
134
+ A config object can be passed to the middleware to override default behaviour.
135
+
136
+ ### Rails
137
+
138
+ ```ruby
139
+ require 'rails'
140
+ require 'api_analytics'
141
+
142
+ Bundler.require(*Rails.groups)
143
+
144
+ module RailsMiddleware
145
+ class Application < Rails::Application
146
+ config.load_defaults 6.1
147
+ config.api_only = true
148
+
149
+ analytics_config = Analytics::Config.new
150
+ analytics_config.get_user_id = ->(env) { env['HTTP_X_AUTH_TOKEN'] }
151
+
152
+ config.middleware.use ::Analytics::Rails, "YOUR-API-KEY", analytics_config # Add middleware
153
+ end
154
+ end
155
+ ```
156
+
157
+ ### Sinatra
158
+
159
+ ```ruby
160
+ require 'sinatra'
161
+ require 'api_analytics'
162
+
163
+ analytics_config = Analytics::Config.new
164
+ analytics_config.get_user_id = ->(env) { env['HTTP_X_AUTH_TOKEN'] }
165
+
166
+ use Analytics::Sinatra, "YOUR-API-KEY", analytics_config # Add middleware
167
+
168
+ before do
169
+ content_type 'application/json'
170
+ end
171
+
172
+ get '/' do
173
+ {message: 'Hello World!'}.to_json
174
+ end
175
+ ```
176
+
177
+ ## Client ID and Privacy
178
+
179
+ By default, API Analytics logs and stores the client IP address of all incoming requests made to your API and infers a location (country) from each IP address if possible. The IP address is used as a form of client identification in the dashboard to estimate the number of users accessing your service.
180
+
181
+ This behaviour can be controlled through a privacy level defined in the configuration of the API middleware. There are three privacy levels to choose from 0 (default) to a maximum of 2. A privacy level of 1 will disable IP address storing, and a value of 2 will also disable location inference.
182
+
183
+ Privacy Levels:
184
+
185
+ - `0` - The client IP address is used to infer a location and then stored for user identification. (default)
186
+ - `1` - The client IP address is used to infer a location and then discarded.
187
+ - `2` - The client IP address is never accessed and location is never inferred.
188
+
189
+ ```ruby
190
+ analytics_config = Analytics::Config.new
191
+ analytics_config.privacy_level = 2 # Disable IP storing and location inference
192
+ ```
193
+
194
+ With any of these privacy levels, there is the option to define a custom user ID as a function of a request by providing a mapper function in the API middleware configuration. For example, your service may require an API key sent in the `X-AUTH-TOKEN` header field that can be used to identify a user. In the dashboard, this custom user ID will identify the user in conjunction with the IP address or as an alternative.
195
+
196
+ ```ruby
197
+ analytics_config = Analytics::Config.new
198
+ analytics_config.get_user_id = ->(env) { env['HTTP_X_AUTH_TOKEN'] }
199
+ ```
200
+
201
+ ## Data and Security
202
+
203
+ All data is stored securely in compliance with The EU General Data Protection Regulation (GDPR).
204
+
205
+ For any given request to your API, data recorded is limited to:
206
+
207
+ - Path requested by client
208
+ - Client IP address
209
+ - Client operating system
210
+ - Client browser
211
+ - Request method (GET, POST, PUT, etc.)
212
+ - Time of request
213
+ - Status code
214
+ - Response time
215
+ - API hostname
216
+ - API framework (Rails, Sinatra)
217
+
218
+ Data collected is only ever used to populate your analytics dashboard. All stored data is pseudo-anonymous, with the API key the only link between you and your logged request data. Should you lose your API key, you will have no method to access your API analytics.
219
+
220
+ ### Data Deletion
221
+
222
+ At any time you can delete all stored data associated with your API key by going to [apianalytics.dev/delete](https://apianalytics.dev/delete) and entering your API key.
223
+
224
+ API keys and their associated logged request data are scheduled to be deleted after 6 months of dashboard inactivity, or if 3 months have elapsed without logging a request.
225
+
226
+ ## Monitoring
227
+
228
+ Active API monitoring can be set up by heading to [apianalytics.dev/monitoring](https://apianalytics.dev/monitoring) to enter your API key. Our servers will regularly ping chosen API endpoints to monitor uptime and response time.
229
+ <!-- Optional email alerts when your endpoints are down can be subscribed to. -->
230
+
231
+ ![Monitoring](https://user-images.githubusercontent.com/41476809/208298759-f937b668-2d86-43a2-b615-6b7f0b2bc20c.png)
232
+
233
+ ## Contributions
234
+
235
+ Contributions, issues and feature requests are welcome.
236
+
237
+ - Fork it (https://github.com/tom-draper/api-analytics)
238
+ - Create your feature branch (`git checkout -b my-new-feature`)
239
+ - Commit your changes (`git commit -am 'Add some feature'`)
240
+ - Push to the branch (`git push origin my-new-feature`)
241
+ - Create a new Pull Request
242
+
243
+ ---
244
+
245
+ If you find value in my work consider supporting me.
246
+
247
+ Buy Me a Coffee: https://www.buymeacoffee.com/tomdraper<br>
248
+ PayPal: https://www.paypal.com/paypalme/tomdraper
data/Rakefile CHANGED
@@ -1,8 +1,10 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/gem_tasks"
4
- require "rubocop/rake_task"
5
-
6
- RuboCop::RakeTask.new
7
-
8
- task default: :rubocop
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+ require "rubocop/rake_task"
6
+
7
+ RSpec::RakeTask.new(:spec)
8
+ RuboCop::RakeTask.new
9
+
10
+ task default: :spec
@@ -1,27 +1,30 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/api_analytics/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "api_analytics"
7
- spec.version = Analytics::VERSION
8
- spec.authors = ["Tom Draper"]
9
- spec.email = ["tomjdraper1@gmail.com"]
10
- spec.summary = "Monitoring and analytics for API applications."
11
- spec.description = "Monitoring and analytics for API applications."
12
- spec.homepage = "https://github.com/tom-draper/api-analytics"
13
- spec.license = "MIT"
14
- spec.required_ruby_version = ">= 2.4.0"
15
-
16
- spec.metadata["homepage_uri"] = spec.homepage
17
- spec.metadata["source_code_uri"] = "https://github.com/tom-draper/api-analytics"
18
-
19
- # Specify which files should be added to the gem when it is released.
20
- # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
- spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
23
- end
24
- spec.bindir = "exe"
25
- spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
26
- spec.require_paths = ["lib"]
27
- end
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/api_analytics/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "api_analytics"
7
+ spec.version = Analytics::VERSION
8
+ spec.authors = ["Tom Draper"]
9
+ spec.email = ["tomjdraper1@gmail.com"]
10
+ spec.summary = "Monitoring and analytics for API applications."
11
+ spec.description = "Monitoring and analytics for API applications."
12
+ spec.homepage = "https://github.com/tom-draper/api-analytics"
13
+ spec.license = "MIT"
14
+ spec.required_ruby_version = ">= 2.7.0"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/tom-draper/api-analytics"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
23
+ end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+ spec.add_development_dependency "rspec", "~> 3.0"
29
+ spec.add_development_dependency "rack", "~> 3.0"
30
+ end
data/bin/console CHANGED
@@ -1,15 +1,15 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "bundler/setup"
5
- require "api_analytics"
6
-
7
- # You can add fixtures and/or initialization code here to make experimenting
8
- # with your gem easier. You can also use a different console, if you like.
9
-
10
- # (If you use this, don't forget to add pry to your Gemfile!)
11
- # require "pry"
12
- # Pry.start
13
-
14
- require "irb"
15
- IRB.start(__FILE__)
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "api_analytics"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup CHANGED
@@ -1,8 +1,8 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
- set -vx
5
-
6
- bundle install
7
-
8
- # Do any other automated setup that you need to do here
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -1,5 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
- module Analytics
4
- VERSION = "1.1.1"
5
- end
1
+ # frozen_string_literal: true
2
+
3
+ module Analytics
4
+ VERSION = "1.1.3"
5
+ end
data/lib/api_analytics.rb CHANGED
@@ -1,81 +1,124 @@
1
- # frozen_string_literal: true
2
-
3
- require 'uri'
4
- require 'net/http'
5
- require 'json'
6
-
7
- module Analytics
8
- class Middleware
9
- def initialize(app, api_key)
10
- @app = app
11
- @api_key = api_key
12
- @requests = Array.new
13
- @last_posted = Time.now
14
- end
15
-
16
- def call(env)
17
- start = Time.now
18
- status, headers, response = @app.call(env)
19
-
20
- request_data = {
21
- hostname: env['HTTP_HOST'],
22
- ip_address: env['REMOTE_ADDR'],
23
- path: env['REQUEST_PATH'],
24
- user_agent: env['HTTP_USER_AGENT'],
25
- method: env['REQUEST_METHOD'],
26
- status: status,
27
- response_time: (Time.now - start).to_f.round,
28
- created_at: Time.now.utc.iso8601
29
- }
30
-
31
- log_request(request_data)
32
-
33
- [status, headers, response]
34
- end
35
-
36
- private
37
-
38
- def post_requests(api_key, requests, framework)
39
- payload = {
40
- api_key: api_key,
41
- requests: requests,
42
- framework: framework
43
- }
44
- uri = URI('https://213.168.248.206/api/log-request')
45
- res = Net::HTTP.post(uri, payload.to_json)
46
- end
47
-
48
- def log_request(request_data)
49
- if @api_key.empty?
50
- return
51
- end
52
- now = Time.now
53
- @requests.push(request_data)
54
- if (now - @last_posted) > 5
55
- requests = @requests.dup
56
- Thread.new {
57
- post_requests(@api_key, requests, @framework)
58
- }
59
- @requests = Array.new
60
- @last_posted = now
61
- end
62
- end
63
- end
64
-
65
- private_constant :Middleware
66
-
67
- class Rails < Middleware
68
- def initialize(app, api_key)
69
- super(app, api_key)
70
- @framework = "Rails"
71
- end
72
- end
73
-
74
- class Sinatra < Middleware
75
- def initialize(app, api_key)
76
- super(app, api_key)
77
- @framework = "Sinatra"
78
- end
79
- end
80
- end
81
-
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require 'net/http'
5
+ require 'json'
6
+ require 'time'
7
+
8
+ module Analytics
9
+ class Middleware
10
+ def initialize(app, api_key, config = Config.new)
11
+ @app = app
12
+ @api_key = api_key
13
+ @config = config
14
+ @framework = "Middleware"
15
+ @requests = []
16
+ @last_posted = Time.now
17
+ @mutex = Mutex.new
18
+ end
19
+
20
+ def call(env)
21
+ start = Time.now
22
+ status, headers, response = @app.call(env)
23
+
24
+ request_data = {
25
+ hostname: @config.get_hostname.call(env),
26
+ ip_address: get_ip_address(env),
27
+ path: @config.get_path.call(env),
28
+ user_agent: @config.get_user_agent.call(env),
29
+ method: env['REQUEST_METHOD'],
30
+ status: status,
31
+ response_time: ((Time.now - start) * 1000).round,
32
+ user_id: @config.get_user_id.call(env),
33
+ created_at: Time.now.utc.iso8601
34
+ }
35
+
36
+ log_request(request_data)
37
+
38
+ [status, headers, response]
39
+ end
40
+
41
+ private
42
+
43
+ def get_ip_address(env)
44
+ return nil if @config.privacy_level >= 2
45
+
46
+ @config.get_ip_address.call(env)
47
+ end
48
+
49
+ def post_requests(requests)
50
+ return if @api_key.to_s.empty?
51
+
52
+ payload = {
53
+ api_key: @api_key,
54
+ requests: requests,
55
+ framework: @framework,
56
+ privacy_level: @config.privacy_level
57
+ }
58
+
59
+ url = @config.server_url.end_with?('/') ? @config.server_url + 'api/log-request' : @config.server_url + '/api/log-request'
60
+ uri = URI(url)
61
+
62
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https', open_timeout: 10, read_timeout: 10) do |http|
63
+ request = Net::HTTP::Post.new(uri, 'Content-Type' => 'application/json')
64
+ request.body = payload.to_json
65
+ http.request(request)
66
+ end
67
+ end
68
+
69
+ def log_request(request_data)
70
+ now = Time.now
71
+ requests_to_post = nil
72
+
73
+ @mutex.synchronize do
74
+ @requests.push(request_data)
75
+ if (now - @last_posted) > 60.0
76
+ requests_to_post = @requests.dup
77
+ @requests = []
78
+ @last_posted = now
79
+ end
80
+ end
81
+
82
+ Thread.new { post_requests(requests_to_post) } if requests_to_post
83
+ end
84
+ end
85
+
86
+ Config = Struct.new(:privacy_level, :server_url, :get_path, :get_ip_address, :get_hostname, :get_user_agent, :get_user_id) do
87
+ def initialize(
88
+ privacy_level = 0,
89
+ server_url = 'https://www.apianalytics-server.com',
90
+ get_path = ->(env) { env['REQUEST_PATH'] },
91
+ get_ip_address = lambda { |env|
92
+ env['HTTP_CF_CONNECTING_IP'] ||
93
+ (env['HTTP_X_FORWARDED_FOR'] && env['HTTP_X_FORWARDED_FOR'].split(',').first&.strip) ||
94
+ env['HTTP_X_REAL_IP'] ||
95
+ env['REMOTE_ADDR']
96
+ },
97
+ get_hostname = ->(env) { env['HTTP_HOST'] },
98
+ get_user_agent = ->(env) { env['HTTP_USER_AGENT'] },
99
+ get_user_id = ->(_env) { nil }
100
+ )
101
+ self.privacy_level = privacy_level
102
+ self.server_url = server_url
103
+ self.get_path = get_path
104
+ self.get_ip_address = get_ip_address
105
+ self.get_hostname = get_hostname
106
+ self.get_user_agent = get_user_agent
107
+ self.get_user_id = get_user_id
108
+ end
109
+ end
110
+
111
+ class Rails < Middleware
112
+ def initialize(app, api_key, config = Config.new)
113
+ super(app, api_key, config)
114
+ @framework = "Rails"
115
+ end
116
+ end
117
+
118
+ class Sinatra < Middleware
119
+ def initialize(app, api_key, config = Config.new)
120
+ super(app, api_key, config)
121
+ @framework = "Sinatra"
122
+ end
123
+ end
124
+ end
metadata CHANGED
@@ -1,15 +1,42 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: api_analytics
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Draper
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2023-04-03 00:00:00.000000000 Z
12
- dependencies: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rspec
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rack
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.0'
13
40
  description: Monitoring and analytics for API applications.
14
41
  email:
15
42
  - tomjdraper1@gmail.com
@@ -18,6 +45,7 @@ extensions: []
18
45
  extra_rdoc_files: []
19
46
  files:
20
47
  - ".gitignore"
48
+ - ".rspec"
21
49
  - ".rubocop.yml"
22
50
  - CHANGELOG.md
23
51
  - CODE_OF_CONDUCT.md
@@ -36,7 +64,6 @@ licenses:
36
64
  metadata:
37
65
  homepage_uri: https://github.com/tom-draper/api-analytics
38
66
  source_code_uri: https://github.com/tom-draper/api-analytics
39
- post_install_message:
40
67
  rdoc_options: []
41
68
  require_paths:
42
69
  - lib
@@ -44,15 +71,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
44
71
  requirements:
45
72
  - - ">="
46
73
  - !ruby/object:Gem::Version
47
- version: 2.4.0
74
+ version: 2.7.0
48
75
  required_rubygems_version: !ruby/object:Gem::Requirement
49
76
  requirements:
50
77
  - - ">="
51
78
  - !ruby/object:Gem::Version
52
79
  version: '0'
53
80
  requirements: []
54
- rubygems_version: 3.2.22
55
- signing_key:
81
+ rubygems_version: 3.6.9
56
82
  specification_version: 4
57
83
  summary: Monitoring and analytics for API applications.
58
84
  test_files: []