Skip to content

Commit d166500

Browse files
author
BRUNER Patrick
committed
added ReadOnlyMemory<char>
1 parent 3e7f15c commit d166500

8 files changed

Lines changed: 203 additions & 101 deletions

File tree

src/ColumnizerLib/ILogLine.cs

Lines changed: 0 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -9,72 +9,4 @@ public interface ILogLine : ITextValue
99
int LineNumber { get; }
1010

1111
#endregion
12-
}
13-
14-
/// <summary>
15-
/// Represents a single log line, including its full text and line number.
16-
/// </summary>
17-
/// <remarks>
18-
/// <para>
19-
/// <b>Purpose:</b> <br/>
20-
/// The <c>LogLine</c> struct encapsulates the content and line number of a log entry. It is used throughout the
21-
/// columnizer and log processing infrastructure to provide a strongly-typed, immutable representation of a log line.
22-
/// </para>
23-
/// <para>
24-
/// <b>Usage:</b> <br/>
25-
/// This struct implements the <see cref="ILogLine"/> interface, allowing it to be used wherever an <c>ILogLine</c>
26-
/// is expected. It provides value semantics and is intended to be lightweight and efficiently passed by value.
27-
/// </para>
28-
/// <para>
29-
/// <b>Relationship to ILogLine:</b> <br/>
30-
/// <c>LogLine</c> is a concrete, immutable implementation of the <see cref="ILogLine"/> interface, providing
31-
/// properties for the full line text and its line number.
32-
/// </para>
33-
/// <para>
34-
/// <b>Why struct instead of record:</b> <br/>
35-
/// A <c>struct</c> is preferred over a <c>record</c> here to avoid heap allocations and to provide value-type
36-
/// semantics, which are beneficial for performance when processing large numbers of log lines. The struct is
37-
/// immutable (readonly), ensuring thread safety and predictability. The previous <c>record</c> implementation
38-
/// was replaced to better align with these performance and semantic requirements.
39-
/// </para>
40-
/// </remarks>
41-
public class LogLine : ILogLineMemory
42-
{
43-
private readonly ReadOnlyMemory<char> _lineMemory;
44-
45-
public LogLine (string fullLine, int lineNumber)
46-
{
47-
FullLine = fullLine;
48-
LineNumber = lineNumber;
49-
}
50-
51-
public LogLine (ReadOnlyMemory<char> lineMemory, int lineNumber) : this(lineMemory.ToString(), lineNumber)
52-
{
53-
FullLineMemory = lineMemory;
54-
LineNumber = lineNumber;
55-
}
56-
57-
public string FullLine => field ??= _lineMemory.ToString();
58-
59-
public int LineNumber { get; }
60-
61-
public string Text => FullLine;
62-
63-
public ReadOnlyMemory<char> FullLineMemory { get; }
64-
65-
public override bool Equals (object obj)
66-
{
67-
return obj is LogLine other &&
68-
FullLine == other.FullLine &&
69-
LineNumber == other.LineNumber;
70-
}
71-
72-
public override int GetHashCode ()
73-
{
74-
return HashCode.Combine(FullLine, LineNumber);
75-
}
76-
77-
public static bool operator == (LogLine left, LogLine right) => left.Equals(right);
78-
79-
public static bool operator != (LogLine left, LogLine right) => !(left == right);
8012
}

src/ColumnizerLib/IPreProcessColumnizer.cs

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Text;
4-
51
namespace ColumnizerLib;
62

73
/// <summary>
@@ -20,7 +16,7 @@ namespace ColumnizerLib;
2016
/// <para>
2117
/// Note that the <see cref="PreProcessLine"/>
2218
/// method is only used when loading a line from disk. Because of internal buffering a log line may
23-
/// be read only once or multiple times. You have to ensure that the behaviour is consistent
19+
/// be read only once or multiple times. You have to ensure that the behaviour is consistent
2420
/// for every call to <see cref="PreProcessLine"/> for a specific line. That's especially true
2521
/// when dropping lines. Dropping a line changes the line count seen by LogExpert. That has implications
2622
/// for things like bookmarks etc.
@@ -52,13 +48,14 @@ public interface IPreProcessColumnizer
5248
/// Detecting the first line in the file is only possible by checking the realLineNum parameter.
5349
/// </para>
5450
/// <para>
55-
/// Remember that the <see cref="PreProcessLine"/> method is called in an early state
56-
/// when loading the file. So the file isn't loaded completely and the internal state
51+
/// Remember that the <see cref="PreProcessLine"/> method is called in an early state
52+
/// when loading the file. So the file isn't loaded completely and the internal state
5753
/// of LogExpert isn't complete. You cannot make any assumptions about file size or other
5854
/// things. The given parameters are the only 'stateful' informations you can rely on.
5955
/// </para>
6056
/// </remarks>
61-
string PreProcessLine(string logLine, int lineNum, int realLineNum);
57+
string PreProcessLine (string logLine, int lineNum, int realLineNum);
6258

6359
#endregion
64-
}
60+
}
61+
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System.Buffers;
2+
3+
namespace ColumnizerLib;
4+
5+
/// <summary>
6+
/// <para>
7+
/// Implement this interface in your columnizer if you want to pre-process every line
8+
/// directly when it's loaded from file system.</para>
9+
/// <para>
10+
/// You can also use this to drop lines.
11+
/// </para>
12+
/// </summary>
13+
/// <remarks>
14+
/// <para>
15+
/// By implementing this interface with your Columnizer you get the ability to modify the
16+
/// content of a log file right before it will be seen by LogExpert.
17+
/// </para>
18+
/// <para>
19+
/// Note that the <see cref="PreProcessLine"/>
20+
/// method is only used when loading a line from disk. Because of internal buffering a log line may
21+
/// be read only once or multiple times. You have to ensure that the behaviour is consistent
22+
/// for every call to <see cref="PreProcessLine"/> for a specific line. That's especially true
23+
/// when dropping lines. Dropping a line changes the line count seen by LogExpert. That has implications
24+
/// for things like bookmarks etc.
25+
/// </para>
26+
/// </remarks>
27+
public interface IPreProcessColumnizerMemory : IPreProcessColumnizer
28+
{
29+
#region Public methods
30+
31+
/// <summary>
32+
/// Memory-optimized preprocessing method that returns <see cref="ReadOnlyMemory{T}"/> to avoid string allocations.
33+
/// </summary>
34+
/// <param name="logLine">Line content as ReadOnlyMemory</param>
35+
/// <param name="lineNum">Line number as seen by LogExpert</param>
36+
/// <param name="realLineNum">Actual line number in the file</param>
37+
/// <returns>The changed content as <see cref="ReadOnlyMemory{T}"/>, the original memory if unchanged, or <see cref="ReadOnlyMemory{T}"/>.Empty to drop the line </returns>
38+
/// <remarks>
39+
/// <para>
40+
/// Return values:
41+
/// - Original memory: Line unchanged, no allocation
42+
/// - <see cref="ReadOnlyMemory{T}"/>.Empty: Drop the line
43+
/// - New memory: Modified line content
44+
/// </para>
45+
/// <para>
46+
/// When creating modified content, consider using <see cref="ArrayPool{T}"/> to reduce allocations
47+
/// for temporary buffers, but the returned memory must be owned (not pooled).
48+
/// </para>
49+
/// </remarks>
50+
///
51+
ReadOnlyMemory<char> PreProcessLine (ReadOnlyMemory<char> logLine, int lineNum, int realLineNum);
52+
53+
#endregion
54+
}
55+

src/ColumnizerLib/LogLine.cs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
namespace ColumnizerLib;
2+
3+
/// <summary>
4+
/// Represents a single log line, including its full text and line number.
5+
/// </summary>
6+
/// <remarks>
7+
/// <para>
8+
/// <b>Purpose:</b> <br/>
9+
/// The <c>LogLine</c> struct encapsulates the content and line number of a log entry. It is used throughout the
10+
/// columnizer and log processing infrastructure to provide a strongly-typed, immutable representation of a log line.
11+
/// </para>
12+
/// <para>
13+
/// <b>Usage:</b> <br/>
14+
/// This struct implements the <see cref="ILogLine"/> interface, allowing it to be used wherever an <c>ILogLine</c>
15+
/// is expected. It provides value semantics and is intended to be lightweight and efficiently passed by value.
16+
/// </para>
17+
/// <para>
18+
/// <b>Relationship to ILogLine:</b> <br/>
19+
/// <c>LogLine</c> is a concrete, immutable implementation of the <see cref="ILogLine"/> interface, providing
20+
/// properties for the full line text and its line number.
21+
/// </para>
22+
/// <para>
23+
/// <b>Why struct instead of record:</b> <br/>
24+
/// A <c>struct</c> is preferred over a <c>record</c> here to avoid heap allocations and to provide value-type
25+
/// semantics, which are beneficial for performance when processing large numbers of log lines. The struct is
26+
/// immutable (readonly), ensuring thread safety and predictability. The previous <c>record</c> implementation
27+
/// was replaced to better align with these performance and semantic requirements.
28+
/// </para>
29+
/// </remarks>
30+
public class LogLine : ILogLineMemory
31+
{
32+
public string FullLine { get; }
33+
34+
public int LineNumber { get; }
35+
36+
public string Text { get; }
37+
38+
public ReadOnlyMemory<char> FullLineMemory { get; }
39+
40+
public ReadOnlyMemory<char> TextMemory { get; }
41+
42+
public LogLine (string fullLine, int lineNumber)
43+
{
44+
FullLine = fullLine;
45+
LineNumber = lineNumber;
46+
FullLineMemory = fullLine.AsMemory();
47+
TextMemory = fullLine.AsMemory();
48+
}
49+
50+
public LogLine (ReadOnlyMemory<char> fullLine, int lineNumber)
51+
{
52+
FullLine = fullLine.ToString();
53+
LineNumber = lineNumber;
54+
FullLineMemory = fullLine;
55+
TextMemory = fullLine;
56+
}
57+
}

src/CsvColumnizer/CsvColumnizer.cs

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ namespace CsvColumnizer;
1717
/// The IPreProcessColumnizer is implemented to read field names from the very first line of the file. Then
1818
/// the line is dropped. So it's not seen by LogExpert. The field names will be used as column names.
1919
/// </summary>
20-
public class CsvColumnizer : ILogLineColumnizer, IInitColumnizer, IColumnizerConfigurator, IPreProcessColumnizer, IColumnizerPriority
20+
public class CsvColumnizer : ILogLineColumnizer, IInitColumnizer, IColumnizerConfigurator, IPreProcessColumnizerMemory, IColumnizerPriority
2121
{
2222
#region Fields
2323

@@ -67,6 +67,38 @@ public string PreProcessLine (string logLine, int lineNum, int realLineNum)
6767
: logLine;
6868
}
6969

70+
public ReadOnlyMemory<char> PreProcessLine (ReadOnlyMemory<char> logLine, int lineNum, int realLineNum)
71+
{
72+
if (realLineNum == 0)
73+
{
74+
// store for later field names and field count retrieval
75+
_firstLine = new CsvLogLine(logLine, 0);
76+
77+
if (_config.MinColumns > 0)
78+
{
79+
using CsvReader csv = new(new StringReader(logLine.ToString()), _config.ReaderConfiguration);
80+
if (csv.Parser.Count < _config.MinColumns)
81+
{
82+
// on invalid CSV don't hide the first line from LogExpert, since the file will be displayed in plain mode
83+
_isValidCsv = false;
84+
return logLine;
85+
}
86+
}
87+
88+
_isValidCsv = true;
89+
}
90+
91+
if (_config.HasFieldNames && realLineNum == 0)
92+
{
93+
return null; // hide from LogExpert
94+
}
95+
96+
return _config.CommentChar != ' ' &&
97+
logLine.Span.StartsWith("" + _config.CommentChar, StringComparison.OrdinalIgnoreCase)
98+
? null
99+
: logLine;
100+
}
101+
70102
public string GetName ()
71103
{
72104
return "CSV Columnizer";

src/CsvColumnizer/CsvLogLine.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
1+
12
using ColumnizerLib;
23

34
namespace CsvColumnizer;
45

5-
public class CsvLogLine(string fullLine, int lineNumber) : ILogLine
6+
public class CsvLogLine (string fullLine, int lineNumber) : ILogLine, ILogLineMemory
67
{
78
#region Properties
89

910
public string FullLine { get; set; } = fullLine;
1011

11-
public int LineNumber { get; set; } = lineNumber;
12+
public int LineNumberMemory { get; set; } = lineNumber;
1213

1314
string ITextValue.Text => FullLine;
1415

16+
public ReadOnlyMemory<char> FullLineMemory { get; }
17+
18+
public ReadOnlyMemory<char> TextMemory { get; }
19+
1520
#endregion
21+
22+
public CsvLogLine (ReadOnlyMemory<char> fullLine, int lineNumber) : this(fullLine.ToString(), lineNumber)
23+
{
24+
FullLine = fullLine.ToString();
25+
LineNumberMemory = lineNumber;
26+
FullLineMemory = fullLine;
27+
TextMemory = fullLine;
28+
}
1629
}

0 commit comments

Comments
 (0)