Skip to content

Commit b949dca

Browse files
authored
[dotnet] Possibility to output internal log messages to file (#13249)
* INitial implementation * With context * Update HttpCommandExecutor.cs * Nullable handlers * Don't capture logger * Log message issuer * Simplify things * Continue * Update nunit adapter to work with dotnet 7 and be more friendly with windows * Rename to LogEventLevel * Typo * Introduce LogContextManager * Typo again * Rename to Timestamp * Make ILogger as static field * Support hierarchical contexts * Rename to EmitMessage * Do not emit message to parent context * Deep copy of loggers and handlers per context * Make fields private * Static works with current log context * Create context with minimum level * Set minimum level for context * Rename to WithHandler * Set minimum level per issuer * Simplify getting internal logger * Use DateTimeOffset * Docs for log event level * Docs for ILogger * Docs for Logger * Docs for others * Make ILogger interface as internal * Revert "Make ILogger interface as internal" This reverts commit 3cf6e48. * First test * Update LogTest.cs * Fix build error * Info minimum log level by default * Remove unnecessary log call in ChromeDriver * Adjust log levels in console output * Make it length fixed * Make webdriver assembly internals visible to tests * Make ILogger hidden from user * More tests for log context * Init * Rename back to AddHandler * Make format script happy? * Make format script happy? * Rename back to SetLevel * Console handler by default * Output logs to stderr * New api to mange log handlers * Use logging in DriverFactory * Revert "Use logging in DriverFactory" This reverts commit e3255a6. * Verbose driver creation in tests * Search driver type in loaded assemblies * Decalare internals visible to in csproj to not conflict with bazel * Clean specific assembly name for driver type * Old school using to make bazel happy * Fix targeting packs for test targets * It works * Small clean up * Lock in ctor * Dispose at process exit * Remove redundant Clone for log handlers * Dispose log handlers when context finishes * Lock writing to the disk globally * Fix new list of log handlers for context * Don't lock in ctor * Add docs * Change format of datetime in file log * Thread safe disposing * Add finilizer * Docs for finilizer * Add tests * Recreating missing dirs
1 parent 1bcb948 commit b949dca

File tree

5 files changed

+155
-23
lines changed

5 files changed

+155
-23
lines changed

dotnet/src/webdriver/Internal/Logging/ConsoleLogHandler.cs

-9
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,5 @@ public void Handle(LogEvent logEvent)
3636
{
3737
Console.Error.WriteLine($"{logEvent.Timestamp:HH:mm:ss.fff} {_levels[(int)logEvent.Level]} {logEvent.IssuedBy.Name}: {logEvent.Message}");
3838
}
39-
40-
/// <summary>
41-
/// Creates a new instance of the <see cref="ConsoleLogHandler"/> class.
42-
/// </summary>
43-
/// <returns>A new instance of the <see cref="ConsoleLogHandler"/> class.</returns>
44-
public ILogHandler Clone()
45-
{
46-
return this;
47-
}
4839
}
4940
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
using System;
2+
using System.IO;
3+
4+
namespace OpenQA.Selenium.Internal.Logging
5+
{
6+
/// <summary>
7+
/// Represents a log handler that writes log events to a file.
8+
/// </summary>
9+
public class FileLogHandler : ILogHandler, IDisposable
10+
{
11+
// performance trick to avoid expensive Enum.ToString() with fixed length
12+
private static readonly string[] _levels = { "TRACE", "DEBUG", " INFO", " WARN", "ERROR" };
13+
14+
private FileStream _fileStream;
15+
private StreamWriter _streamWriter;
16+
17+
private readonly object _lockObj = new object();
18+
private bool _isDisposed;
19+
20+
/// <summary>
21+
/// Initializes a new instance of the <see cref="FileLogHandler"/> class with the specified file path.
22+
/// </summary>
23+
/// <param name="path">The path of the log file.</param>
24+
public FileLogHandler(string path)
25+
{
26+
if (string.IsNullOrEmpty(path)) throw new ArgumentException("File log path cannot be null or empty.", nameof(path));
27+
28+
var directory = Path.GetDirectoryName(path);
29+
if (!string.IsNullOrWhiteSpace(directory) && !Directory.Exists(directory))
30+
{
31+
Directory.CreateDirectory(directory);
32+
}
33+
34+
_fileStream = File.Open(path, FileMode.OpenOrCreate, FileAccess.Write, FileShare.Read);
35+
_fileStream.Seek(0, SeekOrigin.End);
36+
_streamWriter = new StreamWriter(_fileStream, System.Text.Encoding.UTF8)
37+
{
38+
AutoFlush = true
39+
};
40+
}
41+
42+
/// <summary>
43+
/// Handles a log event by writing it to the log file.
44+
/// </summary>
45+
/// <param name="logEvent">The log event to handle.</param>
46+
public void Handle(LogEvent logEvent)
47+
{
48+
lock (_lockObj)
49+
{
50+
_streamWriter.WriteLine($"{logEvent.Timestamp:r} {_levels[(int)logEvent.Level]} {logEvent.IssuedBy.Name}: {logEvent.Message}");
51+
}
52+
}
53+
54+
/// <summary>
55+
/// Disposes the file log handler and releases any resources used.
56+
/// </summary>
57+
public void Dispose()
58+
{
59+
Dispose(true);
60+
GC.SuppressFinalize(this);
61+
}
62+
63+
/// <summary>
64+
/// Finalizes the file log handler instance.
65+
/// </summary>
66+
~FileLogHandler()
67+
{
68+
Dispose(false);
69+
}
70+
71+
/// <summary>
72+
/// Disposes the file log handler and releases any resources used.
73+
/// </summary>
74+
/// <param name="disposing">A flag indicating whether to dispose managed resources.</param>
75+
protected virtual void Dispose(bool disposing)
76+
{
77+
lock (_lockObj)
78+
{
79+
if (!_isDisposed)
80+
{
81+
if (disposing)
82+
{
83+
_streamWriter?.Dispose();
84+
_streamWriter = null;
85+
_fileStream?.Dispose();
86+
_fileStream = null;
87+
}
88+
89+
_isDisposed = true;
90+
}
91+
}
92+
}
93+
}
94+
}

dotnet/src/webdriver/Internal/Logging/ILogHandler.cs

-6
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,5 @@ public interface ILogHandler
2828
/// </summary>
2929
/// <param name="logEvent">The log event to handle.</param>
3030
void Handle(LogEvent logEvent);
31-
32-
/// <summary>
33-
/// Creates a clone of the log handler.
34-
/// </summary>
35-
/// <returns>A clone of the log handler.</returns>
36-
ILogHandler Clone();
3731
}
3832
}

dotnet/src/webdriver/Internal/Logging/LogContext.cs

+18-8
Original file line numberDiff line numberDiff line change
@@ -60,19 +60,16 @@ public ILogContext CreateContext(LogEventLevel minimumLevel)
6060
loggers = new ConcurrentDictionary<Type, ILogger>(_loggers.Select(l => new KeyValuePair<Type, ILogger>(l.Key, new Logger(l.Value.Issuer, minimumLevel))));
6161
}
6262

63-
IList<ILogHandler> handlers = null;
63+
var context = new LogContext(minimumLevel, this, loggers, null);
6464

6565
if (Handlers != null)
6666
{
67-
handlers = new List<ILogHandler>(Handlers.Select(h => h.Clone()));
68-
}
69-
else
70-
{
71-
handlers = new List<ILogHandler>();
67+
foreach (var handler in Handlers)
68+
{
69+
context.Handlers.Add(handler);
70+
}
7271
}
7372

74-
var context = new LogContext(minimumLevel, this, loggers, Handlers);
75-
7673
Log.CurrentContext = context;
7774

7875
return context;
@@ -137,6 +134,19 @@ public ILogContext SetLevel(Type issuer, LogEventLevel level)
137134

138135
public void Dispose()
139136
{
137+
// Dispose log handlers associated with this context
138+
// if they are hot handled by parent context
139+
if (Handlers != null && _parentLogContext != null && _parentLogContext.Handlers != null)
140+
{
141+
foreach (var logHandler in Handlers)
142+
{
143+
if (!_parentLogContext.Handlers.Contains(logHandler))
144+
{
145+
(logHandler as IDisposable)?.Dispose();
146+
}
147+
}
148+
}
149+
140150
Log.CurrentContext = _parentLogContext;
141151
}
142152
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using NUnit.Framework;
2+
using System;
3+
using System.IO;
4+
5+
namespace OpenQA.Selenium.Internal.Logging
6+
{
7+
public class FileLogHandlerTest
8+
{
9+
[Test]
10+
[TestCase(null)]
11+
[TestCase("")]
12+
public void ShouldNotAcceptIncorrectPath(string path)
13+
{
14+
var act = () => new FileLogHandler(path);
15+
16+
Assert.That(act, Throws.ArgumentException);
17+
}
18+
19+
[Test]
20+
public void ShouldHandleLogEvent()
21+
{
22+
var tempFile = Path.GetTempFileName();
23+
24+
try
25+
{
26+
using (var fileLogHandler = new FileLogHandler(tempFile))
27+
{
28+
fileLogHandler.Handle(new LogEvent(typeof(FileLogHandlerTest), DateTimeOffset.Now, LogEventLevel.Info, "test message"));
29+
}
30+
31+
Assert.That(File.ReadAllText(tempFile), Does.Contain("test message"));
32+
}
33+
catch (Exception)
34+
{
35+
throw;
36+
}
37+
finally
38+
{
39+
File.Delete(tempFile);
40+
}
41+
}
42+
}
43+
}

0 commit comments

Comments
 (0)