-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Tag enhancement #12
base: dev
Are you sure you want to change the base?
Tag enhancement #12
Changes from 16 commits
529302d
a4cf7a4
b1b8cbf
64a0eb7
40b40d8
e5187e9
b3d52a8
1bcdeb3
7120c43
4ae80e2
55dcd2b
ddff0cb
723ca69
0659eaa
2b79763
f5a7b88
71ea744
89cb77e
5c81208
308c7c0
493c101
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
using System; | ||
// ReSharper disable MemberCanBePrivate.Global | ||
// ReSharper disable AutoPropertyCanBeMadeGetOnly.Global | ||
// ReSharper disable UnusedAutoPropertyAccessor.Global | ||
|
||
// ReSharper disable ClassNeverInstantiated.Global | ||
|
||
namespace Seq.App.Opsgenie.Classes | ||
{ | ||
public class OpsGenieData | ||
{ | ||
public OpsGenieData() | ||
{ | ||
Success = false; | ||
Action = string.Empty; | ||
ProcessedAt = DateTime.Now; | ||
IntegrationId = string.Empty; | ||
IsSuccess = false; | ||
Status = string.Empty; | ||
AlertId = string.Empty; | ||
Alias = string.Empty; | ||
} | ||
|
||
public bool Success { get; set; } | ||
public string Action { get; set; } | ||
public DateTime ProcessedAt { get; set; } | ||
public string IntegrationId { get; set; } | ||
public bool IsSuccess { get; set; } | ||
public string Status { get; set; } | ||
public string AlertId { get; set; } | ||
public string Alias { get; set; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
// ReSharper disable UnusedMember.Global | ||
|
||
namespace Seq.App.Opsgenie.Classes | ||
{ | ||
public class OpsGenieResponse | ||
{ | ||
public OpsGenieResponse() | ||
{ | ||
Result = string.Empty; | ||
RequestId = string.Empty; | ||
Took = -1; | ||
} | ||
|
||
public OpsGenieData Data { get; set; } | ||
public string Result { get; set; } | ||
public string RequestId { get; set; } | ||
public decimal Took { get; set; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
using System; | ||
using System.Net.Http; | ||
// ReSharper disable UnusedAutoPropertyAccessor.Global | ||
|
||
// ReSharper disable NotAccessedField.Global | ||
|
||
namespace Seq.App.Opsgenie.Classes | ||
{ | ||
public class OpsGenieResult | ||
{ | ||
public OpsGenieResult() | ||
{ | ||
Ok = false; | ||
StatusCode = -1; | ||
HttpResponse = null; | ||
Response = null; | ||
Error = null; | ||
ResponseBody = string.Empty; | ||
} | ||
|
||
public bool Ok { get; set; } | ||
public int StatusCode { get; set; } | ||
public HttpResponseMessage HttpResponse { get; set; } | ||
public OpsGenieResponse Response { get; set; } | ||
public Exception Error { get; set; } | ||
public string ResponseBody { get; set; } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,13 +1,16 @@ | ||
using System.Text.Json.Serialization; | ||
// ReSharper disable UnusedAutoPropertyAccessor.Global | ||
|
||
namespace Seq.App.Opsgenie | ||
namespace Seq.App.Opsgenie.Classes | ||
{ | ||
class Responder | ||
{ | ||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] | ||
public string Username { get; set; } | ||
|
||
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] | ||
public string Name { get; set; } | ||
|
||
public ResponderType Type { get; set; } | ||
} | ||
} | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using System.Threading.Tasks; | ||
using Seq.App.Opsgenie.Classes; | ||
|
||
namespace Seq.App.Opsgenie.Interfaces | ||
{ | ||
interface IOpsgenieApiClient | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one might go in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reorganised into Api and Client |
||
{ | ||
Task<OpsGenieResult> CreateAsync(OpsgenieAlert alert); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,15 +6,15 @@ | |
using System.Text.Json; | ||
using System.Text.Json.Serialization; | ||
using System.Threading.Tasks; | ||
using Seq.Apps; | ||
using Seq.App.Opsgenie.Classes; | ||
using Seq.App.Opsgenie.Interfaces; | ||
|
||
namespace Seq.App.Opsgenie | ||
{ | ||
class OpsgenieApiClient : IOpsgenieApiClient, IDisposable | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This type could also move to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reorganised into Api and Client |
||
{ | ||
const string OpsgenieCreateAlertUrl = "https://api.opsgenie.com/v2/alerts"; | ||
readonly HttpClient _httpClient = new HttpClient(); | ||
readonly Encoding _utf8Encoding = new UTF8Encoding(false); | ||
|
||
static readonly JsonSerializerOptions SerializerOptions = new JsonSerializerOptions | ||
{ | ||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase, | ||
|
@@ -24,23 +24,21 @@ class OpsgenieApiClient : IOpsgenieApiClient, IDisposable | |
} | ||
}; | ||
|
||
public static string Serialize(IEnumerable list) | ||
{ | ||
return JsonSerializer.Serialize(list, SerializerOptions); | ||
} | ||
|
||
public static string Serialize(OpsgenieAlert alert) | ||
{ | ||
return JsonSerializer.Serialize(alert, SerializerOptions); | ||
} | ||
readonly HttpClient _httpClient = new HttpClient(); | ||
readonly Encoding _utf8Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); | ||
|
||
public OpsgenieApiClient(string apiKey) | ||
{ | ||
if (apiKey == null) throw new ArgumentNullException(nameof(apiKey)); | ||
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("GenieKey", apiKey); | ||
} | ||
|
||
public async Task<HttpResponseMessage> CreateAsync(OpsgenieAlert alert) | ||
public void Dispose() | ||
{ | ||
_httpClient.Dispose(); | ||
} | ||
|
||
public async Task<OpsGenieResult> CreateAsync(OpsgenieAlert alert) | ||
{ | ||
if (alert == null) throw new ArgumentNullException(nameof(alert)); | ||
|
||
|
@@ -50,21 +48,41 @@ public async Task<HttpResponseMessage> CreateAsync(OpsgenieAlert alert) | |
"application/json"); | ||
|
||
var response = await _httpClient.PostAsync(OpsgenieCreateAlertUrl, content); | ||
var responseBody = await response.Content.ReadAsStringAsync(); | ||
var result = new OpsGenieResult | ||
{ | ||
StatusCode = (int) response.StatusCode, | ||
Ok = response.IsSuccessStatusCode, | ||
HttpResponse = response, | ||
ResponseBody = responseBody | ||
}; | ||
|
||
try | ||
{ | ||
var opsGenieResponse = | ||
JsonSerializer.Deserialize<OpsGenieResponse>(responseBody, | ||
new JsonSerializerOptions {PropertyNameCaseInsensitive = true}); | ||
|
||
if (!response.IsSuccessStatusCode) | ||
result.Response = opsGenieResponse; | ||
} | ||
catch (Exception ex) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason not to just let the exception bubble up, here, and be handled by the hosting environment? In general, Seq apps shouldn't customize error handling, since the app host does this consistently for all hosted apps and in future we might make improvements up at that layer. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think the perspective was to allow a consistent approach to error handling based on a signal targeted to the specific appid or appinstanceid (or instance name) that included the context/properties around what the alert was. So for example if we fail to connect to OpsGenie to send the alert, we can send an email or Teams or JIRA alert that preserves the context of what we were alerting, by having an error event that preserves this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm still inclined to have this caught for the reasons above. It's fairly serious if an alert doesn't go, and if we can catch what the alert was, then we have opportunity to send an alert via other alerting apps. Overall this speaks to Seq reliability - being able to do fallbacks etc. Unfortunately the hosting environment doesn't preserve the event data ... I suppose it could still throw after this? |
||
{ | ||
var responseBody = await response.Content.ReadAsStringAsync(); | ||
var fragment = responseBody.Substring(0, Math.Min(1024, responseBody.Length)); | ||
throw new SeqAppException( | ||
$"Opsgenie alert creation failed ({response.StatusCode}/{response.ReasonPhrase}): {fragment}"); | ||
result.Ok = false; | ||
result.Error = ex; | ||
} | ||
|
||
return response; | ||
|
||
return result; | ||
} | ||
|
||
public void Dispose() | ||
public static string Serialize(IEnumerable list) | ||
{ | ||
_httpClient.Dispose(); | ||
return JsonSerializer.Serialize(list, SerializerOptions); | ||
} | ||
|
||
public static string Serialize(OpsgenieAlert alert) | ||
{ | ||
return JsonSerializer.Serialize(alert, SerializerOptions); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Organizing into
Classes
andInterfaces
might not give as much insight into the structure of the codebase as some alternatives - could this one go inSeq.App.Opsgenie.Api
, perhaps, since the structure of these types (presumably) matches the requirements of the Opsgenie API?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah should be fine. I'll take a look.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reorganised into Api and Client