mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 08:55:24 +00:00
Website: Send custom metrics from anonymous usage statistics to Datadog (#11362)
. Closes: #10848 Changes: - Updated the `receive-usage-analytics` webhook to send custom metrics to our Datadog account, where we can create graphs and dashboards to track Fleet feature adoption, Fleet/osquery/orbit versions in use, reported host counts, and stored errors. - Added a new config variable: `sails.config.custom.datadogApiKey`
This commit is contained in:
parent
3496011e35
commit
fe07c39f22
@ -39,8 +39,264 @@ module.exports = {
|
|||||||
|
|
||||||
fn: async function (inputs) {
|
fn: async function (inputs) {
|
||||||
|
|
||||||
|
// Create a database record for these usage statistics
|
||||||
await HistoricalUsageSnapshot.create(inputs);
|
await HistoricalUsageSnapshot.create(inputs);
|
||||||
|
|
||||||
|
if(!sails.config.custom.datadogApiKey) {
|
||||||
|
throw new Error('No Datadog API key configured! (Please set sails.config.custom.datadogApiKey)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store strings and booleans as tags.
|
||||||
|
let baseMetricTags = [
|
||||||
|
`fleet_version:${inputs.fleetVersion}`,
|
||||||
|
`license_tier:${inputs.licenseTier}`,
|
||||||
|
`software_inventory_enabled:${inputs.softwareInventoryEnabled}`,
|
||||||
|
`vuln_detection_enabled:${inputs.vulnDetectionEnabled}`,
|
||||||
|
`system_users_enabled:${inputs.systemUsersEnabled}`,
|
||||||
|
`host_status_webhook_enabled:${inputs.hostStatusWebhookEnabled}`,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Create a timestamp in seconds for these metrics
|
||||||
|
let metricsTimestampInSeconds = Math.floor(Date.now() / 1000);
|
||||||
|
|
||||||
|
// Build metrics for the usagle statistics that are numbers
|
||||||
|
let metricsToSendToDatadog = [
|
||||||
|
{
|
||||||
|
metric: 'usage_statistics.fleet_server_stats',
|
||||||
|
type: 3,
|
||||||
|
points: [{
|
||||||
|
timestamp: metricsTimestampInSeconds,
|
||||||
|
value: 1
|
||||||
|
}],
|
||||||
|
resources: [{
|
||||||
|
name: inputs.anonymousIdentifier,
|
||||||
|
type: 'fleet_instance'
|
||||||
|
}],
|
||||||
|
tags: baseMetricTags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metric: 'usage_statistics.num_hosts_enrolled',
|
||||||
|
type: 3,
|
||||||
|
points: [{
|
||||||
|
timestamp: metricsTimestampInSeconds,
|
||||||
|
value: inputs.numHostsEnrolled
|
||||||
|
}],
|
||||||
|
resources: [{
|
||||||
|
name: inputs.anonymousIdentifier,
|
||||||
|
type: 'fleet_instance'
|
||||||
|
}],
|
||||||
|
tags: baseMetricTags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metric: 'usage_statistics.num_users',
|
||||||
|
type: 3,
|
||||||
|
points: [{
|
||||||
|
timestamp: metricsTimestampInSeconds,
|
||||||
|
value: inputs.numUsers
|
||||||
|
}],
|
||||||
|
resources: [{
|
||||||
|
name: inputs.anonymousIdentifier,
|
||||||
|
type: 'fleet_instance'
|
||||||
|
}],
|
||||||
|
tags: baseMetricTags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metric: 'usage_statistics.num_teams',
|
||||||
|
type: 3,
|
||||||
|
points: [{
|
||||||
|
timestamp: metricsTimestampInSeconds,
|
||||||
|
value: inputs.numTeams
|
||||||
|
}],
|
||||||
|
resources: [{
|
||||||
|
name: inputs.anonymousIdentifier,
|
||||||
|
type: 'fleet_instance'
|
||||||
|
}],
|
||||||
|
tags: baseMetricTags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metric: 'usage_statistics.num_policies',
|
||||||
|
type: 3,
|
||||||
|
points: [{
|
||||||
|
timestamp: metricsTimestampInSeconds,
|
||||||
|
value: inputs.numPolicies
|
||||||
|
}],
|
||||||
|
resources: [{
|
||||||
|
name: inputs.anonymousIdentifier,
|
||||||
|
type: 'fleet_instance'
|
||||||
|
}],
|
||||||
|
tags: baseMetricTags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metric: 'usage_statistics.num_labels',
|
||||||
|
type: 3,
|
||||||
|
points: [{
|
||||||
|
timestamp: metricsTimestampInSeconds,
|
||||||
|
value: inputs.numLabels
|
||||||
|
}],
|
||||||
|
resources: [{
|
||||||
|
name: inputs.anonymousIdentifier,
|
||||||
|
type: 'fleet_instance'
|
||||||
|
}],
|
||||||
|
tags: baseMetricTags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metric: 'usage_statistics.num_weekly_active_users',
|
||||||
|
type: 3,
|
||||||
|
points: [{
|
||||||
|
timestamp: metricsTimestampInSeconds,
|
||||||
|
value: inputs.numWeeklyActiveUsers
|
||||||
|
}],
|
||||||
|
resources: [{
|
||||||
|
name: inputs.anonymousIdentifier,
|
||||||
|
type: 'fleet_instance'
|
||||||
|
}],
|
||||||
|
tags: baseMetricTags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metric: 'usage_statistics.num_weekly_policy_violation_days_actual',
|
||||||
|
type: 3,
|
||||||
|
points: [{
|
||||||
|
timestamp: metricsTimestampInSeconds,
|
||||||
|
value: inputs.numWeeklyPolicyViolationDaysActual
|
||||||
|
}],
|
||||||
|
resources: [{
|
||||||
|
name: inputs.anonymousIdentifier,
|
||||||
|
type: 'fleet_instance'
|
||||||
|
}],
|
||||||
|
tags: baseMetricTags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metric: 'usage_statistics.num_weekly_policy_violation_days_possible',
|
||||||
|
type: 3,
|
||||||
|
points: [{
|
||||||
|
timestamp: metricsTimestampInSeconds,
|
||||||
|
value: inputs.numWeeklyPolicyViolationDaysPossible
|
||||||
|
}],
|
||||||
|
resources: [{
|
||||||
|
name: inputs.anonymousIdentifier,
|
||||||
|
type: 'fleet_instance'
|
||||||
|
}],
|
||||||
|
tags: baseMetricTags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
metric: 'usage_statistics.num_hosts_not_responding',
|
||||||
|
type: 3,
|
||||||
|
points: [{
|
||||||
|
timestamp: metricsTimestampInSeconds,
|
||||||
|
value: inputs.numHostsNotResponding
|
||||||
|
}],
|
||||||
|
resources: [{
|
||||||
|
name: inputs.anonymousIdentifier,
|
||||||
|
type: 'fleet_instance'
|
||||||
|
}],
|
||||||
|
tags: baseMetricTags,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
// Build metrics for logged errors
|
||||||
|
if(inputs.storedErrors.length > 0) {
|
||||||
|
// If inputs.storedErrors is not an empty array, we'll iterate through it to build custom metric for each object in the array
|
||||||
|
for(let error of inputs.storedErrors) {
|
||||||
|
// Create a new array of tags for this error
|
||||||
|
let errorTags = _.clone(baseMetricTags);
|
||||||
|
let errorLocation = 1;
|
||||||
|
// Create a tag for each error location
|
||||||
|
for(let location of error.loc) { // iterate throught the location array of this error
|
||||||
|
// Add the error's location as a custom tag (SNAKE_CASED)
|
||||||
|
errorTags.push(`error_location_${errorLocation}:${location.replace(/\s/gi, '_')}`);
|
||||||
|
errorLocation++;
|
||||||
|
}
|
||||||
|
//
|
||||||
|
let metricToAdd = {
|
||||||
|
metric: 'usage_statistics.stored_errors',
|
||||||
|
type: 3,
|
||||||
|
points: [{timestamp: metricsTimestampInSeconds, value: error.count}],
|
||||||
|
resources: [{name: inputs.anonymousIdentifier, type: 'fleet_instance'}],
|
||||||
|
tags: errorTags,
|
||||||
|
};
|
||||||
|
// Add the custom metric to the array of metrics to send to Datadog.
|
||||||
|
metricsToSendToDatadog.push(metricToAdd);
|
||||||
|
}//∞
|
||||||
|
}//fi
|
||||||
|
|
||||||
|
|
||||||
|
// If inputs.hostsEnrolledByOrbitVersion is not an empty array, we'll iterate through it to build custom metric for each object in the array
|
||||||
|
if(inputs.hostsEnrolledByOrbitVersion.length > 0) {
|
||||||
|
for(let version of inputs.hostsEnrolledByOrbitVersion) {
|
||||||
|
let orbitVersionTags = _.clone(baseMetricTags);
|
||||||
|
orbitVersionTags.push(`orbit_version:${version.orbitVersion}`);
|
||||||
|
let metricToAdd = {
|
||||||
|
metric: 'usage_statistics.host_count_by_orbit_version',
|
||||||
|
type: 3,
|
||||||
|
points: [{timestamp: metricsTimestampInSeconds, value:version.numHosts}],
|
||||||
|
resources: [{name: inputs.anonymousIdentifier, type: 'fleet_instance'}],
|
||||||
|
tags: orbitVersionTags,
|
||||||
|
};
|
||||||
|
// Add the custom metric to the array of metrics to send to Datadog.
|
||||||
|
metricsToSendToDatadog.push(metricToAdd);
|
||||||
|
}//∞
|
||||||
|
}//fi
|
||||||
|
|
||||||
|
// If inputs.hostsEnrolledByOsqueryVersion is not an empty array, we'll iterate through it to build custom metric for each object in the array
|
||||||
|
if(inputs.hostsEnrolledByOsqueryVersion.length > 0) {
|
||||||
|
for(let version of inputs.hostsEnrolledByOsqueryVersion) {
|
||||||
|
let osqueryVersionTags = _.clone(baseMetricTags);
|
||||||
|
osqueryVersionTags.push(`osquery_version:${version.osqueryVersion}`);
|
||||||
|
let metricToAdd = {
|
||||||
|
metric: 'usage_statistics.host_count_by_osquery_version',
|
||||||
|
type: 3,
|
||||||
|
points: [{timestamp: metricsTimestampInSeconds, value:version.numHosts}],
|
||||||
|
resources: [{name: inputs.anonymousIdentifier, type: 'fleet_instance'}],
|
||||||
|
tags: osqueryVersionTags,
|
||||||
|
};
|
||||||
|
// Add the custom metric to the array of metrics to send to Datadog.
|
||||||
|
metricsToSendToDatadog.push(metricToAdd);
|
||||||
|
}//∞
|
||||||
|
}//fi
|
||||||
|
|
||||||
|
// If the hostByOperatingSystem is not an empty object, we'll iterate through the object to build metrics for each type of operating system.
|
||||||
|
// See https://fleetdm.com/docs/using-fleet/usage-statistics#what-is-included-in-usage-statistics-in-fleet to see an example of a hostByOperatingSystem send by Fleet instances.
|
||||||
|
if(_.keys(inputs.hostsEnrolledByOperatingSystem).length > 0) {
|
||||||
|
// Iterate through each array of objects
|
||||||
|
for(let operatingSystem in inputs.hostsEnrolledByOperatingSystem) {
|
||||||
|
// For every object in the array, we'll send a metric to track host count for each operating system version.
|
||||||
|
for(let osVersion of inputs.hostsEnrolledByOperatingSystem[operatingSystem]) {
|
||||||
|
// Only continue if the object in the array has a numEnrolled and version value.
|
||||||
|
if(osVersion.numEnrolled && osVersion.version) {
|
||||||
|
// Clone the baseMetricTags array, each metric will have the operating version name added as a `os_version_name` tag
|
||||||
|
let osInfoTags = _.clone(baseMetricTags);
|
||||||
|
osInfoTags.push(`os_version_name:${osVersion.version}`);
|
||||||
|
let metricToAdd = {
|
||||||
|
metric: 'usage_statistics.host_count_by_os_version',
|
||||||
|
type: 3,
|
||||||
|
points: [{timestamp: metricsTimestampInSeconds, value:osVersion.numEnrolled}],
|
||||||
|
resources: [{name: operatingSystem, type: 'os_type'}],
|
||||||
|
tags: osInfoTags,
|
||||||
|
};
|
||||||
|
// Add the custom metric to the array of metrics to send to Datadog.
|
||||||
|
metricsToSendToDatadog.push(metricToAdd);
|
||||||
|
}//fi
|
||||||
|
}//∞
|
||||||
|
}//∞
|
||||||
|
}//fi
|
||||||
|
|
||||||
|
await sails.helpers.http.post.with({
|
||||||
|
url: 'https://api.us5.datadoghq.com/api/v2/series',
|
||||||
|
data: {
|
||||||
|
series: metricsToSendToDatadog,
|
||||||
|
},
|
||||||
|
headers: {
|
||||||
|
'DD-API-KEY': sails.config.custom.datadogApiKey,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
}
|
||||||
|
}).tolerate((err)=>{
|
||||||
|
// If there was an error sending metrics to Datadog, we'll log the error in a warning, but we won't throw an error.
|
||||||
|
// This way, we'll still return a 200 status to the Fleet instance that sent usage analytics.
|
||||||
|
sails.log.warn(`When the receive-usage-analytics webhook tried to send metrics to Datadog, an error occured. Raw error: ${require('util').inspect(err)}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
1
website/config/custom.js
vendored
1
website/config/custom.js
vendored
@ -186,6 +186,7 @@ module.exports.custom = {
|
|||||||
// githubBotWebhookSecret: '…',
|
// githubBotWebhookSecret: '…',
|
||||||
// slackWebhookUrlForGithubBot: '…',
|
// slackWebhookUrlForGithubBot: '…',
|
||||||
// mergeFreezeAccessToken: '…',
|
// mergeFreezeAccessToken: '…',
|
||||||
|
// datadogApiKey: '…',
|
||||||
|
|
||||||
//…
|
//…
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user