Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Sentry.sln
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "1 - Solution Items", "1 - S
README.md = README.md
EndProjectSection
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{77454495-55EE-4B40-A089-71B9E8F82E89}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sentry.Samples.Console.Basic", "samples\Sentry.Samples.Console.Basic\Sentry.Samples.Console.Basic.csproj", "{65F5A969-B386-48F5-8B7E-6C90D6C720BC}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand All @@ -42,13 +46,18 @@ Global
{1E0F969B-67F9-4FCC-BCBF-596DB6460C7C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E0F969B-67F9-4FCC-BCBF-596DB6460C7C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E0F969B-67F9-4FCC-BCBF-596DB6460C7C}.Release|Any CPU.Build.0 = Release|Any CPU
{65F5A969-B386-48F5-8B7E-6C90D6C720BC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65F5A969-B386-48F5-8B7E-6C90D6C720BC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65F5A969-B386-48F5-8B7E-6C90D6C720BC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65F5A969-B386-48F5-8B7E-6C90D6C720BC}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{F2486CC8-FAB7-4775-976F-C5A4CF97867F} = {AF6AF4C7-8AA2-4D59-8064-2D79560904EB}
{1E0F969B-67F9-4FCC-BCBF-596DB6460C7C} = {83263231-1A2A-4733-B759-EEFF14E8C5D5}
{65F5A969-B386-48F5-8B7E-6C90D6C720BC} = {77454495-55EE-4B40-A089-71B9E8F82E89}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {0C652B1A-DF72-4EE5-A98B-194FE2C054F6}
Expand Down
12 changes: 12 additions & 0 deletions samples/Sentry.Samples.Console.Basic/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace Sentry.Samples.Console.Basic
{
static class Program
{
static void Main()
{
var sentry = new HttpSentryClient();
// This exception is captured and sent to Sentry
throw null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="../../src/Sentry/Sentry.csproj" />
</ItemGroup>

</Project>
9 changes: 0 additions & 9 deletions src/Sentry/Class1.cs

This file was deleted.

8 changes: 8 additions & 0 deletions src/Sentry/Constants.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Sentry
{
internal static class Constants
{
public const string DsnEnvironmentVariable = "SENTRY_DSN";
public const string DisableSdkDsnValue = "disabled";
}
}
178 changes: 178 additions & 0 deletions src/Sentry/Dsn.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
using System;
using System.Diagnostics;

namespace Sentry
{
/// <summary>
/// The Data Source Name of a given project in Sentry.
/// </summary>
/// <remarks>
/// <see href="https://docs.sentry.io/quickstart/#configure-the-dsn"/>
/// </remarks>
public class Dsn
{
private readonly string _dsn;

/// <summary>
/// The project ID which the authenticated user is bound to.
/// </summary>
public string ProjectId { get; }
/// <summary>
/// An optional path of which Sentry is hosted
/// </summary>
public string Path { get; }
/// <summary>
/// The optional secret key to authenticate the SDK.
/// </summary>
public string SecretKey { get; }
/// <summary>
/// The required public key to authenticate the SDK.
/// </summary>
public string PublicKey { get; }
/// <summary>
/// The URI used to communicate with Sentry
/// </summary>
public Uri SentryUri { get; }

/// <summary>
/// Initializes a new instance of the <see cref="Dsn"/> class.
/// </summary>
/// <param name="dsn">The DSN in the format: {PROTOCOL}://{PUBLIC_KEY}@{HOST}/{PATH}{PROJECT_ID}</param>
/// <remarks>
/// A legacy DSN containing a secret will also be accepted: {PROTOCOL}://{PUBLIC_KEY}:{SECRET_KEY}@{HOST}/{PATH}{PROJECT_ID}
/// </remarks>
public Dsn(string dsn)
{
var parsed = Parse(dsn, throwOnError: true);
Debug.Assert(parsed != null, "Parse should throw instead of returning null!");

var (projectId, path, secretKey, publicKey, sentryUri) = parsed.Value;

_dsn = dsn;
ProjectId = projectId;
Path = path;
SecretKey = secretKey;
PublicKey = publicKey;
SentryUri = sentryUri;
}

private Dsn(string dsn, string projectId, string path, string secretKey, string publicKey, Uri sentryUri)
{
_dsn = dsn;
ProjectId = projectId;
Path = path;
SecretKey = secretKey;
PublicKey = publicKey;
SentryUri = sentryUri;
}

/// <summary>
/// Tries to parse the string into a <see cref="Dsn"/>
/// </summary>
/// <param name="dsn">The string to attempt parsing.</param>
/// <param name="finalDsn">The <see cref="Dsn"/> when successfully parsed.</param>
/// <returns><c>true</c> if the string is a valid <see cref="Dsn"/> as was successfully parsed. Otherwise, <c>false</c>.</returns>
public static bool TryParse(string dsn, out Dsn finalDsn)
{
try
{
var parsed = Parse(dsn, throwOnError: false);
if (!parsed.HasValue)
{
finalDsn = null;
return false;
}

var (projectId, path, secretKey, publicKey, sentryUri) = parsed.Value;

finalDsn = new Dsn(
dsn,
projectId,
path,
secretKey,
publicKey,
sentryUri);

return true;
}
catch
{
// Parse should not throw though!
finalDsn = null;
return false;
}
}

private static (string projectId, string path, string secretKey, string publicKey, Uri sentryUri)?
Parse(string dsn, bool throwOnError)
{
Uri uri;
if (throwOnError)
{
uri = new Uri(dsn); // Throws the UriFormatException one would expect
}
else if (Uri.TryCreate(dsn, UriKind.Absolute, out var parsedUri))
{
uri = parsedUri;
}
else
{
return null;
}

// uri.UserInfo returns empty string instead of null when no user info data is provided
if (string.IsNullOrWhiteSpace(uri.UserInfo))
{
if (throwOnError)
{
throw new ArgumentException("Invalid DSN: No public key provided.");
}
return null;
}

var keys = uri.UserInfo.Split(':');
var publicKey = keys[0];
if (string.IsNullOrWhiteSpace(publicKey))
{
if (throwOnError)
{
throw new ArgumentException("Invalid DSN: No public key provided.");
}
return null;
}

string secretKey = null;
if (keys.Length > 1)
{
secretKey = keys[1];
}

var path = uri.AbsolutePath.Substring(0, uri.AbsolutePath.LastIndexOf('/'));
var projectId = uri.AbsoluteUri.Substring(uri.AbsoluteUri.LastIndexOf('/') + 1);

if (string.IsNullOrWhiteSpace(projectId))
{
if (throwOnError)
{
throw new ArgumentException("Invalid DSN: A Project Id is required.");
}
return null;
}

var builder = new UriBuilder
{
Scheme = uri.Scheme,
Host = uri.DnsSafeHost,
Port = uri.Port,
Path = $"{path}/api/{projectId}/store/"
};

return (projectId, path, secretKey, publicKey, builder.Uri);
}

/// <summary>
/// The original DSN string used to create this instance
/// </summary>
public override string ToString() => _dsn;
}
}
35 changes: 35 additions & 0 deletions src/Sentry/HttpSentryClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System;
using System.Threading;
using System.Threading.Tasks;

namespace Sentry
{
///
public class HttpSentryClient : ISentryClient, IDisposable
{
///
public HttpSentryClient()
{
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
}

private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
// TODO: A proper implementation
CaptureEventAsync(new SentryEvent(e.ExceptionObject as Exception));
}

///
public Task<SentryResponse> CaptureEventAsync(SentryEvent @event, CancellationToken cancellationToken = default)
=> Task.FromResult(new SentryResponse(false));

///
public SentryResponse CaptureEvent(SentryEvent @event) => new SentryResponse(false);

///
public void Dispose()
{
AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException;
}
}
}
26 changes: 26 additions & 0 deletions src/Sentry/ISentryClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System.Threading;
using System.Threading.Tasks;

namespace Sentry
{
/// <summary>
/// Sentry client
/// </summary>
public interface ISentryClient
{
/// <summary>
/// Sends the <see cref="SentryEvent" /> to Sentry asynchronously
/// </summary>
/// <param name="event">The event to send to Sentry.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns><see cref="SentryResponse"/></returns>
Task<SentryResponse> CaptureEventAsync(SentryEvent @event, CancellationToken cancellationToken = default);

/// <summary>
/// Sends the <see cref="SentryEvent" /> to Sentry
/// </summary>
/// <param name="event">The event to send to Sentry.</param>
/// <returns><see cref="SentryResponse"/></returns>
SentryResponse CaptureEvent(SentryEvent @event);
}
}
12 changes: 12 additions & 0 deletions src/Sentry/JsonSerializer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;

namespace Sentry
{
internal static class JsonSerializer
{
private static readonly StringEnumConverter StringEnumConverter = new StringEnumConverter();

public static string SerializeObject<T>(T @object) => JsonConvert.SerializeObject(@object, StringEnumConverter);
}
}
6 changes: 6 additions & 0 deletions src/Sentry/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
using System;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("Sentry.Tests")]

[assembly: CLSCompliant(true)]
Loading