Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Features

- Added a flag to options `DisableFileWrite` to allow users to opt-out of all file writing operations. Note that toggling this will affect features such as offline caching and auto-session tracking and release health as these rely on some file persistency ([#3614](https://github.com/getsentry/sentry-dotnet/pull/3614))
- All exceptions are now added as breadcrumbs on future events. Previously this was only the case for exceptions captured via the `Sentry.SeriLog` or `Sentry.Extensions.Logging` integrations. ([#3584](https://github.com/getsentry/sentry-dotnet/pull/3584))

### Fixes
Expand Down
61 changes: 0 additions & 61 deletions samples/Sentry.Samples.Console.Basic/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,64 +35,3 @@
options.TracesSampleRate = 1.0;
});

// This starts a new transaction and attaches it to the scope.
var transaction = SentrySdk.StartTransaction("Program Main", "function");
SentrySdk.ConfigureScope(scope => scope.Transaction = transaction);

// Do some work. (This is where you'd have your own application logic.)
await FirstFunction();
await SecondFunction();
await ThirdFunction();

// Always try to finish the transaction successfully.
// Unhandled exceptions will fail the transaction automatically.
// Optionally, you can try/catch the exception, and call transaction.Finish(exception) on failure.
transaction.Finish();

async Task FirstFunction()
{
// This is an example of making an HttpRequest. A trace us automatically captured by Sentry for this.
var messageHandler = new SentryHttpMessageHandler();
var httpClient = new HttpClient(messageHandler, true);
var html = await httpClient.GetStringAsync("https://example.com/");
Console.WriteLine(html);
}

async Task SecondFunction()
{
var span = transaction.StartChild("function", nameof(SecondFunction));

try
{
// Simulate doing some work
await Task.Delay(100);

// Throw an exception
throw new ApplicationException("Something happened!");
}
catch (Exception exception)
{
// This is an example of capturing a handled exception.
SentrySdk.CaptureException(exception);
span.Finish(exception);
}

span.Finish();
}

async Task ThirdFunction()
{
var span = transaction.StartChild("function", nameof(ThirdFunction));
try
{
// Simulate doing some work
await Task.Delay(100);

// This is an example of an unhandled exception. It will be captured automatically.
throw new InvalidOperationException("Something happened that crashed the app!");
}
finally
{
span.Finish();
}
}
2 changes: 2 additions & 0 deletions src/Sentry/BindableSentryOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ internal partial class BindableSentryOptions
public string? CacheDirectoryPath { get; set; }
public bool? CaptureFailedRequests { get; set; }
public List<string>? FailedRequestTargets { get; set; }
public bool? DisableFileWrite { get; set; }
public TimeSpan? InitCacheFlushTimeout { get; set; }
public Dictionary<string, string>? DefaultTags { get; set; }
public bool? EnableTracing { get; set; }
Expand Down Expand Up @@ -81,6 +82,7 @@ public void ApplyTo(SentryOptions options)
options.CacheDirectoryPath = CacheDirectoryPath ?? options.CacheDirectoryPath;
options.CaptureFailedRequests = CaptureFailedRequests ?? options.CaptureFailedRequests;
options.FailedRequestTargets = FailedRequestTargets?.Select(s => new SubstringOrRegexPattern(s)).ToList() ?? options.FailedRequestTargets;
options.DisableFileWrite = DisableFileWrite ?? options.DisableFileWrite;
options.InitCacheFlushTimeout = InitCacheFlushTimeout ?? options.InitCacheFlushTimeout;
options.DefaultTags = DefaultTags ?? options.DefaultTags;
#pragma warning disable CS0618 // Type or member is obsolete
Expand Down
16 changes: 10 additions & 6 deletions src/Sentry/GlobalSessionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public GlobalSessionManager(
_options = options;
_clock = clock ?? SystemClock.Clock;
_persistedSessionProvider = persistedSessionProvider
?? (filePath => Json.Load(filePath, PersistedSessionUpdate.FromJson));
?? (filePath => Json.Load(_options.FileSystem, filePath, PersistedSessionUpdate.FromJson));

// TODO: session file should really be process-isolated, but we
// don't have a proper mechanism for that right now.
Expand All @@ -53,14 +53,18 @@ private void PersistSession(SessionUpdate update, DateTimeOffset? pauseTimestamp

try
{
Directory.CreateDirectory(_persistenceDirectoryPath);
_options.LogDebug("Creating persistence directory for session file at '{0}'.", _persistenceDirectoryPath);

_options.LogDebug("Created persistence directory for session file '{0}'.", _persistenceDirectoryPath);
if (!_options.FileSystem.CreateDirectory(_persistenceDirectoryPath))
{
_options.LogError("Failed to create persistent directory for session file.");
return;
}

var filePath = Path.Combine(_persistenceDirectoryPath, PersistedSessionFileName);

var persistedSessionUpdate = new PersistedSessionUpdate(update, pauseTimestamp);
persistedSessionUpdate.WriteToFile(filePath, _options.DiagnosticLogger);
persistedSessionUpdate.WriteToFile(_options.FileSystem, filePath, _options.DiagnosticLogger);

_options.LogDebug("Persisted session to a file '{0}'.", filePath);
}
Expand All @@ -86,7 +90,7 @@ private void DeletePersistedSession()
{
try
{
var contents = File.ReadAllText(filePath);
var contents = _options.FileSystem.ReadAllTextFromFile(filePath);
_options.LogDebug("Deleting persisted session file with contents: {0}", contents);
}
catch (Exception ex)
Expand All @@ -95,7 +99,7 @@ private void DeletePersistedSession()
}
}

File.Delete(filePath);
_options.FileSystem.DeleteFile(filePath);

_options.LogInfo("Deleted persisted session file '{0}'.", filePath);
}
Expand Down
27 changes: 23 additions & 4 deletions src/Sentry/Http/HttpTransportBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -386,9 +386,18 @@ private void HandleFailure(HttpResponseMessage response, Envelope envelope)
var destination = Path.Combine(destinationDirectory, "envelope_too_large",
(eventId ?? SentryId.Create()).ToString());

Directory.CreateDirectory(Path.GetDirectoryName(destination)!);
if (!_options.FileSystem.CreateDirectory(Path.GetDirectoryName(destination)!))
{
_options.LogError("Failed to create directory to store the envelope.");
return;
}

var envelopeFile = File.Create(destination);
var envelopeFile = _options.FileSystem.CreateFileForWriting(destination);
if (envelopeFile == Stream.Null)
{
_options.LogError("Failed to create envelope file.");
return;
}

using (envelopeFile)
{
Expand Down Expand Up @@ -442,9 +451,19 @@ private async Task HandleFailureAsync(HttpResponseMessage response, Envelope env
var destination = Path.Combine(destinationDirectory, "envelope_too_large",
(eventId ?? SentryId.Create()).ToString());

Directory.CreateDirectory(Path.GetDirectoryName(destination)!);
if (!_options.FileSystem.CreateDirectory(Path.GetDirectoryName(destination)!))
{
_options.LogError("Failed to create directory to store the envelope.");
return;
}

var envelopeFile = _options.FileSystem.CreateFileForWriting(destination);
if (envelopeFile == Stream.Null)
{
_options.LogError("Failed to create envelope file.");
return;
}

var envelopeFile = File.Create(destination);
#if NETFRAMEWORK || NETSTANDARD2_0
using (envelopeFile)
#else
Expand Down
10 changes: 8 additions & 2 deletions src/Sentry/ISentryJsonSerializable.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Sentry.Extensibility;
using Sentry.Internal;

namespace Sentry;

Expand All @@ -19,9 +20,14 @@ public interface ISentryJsonSerializable

internal static class JsonSerializableExtensions
{
public static void WriteToFile(this ISentryJsonSerializable serializable, string filePath, IDiagnosticLogger? logger)
public static void WriteToFile(this ISentryJsonSerializable serializable, IFileSystem fileSystem, string filePath, IDiagnosticLogger? logger)
{
using var file = File.Create(filePath);
using var file = fileSystem.CreateFileForWriting(filePath);
if (file == Stream.Null)
{
return;
}

using var writer = new Utf8JsonWriter(file);

serializable.WriteTo(writer, logger);
Expand Down
3 changes: 2 additions & 1 deletion src/Sentry/Internal/DebugStackTrace.cs
Original file line number Diff line number Diff line change
Expand Up @@ -526,7 +526,8 @@ private static void DemangleLambdaReturnType(SentryStackFrame frame)
{
return reader.Invoke(assemblyName);
}
var assembly = File.OpenRead(assemblyName);

var assembly = options.FileSystem.OpenFileForReading(assemblyName);
return new PEReader(assembly);
}
catch (Exception)
Expand Down
93 changes: 77 additions & 16 deletions src/Sentry/Internal/FileSystem.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
using Sentry.Extensibility;

namespace Sentry.Internal;

internal class FileSystem : IFileSystem
{
public static IFileSystem Instance { get; } = new FileSystem();
private readonly SentryOptions? _options;

private FileSystem()
public FileSystem(SentryOptions? options)
{
_options = options;
}

public IEnumerable<string> EnumerateFiles(string path) => Directory.EnumerateFiles(path);
Expand All @@ -16,38 +19,96 @@ public IEnumerable<string> EnumerateFiles(string path, string searchPattern) =>
public IEnumerable<string> EnumerateFiles(string path, string searchPattern, SearchOption searchOption) =>
Directory.EnumerateFiles(path, searchPattern, searchOption);

public void CreateDirectory(string path) => Directory.CreateDirectory(path);
public bool CreateDirectory(string path)
{
if (!_options?.DisableFileWrite is false)
{
_options?.LogDebug("Skipping creating directory. Writing to file system has been explicitly disabled.");
return false;
}

public void DeleteDirectory(string path, bool recursive = false) => Directory.Delete(path, recursive);
Directory.CreateDirectory(path);
return true;
}

public bool DeleteDirectory(string path, bool recursive = false)
{
if (!_options?.DisableFileWrite is false)
{
_options?.LogDebug("Skipping deleting directory. Writing to file system has been explicitly disabled.");
return false;
}

Directory.Delete(path, recursive);
return true;
}

public bool DirectoryExists(string path) => Directory.Exists(path);

public bool FileExists(string path) => File.Exists(path);

public void MoveFile(string sourceFileName, string destFileName, bool overwrite = false)
public bool MoveFile(string sourceFileName, string destFileName, bool overwrite = false)
{
if (!_options?.DisableFileWrite is false)
{
_options?.LogDebug("Skipping moving file. Writing to file system has been explicitly disabled.");
return false;
}

#if NETCOREAPP3_0_OR_GREATER
File.Move(sourceFileName, destFileName, overwrite);
#else
if (overwrite)
{
File.Copy(sourceFileName, destFileName, overwrite: true);
File.Delete(sourceFileName);
}
else
{
File.Move(sourceFileName, destFileName);
}
if (overwrite)
{
File.Copy(sourceFileName, destFileName, overwrite: true);
File.Delete(sourceFileName);
}
else
{
File.Move(sourceFileName, destFileName);
}
#endif
return true;
}

public void DeleteFile(string path) => File.Delete(path);
public bool DeleteFile(string path)
{
if (!_options?.DisableFileWrite is false)
{
_options?.LogDebug("Skipping deleting file. Writing to file system has been explicitly disabled.");
return false;
}

File.Delete(path);
return true;
}

public DateTimeOffset GetFileCreationTime(string path) => new FileInfo(path).CreationTimeUtc;

public string ReadAllTextFromFile(string path) => File.ReadAllText(path);

public Stream OpenFileForReading(string path) => File.OpenRead(path);

public Stream CreateFileForWriting(string path) => File.Create(path);
public Stream CreateFileForWriting(string path)
{
if (!_options?.DisableFileWrite is false)
{
_options?.LogDebug("Skipping file for writing. Writing to file system has been explicitly disabled.");
return Stream.Null;
}

return File.Create(path);
}

public bool WriteAllTextToFile(string path, string contents)
{
if (!_options?.DisableFileWrite is false)
{
_options?.LogDebug("Skipping writing all text to file. Writing to file system has been explicitly disabled.");
return false;
}

File.WriteAllText(path, contents);
return true;
}
}
9 changes: 5 additions & 4 deletions src/Sentry/Internal/IFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ internal interface IFileSystem
IEnumerable<string> EnumerateFiles(string path);
IEnumerable<string> EnumerateFiles(string path, string searchPattern);
IEnumerable<string> EnumerateFiles(string path, string searchPattern, SearchOption searchOption);
void CreateDirectory(string path);
void DeleteDirectory(string path, bool recursive = false);
bool CreateDirectory(string path);
bool DeleteDirectory(string path, bool recursive = false);
bool DirectoryExists(string path);
bool FileExists(string path);
void MoveFile(string sourceFileName, string destFileName, bool overwrite = false);
void DeleteFile(string path);
bool MoveFile(string sourceFileName, string destFileName, bool overwrite = false);
bool DeleteFile(string path);
DateTimeOffset GetFileCreationTime(string path);
string ReadAllTextFromFile(string file);
Stream OpenFileForReading(string path);
Stream CreateFileForWriting(string path);
bool WriteAllTextToFile(string path, string contents);
}
Loading