Skip to content

Commit 2e3bacd

Browse files
committed
First Release
1 parent 82bbc82 commit 2e3bacd

15 files changed

+12042
-1
lines changed

README.md

+92-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,93 @@
11
# sematext-metrics
2-
A .Net Standard Client for Sematext Custom Metrics
2+
3+
A .Net Standard Client for [Sematext Custom Metrics](https://sematext.com/docs/monitoring/custom-metrics/)
4+
5+
A free account to generate API tokens is available at [www.sematext.com](https://apps.sematext.com/users-web/register.do)
6+
7+
## Installation
8+
9+
### Package Manager
10+
11+
```powershell
12+
Install-Package Sematext-Metrics
13+
```
14+
15+
### .Net CLI
16+
17+
```powershell
18+
dotnet add package Sematext-Metrics
19+
```
20+
21+
## Initialization
22+
23+
Once you create an account and application via the [sematext web ui](https://sematext.com) you will have an app token to use when you initialize the `MetricsClient`
24+
25+
```csharp
26+
var client = new MetricsClient(<YOUR APP TOKEN>);
27+
```
28+
29+
## Usage
30+
31+
### Send a single metric
32+
33+
```csharp
34+
// create a new metric
35+
var metric = new Metric(timestamp: TimeHelper.EpochFromUtc,
36+
name: "coffee-consumed",
37+
value: 4,
38+
aggregateType: AggregateTypes.Sum,
39+
filter1: "floor=1",
40+
filter2: "strength=bold");
41+
42+
// send the metric to sematext
43+
await client.SendAsync(metric);
44+
```
45+
46+
### Send multiple metrics
47+
48+
```csharp
49+
// create a list to hold multple metrics
50+
var metrics = new List<Metric>();
51+
52+
// create the first metric
53+
metrics.Add(new Metric(timestamp: TimeHelper.EpochFromUtc,
54+
name: "coffee-consumed",
55+
value: 4,
56+
aggregateType: AggregateTypes.Sum,
57+
filter1: "floor=1",
58+
filter2: "strength=bold"));
59+
60+
// create the second metric
61+
metrics.Add(new Metric(timestamp: TimeHelper.EpochFromUtc,
62+
name: "coffee-consumed",
63+
value: 1,
64+
aggregateType: AggregateTypes.Sum,
65+
filter1: "floor=3",
66+
filter2: "strength=medium"));
67+
68+
// send the metrics to sematext
69+
await client.SendAsync(metrics);
70+
```
71+
72+
## Configuration
73+
74+
To send metrics to a different api endpoint (if you are running an on-premise SPM setup) you can override the default endpoint when you initialize the `MetricsClient`
75+
76+
```csharp
77+
var customEndpoint = new Uri("http://spm-receiver.example.com/spm-receiver/custom/receive.raw");
78+
var client = new MetricsClient(<YOUR APP TOKEN>, customEndpoint);
79+
```
80+
81+
## Running the unit tests
82+
83+
If you want to run the unit tests in the solution you first need to set an environment variable named `SEMATEXT_APP_TOKEN`. This environment variable is used in the `TestCaseBase` class when it creates the `MetricsClient` that is used to run the unit tests.
84+
85+
### Powershell
86+
87+
```powershell
88+
[Environment]::SetEnvironmentVariable("SEMATEXT_APP_TOKEN", "<YOUR APP TOKEN>", "User")
89+
```
90+
91+
## Further Reading
92+
93+
https://sematext.com/docs/monitoring/custom-metrics/

src/Sematext/AggregateTypes.cs

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
namespace Sematext
2+
{
3+
public enum AggregateTypes
4+
{
5+
Avg,
6+
Max,
7+
Min,
8+
Sum
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using System.Net;
3+
4+
namespace Sematext.Exceptions
5+
{
6+
public class SematextApiException : Exception
7+
{
8+
public string Content { get; private set; }
9+
public HttpStatusCode StatusCode { get; private set; }
10+
11+
public SematextApiException(HttpStatusCode statusCode, string content) : base($"Status Code: {statusCode}")
12+
{
13+
StatusCode = statusCode;
14+
Content = content;
15+
}
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using System;
2+
3+
namespace Sematext.Exceptions
4+
{
5+
public class SematextValidationException : Exception
6+
{
7+
public SematextValidationException(string message) : base(message)
8+
{
9+
}
10+
}
11+
}

src/Sematext/Helpers/TimeHelper.cs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using System;
2+
3+
namespace Sematext.Helpers
4+
{
5+
public static class TimeHelper
6+
{
7+
public static long EpochFromUtc => DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
8+
}
9+
}

src/Sematext/Metric.cs

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
using Sematext.Exceptions;
2+
using Sematext.Helpers;
3+
4+
namespace Sematext
5+
{
6+
public class Metric
7+
{
8+
public string Aggregation { get; private set; }
9+
10+
public string Filter1 { get; private set; }
11+
12+
public string Filter2 { get; private set; }
13+
14+
public string Name { get; private set; }
15+
16+
public long Timestamp { get; private set; }
17+
18+
public int Value { get; private set; }
19+
20+
/// <summary>
21+
/// Creates a new Metric
22+
/// </summary>
23+
/// <param name="timestamp">Time in milliseconds since Epoch when the Metric was recorded</param>
24+
/// <param name="name">The name of the Metric</param>
25+
/// <param name="value">The value of the Metric</param>
26+
/// <param name="aggregateType">The aggregation type to use for the metric</param>
27+
/// <param name="filter1">The value for filter1</param>
28+
/// <param name="filter2">The value for filter2</param>
29+
public Metric(long timestamp, string name, int value, AggregateTypes aggregateType, string filter1 = null, string filter2 = null)
30+
{
31+
this.SetTimestamp(timestamp)
32+
.SetName(name)
33+
.SetValue(value)
34+
.SetAggregation(aggregateType)
35+
.SetFilter1(filter1)
36+
.SetFilter2(filter2);
37+
}
38+
39+
/// <summary>
40+
/// Creates a new Metric using the current UTC time as the timestamp
41+
/// </summary>
42+
/// <param name="name">The name of the Metric</param>
43+
/// <param name="value">The value of the Metric</param>
44+
/// <param name="aggregateType">The aggregation type to use for the metric</param>
45+
/// <param name="filter1">The value for filter1</param>
46+
/// <param name="filter2">The value for filter2</param>
47+
public Metric(string name, int value, AggregateTypes aggregateType, string filter1 = null, string filter2 = null)
48+
{
49+
this.SetTimestamp(TimeHelper.EpochFromUtc)
50+
.SetName(name)
51+
.SetValue(value)
52+
.SetAggregation(aggregateType)
53+
.SetFilter1(filter1)
54+
.SetFilter2(filter2);
55+
}
56+
57+
public Metric SetAggregation(AggregateTypes aggregateType)
58+
{
59+
// convert the enum to it's lowercase string representation
60+
Aggregation = aggregateType.ToString().ToLowerInvariant();
61+
return this;
62+
}
63+
64+
public Metric SetFilter1(string filter1)
65+
{
66+
if (string.IsNullOrWhiteSpace(filter1))
67+
{
68+
// filter1 is not required, so we'll clear it out
69+
Filter1 = null;
70+
}
71+
else if (filter1.Length > 255)
72+
{
73+
throw new SematextValidationException($"{nameof(filter1)} must be between 1 and 255 characters.");
74+
}
75+
else
76+
{
77+
Filter1 = filter1;
78+
}
79+
80+
return this;
81+
}
82+
83+
public Metric SetFilter2(string filter2)
84+
{
85+
if (string.IsNullOrWhiteSpace(filter2))
86+
{
87+
// filter2 is not required, so we'll clear it out
88+
Filter1 = null;
89+
}
90+
else if (filter2.Length > 255)
91+
{
92+
throw new SematextValidationException($"{nameof(filter2)} must be between 1 and 255 characters.");
93+
}
94+
else
95+
{
96+
Filter2 = filter2;
97+
}
98+
99+
return this;
100+
}
101+
102+
public Metric SetName(string name)
103+
{
104+
if (string.IsNullOrWhiteSpace(name) || name.Length > 255)
105+
{
106+
throw new SematextValidationException($"{nameof(name)} must be between 1 and 255 characters.");
107+
}
108+
109+
this.Name = name;
110+
return this;
111+
}
112+
113+
public Metric SetTimestamp(long timestamp)
114+
{
115+
this.Timestamp = timestamp;
116+
return this;
117+
}
118+
119+
public Metric SetValue(int value)
120+
{
121+
Value = value;
122+
return this;
123+
}
124+
}
125+
}

src/Sematext/MetricsClient.cs

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using Newtonsoft.Json;
2+
using Newtonsoft.Json.Serialization;
3+
using Sematext.Exceptions;
4+
using System;
5+
using System.Collections;
6+
using System.Linq;
7+
using System.Net.Http;
8+
using System.Text;
9+
using System.Threading.Tasks;
10+
11+
namespace Sematext
12+
{
13+
public class MetricsClient
14+
{
15+
private static readonly HttpClient Client = new HttpClient();
16+
private static readonly Uri DefaultUrl = new Uri("http://spm-receiver.sematext.com/receiver/custom/receive.json?token=");
17+
private readonly JsonSerializerSettings _serializerSettings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver() };
18+
private readonly Uri _url;
19+
20+
public MetricsClient(string appToken, Uri endpointUrl = null)
21+
{
22+
// if the app token is not provided throw an exception
23+
if (string.IsNullOrWhiteSpace(appToken))
24+
{
25+
throw new SematextValidationException("You must provide a valid app token.");
26+
}
27+
28+
// if the user does not provide a non-default endpoint url, then use the default
29+
_url = endpointUrl ?? new Uri($"{DefaultUrl}{appToken}");
30+
}
31+
32+
public async Task SendAsync(Metric metric)
33+
{
34+
await SendAsync(new[] { metric });
35+
}
36+
37+
public async Task SendAsync(Metric[] metrics)
38+
{
39+
// we can only send 100 metrics at a time
40+
var numOfMetrics = metrics.Length;
41+
42+
if (numOfMetrics > 100)
43+
{
44+
var batches = Math.Ceiling(numOfMetrics / 100.0);
45+
46+
for (var i = 0; i < batches; i++)
47+
{
48+
// pull out 100 metrics
49+
var nextBatch = metrics.Skip(100 * i).Take(100);
50+
await SendBatch(nextBatch);
51+
}
52+
}
53+
else
54+
{
55+
await SendBatch(metrics);
56+
}
57+
}
58+
59+
private async Task SendBatch(IEnumerable metrics)
60+
{
61+
var payload = new
62+
{
63+
datapoints = metrics
64+
};
65+
66+
var result = await Client
67+
.PostAsync(_url, new StringContent(JsonConvert.SerializeObject(payload, _serializerSettings), Encoding.UTF8, "application/json"))
68+
.ConfigureAwait(false);
69+
70+
if (!result.IsSuccessStatusCode)
71+
{
72+
var content = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
73+
throw new SematextApiException(result.StatusCode, content);
74+
}
75+
}
76+
}
77+
}

src/Sematext/Sematext.csproj

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>netstandard2.0</TargetFramework>
5+
</PropertyGroup>
6+
7+
<ItemGroup>
8+
<PackageReference Include="Newtonsoft.Json" Version="10.0.3" />
9+
</ItemGroup>
10+
11+
<PropertyGroup>
12+
<TargetFramework>netstandard2.0</TargetFramework>
13+
<PackageId>Sematext-Metrics</PackageId>
14+
<PackageVersion>1.0.0</PackageVersion>
15+
<Authors>Wes Shaddix</Authors>
16+
<Description>A custom metrics client for .Net</Description>
17+
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
18+
<PackageReleaseNotes>Initial Release</PackageReleaseNotes>
19+
<PackageTags>sematext spm metrics</PackageTags>
20+
</PropertyGroup>
21+
22+
</Project>

0 commit comments

Comments
 (0)