Skip to content

Commit

Permalink
add synchronization to TextWriterLogComponent + several minor tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
dninemfive committed Jul 20, 2024
1 parent 45958be commit 499b753
Show file tree
Hide file tree
Showing 7 changed files with 190 additions and 72 deletions.
103 changes: 103 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
[*.cs]

# IDE0290: Use primary constructor
# Primary constructors interfere with documentation clarity even though they're cool
csharp_style_prefer_primary_constructors = false:suggestion
csharp_indent_labels = no_change
csharp_using_directive_placement = outside_namespace:silent
csharp_prefer_simple_using_statement = true:suggestion
csharp_prefer_braces = true:silent
csharp_style_namespace_declarations = file_scoped:suggestion
csharp_style_prefer_method_group_conversion = true:suggestion
csharp_style_prefer_top_level_statements = false:warning
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_lambdas = true:suggestion
csharp_style_expression_bodied_local_functions = false:silent

[*.{cs,vb}]
dotnet_style_operator_placement_when_wrapping = beginning_of_line
tab_width = 4
indent_size = 4
end_of_line = crlf
dotnet_style_coalesce_expression = true:suggestion
dotnet_style_null_propagation = true:suggestion
dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
dotnet_style_prefer_auto_properties = true:silent
dotnet_style_object_initializer = true:suggestion
dotnet_style_prefer_collection_expression = true:suggestion
dotnet_style_collection_initializer = true:suggestion
dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
[*.cs]
#### Naming styles ####

# Naming rules

dotnet_naming_rule.private_or_internal_field_should_be_begins_with_underscore.severity = suggestion
dotnet_naming_rule.private_or_internal_field_should_be_begins_with_underscore.symbols = private_or_internal_field
dotnet_naming_rule.private_or_internal_field_should_be_begins_with_underscore.style = begins_with_underscore

# Symbol specifications

dotnet_naming_symbols.private_or_internal_field.applicable_kinds = field
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected
dotnet_naming_symbols.private_or_internal_field.required_modifiers =

# Naming styles

dotnet_naming_style.begins_with_underscore.required_prefix = _
dotnet_naming_style.begins_with_underscore.required_suffix =
dotnet_naming_style.begins_with_underscore.word_separator =
dotnet_naming_style.begins_with_underscore.capitalization = camel_case

[*.{cs,vb}]
#### Naming styles ####

# Naming rules

dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion
dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface
dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i

dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.types_should_be_pascal_case.symbols = types
dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case

dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members
dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case

# Symbol specifications

dotnet_naming_symbols.interface.applicable_kinds = interface
dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.interface.required_modifiers =

dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum
dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.types.required_modifiers =

dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
dotnet_naming_symbols.non_field_members.required_modifiers =

# Naming styles

dotnet_naming_style.begins_with_i.required_prefix = I
dotnet_naming_style.begins_with_i.required_suffix =
dotnet_naming_style.begins_with_i.word_separator =
dotnet_naming_style.begins_with_i.capitalization = pascal_case

dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case

dotnet_naming_style.pascal_case.required_prefix =
dotnet_naming_style.pascal_case.required_suffix =
dotnet_naming_style.pascal_case.word_separator =
dotnet_naming_style.pascal_case.capitalization = pascal_case
16 changes: 10 additions & 6 deletions logging/Log.Components.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,16 +22,18 @@ public static ILogComponent WriteTextTo(string filePath)
/// An <see cref="ILogComponent"/> which writes to the specified stream.
/// </summary>
/// <param name="stream">The stream to write to.</param>
public static ILogComponent FromStream(Stream stream)
=> new StreamLogComponent(stream);
/// <param name="synchronize"><inheritdoc cref="TextWriterLogComponent(TextWriter, bool)" path="/param[@name='synchronize']"/></param>
public static ILogComponent FromStream(Stream stream, bool synchronize = false)
=> new TextWriterLogComponent(stream, synchronize);
/// <summary>
/// An <see cref="ILogComponent"/> which uses the specified <see cref="StreamWriter"/>.
/// </summary>
/// <param name="streamWriter">
/// The <see cref="StreamWriter"/> which will handle writing for this log component.
/// </param>
public static ILogComponent FromStreamWriter(StreamWriter streamWriter)
=> new StreamLogComponent(streamWriter);
/// <param name="synchronize"><inheritdoc cref="TextWriterLogComponent(TextWriter, bool)" path="/param[@name='synchronize']"/></param>
public static ILogComponent FromStreamWriter(StreamWriter streamWriter, bool synchronize = false)
=> new TextWriterLogComponent(streamWriter, synchronize);
/// <summary>
/// Creates a new <see cref="ILogComponent"/> which writes to the specified file using a stream.
/// </summary>
Expand All @@ -42,19 +44,21 @@ public static ILogComponent FromStreamWriter(StreamWriter streamWriter)
/// bit set, i.e. be either <c>Write</c> or <c>ReadWrite</c>.
/// </param>
/// <param name="share">The access other threads will have to the file in question.</param>
/// <param name="synchronize"><inheritdoc cref="TextWriterLogComponent(TextWriter, bool)" path="/param[@name='synchronize']"/></param>
/// <remarks>
/// <b>NOTE:</b> by default, this will <b>overwrite</b> the file at the specified <paramref
/// name="path"/>! You can change this by setting a different <paramref name="mode"/>.
/// </remarks>
public static ILogComponent OpenFile(string path,
FileMode mode = FileMode.Create,
FileAccess access = FileAccess.ReadWrite,
FileShare share = FileShare.ReadWrite)
FileShare share = FileShare.ReadWrite,
bool synchronize = false)
{
if ((access & FileAccess.Write) != FileAccess.Write)
throw new Exception($"Cannot use a FileStream for writing if it doesn't have the Write permission!");
FileStream fs = File.Open(path, mode, access, share);
return new StreamLogComponent(fs);
return new TextWriterLogComponent(fs, synchronize);
}
}
}
18 changes: 12 additions & 6 deletions logging/Log.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ public partial class Log(params ILogComponent[] components) : IDisposable, IAsyn
private readonly IEnumerable<ILogComponent> _components = components;
private bool _disposed = false;
/// <summary>
/// Produces a <c>Log</c> which writes to the standard <see cref="Console"/> and to a
/// Creates a <see cref="Log"/> which writes to the standard <see cref="Console"/> and to a
/// newly-created file with the specified <paramref name="fileName"/>.
/// </summary>
/// <param name="fileName">The path to the file the <c>Log</c> will write to.</param>
/// <returns>A <c>Log</c> as described above.</returns>
/// <remarks><b>NOTE:</b> this will <b>overwrite</b> any existing file at the specified path!</remarks>
public static Log ConsoleAndFile(string fileName)
=> new(Components.Console, Components.OpenFile(fileName));
/// <param name="synchronize"><inheritdoc cref="TextWriterLogComponent(TextWriter, bool)" path="/param[@name='synchronize']"/></param>
public static Log ConsoleAndFile(string fileName, bool synchronize = false)
=> new(Components.Console, Components.OpenFile(fileName, synchronize: synchronize));
/// <summary>
/// Asynchronously writes to all of this <c>Log</c>'s components.
/// Asynchronously writes to all of this <see cref="Log"/>'s components.
/// </summary>
/// <param name="obj">The item to write.</param>
/// <returns>A <see cref="Task"/> indicating completion.</returns>
Expand All @@ -27,7 +27,7 @@ public async Task Write(object? obj)
await component.Write(obj);
}
/// <summary>
/// Asynchronously writes a line to all of this <c>Log</c>'s components.
/// Asynchronously writes a line to all of this <see cref="Log"/>'s components.
/// </summary>
/// <param name="obj">The item to write.</param>
/// <returns>A <see cref="Task"/> indicating completion.</returns>
Expand All @@ -42,9 +42,12 @@ public async Task WriteLine(object? obj)
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
foreach (ILogComponent component in _components)
if (component is IDisposable disposable)
disposable.Dispose();
}
GC.SuppressFinalize(this);
}
/// <summary>
Expand All @@ -53,6 +56,8 @@ public void Dispose()
public async ValueTask DisposeAsync()
{
if (!_disposed)
{
_disposed = true;
foreach (ILogComponent component in _components)
{
if (component is IAsyncDisposable asyncDisposable)
Expand All @@ -64,6 +69,7 @@ public async ValueTask DisposeAsync()
disposable.Dispose();
}
}
}
GC.SuppressFinalize(this);
}
}
60 changes: 0 additions & 60 deletions logging/StreamLogComponent.cs

This file was deleted.

56 changes: 56 additions & 0 deletions logging/TextWriterLogComponent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.IO;

namespace d9.utl;
/// <summary>
/// An <see cref="ILogComponent"/> which wraps a <see cref="TextWriter"/> and allows it to be disposed.
/// </summary>
public class TextWriterLogComponent
: ILogComponent, IDisposable, IAsyncDisposable
{
private readonly TextWriter _writer;
private bool _disposed = false;
/// <summary>
/// Creates a <see cref="TextWriterLogComponent"/> from the specified <paramref name="textWriter"/>.
/// </summary>
/// <param name="textWriter">The <see cref="TextWriter"/> from which to create the component.</param>
/// <param name="synchronize">If <see langword="true"/>, the underlying writer will be a <see href="https://learn.microsoft.com/en-us/dotnet/api/system.io.textwriter.synchronized">synchronized</see> wrapper and therefore thread-safe.</param>
public TextWriterLogComponent(TextWriter textWriter, bool synchronize = false)
=> _writer = synchronize? TextWriter.Synchronized(textWriter) : textWriter;
/// <summary>
/// Creates a <see cref="TextWriterLogComponent"/> from the specified <paramref name="stream"/> by creating a <see cref="StreamWriter"/> which writes to it.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> from which to create the component.</param>
/// <remarks><b>NOTE:</b> disposing this component will dispose the <see cref="StreamWriter"/> it creates, and therefore the <see cref="Stream"/> you pass in.</remarks>
/// <param name="synchronize"><inheritdoc cref="TextWriterLogComponent(TextWriter, bool)" path="/param[@name='synchronize']"/></param>
public TextWriterLogComponent(Stream stream, bool synchronize = false) : this(new StreamWriter(stream), synchronize) { }
/// <inheritdoc cref="ILogComponent.Write(object?)"/>
public Task Write(object? obj)
=> _writer.WriteAsync($"{obj}");
/// <inheritdoc cref="ILogComponent.WriteLine(object?)"/>
public Task WriteLine(object? obj)
=> _writer.WriteLineAsync($"{obj}");
/// <summary>
/// Implements <see cref="IDisposable"/>.
/// </summary>
public void Dispose()
{
if (!_disposed)
{
_disposed = true;
_writer.Dispose();
}
GC.SuppressFinalize(this);
}
/// <summary>
/// Implements <see cref="IAsyncDisposable"/>.
/// </summary>
public async ValueTask DisposeAsync()
{
if (!_disposed)
{
_disposed = true;
await _writer.DisposeAsync();
}
GC.SuppressFinalize(this);
}
}
4 changes: 4 additions & 0 deletions test/Tests_Log.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace d9.utl;
internal class Tests_Log
{
}
5 changes: 5 additions & 0 deletions utl.sln
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "utl", "utl.csproj", "{2131B
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "utl-unit-tests", "test\utl-unit-tests.csproj", "{9EC12F4C-9230-4DA9-B3EE-2AB3EDC28D3B}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{CF31EFC8-6C34-4222-AC8F-897C3AAFABF3}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down

0 comments on commit 499b753

Please sign in to comment.