Skip to content
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

fix: Route file writing through FileSystem #3614

Merged
merged 19 commits into from
Sep 26, 2024
Merged
12 changes: 9 additions & 3 deletions src/Sentry/GlobalSessionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,20 @@ 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))
bitsandfoxes marked this conversation as resolved.
Show resolved Hide resolved
{
_options.LogInfo("Failed to create persistent directory for session file.");
bitsandfoxes marked this conversation as resolved.
Show resolved Hide resolved
return;
}

_options.LogDebug("Created persistence directory for session file");
bitsandfoxes marked this conversation as resolved.
Show resolved Hide resolved

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 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.");
bitsandfoxes marked this conversation as resolved.
Show resolved Hide resolved
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.");
bitsandfoxes marked this conversation as resolved.
Show resolved Hide resolved
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
95 changes: 79 additions & 16 deletions src/Sentry/Internal/FileSystem.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
using Sentry.Extensibility;

namespace Sentry.Internal;

internal class FileSystem : IFileSystem
{
public static IFileSystem Instance { get; } = new FileSystem();
public static IFileSystem Instance { get; } = new FileSystem(SentrySdk.CurrentOptions);

private readonly SentryOptions? _options;

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

public IEnumerable<string> EnumerateFiles(string path) => Directory.EnumerateFiles(path);
Expand All @@ -16,38 +21,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?.LogWarning("Skipping creating directory. Writing to file system has been explicitly disabled.");
return false;
}

Directory.CreateDirectory(path);
return true;
}

public bool DeleteDirectory(string path, bool recursive = false)
{
if (_options?.DisableFileWrite is false)
{
_options?.LogWarning("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 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?.LogWarning("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?.LogWarning("Skipping deleting file. Writing to file system has been explicitly disabled.");
bitsandfoxes marked this conversation as resolved.
Show resolved Hide resolved
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?.LogWarning("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?.LogWarning("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);
}
19 changes: 17 additions & 2 deletions src/Sentry/Internal/InstallationIdHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,24 @@ 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);

Expand All @@ -68,7 +79,11 @@ internal class InstallationIdHelper(SentryOptions options)

// 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
18 changes: 17 additions & 1 deletion src/Sentry/Internal/SdkComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,29 @@ public SdkComposer(SentryOptions options)

private ITransport CreateTransport()
{
_options.LogDebug("Creating transport.");

// Start from either the transport given on options, or create a new HTTP transport.
var transport = _options.Transport ?? CreateHttpTransport();

// When a cache directory path is given, wrap the transport in a caching transport.
if (!string.IsNullOrWhiteSpace(_options.CacheDirectoryPath))
{
transport = CachingTransport.Create(transport, _options);
_options.LogDebug("Cache directory path is specified.");

if (_options.DisableFileWrite)
{
_options.LogInfo("File writing is disabled, Skipping caching transport creation.");
}
else
{
_options.LogDebug("File writing is enabled, wrapping transport in caching transport.");
transport = CachingTransport.Create(transport, _options);
}
}
else
{
_options.LogDebug("No cache directory path specified. Skipping caching transport creation.");
}

// Wrap the transport with the Spotlight one that double sends the envelope: Sentry + Spotlight
Expand Down
5 changes: 5 additions & 0 deletions src/Sentry/SentryOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,11 @@ public IList<SubstringOrRegexPattern> FailedRequestTargets
/// </summary>
internal IFileSystem FileSystem { get; set; } = Internal.FileSystem.Instance;

/// <summary>
/// Allows to disable the SDKs writing to disk operations
/// </summary>
public bool DisableFileWrite { get; set; }

/// <summary>
/// If set to a positive value, Sentry will attempt to flush existing local event cache when initializing.
/// Set to <see cref="TimeSpan.Zero"/> to disable this feature.
Expand Down
32 changes: 27 additions & 5 deletions test/Sentry.Testing/FakeFileSystem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,23 @@ public IEnumerable<string> EnumerateFiles(string path, string searchPattern) =>
public IEnumerable<string> EnumerateFiles(string path, string searchPattern, SearchOption searchOption) =>
_mockFileSystem.Directory.EnumerateFiles(path, searchPattern, searchOption);

public void CreateDirectory(string path) => _mockFileSystem.Directory.CreateDirectory(path);
public bool CreateDirectory(string path)
{
_mockFileSystem.Directory.CreateDirectory(path);
return true;
}

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

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

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

public void MoveFile(string sourceFileName, string destFileName, bool overwrite = false)
public bool MoveFile(string sourceFileName, string destFileName, bool overwrite = false)
{
#if NET5_0_OR_GREATER
_mockFileSystem.File.Move(sourceFileName, destFileName, overwrite);
Expand All @@ -39,9 +46,15 @@ public void MoveFile(string sourceFileName, string destFileName, bool overwrite
_mockFileSystem.File.Move(sourceFileName, destFileName);
}
#endif

return true;
}

public void DeleteFile(string path) => _mockFileSystem.File.Delete(path);
public bool DeleteFile(string path)
{
_mockFileSystem.File.Delete(path);
return true;
}

public DateTimeOffset GetFileCreationTime(string path) =>
_mockFileSystem.FileInfo.New(path).CreationTimeUtc;
Expand All @@ -50,5 +63,14 @@ public DateTimeOffset GetFileCreationTime(string path) =>

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

public Stream CreateFileForWriting(string path) => _mockFileSystem.File.Create(path);
public Stream CreateFileForWriting(string path)
{
return _mockFileSystem.File.Create(path);
}

public bool WriteAllTextToFile(string path, string contents)
{
_mockFileSystem.File.WriteAllText(path, contents);
return true;
}
}
Loading