diff --git a/microservice/beamable.tooling.common/Microservice/AdminRoutes.cs b/microservice/beamable.tooling.common/Microservice/AdminRoutes.cs
index d110b983c9..73a44d0f31 100644
--- a/microservice/beamable.tooling.common/Microservice/AdminRoutes.cs
+++ b/microservice/beamable.tooling.common/Microservice/AdminRoutes.cs
@@ -70,7 +70,7 @@ public string HealthCheck()
{
return "responsive";
}
-
+
///
/// Generates an OpenAPI/Swagger 3.0 document that describes the available service endpoints.
///
@@ -84,20 +84,32 @@ public string HealthCheck()
[CustomResponseSerializationAttribute]
public string Docs()
{
- var docs = new ServiceDocGenerator();
- var ctx = GlobalProvider.GetService();
- var doc = docs.Generate(ctx, GlobalProvider);
+ // Suppress info-level logs during OpenAPI doc generation to avoid noise
+ //Wrapping with try/finally to ensure log level is restored even if an exception occurs
+ var previousLogLevel = MicroserviceLogLevelContext.CurrentLogLevel.Value;
+ MicroserviceLogLevelContext.CurrentLogLevel.Value = LogLevel.Warning;
+
+ try
+ {
+ var docs = new ServiceDocGenerator();
+ var ctx = GlobalProvider.GetService();
+ var doc = docs.Generate(ctx, GlobalProvider);
- if (!string.IsNullOrEmpty(PublicHost))
- {
- doc.Servers.Add(new OpenApiServer { Url = PublicHost });
- }
+ if (!string.IsNullOrEmpty(PublicHost))
+ {
+ doc.Servers.Add(new OpenApiServer { Url = PublicHost });
+ }
- var outputString = doc.Serialize(OpenApiSpecVersion.OpenApi3_0, OpenApiFormat.Json);
+ var outputString = doc.Serialize(OpenApiSpecVersion.OpenApi3_0, OpenApiFormat.Json);
- return outputString;
- }
+ return outputString;
+ }
+ finally
+ {
+ MicroserviceLogLevelContext.CurrentLogLevel.Value = previousLogLevel;
+ }
+ }
///
/// Fetch various Beamable SDK metadata for the Microservice
diff --git a/microservice/beamable.tooling.common/Microservice/IMicroserviceArgs.cs b/microservice/beamable.tooling.common/Microservice/IMicroserviceArgs.cs
index 412485fac6..fca1c7a5ec 100644
--- a/microservice/beamable.tooling.common/Microservice/IMicroserviceArgs.cs
+++ b/microservice/beamable.tooling.common/Microservice/IMicroserviceArgs.cs
@@ -8,6 +8,12 @@ public enum LogOutputType
DEFAULT, STRUCTURED, UNSTRUCTURED, FILE, STRUCTURED_AND_FILE
}
+public enum AdminRouteLogPolicy
+{
+ Protected = 0, //Default where Admin logs are restricted to warning and above
+ FollowServiceRules = 1 //Admin logs follow service rules
+}
+
public interface IMicroserviceArgs : IRealmInfo, IActivityProviderArgs
{
public IDependencyProviderScope ServiceScope { get; }
@@ -59,4 +65,5 @@ public interface IMicroserviceArgs : IRealmInfo, IActivityProviderArgs
public bool AllowStartupWithoutBeamableSettings { get; }
public int MaxUniqueEventBindingCount { get; }
void SetResolvedCid(string resolvedCid);
+ public AdminRouteLogPolicy AdminRouteLogPolicy { get; }
}
diff --git a/microservice/beamable.tooling.common/Microservice/MicroserviceArgs.cs b/microservice/beamable.tooling.common/Microservice/MicroserviceArgs.cs
index d26da88847..48840fb7e3 100644
--- a/microservice/beamable.tooling.common/Microservice/MicroserviceArgs.cs
+++ b/microservice/beamable.tooling.common/Microservice/MicroserviceArgs.cs
@@ -68,6 +68,8 @@ public void SetResolvedCid(string resolvedCid)
{
throw new NotImplementedException();
}
+
+ public AdminRouteLogPolicy AdminRouteLogPolicy { get; set; }
}
public static class MicroserviceArgsExtensions
@@ -126,6 +128,7 @@ public static MicroserviceArgs Copy(this IMicroserviceArgs args, Action(rawPolicy, true, out var policy) ?
+ policy :
+ AdminRouteLogPolicy.Protected;
+ }
+ }
+
public string Host => Environment.GetEnvironmentVariable("HOST");
public string Secret => Environment.GetEnvironmentVariable("SECRET");
public string NamePrefix => Environment.GetEnvironmentVariable("NAME_PREFIX") ?? "";
diff --git a/microservice/beamable.tooling.common/Microservice/MicroserviceLogLevelContext.cs b/microservice/beamable.tooling.common/Microservice/MicroserviceLogLevelContext.cs
new file mode 100644
index 0000000000..92b77f6a66
--- /dev/null
+++ b/microservice/beamable.tooling.common/Microservice/MicroserviceLogLevelContext.cs
@@ -0,0 +1,10 @@
+using Microsoft.Extensions.Logging;
+
+namespace Beamable.Server
+{
+ public static class MicroserviceLogLevelContext
+ {
+ public static readonly AsyncLocal CurrentLogLevel = new();
+ }
+
+}
diff --git a/microservice/microservice/Api/RealmConfig/RealmConfigService.cs b/microservice/microservice/Api/RealmConfig/RealmConfigService.cs
index c56f0b7cff..6bd7f6d324 100644
--- a/microservice/microservice/Api/RealmConfig/RealmConfigService.cs
+++ b/microservice/microservice/Api/RealmConfig/RealmConfigService.cs
@@ -40,7 +40,7 @@ public RealmConfigService(IBeamableRequester requester, SocketRequesterContext c
public void UpdateLogLevel()
{
var level = GetLogLevel();
- MicroserviceBootstrapper.ContextLogLevel.Value = level;
+ MicroserviceLogLevelContext.CurrentLogLevel.Value = level;
}
private LogLevel GetLogLevel()
diff --git a/microservice/microservice/dbmicroservice/BeamableMicroService.cs b/microservice/microservice/dbmicroservice/BeamableMicroService.cs
index 7e9a94ba65..a314c34963 100644
--- a/microservice/microservice/dbmicroservice/BeamableMicroService.cs
+++ b/microservice/microservice/dbmicroservice/BeamableMicroService.cs
@@ -794,50 +794,16 @@ async Task HandleClientMessage(MicroserviceRequestContext ctx, Stopwatch sw, Bea
}
}
- async Task HandleWebsocketMessage(IConnection ws, JsonDocument document, Stopwatch sw)
+ ///
+ /// Applies logging context rules based on the provided request context.
+ /// This method utilizes the microservice's routing key and logging context service
+ /// to determine the appropriate log level and any additional logging rules to be applied.
+ /// It ensures that logging behavior is dynamically adjusted to reflect the specified configuration.
+ ///
+ /// The context of the current request, providing necessary information
+ /// for logging configuration, such as routing keys and associated metadata.
+ private void ApplyLoggingContextRules(RequestContext ctx)
{
-
- MicroserviceRequestContext ctx = null;
- using (var requestActivity = _activityProvider.Create(Otel.TRACE_CONSTRUCT_CTX, importance: TelemetryImportance.VERBOSE))
- {
- if (!document.TryBuildRequestContext(InstanceArgs, out ctx))
- {
- requestActivity.SetStatus(ActivityStatusCode.Error);
- Log.Debug("WS Message contains no data. Cannot handle. Skipping message.");
- return;
- }
- requestActivity.SetStatus(ActivityStatusCode.Ok);
- }
-
- BeamActivity parentActivity = BeamActivity.Noop;
- if (_socketRequesterContext.TryGetListener(ctx.Id, out var existingListener))
- {
- parentActivity = existingListener.Activity;
- }
-
-
- using var activity = _activityProvider.Create(Otel.TRACE_WS, parentActivity);
- MicroserviceRequester.ContextActivity.Value = activity;
- ctx.ActivityContext = activity;
-
-
- var extraOtelTags = _telemetryProviders.CreateRequestAttributes(InstanceArgs, ctx, ConnectionId);
- activity.SetTags(extraOtelTags);
-
-
- // First get the Global Realm Config Log Level and apply it by running UpdateLogLevel
- var configService = InstanceArgs.ServiceScope.GetService();
- if (ctx.Path?.StartsWith(_adminPrefix) ?? false)
- {
- // when the path starts with admin, use warning.
- MicroserviceBootstrapper.ContextLogLevel.Value = LogLevel.Warning;
- }
- else
- {
- // otherwise, allow default behaviour.
- configService.UpdateLogLevel();
- }
-
string routingKey = InstanceArgs.GetRoutingKey().GetOrElse(string.Empty);
try
{
@@ -901,7 +867,7 @@ async Task HandleWebsocketMessage(IConnection ws, JsonDocument document, Stopwat
}
- MicroserviceBootstrapper.ContextLogLevel.Value = contextLogLevel;
+ MicroserviceLogLevelContext.CurrentLogLevel.Value = contextLogLevel;
void TryToSetNewLogLevel(LogLevel newLogLevel)
{
@@ -918,7 +884,63 @@ void TryToSetNewLogLevel(LogLevel newLogLevel)
{
BeamableZLoggerProvider.Instance.Error(ex);
}
+ }
+
+ ///
+ /// Adjusts the log level for the current request context based on specific policy criteria.
+ /// Determines whether the request is for a protected admin route and sets the log level accordingly.
+ /// If the route is not protected, applies custom logging rules and updates the log level
+ /// using the service configuration.
+ ///
+ /// The request context object containing information about the current request,
+ /// including the request path.
+ private void ApplyRequestLogLevel(RequestContext ctx)
+ {
+ var isProtectedAdminRoute = ctx.Path?.StartsWith(_adminPrefix) == true
+ && InstanceArgs.AdminRouteLogPolicy == AdminRouteLogPolicy.Protected;
+
+ if (isProtectedAdminRoute)
+ {
+ MicroserviceLogLevelContext.CurrentLogLevel.Value = LogLevel.Warning;
+ }
+ else
+ {
+ var configureService = InstanceArgs.ServiceScope.GetService();
+ configureService.UpdateLogLevel();
+ ApplyLoggingContextRules(ctx);
+ }
+ }
+
+ async Task HandleWebsocketMessage(IConnection ws, JsonDocument document, Stopwatch sw)
+ {
+ MicroserviceRequestContext ctx = null;
+ using (var requestActivity = _activityProvider.Create(Otel.TRACE_CONSTRUCT_CTX, importance: TelemetryImportance.VERBOSE))
+ {
+ if (!document.TryBuildRequestContext(InstanceArgs, out ctx))
+ {
+ requestActivity.SetStatus(ActivityStatusCode.Error);
+ Log.Debug("WS Message contains no data. Cannot handle. Skipping message.");
+ return;
+ }
+ requestActivity.SetStatus(ActivityStatusCode.Ok);
+ }
+
+ BeamActivity parentActivity = BeamActivity.Noop;
+ if (_socketRequesterContext.TryGetListener(ctx.Id, out var existingListener))
+ {
+ parentActivity = existingListener.Activity;
+ }
+
+
+ using var activity = _activityProvider.Create(Otel.TRACE_WS, parentActivity);
+ MicroserviceRequester.ContextActivity.Value = activity;
+ ctx.ActivityContext = activity;
+
+
+ var extraOtelTags = _telemetryProviders.CreateRequestAttributes(InstanceArgs, ctx, ConnectionId);
+ activity.SetTags(extraOtelTags);
+ ApplyRequestLogLevel(ctx);
var logger = BeamableZLoggerProvider.LogContext.Value = InstanceArgs.ServiceScope.GetLogger();
using var scope = logger.BeginScope(extraOtelTags.ToDictionary());
diff --git a/microservice/microservice/dbmicroservice/MicroserviceBootstrapper.cs b/microservice/microservice/dbmicroservice/MicroserviceBootstrapper.cs
index c57424af3e..0f4eec4006 100644
--- a/microservice/microservice/dbmicroservice/MicroserviceBootstrapper.cs
+++ b/microservice/microservice/dbmicroservice/MicroserviceBootstrapper.cs
@@ -17,7 +17,8 @@ public class SingleUseUserContext : IUserContext
public static partial class MicroserviceBootstrapper
{
- public static AsyncLocal ContextLogLevel = new();
+ [Obsolete("Use MicroserviceLogLevelContext.CurrentLogLevel instead.")]
+ public static AsyncLocal ContextLogLevel => MicroserviceLogLevelContext.CurrentLogLevel;
private static BeamServiceConfigBuilder _preparedBuilder;
diff --git a/microservice/microservice/dbmicroservice/MicroserviceStartupUtil.cs b/microservice/microservice/dbmicroservice/MicroserviceStartupUtil.cs
index ff46621505..6f8094405f 100644
--- a/microservice/microservice/dbmicroservice/MicroserviceStartupUtil.cs
+++ b/microservice/microservice/dbmicroservice/MicroserviceStartupUtil.cs
@@ -102,7 +102,7 @@ public static async Task Begin(IBeamServiceConfig configurat
if (startupCtx.IsGeneratingOapi)
{
LogUtil.TryParseSystemLogLevel(configuredArgs.OapiGenLogLevel, out var defaultLevel, LogLevel.Information);
- MicroserviceBootstrapper.ContextLogLevel.Value = defaultLevel;
+ MicroserviceLogLevelContext.CurrentLogLevel.Value = defaultLevel;
await GenerateOpenApiSpecification(startupCtx, configurator);
startupCtx.result.GeneratedClient = true;
return startupCtx.result;
@@ -380,7 +380,7 @@ private static void ConfigureLogging(IBeamServiceConfig configurator, StartupCon
defaultLogLevel = LogLevel.Warning;
}
- MicroserviceBootstrapper.ContextLogLevel.Value = defaultLogLevel;
+ MicroserviceLogLevelContext.CurrentLogLevel.Value = defaultLogLevel;
var debugLogOptions = UseBeamJsonFormatter(new ZLoggerOptions());
ctx.debugLogProcessor = new DebugLogProcessor(debugLogOptions);
@@ -392,7 +392,7 @@ private static void ConfigureLogging(IBeamServiceConfig configurator, StartupCon
// all logs are valid, but may not pass the filter.
builder.SetMinimumLevel(LogLevel.Trace);
- builder.AddFilter(level => level >= MicroserviceBootstrapper.ContextLogLevel.Value);
+ builder.AddFilter(level => level >= MicroserviceLogLevelContext.CurrentLogLevel.Value);
if (!ctx.InDocker)
{
builder.AddZLoggerLogProcessor(ctx.debugLogProcessor);
diff --git a/microservice/microserviceTests/OpenAPITests/DocTests.cs b/microservice/microserviceTests/OpenAPITests/DocTests.cs
index d20259b309..bbbc17c44d 100644
--- a/microservice/microserviceTests/OpenAPITests/DocTests.cs
+++ b/microservice/microserviceTests/OpenAPITests/DocTests.cs
@@ -11,17 +11,19 @@
using NUnit.Framework;
using System;
using System.Collections.Generic;
-using System.Threading.Tasks;
-using Beamable.Common.Dependencies;
-using microserviceTests.microservice.Util;
+using System.Threading.Tasks;
+using Beamable.Common.Dependencies;
+using microserviceTests.microservice.Util;
+using microservice.Common;
+using Microsoft.Extensions.Logging;
namespace microserviceTests.OpenAPITests;
[Serializable]
public class DocTests
{
- public class ExampleAttrs : ITelemetryAttributeProvider
- {
+ public class ExampleAttrs : ITelemetryAttributeProvider
+ {
public List GetDescriptors()
{
return new List
@@ -47,11 +49,34 @@ public void CreateConnectionAttributes(IConnectionAttributeContext ctx)
public void CreateRequestAttributes(IRequestAttributeContext ctx)
{
- }
- }
-
- [Test]
- public void TestMethodScanning()
+ }
+ }
+
+ public class RecordingTelemetryAttributeProvider : ITelemetryAttributeProvider
+ {
+ public static readonly List SeenLogLevels = new List();
+
+ public List GetDescriptors()
+ {
+ SeenLogLevels.Add(MicroserviceLogLevelContext.CurrentLogLevel.Value);
+ return new List();
+ }
+
+ public void CreateDefaultAttributes(IDefaultAttributeContext ctx)
+ {
+ }
+
+ public void CreateConnectionAttributes(IConnectionAttributeContext ctx)
+ {
+ }
+
+ public void CreateRequestAttributes(IRequestAttributeContext ctx)
+ {
+ }
+ }
+
+ [Test]
+ public void TestMethodScanning()
{
LoggingUtil.InitTestCorrelator();
var gen = new ServiceDocGenerator();
@@ -72,11 +97,63 @@ public void TestMethodScanning()
// Assert.AreEqual(1, reqSchema.Required.Count);
var outputString = doc.Serialize(OpenApiSpecVersion.OpenApi3_0, OpenApiFormat.Json);
- Console.WriteLine(outputString);
- }
-
- [Microservice("docs")]
- public class DocService : Microservice
+ Console.WriteLine(outputString);
+ }
+
+ [Test]
+ public void AdminRoutesDocsTemporarilyRaisesLogLevelAndRestoresPreviousValue()
+ {
+ LoggingUtil.InitTestCorrelator();
+ RecordingTelemetryAttributeProvider.SeenLogLevels.Clear();
+
+ var originalLogLevel = MicroserviceLogLevelContext.CurrentLogLevel.Value;
+ var previousLogLevel = LogLevel.Debug;
+ MicroserviceLogLevelContext.CurrentLogLevel.Value = previousLogLevel;
+
+ try
+ {
+ var args = new TestArgs();
+ var microserviceAttribute = new MicroserviceAttribute("docs");
+ var startupContext = new StartupContext
+ {
+ args = args,
+ attributes = microserviceAttribute,
+ routeSources = new[]
+ {
+ new BeamRouteSource
+ {
+ InstanceType = typeof(DocService)
+ }
+ }
+ };
+
+ var builder = new DependencyBuilder();
+ builder.AddSingleton(args);
+ builder.AddSingleton(startupContext);
+ builder.AddSingleton>();
+ builder.AddSingleton();
+
+ var routes = new AdminRoutes
+ {
+ GlobalProvider = builder.Build(),
+ MicroserviceAttribute = microserviceAttribute
+ };
+
+ var outputString = routes.Docs();
+
+ Assert.That(outputString, Does.Contain("\"openapi\""));
+ Assert.That(RecordingTelemetryAttributeProvider.SeenLogLevels, Is.Not.Empty);
+ Assert.That(RecordingTelemetryAttributeProvider.SeenLogLevels, Has.All.EqualTo(LogLevel.Warning));
+ Assert.AreEqual(previousLogLevel, MicroserviceLogLevelContext.CurrentLogLevel.Value);
+ }
+ finally
+ {
+ MicroserviceLogLevelContext.CurrentLogLevel.Value = originalLogLevel;
+ }
+ }
+
+ [Microservice("docs")]
+ public class DocService : Microservice
{
[ServerCallable]
diff --git a/microservice/microserviceTests/microservice/TestArgs.cs b/microservice/microserviceTests/microservice/TestArgs.cs
index 926a41ae95..05aa74aaee 100644
--- a/microservice/microserviceTests/microservice/TestArgs.cs
+++ b/microservice/microserviceTests/microservice/TestArgs.cs
@@ -29,9 +29,9 @@ public TestSetup(IConnectionProvider provider, IContentResolver resolver=null)
_provider = provider;
}
- public async Task Start(TestArgs dudArgs=null, Action configurator=null) where T : Microservice
- {
- var args = new TestArgs();
+ public async Task Start(TestArgs dudArgs=null, Action configurator=null) where T : Microservice
+ {
+ var args = dudArgs ?? new TestArgs();
var attr = typeof(T).GetCustomAttribute();
@@ -171,14 +171,16 @@ public class TestArgs : IMicroserviceArgs
public bool OtelExporterShouldRetry { get; }
public bool OtelExporterStandardEnabled => false;
public string OtelExporterRetryMaxSize { get; }
- public bool AllowStartupWithoutBeamableSettings => false;
- public int MaxUniqueEventBindingCount => 100;
- public bool SkipLocalEnv => true;
- public bool SkipAliasResolve => true;
-
- public void SetResolvedCid(string resolvedCid)
- {
- throw new NotImplementedException();
- }
- }
-}
+ public bool AllowStartupWithoutBeamableSettings => false;
+ public int MaxUniqueEventBindingCount => 100;
+ public bool SkipLocalEnv => true;
+ public bool SkipAliasResolve => true;
+ public AdminRouteLogPolicy AdminRouteLogPolicy { get; set; } = AdminRouteLogPolicy.Protected;
+
+ public void SetResolvedCid(string resolvedCid)
+ {
+ throw new NotImplementedException();
+ }
+
+ }
+}
diff --git a/microservice/microserviceTests/microservice/dbmicroservice/BeamableMicroServiceTests/AdminRouteLogPolicyTests.cs b/microservice/microserviceTests/microservice/dbmicroservice/BeamableMicroServiceTests/AdminRouteLogPolicyTests.cs
new file mode 100644
index 0000000000..9b61fcc58e
--- /dev/null
+++ b/microservice/microserviceTests/microservice/dbmicroservice/BeamableMicroServiceTests/AdminRouteLogPolicyTests.cs
@@ -0,0 +1,226 @@
+using Beamable.Api.Autogenerated.Models;
+using Beamable.Common;
+using Beamable.Common.Dependencies;
+using Beamable.Microservice.Tests.Socket;
+using Beamable.Server;
+using Beamable.Server.Api.Logs;
+using Beamable.Server.Api.RealmConfig;
+using Microsoft.Extensions.Logging;
+using NUnit.Framework;
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using ClientRequest = Beamable.Microservice.Tests.Socket.ClientRequest;
+
+namespace microserviceTests.microservice.dbmicroservice.BeamableMicroServiceTests;
+
+[TestFixture]
+public class AdminRouteLogPolicyTests : CommonTest
+{
+ private const string AdminRouteLogPolicyEnvVar = "BEAM_ADMIN_ROUTE_LOG_POLICY";
+
+ [TestCase(null, AdminRouteLogPolicy.Protected)]
+ [TestCase("", AdminRouteLogPolicy.Protected)]
+ [TestCase("Protected", AdminRouteLogPolicy.Protected)]
+ [TestCase("FollowServiceRules", AdminRouteLogPolicy.FollowServiceRules)]
+ [TestCase("followservicerules", AdminRouteLogPolicy.FollowServiceRules)]
+ [TestCase("NotARealPolicy", AdminRouteLogPolicy.Protected)]
+ [NonParallelizable]
+ public void EnvironmentArgsParsesAdminRouteLogPolicy(string rawPolicy, AdminRouteLogPolicy expectedPolicy)
+ {
+ var previousPolicy = Environment.GetEnvironmentVariable(AdminRouteLogPolicyEnvVar);
+ try
+ {
+ Environment.SetEnvironmentVariable(AdminRouteLogPolicyEnvVar, rawPolicy);
+ var args = new EnvironmentArgs();
+
+ Assert.AreEqual(expectedPolicy, args.AdminRouteLogPolicy);
+ }
+ finally
+ {
+ Environment.SetEnvironmentVariable(AdminRouteLogPolicyEnvVar, previousPolicy);
+ }
+ }
+
+ [Test]
+ [NonParallelizable]
+ public async Task ProtectedAdminRouteDoesNotApplyServiceLogRules()
+ {
+ var realmConfig = new RecordingRealmConfigService(LogLevel.Debug);
+ var loggingContext = new RecordingLoggingContextService();
+ TestSocket testSocket = null;
+
+ var ms = new TestSetup(new TestSocketProvider(socket =>
+ {
+ testSocket = socket;
+ socket
+ .AddStandardMessageHandlers()
+ .AddMessageHandler(
+ MessageMatcher
+ .WithReqId(1)
+ .WithStatus(200)
+ .WithPayload(payload => payload == "responsive"),
+ MessageResponder.NoResponse(),
+ MessageFrequency.OnlyOnce());
+ }));
+
+ await ms.Start(new TestArgs
+ {
+ AdminRouteLogPolicy = AdminRouteLogPolicy.Protected
+ }, builder => RegisterLogServices(builder, realmConfig, loggingContext));
+
+ Assert.IsTrue(ms.HasInitialized);
+ var updateLogLevelCount = realmConfig.UpdateLogLevelCount;
+ var getLogLevelContextCount = loggingContext.GetLogLevelContextCount;
+
+ testSocket.SendToClient(ClientRequest.Callable("micro_log_policy", "admin/HealthCheck", 1));
+ await Task.Delay(50);
+
+ Assert.AreEqual(updateLogLevelCount, realmConfig.UpdateLogLevelCount);
+ Assert.AreEqual(getLogLevelContextCount, loggingContext.GetLogLevelContextCount);
+
+ await ms.OnShutdown(this, null);
+ Assert.IsTrue(testSocket.AllMocksCalled());
+ }
+
+ [Test]
+ [NonParallelizable]
+ public async Task FollowServiceRulesAdminRouteAppliesServiceLogRules()
+ {
+ var realmConfig = new RecordingRealmConfigService(LogLevel.Debug);
+ var loggingContext = new RecordingLoggingContextService();
+ TestSocket testSocket = null;
+
+ var ms = new TestSetup(new TestSocketProvider(socket =>
+ {
+ testSocket = socket;
+ socket
+ .AddStandardMessageHandlers()
+ .AddMessageHandler(
+ MessageMatcher
+ .WithReqId(1)
+ .WithStatus(200)
+ .WithPayload(payload => payload == "responsive"),
+ MessageResponder.NoResponse(),
+ MessageFrequency.OnlyOnce());
+ }));
+
+ await ms.Start(new TestArgs
+ {
+ AdminRouteLogPolicy = AdminRouteLogPolicy.FollowServiceRules
+ }, builder => RegisterLogServices(builder, realmConfig, loggingContext));
+
+ Assert.IsTrue(ms.HasInitialized);
+ var updateLogLevelCount = realmConfig.UpdateLogLevelCount;
+ var getLogLevelContextCount = loggingContext.GetLogLevelContextCount;
+
+ testSocket.SendToClient(ClientRequest.Callable("micro_log_policy", "admin/HealthCheck", 1));
+ await Task.Delay(50);
+
+ Assert.AreEqual(updateLogLevelCount + 1, realmConfig.UpdateLogLevelCount);
+ Assert.AreEqual(getLogLevelContextCount + 1, loggingContext.GetLogLevelContextCount);
+
+ await ms.OnShutdown(this, null);
+ Assert.IsTrue(testSocket.AllMocksCalled());
+ }
+
+ [Test]
+ [NonParallelizable]
+ public async Task ProtectedPolicyStillAppliesServiceLogRulesForNonAdminRoute()
+ {
+ var realmConfig = new RecordingRealmConfigService(LogLevel.Debug);
+ var loggingContext = new RecordingLoggingContextService();
+ TestSocket testSocket = null;
+
+ var ms = new TestSetup(new TestSocketProvider(socket =>
+ {
+ testSocket = socket;
+ socket
+ .AddStandardMessageHandlers()
+ .AddMessageHandler(
+ MessageMatcher
+ .WithReqId(1)
+ .WithStatus(200)
+ .WithPayload(payload => payload == "ok"),
+ MessageResponder.NoResponse(),
+ MessageFrequency.OnlyOnce());
+ }));
+
+ await ms.Start(new TestArgs
+ {
+ AdminRouteLogPolicy = AdminRouteLogPolicy.Protected
+ }, builder => RegisterLogServices(builder, realmConfig, loggingContext));
+
+ Assert.IsTrue(ms.HasInitialized);
+ var updateLogLevelCount = realmConfig.UpdateLogLevelCount;
+ var getLogLevelContextCount = loggingContext.GetLogLevelContextCount;
+
+ testSocket.SendToClient(ClientRequest.ClientCallable("micro_log_policy", nameof(LogPolicyService.Ping), 1, 1));
+ await Task.Delay(50);
+
+ Assert.AreEqual(updateLogLevelCount + 1, realmConfig.UpdateLogLevelCount);
+ Assert.AreEqual(getLogLevelContextCount + 1, loggingContext.GetLogLevelContextCount);
+
+ await ms.OnShutdown(this, null);
+ Assert.IsTrue(testSocket.AllMocksCalled());
+ }
+
+ private static void RegisterLogServices(IDependencyBuilder builder, RecordingRealmConfigService realmConfig, RecordingLoggingContextService loggingContext)
+ {
+ builder.RemoveIfExists();
+ builder.RemoveIfExists();
+ builder.RemoveIfExists();
+ builder.AddSingleton(realmConfig);
+ builder.AddSingleton(realmConfig);
+ builder.AddSingleton(loggingContext);
+ }
+
+ private class RecordingRealmConfigService : IRealmConfigService
+ {
+ private readonly LogLevel _level;
+
+ public RecordingRealmConfigService(LogLevel level)
+ {
+ _level = level;
+ }
+
+ public int UpdateLogLevelCount { get; private set; }
+
+ public Promise GetRealmConfigSettings()
+ {
+ return Promise.Successful(new RealmConfig(new Dictionary()));
+ }
+
+ public void UpdateLogLevel()
+ {
+ UpdateLogLevelCount++;
+ MicroserviceLogLevelContext.CurrentLogLevel.Value = _level;
+ }
+ }
+
+ private class RecordingLoggingContextService : ILoggingContextService
+ {
+ public int GetLogLevelContextCount { get; private set; }
+
+ public Promise> GetAllLoggingContexts()
+ {
+ return Promise>.Successful(new Dictionary());
+ }
+
+ public BeamoV2ServiceLoggingContext GetLogLevelContext(string serviceName, string routingKey)
+ {
+ GetLogLevelContextCount++;
+ return null;
+ }
+ }
+
+ [Microservice("log_policy", EnableEagerContentLoading = false)]
+ public class LogPolicyService : Microservice
+ {
+ [ClientCallable]
+ public string Ping()
+ {
+ return "ok";
+ }
+ }
+}