Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 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
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
109 changes: 93 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,112 @@ 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 DirectoryInfo? CreateDirectory(string path)
{
if (!_options?.DisableFileWrite is false)
{
_options?.LogDebug("Skipping creating directory. Writing to file system has been explicitly disabled.");
return null;
}

return Directory.CreateDirectory(path);
}

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;
}

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

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 null;
}

#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

if (File.Exists(sourceFileName) || !File.Exists(destFileName))
{
return false;
}

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 null;
}

File.Delete(path);
return !File.Exists(path);
}

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 OpenFileForReading(string path, bool useAsync, FileMode fileMode = FileMode.Open, FileAccess fileAccess = FileAccess.Read, FileShare fileShare = FileShare.ReadWrite, int bufferSize = 4096)
{
return new FileStream(
path,
fileMode,
fileAccess,
fileShare,
bufferSize: bufferSize,
useAsync: useAsync);
}

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 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 null;
}

File.WriteAllText(path, contents);
return File.Exists(path);
}
}
21 changes: 14 additions & 7 deletions src/Sentry/Internal/IFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,21 @@ 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);
DirectoryInfo? 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);
string? ReadAllTextFromFile(string file);
Stream? OpenFileForReading(string path);
Stream? OpenFileForReading(string path,
bool useAsync,
FileMode fileMode = FileMode.Open,
FileAccess fileAccess = FileAccess.Read,
FileShare fileShare = FileShare.ReadWrite,
int bufferSize = 4096);
Stream? CreateFileForWriting(string path);
bool? WriteAllTextToFile(string path, string contents);
}
23 changes: 19 additions & 4 deletions src/Sentry/Internal/InstallationIdHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,28 +47,43 @@ internal class InstallationIdHelper(SentryOptions options)

private string? TryGetPersistentInstallationId()
{
if (options.DisableFileWrite)
{
options.LogDebug("File write has been disabled via the options. Skipping trying to get persistent installation ID.");
return null;
}

try
{
var rootPath = options.CacheDirectoryPath ??
Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
var directoryPath = Path.Combine(rootPath, "Sentry", options.Dsn!.GetHashString());
var fileSystem = options.FileSystem;

Directory.CreateDirectory(directoryPath);
if (!fileSystem.CreateDirectory(directoryPath))
{
options.LogDebug("Failed to create a directory for installation ID file ({0}).", directoryPath);
return null;
}

options.LogDebug("Created directory for installation ID file ({0}).", directoryPath);

var filePath = Path.Combine(directoryPath, ".installation");

// Read installation ID stored in a file
if (File.Exists(filePath))
if (fileSystem.FileExists(filePath))
{
return File.ReadAllText(filePath);
return fileSystem.ReadAllTextFromFile(filePath);
}
options.LogDebug("File containing installation ID does not exist ({0}).", filePath);

// Generate new installation ID and store it in a file
var id = Guid.NewGuid().ToString();
File.WriteAllText(filePath, id);
if (!fileSystem.WriteAllTextToFile(filePath, id))
{
options.LogDebug("Failed to write Installation ID to file ({0}).", filePath);
return null;
}

options.LogDebug("Saved installation ID '{0}' to file '{1}'.", id, filePath);
return id;
Expand Down
4 changes: 2 additions & 2 deletions src/Sentry/Internal/Json.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ public static T Parse<T>(string json, Func<JsonElement, T> factory)
return factory.Invoke(jsonDocument.RootElement);
}

public static T Load<T>(string filePath, Func<JsonElement, T> factory)
public static T Load<T>(IFileSystem fileSystem, string filePath, Func<JsonElement, T> factory)
{
using var file = File.OpenRead(filePath);
using var file = fileSystem.OpenFileForReading(filePath);
using var jsonDocument = JsonDocument.Parse(file);
return factory.Invoke(jsonDocument.RootElement);
}
Expand Down
Loading