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"; + } + } +}