Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/OpenTelemetry/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@
them to supported data types (long for int/short, double for float). For
invalid attributes we now throw an exception instead of logging an error.
([#1720](https://github.com/open-telemetry/opentelemetry-dotnet/pull/1720))
* 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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
46 changes: 43 additions & 3 deletions src/OpenTelemetry/Internal/SelfDiagnosticsConfigRefresher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -53,6 +53,7 @@ internal class SelfDiagnosticsConfigRefresher : IDisposable
// 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
Expand Down Expand Up @@ -193,6 +194,12 @@ private void CloseLogFile()

mmf.Dispose();
}

FileStream fs = Interlocked.CompareExchange(
ref this.underlyingFileStreamForMemoryMappedFile,
null,
this.underlyingFileStreamForMemoryMappedFile);
fs?.Dispose();
}

private void OpenLogFile(string newLogDirectory, int newFileSize)
Expand All @@ -203,7 +210,40 @@ private void OpenLogFile(string newLogDirectory, int newFileSize)
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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
}