Skip to content
Open
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
34 changes: 23 additions & 11 deletions microservice/beamable.tooling.common/Microservice/AdminRoutes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public string HealthCheck()
{
return "responsive";
}

/// <summary>
/// Generates an OpenAPI/Swagger 3.0 document that describes the available service endpoints.
/// </summary>
Expand All @@ -84,20 +84,32 @@ public string HealthCheck()
[CustomResponseSerializationAttribute]
public string Docs()
{
var docs = new ServiceDocGenerator();
var ctx = GlobalProvider.GetService<StartupContext>();
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<StartupContext>();
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;
}
}

/// <summary>
/// Fetch various Beamable SDK metadata for the Microservice
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down Expand Up @@ -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; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ public void SetResolvedCid(string resolvedCid)
{
throw new NotImplementedException();
}

public AdminRouteLogPolicy AdminRouteLogPolicy { get; set; }
}

public static class MicroserviceArgsExtensions
Expand Down Expand Up @@ -126,6 +128,7 @@ public static MicroserviceArgs Copy(this IMicroserviceArgs args, Action<Microser
SkipAliasResolve = args.SkipAliasResolve,
MaxUniqueEventBindingCount = args.MaxUniqueEventBindingCount,
AllowStartupWithoutBeamableSettings = args.AllowStartupWithoutBeamableSettings,
AdminRouteLogPolicy = args.AdminRouteLogPolicy
};
configurator?.Invoke(next);
return next;
Expand Down Expand Up @@ -228,6 +231,18 @@ public void SetResolvedCid(string resolvedCid)
//CustomerID = resolvedCid;
}

//Default Admin logging policy = Protected
public AdminRouteLogPolicy AdminRouteLogPolicy
{
get
{
var rawPolicy = Environment.GetEnvironmentVariable("BEAM_ADMIN_ROUTE_LOG_POLICY");
return Enum.TryParse<AdminRouteLogPolicy>(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") ?? "";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using Microsoft.Extensions.Logging;

namespace Beamable.Server
{
public static class MicroserviceLogLevelContext
{
public static readonly AsyncLocal<LogLevel> CurrentLogLevel = new();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
110 changes: 66 additions & 44 deletions microservice/microservice/dbmicroservice/BeamableMicroService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -794,50 +794,16 @@ async Task HandleClientMessage(MicroserviceRequestContext ctx, Stopwatch sw, Bea
}
}

async Task HandleWebsocketMessage(IConnection ws, JsonDocument document, Stopwatch sw)
/// <summary>
/// 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.
/// </summary>
/// <param name="ctx">The context of the current request, providing necessary information
/// for logging configuration, such as routing keys and associated metadata.</param>
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<IRealmConfigService>();
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
{
Expand Down Expand Up @@ -901,7 +867,7 @@ async Task HandleWebsocketMessage(IConnection ws, JsonDocument document, Stopwat
}


MicroserviceBootstrapper.ContextLogLevel.Value = contextLogLevel;
MicroserviceLogLevelContext.CurrentLogLevel.Value = contextLogLevel;

void TryToSetNewLogLevel(LogLevel newLogLevel)
{
Expand All @@ -918,7 +884,63 @@ void TryToSetNewLogLevel(LogLevel newLogLevel)
{
BeamableZLoggerProvider.Instance.Error(ex);
}
}

/// <summary>
/// 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.
/// </summary>
/// <param name="ctx">The request context object containing information about the current request,
/// including the request path.</param>
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<IRealmConfigService>();
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<BeamableMicroService>();
using var scope = logger.BeginScope(extraOtelTags.ToDictionary());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public class SingleUseUserContext : IUserContext

public static partial class MicroserviceBootstrapper
{
public static AsyncLocal<LogLevel> ContextLogLevel = new();
[Obsolete("Use MicroserviceLogLevelContext.CurrentLogLevel instead.")]
public static AsyncLocal<LogLevel> ContextLogLevel => MicroserviceLogLevelContext.CurrentLogLevel;


private static BeamServiceConfigBuilder _preparedBuilder;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ public static async Task<MicroserviceResult> 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;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
Loading
Loading