Allow viewing the log file for SelfDiagnostics for .NET Framework (#1693)
* Fix different FileShare behavior in MemoryMappedFile between .NET Framework and .NET Core for SelfDiagnostics
* Create Stress Test to test self diagnostics log file's file share status
(cherry picked from commit 9fe60757c4254b67c1d166fc5f42eb795d424bf9)
* Revert "Create Stress Test to test self diagnostics log file's file share status"
This reverts commit 1007ffef42.
* Dispose underlyingFileStreamForMemoryMappedFile after memoryMappedFile is disposed.
* Add unit test for validating that the log file can be opened as read-only by others while being used by SelfDiagnosticsModule
* Remove en-us in links. Clean up config file. Add change log.
* minor: improve wording
* Fix error SA1501: Statement should not be on a single line
This commit is contained in:
parent
4f39a58048
commit
4b5388bdc1
|
|
@ -33,6 +33,9 @@
|
|||
* `BatchExportProcessor` will now flush any remaining spans left in a `Batch`
|
||||
after the export operation has completed.
|
||||
([#1726](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1726))
|
||||
* Fixed a bug to allow the Self Diagnostics log file to be opened simutaneously
|
||||
by another process in read-only mode for .NET Framework.
|
||||
([#1693](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1693))
|
||||
|
||||
## 1.0.0-rc1.1
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ namespace OpenTelemetry.Internal
|
|||
{
|
||||
internal class SelfDiagnosticsConfigParser
|
||||
{
|
||||
private const string ConfigFileName = "OTEL_DIAGNOSTICS.json";
|
||||
public const string ConfigFileName = "OTEL_DIAGNOSTICS.json";
|
||||
private const int FileSizeLowerLimit = 1024; // Lower limit for log file size in KB: 1MB
|
||||
private const int FileSizeUpperLimit = 128 * 1024; // Upper limit for log file size in KB: 128MB
|
||||
|
||||
|
|
|
|||
|
|
@ -34,9 +34,9 @@ namespace OpenTelemetry.Internal
|
|||
/// </summary>
|
||||
internal class SelfDiagnosticsConfigRefresher : IDisposable
|
||||
{
|
||||
private const int ConfigurationUpdatePeriodMilliSeconds = 10000;
|
||||
public static readonly byte[] MessageOnNewFile = Encoding.UTF8.GetBytes("Successfully opened file.\n");
|
||||
|
||||
private static readonly byte[] MessageOnNewFile = Encoding.UTF8.GetBytes("Successfully opened file.\n");
|
||||
private const int ConfigurationUpdatePeriodMilliSeconds = 10000;
|
||||
|
||||
private readonly CancellationTokenSource cancellationTokenSource;
|
||||
private readonly Task worker;
|
||||
|
|
@ -53,6 +53,7 @@ namespace OpenTelemetry.Internal
|
|||
// Once the configuration file is valid, an eventListener object will be created.
|
||||
// Commented out for now to avoid the "field was never used" compiler error.
|
||||
private SelfDiagnosticsEventListener eventListener;
|
||||
private volatile FileStream underlyingFileStreamForMemoryMappedFile;
|
||||
private volatile MemoryMappedFile memoryMappedFile;
|
||||
private string logDirectory; // Log directory for log files
|
||||
private int logFileSize; // Log file size in bytes
|
||||
|
|
@ -193,6 +194,12 @@ namespace OpenTelemetry.Internal
|
|||
|
||||
mmf.Dispose();
|
||||
}
|
||||
|
||||
FileStream fs = Interlocked.CompareExchange(
|
||||
ref this.underlyingFileStreamForMemoryMappedFile,
|
||||
null,
|
||||
this.underlyingFileStreamForMemoryMappedFile);
|
||||
fs?.Dispose();
|
||||
}
|
||||
|
||||
private void OpenLogFile(string newLogDirectory, int newFileSize)
|
||||
|
|
@ -203,7 +210,40 @@ namespace OpenTelemetry.Internal
|
|||
var fileName = Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName) + "."
|
||||
+ Process.GetCurrentProcess().Id + ".log";
|
||||
var filePath = Path.Combine(newLogDirectory, fileName);
|
||||
this.memoryMappedFile = MemoryMappedFile.CreateFromFile(filePath, FileMode.Create, null, newFileSize);
|
||||
|
||||
// Because the API [MemoryMappedFile.CreateFromFile][1](the string version) behaves differently on
|
||||
// .NET Framework and .NET Core, here I am using the [FileStream version][2] of it.
|
||||
// Taking the last four prameter values from [.NET Framework]
|
||||
// (https://referencesource.microsoft.com/#system.core/System/IO/MemoryMappedFiles/MemoryMappedFile.cs,148)
|
||||
// and [.NET Core]
|
||||
// (https://github.com/dotnet/runtime/blob/master/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.cs#L152)
|
||||
// The parameter for FileAccess is different in type but the same in rules, both are Read and Write.
|
||||
// The parameter for FileShare is different in values and in behavior.
|
||||
// .NET Framework doesn't allow sharing but .NET Core allows reading by other programs.
|
||||
// The last two parameters are the same values for both frameworks.
|
||||
// [1]: https://docs.microsoft.com/dotnet/api/system.io.memorymappedfiles.memorymappedfile.createfromfile?view=net-5.0#System_IO_MemoryMappedFiles_MemoryMappedFile_CreateFromFile_System_String_System_IO_FileMode_System_String_System_Int64_
|
||||
// [2]: https://docs.microsoft.com/dotnet/api/system.io.memorymappedfiles.memorymappedfile.createfromfile?view=net-5.0#System_IO_MemoryMappedFiles_MemoryMappedFile_CreateFromFile_System_IO_FileStream_System_String_System_Int64_System_IO_MemoryMappedFiles_MemoryMappedFileAccess_System_IO_HandleInheritability_System_Boolean_
|
||||
this.underlyingFileStreamForMemoryMappedFile =
|
||||
new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite, FileShare.Read, 0x1000, FileOptions.None);
|
||||
|
||||
// The parameter values for MemoryMappedFileSecurity, HandleInheritability and leaveOpen are the same
|
||||
// values for .NET Framework and .NET Core:
|
||||
// https://referencesource.microsoft.com/#system.core/System/IO/MemoryMappedFiles/MemoryMappedFile.cs,172
|
||||
// https://github.com/dotnet/runtime/blob/master/src/libraries/System.IO.MemoryMappedFiles/src/System/IO/MemoryMappedFiles/MemoryMappedFile.cs#L168-L179
|
||||
this.memoryMappedFile = MemoryMappedFile.CreateFromFile(
|
||||
this.underlyingFileStreamForMemoryMappedFile,
|
||||
null,
|
||||
newFileSize,
|
||||
MemoryMappedFileAccess.ReadWrite,
|
||||
#if NET452
|
||||
// Only .NET Framework 4.5.2 among all .NET Framework versions is lacking a method omitting this
|
||||
// default value for MemoryMappedFileSecurity.
|
||||
// https://docs.microsoft.com/dotnet/api/system.io.memorymappedfiles.memorymappedfile.createfromfile?view=netframework-4.5.2
|
||||
// .NET Core simply doesn't support this parameter.
|
||||
null,
|
||||
#endif
|
||||
HandleInheritability.None,
|
||||
false);
|
||||
this.logDirectory = newLogDirectory;
|
||||
this.logFileSize = newFileSize;
|
||||
this.logFilePosition = MessageOnNewFile.Length;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
// <copyright file="SelfDiagnosticsConfigRefresherTest.cs" company="OpenTelemetry Authors">
|
||||
// Copyright The OpenTelemetry Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
// </copyright>
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using Xunit;
|
||||
|
||||
namespace OpenTelemetry.Internal.Tests
|
||||
{
|
||||
public class SelfDiagnosticsConfigRefresherTest
|
||||
{
|
||||
private static readonly string ConfigFilePath = SelfDiagnosticsConfigParser.ConfigFileName;
|
||||
private static readonly byte[] MessageOnNewFile = SelfDiagnosticsConfigRefresher.MessageOnNewFile;
|
||||
|
||||
[Fact]
|
||||
[Trait("Platform", "Any")]
|
||||
public void SelfDiagnosticsConfigRefresher_FileShare()
|
||||
{
|
||||
try
|
||||
{
|
||||
CreateConfigFile();
|
||||
using var configRefresher = new SelfDiagnosticsConfigRefresher();
|
||||
|
||||
var outputFileName = Path.GetFileName(Process.GetCurrentProcess().MainModule.FileName) + "."
|
||||
+ Process.GetCurrentProcess().Id + ".log";
|
||||
var outputFilePath = Path.Combine(".", outputFileName);
|
||||
using var file = File.Open(outputFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||
byte[] actualBytes = new byte[MessageOnNewFile.Length];
|
||||
file.Read(actualBytes, 0, actualBytes.Length);
|
||||
Assert.Equal(MessageOnNewFile, actualBytes);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
File.Delete(ConfigFilePath);
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateConfigFile()
|
||||
{
|
||||
string configJson = @"{
|
||||
""LogDirectory"": ""."",
|
||||
""FileSize"": 1024,
|
||||
""LogLevel"": ""Error""
|
||||
}";
|
||||
using FileStream file = File.Open(ConfigFilePath, FileMode.Create, FileAccess.Write);
|
||||
byte[] configBytes = Encoding.UTF8.GetBytes(configJson);
|
||||
file.Write(configBytes, 0, configBytes.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue