Skip to content

Add logic to send logs through File Sink #2825

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Aug 22, 2025
Merged
Show file tree
Hide file tree
Changes from all 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: 2 additions & 1 deletion src/Config/Azure.DataApiBuilder.Config.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,15 @@
<PackageReference Include="Microsoft.AspNetCore.Authorization" />
<PackageReference Include="Microsoft.IdentityModel.Protocols" />
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" />
<PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="System.IO.Abstractions" />
<PackageReference Include="System.Drawing.Common" />
<PackageReference Include="Microsoft.Data.SqlClient" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
<PackageReference Include="Humanizer" />
<PackageReference Include="Npgsql" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
</ItemGroup>

<ItemGroup>
Expand Down
5 changes: 3 additions & 2 deletions src/Config/Converters/FileSinkConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Text.Json;
using System.Text.Json.Serialization;
using Azure.DataApiBuilder.Config.ObjectModel;
using Serilog;

namespace Azure.DataApiBuilder.Config.Converters;
class FileSinkConverter : JsonConverter<FileSinkOptions>
Expand Down Expand Up @@ -31,7 +32,7 @@ public FileSinkConverter(bool replaceEnvVar)
{
bool? enabled = null;
string? path = null;
RollingIntervalMode? rollingInterval = null;
RollingInterval? rollingInterval = null;
int? retainedFileCountLimit = null;
int? fileSizeLimitBytes = null;

Expand Down Expand Up @@ -66,7 +67,7 @@ public FileSinkConverter(bool replaceEnvVar)
case "rolling-interval":
if (reader.TokenType is not JsonTokenType.Null)
{
rollingInterval = EnumExtensions.Deserialize<RollingIntervalMode>(reader.DeserializeString(_replaceEnvVar)!);
rollingInterval = EnumExtensions.Deserialize<RollingInterval>(reader.DeserializeString(_replaceEnvVar)!);
}

break;
Expand Down
21 changes: 11 additions & 10 deletions src/Config/ObjectModel/FileSinkOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Diagnostics.CodeAnalysis;
using System.Text.Json.Serialization;
using Serilog;

namespace Azure.DataApiBuilder.Config.ObjectModel;

Expand All @@ -19,12 +20,12 @@ public record FileSinkOptions
/// <summary>
/// Default path for File Sink.
/// </summary>
public const string DEFAULT_PATH = "/logs/dab-log.txt";
public const string DEFAULT_PATH = @"logs\dab-log.txt";

/// <summary>
/// Default rolling interval for File Sink.
/// </summary>
public const string DEFAULT_ROLLING_INTERVAL = nameof(RollingIntervalMode.Day);
public const string DEFAULT_ROLLING_INTERVAL = nameof(Serilog.RollingInterval.Day);

/// <summary>
/// Default retained file count limit for File Sink.
Expand All @@ -44,25 +45,25 @@ public record FileSinkOptions
/// <summary>
/// Path to the file where logs will be uploaded.
/// </summary>
public string? Path { get; init; }
public string Path { get; init; }

/// <summary>
/// Time it takes for files with logs to be discarded.
/// </summary>
public string? RollingInterval { get; init; }
public string RollingInterval { get; init; }

/// <summary>
/// Amount of files that can exist simultaneously in which logs are saved.
/// </summary>
public int? RetainedFileCountLimit { get; init; }
public int RetainedFileCountLimit { get; init; }

/// <summary>
/// File size limit in bytes before a new file needs to be created.
/// </summary>
public int? FileSizeLimitBytes { get; init; }
public int FileSizeLimitBytes { get; init; }

[JsonConstructor]
public FileSinkOptions(bool? enabled = null, string? path = null, RollingIntervalMode? rollingInterval = null, int? retainedFileCountLimit = null, int? fileSizeLimitBytes = null)
public FileSinkOptions(bool? enabled = null, string? path = null, RollingInterval? rollingInterval = null, int? retainedFileCountLimit = null, int? fileSizeLimitBytes = null)
{
if (enabled is not null)
{
Expand All @@ -86,7 +87,7 @@ public FileSinkOptions(bool? enabled = null, string? path = null, RollingInterva

if (rollingInterval is not null)
{
RollingInterval = rollingInterval.ToString();
RollingInterval = ((RollingInterval)rollingInterval).ToString();
UserProvidedRollingInterval = true;
}
else
Expand All @@ -96,7 +97,7 @@ public FileSinkOptions(bool? enabled = null, string? path = null, RollingInterva

if (retainedFileCountLimit is not null)
{
RetainedFileCountLimit = retainedFileCountLimit;
RetainedFileCountLimit = (int)retainedFileCountLimit;
UserProvidedRetainedFileCountLimit = true;
}
else
Expand All @@ -106,7 +107,7 @@ public FileSinkOptions(bool? enabled = null, string? path = null, RollingInterva

if (fileSizeLimitBytes is not null)
{
FileSizeLimitBytes = fileSizeLimitBytes;
FileSizeLimitBytes = (int)fileSizeLimitBytes;
UserProvidedFileSizeLimitBytes = true;
}
else
Expand Down
44 changes: 0 additions & 44 deletions src/Config/ObjectModel/RollingIntervalMode.cs

This file was deleted.

2 changes: 2 additions & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@
<PackageVersion Include="Newtonsoft.Json.Schema" Version="3.0.14" />
<PackageVersion Include="Npgsql" Version="8.0.3" />
<PackageVersion Include="Polly" Version="7.2.3" />
<PackageVersion Include="Serilog.Extensions.Logging" Version="9.0.2" />
<PackageVersion Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="6.5.0" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<PackageVersion Include="System.Drawing.Common" Version="8.0.3" />
Expand Down
17 changes: 9 additions & 8 deletions src/Service.Tests/Configuration/ConfigurationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Moq.Protected;
using Serilog;
using VerifyMSTest;
using static Azure.DataApiBuilder.Config.FileSystemRuntimeConfigLoader;
using static Azure.DataApiBuilder.Service.Tests.Configuration.ConfigurationEndpoints;
Expand Down Expand Up @@ -4170,21 +4171,21 @@ public void AzureLogAnalyticsSerialization(
/// </summary>
[DataTestMethod]
[TestCategory(TestCategory.MSSQL)]
[DataRow(true, "/file/path/exists.txt", RollingIntervalMode.Minute, 27, 256, true, "/file/path/exists.txt", RollingIntervalMode.Minute, 27, 256)]
[DataRow(true, "/test/path.csv", RollingIntervalMode.Hour, 10, 3000, true, "/test/path.csv", RollingIntervalMode.Hour, 10, 3000)]
[DataRow(false, "C://absolute/file/path.log", RollingIntervalMode.Month, 2147483647, 2048, false, "C://absolute/file/path.log", RollingIntervalMode.Month, 2147483647, 2048)]
[DataRow(false, "D://absolute/test/path.txt", RollingIntervalMode.Year, 10, 2147483647, false, "D://absolute/test/path.txt", RollingIntervalMode.Year, 10, 2147483647)]
[DataRow(false, "", RollingIntervalMode.Infinite, 5, 512, false, "", RollingIntervalMode.Infinite, 5, 512)]
[DataRow(null, null, null, null, null, false, "/logs/dab-log.txt", RollingIntervalMode.Day, 1, 1048576)]
[DataRow(true, "/file/path/exists.txt", RollingInterval.Minute, 27, 256, true, "/file/path/exists.txt", RollingInterval.Minute, 27, 256)]
[DataRow(true, "/test/path.csv", RollingInterval.Hour, 10, 3000, true, "/test/path.csv", RollingInterval.Hour, 10, 3000)]
[DataRow(false, "C://absolute/file/path.log", RollingInterval.Month, 2147483647, 2048, false, "C://absolute/file/path.log", RollingInterval.Month, 2147483647, 2048)]
[DataRow(false, "D://absolute/test/path.txt", RollingInterval.Year, 10, 2147483647, false, "D://absolute/test/path.txt", RollingInterval.Year, 10, 2147483647)]
[DataRow(false, "", RollingInterval.Infinite, 5, 512, false, "", RollingInterval.Infinite, 5, 512)]
[DataRow(null, null, null, null, null, false, "/logs/dab-log.txt", RollingInterval.Day, 1, 1048576)]
public void FileSinkSerialization(
bool? enabled,
string? path,
RollingIntervalMode? rollingInterval,
RollingInterval? rollingInterval,
int? retainedFileCountLimit,
int? fileSizeLimitBytes,
bool expectedEnabled,
string expectedPath,
RollingIntervalMode expectedRollingInterval,
RollingInterval expectedRollingInterval,
int expectedRetainedFileCountLimit,
int expectedFileSizeLimitBytes)
{
Expand Down
163 changes: 163 additions & 0 deletions src/Service.Tests/Configuration/Telemetry/FileSinkTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.IO;
using System.Net.Http;
using System.Threading.Tasks;
using Azure.DataApiBuilder.Config.ObjectModel;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Serilog;
using Serilog.Core;
using static Azure.DataApiBuilder.Service.Tests.Configuration.ConfigurationTests;

namespace Azure.DataApiBuilder.Service.Tests.Configuration.Telemetry;

/// <summary>
/// Contains tests for File Sink functionality.
/// </summary>
[TestClass, TestCategory(TestCategory.MSSQL)]
public class FileSinkTests
{
public TestContext TestContext { get; set; }

private const string CONFIG_WITH_TELEMETRY = "dab-file-sink-test-config.json";
private const string CONFIG_WITHOUT_TELEMETRY = "dab-no-file-sink-test-config.json";
private static RuntimeConfig _configuration;

/// <summary>
/// This is a helper function that creates runtime config file with specified telemetry options.
/// </summary>
/// <param name="configFileName">Name of the config file to be created.</param>
/// <param name="isFileSinkEnabled">Whether File Sink is enabled or not.</param>
/// <param name="fileSinkPath">Path where logs will be sent to.</param>
/// <param name="rollingInterval">Time it takes for logs to roll over to next file.</param>
private static void SetUpTelemetryInConfig(string configFileName, bool isFileSinkEnabled, string fileSinkPath, RollingInterval? rollingInterval = null)
{
DataSource dataSource = new(DatabaseType.MSSQL,
GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null);

_configuration = InitMinimalRuntimeConfig(dataSource, graphqlOptions: new(), restOptions: new());

TelemetryOptions _testTelemetryOptions = new(File: new FileSinkOptions(isFileSinkEnabled, fileSinkPath, rollingInterval));
_configuration = _configuration with { Runtime = _configuration.Runtime with { Telemetry = _testTelemetryOptions } };

File.WriteAllText(configFileName, _configuration.ToJson());
}

/// <summary>
/// Cleans up the test environment by deleting the runtime config with telemetry options.
/// </summary>
[TestCleanup]
public void CleanUpTelemetryConfig()
{
if (File.Exists(CONFIG_WITH_TELEMETRY))
{
File.Delete(CONFIG_WITH_TELEMETRY);
}

if (File.Exists(CONFIG_WITHOUT_TELEMETRY))
{
File.Delete(CONFIG_WITHOUT_TELEMETRY);
}
}

/// <summary>
/// Tests if the services are correctly enabled for File Sink.
/// </summary>
[TestMethod]
public void TestFileSinkServicesEnabled()
{
// Arrange
SetUpTelemetryInConfig(CONFIG_WITH_TELEMETRY, true, "/dab-log-test/file-sink-file.txt");

string[] args = new[]
{
$"--ConfigFileName={CONFIG_WITH_TELEMETRY}"
};
using TestServer server = new(Program.CreateWebHostBuilder(args));

// Additional assertions to check if File Sink is enabled correctly in services
IServiceProvider serviceProvider = server.Services;
LoggerConfiguration serilogLoggerConfiguration = serviceProvider.GetService<LoggerConfiguration>();
Logger serilogLogger = serviceProvider.GetService<Logger>();

// If serilogLoggerConfiguration and serilogLogger are not null, File Sink is enabled
Assert.IsNotNull(serilogLoggerConfiguration, "LoggerConfiguration for Serilog should be registered.");
Assert.IsNotNull(serilogLogger, "Logger for Serilog should be registered.");
}

/// <summary>
/// Tests if the logs are flushed to the proper path when File Sink is enabled.
/// </summary>
/// <summary>
/// Tests if the logs are flushed to the proper path when File Sink is enabled.
/// </summary>
[DataTestMethod]
[DataRow("file-sink-test-file.txt")]
[DataRow("file-sink-test-file.log")]
[DataRow("file-sink-test-file.csv")]
public async Task TestFileSinkSucceed(string fileName)
{
// Arrange
SetUpTelemetryInConfig(CONFIG_WITH_TELEMETRY, true, fileName, RollingInterval.Infinite);

string[] args = new[]
{
$"--ConfigFileName={CONFIG_WITH_TELEMETRY}"
};
using TestServer server = new(Program.CreateWebHostBuilder(args));

// Act
using (HttpClient client = server.CreateClient())
{
HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/Book");
await client.SendAsync(restRequest);
}

server.Dispose();

// Assert
Assert.IsTrue(File.Exists(fileName));

bool containsInfo = false;
string[] allLines = File.ReadAllLines(fileName);
foreach (string line in allLines)
{
containsInfo = line.Contains("INF");
if (containsInfo)
{
break;
}
}

Assert.IsTrue(containsInfo);
}

/// <summary>
/// Tests if the services are correctly disabled for File Sink.
/// </summary>
[TestMethod]
public void TestFileSinkServicesDisabled()
{
// Arrange
SetUpTelemetryInConfig(CONFIG_WITHOUT_TELEMETRY, false, null);

string[] args = new[]
{
$"--ConfigFileName={CONFIG_WITHOUT_TELEMETRY}"
};
using TestServer server = new(Program.CreateWebHostBuilder(args));

// Additional assertions to check if File Sink is enabled correctly in services
IServiceProvider serviceProvider = server.Services;
LoggerConfiguration serilogLoggerConfiguration = serviceProvider.GetService<LoggerConfiguration>();
Logger serilogLogger = serviceProvider.GetService<Logger>();

// If serilogLoggerConfiguration and serilogLogger are null, File Sink is disabled
Assert.IsNull(serilogLoggerConfiguration, "LoggerConfiguration for Serilog should not be registered.");
Assert.IsNull(serilogLogger, "Logger for Serilog should not be registered.");
}
}
2 changes: 2 additions & 0 deletions src/Service/Azure.DataApiBuilder.Service.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@
<PackageReference Include="Npgsql" />
<PackageReference Include="Polly" />
<PackageReference Include="DotNetEnv" />
<PackageReference Include="Serilog.Extensions.Logging" />
<PackageReference Include="Serilog.Sinks.File" />
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" />
<PackageReference Include="System.CommandLine" />
<PackageReference Include="System.IO.Abstractions" />
Expand Down
Loading
Loading