Compare commits
1 Commits
681c5e2ffc
...
Features/D
| Author | SHA1 | Date | |
|---|---|---|---|
|
f6dfc1363f
|
15
.gitignore
vendored
15
.gitignore
vendored
@@ -4,6 +4,21 @@
|
|||||||
##
|
##
|
||||||
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||||
|
|
||||||
|
#Kevins project namager :) - trace [934875333]
|
||||||
|
.tmp/
|
||||||
|
.manifest/
|
||||||
|
Monospace/
|
||||||
|
VMspace/
|
||||||
|
Sharedspace/
|
||||||
|
Graphene/
|
||||||
|
Graph2Code-Jibo
|
||||||
|
Shovel-netProj
|
||||||
|
Shoveled-Jibo-Cloud
|
||||||
|
Shoveled-Jibo-Cloud-OpenMemory
|
||||||
|
latest.ShovelDump
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# User-specific files
|
# User-specific files
|
||||||
*.rsuser
|
*.rsuser
|
||||||
*.suo
|
*.suo
|
||||||
|
|||||||
86
JiboExperiments.sln
Normal file
86
JiboExperiments.sln
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.5.2.0
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OpenJibo", "OpenJibo", "{2FDD1CD9-89DA-D176-F85D-DC517FF08BF4}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{9CD502EA-259A-A102-F54F-DB66ECB43CCA}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jibo.Runtime.Abstractions", "OpenJibo\src\Jibo.Runtime.Abstractions\Jibo.Runtime.Abstractions.csproj", "{4EC1F8A2-7A15-79FC-2A37-9620624156F8}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Playground", "OpenJibo\src\Playground\Playground.csproj", "{61A125DD-6776-6FF9-D0B9-9945ADBCC0E1}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{C6EF17FD-82CB-6C4D-B0EB-AB57E442D309}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jibo.Cloud.Tests", "OpenJibo\tests\Jibo.Cloud.Tests\Jibo.Cloud.Tests.csproj", "{C18A6AEA-FD8E-FDAF-1589-0BC2EF6C8F46}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Jibo.Cloud", "Jibo.Cloud", "{1E709A93-6AAE-CBDE-D98F-8B1F8D079AE6}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dotnet", "dotnet", "{7A0D8E3B-15D1-0621-86F9-1CAFD1E26384}"
|
||||||
|
EndProject
|
||||||
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{42A75C5C-1B56-2C7E-5D8B-C570665075F4}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jibo.Cloud.Api", "OpenJibo\src\Jibo.Cloud\dotnet\src\Jibo.Cloud.Api\Jibo.Cloud.Api.csproj", "{888E2B18-7919-73EF-DF00-AD1A4EA157FF}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jibo.Cloud.Application", "OpenJibo\src\Jibo.Cloud\dotnet\src\Jibo.Cloud.Application\Jibo.Cloud.Application.csproj", "{EEDE5906-13C3-E9FB-0AFB-27376A77F1AD}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jibo.Cloud.Domain", "OpenJibo\src\Jibo.Cloud\dotnet\src\Jibo.Cloud.Domain\Jibo.Cloud.Domain.csproj", "{6B4AD66C-CACD-D9D6-4803-33A5DB0C7F4C}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jibo.Cloud.Infrastructure", "OpenJibo\src\Jibo.Cloud\dotnet\src\Jibo.Cloud.Infrastructure\Jibo.Cloud.Infrastructure.csproj", "{5BD9420F-7E77-81A2-713B-8FDBF17C2D6E}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{4EC1F8A2-7A15-79FC-2A37-9620624156F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{4EC1F8A2-7A15-79FC-2A37-9620624156F8}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{4EC1F8A2-7A15-79FC-2A37-9620624156F8}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{4EC1F8A2-7A15-79FC-2A37-9620624156F8}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{61A125DD-6776-6FF9-D0B9-9945ADBCC0E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{61A125DD-6776-6FF9-D0B9-9945ADBCC0E1}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{61A125DD-6776-6FF9-D0B9-9945ADBCC0E1}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{61A125DD-6776-6FF9-D0B9-9945ADBCC0E1}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{C18A6AEA-FD8E-FDAF-1589-0BC2EF6C8F46}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{C18A6AEA-FD8E-FDAF-1589-0BC2EF6C8F46}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{C18A6AEA-FD8E-FDAF-1589-0BC2EF6C8F46}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{C18A6AEA-FD8E-FDAF-1589-0BC2EF6C8F46}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{888E2B18-7919-73EF-DF00-AD1A4EA157FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{888E2B18-7919-73EF-DF00-AD1A4EA157FF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{888E2B18-7919-73EF-DF00-AD1A4EA157FF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{888E2B18-7919-73EF-DF00-AD1A4EA157FF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{EEDE5906-13C3-E9FB-0AFB-27376A77F1AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{EEDE5906-13C3-E9FB-0AFB-27376A77F1AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{EEDE5906-13C3-E9FB-0AFB-27376A77F1AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{EEDE5906-13C3-E9FB-0AFB-27376A77F1AD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{6B4AD66C-CACD-D9D6-4803-33A5DB0C7F4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{6B4AD66C-CACD-D9D6-4803-33A5DB0C7F4C}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{6B4AD66C-CACD-D9D6-4803-33A5DB0C7F4C}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{6B4AD66C-CACD-D9D6-4803-33A5DB0C7F4C}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{5BD9420F-7E77-81A2-713B-8FDBF17C2D6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{5BD9420F-7E77-81A2-713B-8FDBF17C2D6E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{5BD9420F-7E77-81A2-713B-8FDBF17C2D6E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{5BD9420F-7E77-81A2-713B-8FDBF17C2D6E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(NestedProjects) = preSolution
|
||||||
|
{9CD502EA-259A-A102-F54F-DB66ECB43CCA} = {2FDD1CD9-89DA-D176-F85D-DC517FF08BF4}
|
||||||
|
{4EC1F8A2-7A15-79FC-2A37-9620624156F8} = {9CD502EA-259A-A102-F54F-DB66ECB43CCA}
|
||||||
|
{61A125DD-6776-6FF9-D0B9-9945ADBCC0E1} = {9CD502EA-259A-A102-F54F-DB66ECB43CCA}
|
||||||
|
{C6EF17FD-82CB-6C4D-B0EB-AB57E442D309} = {2FDD1CD9-89DA-D176-F85D-DC517FF08BF4}
|
||||||
|
{C18A6AEA-FD8E-FDAF-1589-0BC2EF6C8F46} = {C6EF17FD-82CB-6C4D-B0EB-AB57E442D309}
|
||||||
|
{1E709A93-6AAE-CBDE-D98F-8B1F8D079AE6} = {9CD502EA-259A-A102-F54F-DB66ECB43CCA}
|
||||||
|
{7A0D8E3B-15D1-0621-86F9-1CAFD1E26384} = {1E709A93-6AAE-CBDE-D98F-8B1F8D079AE6}
|
||||||
|
{42A75C5C-1B56-2C7E-5D8B-C570665075F4} = {7A0D8E3B-15D1-0621-86F9-1CAFD1E26384}
|
||||||
|
{888E2B18-7919-73EF-DF00-AD1A4EA157FF} = {42A75C5C-1B56-2C7E-5D8B-C570665075F4}
|
||||||
|
{EEDE5906-13C3-E9FB-0AFB-27376A77F1AD} = {42A75C5C-1B56-2C7E-5D8B-C570665075F4}
|
||||||
|
{6B4AD66C-CACD-D9D6-4803-33A5DB0C7F4C} = {42A75C5C-1B56-2C7E-5D8B-C570665075F4}
|
||||||
|
{5BD9420F-7E77-81A2-713B-8FDBF17C2D6E} = {42A75C5C-1B56-2C7E-5D8B-C570665075F4}
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {E18C2B78-D343-47FC-9314-42977AE46261}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
22
OpenJibo/docs/logging.md
Normal file
22
OpenJibo/docs/logging.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
# Logging argument!
|
||||||
|
- - -
|
||||||
|
|
||||||
|
using the new `DetailedOperationLogger` class you can do tiered logging , from level 1 -10
|
||||||
|
|
||||||
|
you can `LogStep` at any level, and it will only log if the log level is 4+
|
||||||
|
`logstate` at any level, and it will only log if the log level is 5+ (state tracking)
|
||||||
|
`logDecision` at any level, and it will only log if the log level is 3+ (decision points)
|
||||||
|
`logTiming` at any level, and it will only log if the log level is 6= (timing performance metrics)
|
||||||
|
`logPayload` at any level, and it will only log if the log level is 8+ (payload data)
|
||||||
|
`logExternalCall` at any level, and it will only log if the log level is 5+ (external service calls)
|
||||||
|
`LogMatch` at any level, and it will only log if the log level is 4+ (pattern matching)
|
||||||
|
|
||||||
|
|
||||||
|
i didnt touch the existing logging but its easy to implement the new logging system in the existing code
|
||||||
|
|
||||||
|
you can see implementations at:
|
||||||
|
- OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Telemetry/FileWebSocketTelemetrySink.cs
|
||||||
|
- OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Infrastructure/Telemetry/FileWebSocketTelemetrySink.cs
|
||||||
|
|
||||||
|
the parser is also inside :
|
||||||
|
`OpenJibo/src/Jibo.Cloud/dotnet/src/Jibo.Cloud.Api/Logging/LogLevelConfigurator.cs`
|
||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Jibo.Cloud.Api.Logging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures logging levels based on command-line arguments.
|
||||||
|
/// Higher log values = more verbose logging.
|
||||||
|
/// </summary>
|
||||||
|
public static class LogLevelConfigurator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Parses the log level from command-line arguments (format: log=N where N is 0-10).
|
||||||
|
/// Returns null if no log argument is found.
|
||||||
|
/// </summary>
|
||||||
|
public static int? ParseLogLevelFromArgs(string[] args)
|
||||||
|
{
|
||||||
|
foreach (var arg in args)
|
||||||
|
{
|
||||||
|
if (arg.StartsWith("log=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var value = arg["log=".Length..];
|
||||||
|
if (int.TryParse(value, out var level) && level >= 0)
|
||||||
|
{
|
||||||
|
return Math.Min(level, 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Configures logging level based on the numeric intensity (0-10).
|
||||||
|
/// Higher values enable more verbose logging.
|
||||||
|
/// </summary>
|
||||||
|
public static void ConfigureLogging(WebApplicationBuilder builder, int logLevel)
|
||||||
|
{
|
||||||
|
builder.Logging.ClearProviders();
|
||||||
|
builder.Logging.AddConsole();
|
||||||
|
builder.Logging.AddDebug();
|
||||||
|
|
||||||
|
var level = MapToLogLevel(logLevel);
|
||||||
|
|
||||||
|
builder.Logging.SetMinimumLevel(level);
|
||||||
|
|
||||||
|
builder.Logging.AddFilter("Microsoft.AspNetCore", logLevel >= 8 ? LogLevel.Debug : LogLevel.Warning);
|
||||||
|
builder.Logging.AddFilter("Microsoft.Hosting", logLevel >= 7 ? LogLevel.Information : LogLevel.Warning);
|
||||||
|
builder.Logging.AddFilter("System", logLevel >= 9 ? LogLevel.Debug : LogLevel.Warning);
|
||||||
|
|
||||||
|
builder.Logging.AddFilter("Jibo.Cloud", logLevel >= 5 ? LogLevel.Debug : LogLevel.Information);
|
||||||
|
builder.Logging.AddFilter("Jibo.Cloud.Application", logLevel >= 3 ? LogLevel.Debug : LogLevel.Information);
|
||||||
|
builder.Logging.AddFilter("Jibo.Cloud.Infrastructure", logLevel >= 4 ? LogLevel.Debug : LogLevel.Information);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LogLevel MapToLogLevel(int value)
|
||||||
|
{
|
||||||
|
return value switch
|
||||||
|
{
|
||||||
|
0 => LogLevel.Error,
|
||||||
|
1 => LogLevel.Warning,
|
||||||
|
2 => LogLevel.Warning,
|
||||||
|
3 => LogLevel.Information,
|
||||||
|
4 => LogLevel.Information,
|
||||||
|
5 => LogLevel.Information,
|
||||||
|
6 => LogLevel.Debug,
|
||||||
|
7 => LogLevel.Debug,
|
||||||
|
8 => LogLevel.Debug,
|
||||||
|
9 => LogLevel.Trace,
|
||||||
|
10 => LogLevel.Trace,
|
||||||
|
_ => LogLevel.Information
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Net.WebSockets;
|
using System.Net.WebSockets;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Jibo.Cloud.Api.Logging;
|
||||||
using Jibo.Cloud.Application.Abstractions;
|
using Jibo.Cloud.Application.Abstractions;
|
||||||
using Jibo.Cloud.Application.Services;
|
using Jibo.Cloud.Application.Services;
|
||||||
using Jibo.Cloud.Domain.Models;
|
using Jibo.Cloud.Domain.Models;
|
||||||
@@ -7,7 +8,13 @@ using Jibo.Cloud.Infrastructure.DependencyInjection;
|
|||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddOpenJiboCloud(builder.Configuration);
|
var logLevel = LogLevelConfigurator.ParseLogLevelFromArgs(args);
|
||||||
|
if (logLevel.HasValue)
|
||||||
|
{
|
||||||
|
LogLevelConfigurator.ConfigureLogging(builder, logLevel.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.Services.AddOpenJiboCloud(builder.Configuration, logLevel);
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
|
||||||
<ProjectReference Include="..\Jibo.Cloud.Domain\Jibo.Cloud.Domain.csproj" />
|
<ProjectReference Include="..\Jibo.Cloud.Domain\Jibo.Cloud.Domain.csproj" />
|
||||||
<ProjectReference Include="..\..\..\..\Jibo.Runtime.Abstractions\Jibo.Runtime.Abstractions.csproj" />
|
<ProjectReference Include="..\..\..\..\Jibo.Runtime.Abstractions\Jibo.Runtime.Abstractions.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace Jibo.Cloud.Application.Logging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides detailed operation logging that activates based on log intensity level.
|
||||||
|
/// Higher log levels = more detailed logging.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class DetailedOperationLogger
|
||||||
|
{
|
||||||
|
private readonly ILogger _logger;
|
||||||
|
private readonly int _configuredLogLevel;
|
||||||
|
|
||||||
|
public DetailedOperationLogger(ILogger logger, int? configuredLogLevel = null)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_configuredLogLevel = configuredLogLevel ?? 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Log method entry at Debug level when log level >= 3
|
||||||
|
/// </summary>
|
||||||
|
public void LogEntry(string methodName, params (string Key, object? Value)[] parameters)
|
||||||
|
{
|
||||||
|
if (_configuredLogLevel < 3) return;
|
||||||
|
|
||||||
|
if (_logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
var paramStr = parameters.Length > 0
|
||||||
|
? string.Join(", ", parameters.Select(p => $"{p.Key}={p.Value}"))
|
||||||
|
: "none";
|
||||||
|
_logger.LogDebug("[ENTRY] {MethodName}({Parameters})", methodName, paramStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Log method exit at Debug level when log level >= 3
|
||||||
|
/// </summary>
|
||||||
|
public void LogExit(string methodName, string? result = null)
|
||||||
|
{
|
||||||
|
if (_configuredLogLevel < 3) return;
|
||||||
|
|
||||||
|
if (_logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
var resultStr = result ?? "void";
|
||||||
|
_logger.LogDebug("[EXIT] {MethodName} -> {Result}", methodName, resultStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Log a detailed operation step at Debug level when log level >= 4
|
||||||
|
/// </summary>
|
||||||
|
public void LogStep(string operation, string step, string? details = null)
|
||||||
|
{
|
||||||
|
if (_configuredLogLevel < 4) return;
|
||||||
|
|
||||||
|
if (_logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
var detailStr = details != null ? $" | {details}" : "";
|
||||||
|
_logger.LogDebug("[STEP] {Operation}.{Step}{Details}", operation, step, detailStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Log state information at Debug level when log level >= 5
|
||||||
|
/// </summary>
|
||||||
|
public void LogState(string context, string stateName, object? value)
|
||||||
|
{
|
||||||
|
if (_configuredLogLevel < 5) return;
|
||||||
|
|
||||||
|
if (_logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("[STATE] {Context}.{StateName} = {Value}", context, stateName, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Log decision information at Information level when log level >= 3
|
||||||
|
/// </summary>
|
||||||
|
public void LogDecision(string context, string decision, string? reason = null)
|
||||||
|
{
|
||||||
|
if (_configuredLogLevel < 3) return;
|
||||||
|
|
||||||
|
if (_logger.IsEnabled(LogLevel.Information))
|
||||||
|
{
|
||||||
|
var reasonStr = reason != null ? $" (reason: {reason})" : "";
|
||||||
|
_logger.LogInformation("[DECISION] {Context}: {Decision}{Reason}", context, decision, reasonStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Log performance timing at Debug level when log level >= 6
|
||||||
|
/// </summary>
|
||||||
|
public void LogTiming(string operation, long elapsedMs)
|
||||||
|
{
|
||||||
|
if (_configuredLogLevel < 6) return;
|
||||||
|
|
||||||
|
if (_logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("[TIMING] {Operation} completed in {ElapsedMs}ms", operation, elapsedMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Log data payload at Trace level when log level >= 8
|
||||||
|
/// </summary>
|
||||||
|
public void LogPayload(string context, string dataType, int dataSize, string? preview = null)
|
||||||
|
{
|
||||||
|
if (_configuredLogLevel < 8) return;
|
||||||
|
|
||||||
|
if (_logger.IsEnabled(LogLevel.Trace))
|
||||||
|
{
|
||||||
|
var previewStr = preview != null ? $" preview: {preview}" : "";
|
||||||
|
_logger.LogTrace("[PAYLOAD] {Context} {DataType} size={Size}{Preview}", context, dataType, dataSize, previewStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Log external call at Debug level when log level >= 5
|
||||||
|
/// </summary>
|
||||||
|
public void LogExternalCall(string service, string operation, string? details = null)
|
||||||
|
{
|
||||||
|
if (_configuredLogLevel < 5) return;
|
||||||
|
|
||||||
|
if (_logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
var detailStr = details != null ? $" ({details})" : "";
|
||||||
|
_logger.LogDebug("[EXTERNAL] {Service}.{Operation}{Details}", service, operation, detailStr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Log match/pattern information at Debug level when log level >= 4
|
||||||
|
/// </summary>
|
||||||
|
public void LogMatch(string context, string pattern, string input, bool matched)
|
||||||
|
{
|
||||||
|
if (_configuredLogLevel < 4) return;
|
||||||
|
|
||||||
|
if (_logger.IsEnabled(LogLevel.Debug))
|
||||||
|
{
|
||||||
|
_logger.LogDebug("[MATCH] {Context}: Pattern '{Pattern}' against '{Input}' => {Result}",
|
||||||
|
context, pattern, input, matched ? "MATCHED" : "NO MATCH");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Jibo.Cloud.Application.Abstractions;
|
using Jibo.Cloud.Application.Abstractions;
|
||||||
|
using Jibo.Cloud.Application.Logging;
|
||||||
using Jibo.Cloud.Domain.Models;
|
using Jibo.Cloud.Domain.Models;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Jibo.Cloud.Application.Services;
|
namespace Jibo.Cloud.Application.Services;
|
||||||
|
|
||||||
public sealed class JiboCloudProtocolService(ICloudStateStore stateStore)
|
public sealed class JiboCloudProtocolService(
|
||||||
|
ICloudStateStore stateStore,
|
||||||
|
ILogger<JiboCloudProtocolService> logger)
|
||||||
{
|
{
|
||||||
|
private readonly DetailedOperationLogger _detailedLogger = new(logger);
|
||||||
private static readonly string[] AcceptedHosts =
|
private static readonly string[] AcceptedHosts =
|
||||||
[
|
[
|
||||||
"api.jibo.com",
|
"api.jibo.com",
|
||||||
@@ -16,16 +21,25 @@ public sealed class JiboCloudProtocolService(ICloudStateStore stateStore)
|
|||||||
|
|
||||||
public Task<ProtocolDispatchResult> DispatchAsync(ProtocolEnvelope envelope, CancellationToken cancellationToken = default)
|
public Task<ProtocolDispatchResult> DispatchAsync(ProtocolEnvelope envelope, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogEntry(nameof(DispatchAsync),
|
||||||
|
("method", envelope.Method),
|
||||||
|
("path", envelope.Path),
|
||||||
|
("host", envelope.HostName),
|
||||||
|
("servicePrefix", envelope.ServicePrefix),
|
||||||
|
("operation", envelope.Operation));
|
||||||
|
|
||||||
if (envelope.Method.Equals("GET", StringComparison.OrdinalIgnoreCase) &&
|
if (envelope.Method.Equals("GET", StringComparison.OrdinalIgnoreCase) &&
|
||||||
envelope.Path == "/" &&
|
envelope.Path == "/" &&
|
||||||
string.IsNullOrWhiteSpace(envelope.ServicePrefix))
|
string.IsNullOrWhiteSpace(envelope.ServicePrefix))
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogExit(nameof(DispatchAsync), "NoContent");
|
||||||
return Task.FromResult(ProtocolDispatchResult.NoContent());
|
return Task.FromResult(ProtocolDispatchResult.NoContent());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (envelope.Method.Equals("GET", StringComparison.OrdinalIgnoreCase) &&
|
if (envelope.Method.Equals("GET", StringComparison.OrdinalIgnoreCase) &&
|
||||||
envelope.Path.Equals("/health", StringComparison.OrdinalIgnoreCase))
|
envelope.Path.Equals("/health", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogExit(nameof(DispatchAsync), "Health");
|
||||||
return Task.FromResult(ProtocolDispatchResult.Ok(new { ok = true, host = envelope.HostName }));
|
return Task.FromResult(ProtocolDispatchResult.Ok(new { ok = true, host = envelope.HostName }));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,6 +59,8 @@ public sealed class JiboCloudProtocolService(ICloudStateStore stateStore)
|
|||||||
|
|
||||||
if (!AcceptedHosts.Contains(envelope.HostName, StringComparer.OrdinalIgnoreCase))
|
if (!AcceptedHosts.Contains(envelope.HostName, StringComparer.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogDecision(nameof(DispatchAsync), "HostNotAccepted", envelope.HostName);
|
||||||
|
_detailedLogger.LogExit(nameof(DispatchAsync), "NotAccepted");
|
||||||
return Task.FromResult(ProtocolDispatchResult.Ok(new
|
return Task.FromResult(ProtocolDispatchResult.Ok(new
|
||||||
{
|
{
|
||||||
ok = true,
|
ok = true,
|
||||||
@@ -53,26 +69,32 @@ public sealed class JiboCloudProtocolService(ICloudStateStore stateStore)
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_detailedLogger.LogStep(nameof(DispatchAsync), "ServicePrefixResolved", $"prefix={envelope.ServicePrefix}, operation={envelope.Operation}");
|
||||||
|
|
||||||
var servicePrefix = envelope.ServicePrefix ?? string.Empty;
|
var servicePrefix = envelope.ServicePrefix ?? string.Empty;
|
||||||
var operation = envelope.Operation ?? string.Empty;
|
var operation = envelope.Operation ?? string.Empty;
|
||||||
|
|
||||||
if (servicePrefix.StartsWith("Log_", StringComparison.OrdinalIgnoreCase))
|
if (servicePrefix.StartsWith("Log_", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogStep(nameof(DispatchAsync), "HandlerSelected", "Log");
|
||||||
return Task.FromResult(HandleLog(operation, envelope));
|
return Task.FromResult(HandleLog(operation, envelope));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (servicePrefix.StartsWith("Backup_", StringComparison.OrdinalIgnoreCase))
|
if (servicePrefix.StartsWith("Backup_", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogStep(nameof(DispatchAsync), "HandlerSelected", "Backup");
|
||||||
return Task.FromResult(HandleBackup(operation));
|
return Task.FromResult(HandleBackup(operation));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (servicePrefix.StartsWith("Account_", StringComparison.OrdinalIgnoreCase))
|
if (servicePrefix.StartsWith("Account_", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogStep(nameof(DispatchAsync), "HandlerSelected", "Account");
|
||||||
return Task.FromResult(HandleAccount(operation, envelope));
|
return Task.FromResult(HandleAccount(operation, envelope));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (servicePrefix.StartsWith("Notification_", StringComparison.OrdinalIgnoreCase))
|
if (servicePrefix.StartsWith("Notification_", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogStep(nameof(DispatchAsync), "HandlerSelected", "Notification");
|
||||||
return Task.FromResult(HandleNotification(operation, envelope));
|
return Task.FromResult(HandleNotification(operation, envelope));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -98,6 +120,7 @@ public sealed class JiboCloudProtocolService(ICloudStateStore stateStore)
|
|||||||
|
|
||||||
if (servicePrefix.StartsWith("Robot_", StringComparison.OrdinalIgnoreCase))
|
if (servicePrefix.StartsWith("Robot_", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogStep(nameof(DispatchAsync), "HandlerSelected", "Robot");
|
||||||
return Task.FromResult(HandleRobot(operation, envelope));
|
return Task.FromResult(HandleRobot(operation, envelope));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +129,8 @@ public sealed class JiboCloudProtocolService(ICloudStateStore stateStore)
|
|||||||
return Task.FromResult(HandleUpdate(operation, envelope));
|
return Task.FromResult(HandleUpdate(operation, envelope));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_detailedLogger.LogDecision(nameof(DispatchAsync), "UnknownHandler", $"{servicePrefix}.{operation}");
|
||||||
|
_detailedLogger.LogExit(nameof(DispatchAsync), "DefaultResponse");
|
||||||
return Task.FromResult(ProtocolDispatchResult.Ok(new
|
return Task.FromResult(ProtocolDispatchResult.Ok(new
|
||||||
{
|
{
|
||||||
ok = true,
|
ok = true,
|
||||||
|
|||||||
@@ -1,19 +1,31 @@
|
|||||||
using Jibo.Cloud.Application.Abstractions;
|
using Jibo.Cloud.Application.Abstractions;
|
||||||
|
using Jibo.Cloud.Application.Logging;
|
||||||
using Jibo.Runtime.Abstractions;
|
using Jibo.Runtime.Abstractions;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Jibo.Cloud.Application.Services;
|
namespace Jibo.Cloud.Application.Services;
|
||||||
|
|
||||||
public sealed class JiboInteractionService(
|
public sealed class JiboInteractionService(
|
||||||
JiboExperienceContentCache contentCache,
|
JiboExperienceContentCache contentCache,
|
||||||
IJiboRandomizer randomizer)
|
IJiboRandomizer randomizer,
|
||||||
|
ILogger<JiboInteractionService> logger)
|
||||||
{
|
{
|
||||||
|
private readonly DetailedOperationLogger _detailedLogger = new(logger);
|
||||||
public async Task<JiboInteractionDecision> BuildDecisionAsync(TurnContext turn, CancellationToken cancellationToken = default)
|
public async Task<JiboInteractionDecision> BuildDecisionAsync(TurnContext turn, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogEntry(nameof(BuildDecisionAsync),
|
||||||
|
("transcript", turn.NormalizedTranscript ?? turn.RawTranscript),
|
||||||
|
("inputMode", turn.InputMode),
|
||||||
|
("sourceKind", turn.SourceKind));
|
||||||
|
|
||||||
var catalog = await contentCache.GetCatalogAsync(cancellationToken);
|
var catalog = await contentCache.GetCatalogAsync(cancellationToken);
|
||||||
var transcript = (turn.NormalizedTranscript ?? turn.RawTranscript ?? string.Empty).Trim();
|
var transcript = (turn.NormalizedTranscript ?? turn.RawTranscript ?? string.Empty).Trim();
|
||||||
var lowered = transcript.ToLowerInvariant();
|
var lowered = transcript.ToLowerInvariant();
|
||||||
|
|
||||||
|
_detailedLogger.LogState(nameof(BuildDecisionAsync), "NormalizedTranscript", transcript);
|
||||||
|
_detailedLogger.LogState(nameof(BuildDecisionAsync), "ClientIntent", turn.Attributes.TryGetValue("clientIntent", out var ci) ? ci : null);
|
||||||
var referenceLocalTime = TryResolveReferenceLocalTime(turn);
|
var referenceLocalTime = TryResolveReferenceLocalTime(turn);
|
||||||
var clientIntent = turn.Attributes.TryGetValue("clientIntent", out var rawClientIntent)
|
var clientIntent = turn.Attributes.TryGetValue("clientIntent", out var rawClientIntent)
|
||||||
? rawClientIntent?.ToString()
|
? rawClientIntent?.ToString()
|
||||||
@@ -40,7 +52,10 @@ public sealed class JiboInteractionService(
|
|||||||
isYesNoTurn,
|
isYesNoTurn,
|
||||||
isTimerValueTurn,
|
isTimerValueTurn,
|
||||||
isAlarmValueTurn);
|
isAlarmValueTurn);
|
||||||
return semanticIntent switch
|
|
||||||
|
_detailedLogger.LogDecision(nameof(BuildDecisionAsync), "SemanticIntentResolved", semanticIntent);
|
||||||
|
|
||||||
|
var decision = semanticIntent switch
|
||||||
{
|
{
|
||||||
"joke" => BuildJokeDecision(catalog),
|
"joke" => BuildJokeDecision(catalog),
|
||||||
"dance" => BuildRandomDanceDecision(catalog),
|
"dance" => BuildRandomDanceDecision(catalog),
|
||||||
@@ -85,6 +100,9 @@ public sealed class JiboInteractionService(
|
|||||||
"news" => BuildNewsDecision(catalog),
|
"news" => BuildNewsDecision(catalog),
|
||||||
_ => new JiboInteractionDecision("chat", BuildGenericReply(catalog, transcript, lowered))
|
_ => new JiboInteractionDecision("chat", BuildGenericReply(catalog, transcript, lowered))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_detailedLogger.LogExit(nameof(BuildDecisionAsync), $"intent={decision.IntentName}, skill={decision.SkillName ?? "null"}");
|
||||||
|
return decision;
|
||||||
}
|
}
|
||||||
|
|
||||||
private JiboInteractionDecision BuildJokeDecision(JiboExperienceCatalog catalog)
|
private JiboInteractionDecision BuildJokeDecision(JiboExperienceCatalog catalog)
|
||||||
|
|||||||
@@ -1,53 +1,83 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Jibo.Cloud.Application.Abstractions;
|
using Jibo.Cloud.Application.Abstractions;
|
||||||
|
using Jibo.Cloud.Application.Logging;
|
||||||
using Jibo.Cloud.Domain.Models;
|
using Jibo.Cloud.Domain.Models;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Jibo.Cloud.Application.Services;
|
namespace Jibo.Cloud.Application.Services;
|
||||||
|
|
||||||
public sealed class JiboWebSocketService(
|
public sealed class JiboWebSocketService(
|
||||||
ICloudStateStore stateStore,
|
ICloudStateStore stateStore,
|
||||||
IWebSocketTelemetrySink telemetrySink,
|
IWebSocketTelemetrySink telemetrySink,
|
||||||
WebSocketTurnFinalizationService turnFinalizationService)
|
WebSocketTurnFinalizationService turnFinalizationService,
|
||||||
|
ILogger<JiboWebSocketService> logger)
|
||||||
{
|
{
|
||||||
|
private readonly DetailedOperationLogger _detailedLogger = new(logger);
|
||||||
public CloudSession GetOrCreateSession(WebSocketMessageEnvelope envelope)
|
public CloudSession GetOrCreateSession(WebSocketMessageEnvelope envelope)
|
||||||
{
|
{
|
||||||
return stateStore.FindSessionByToken(envelope.Token ?? string.Empty) ??
|
_detailedLogger.LogEntry(nameof(GetOrCreateSession),
|
||||||
|
("token", envelope.Token),
|
||||||
|
("kind", envelope.Kind),
|
||||||
|
("host", envelope.HostName));
|
||||||
|
|
||||||
|
var session = stateStore.FindSessionByToken(envelope.Token ?? string.Empty) ??
|
||||||
stateStore.OpenSession(envelope.Kind, null, envelope.Token, envelope.HostName, envelope.Path);
|
stateStore.OpenSession(envelope.Kind, null, envelope.Token, envelope.HostName, envelope.Path);
|
||||||
|
|
||||||
|
_detailedLogger.LogExit(nameof(GetOrCreateSession), $"sessionId={session.SessionId}");
|
||||||
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyList<WebSocketReply>> HandleMessageAsync(WebSocketMessageEnvelope envelope, CancellationToken cancellationToken = default)
|
public async Task<IReadOnlyList<WebSocketReply>> HandleMessageAsync(WebSocketMessageEnvelope envelope, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogEntry(nameof(HandleMessageAsync),
|
||||||
|
("isBinary", envelope.IsBinary),
|
||||||
|
("textLength", envelope.Text?.Length ?? 0),
|
||||||
|
("binaryLength", envelope.Binary?.Length ?? 0));
|
||||||
|
|
||||||
var session = GetOrCreateSession(envelope);
|
var session = GetOrCreateSession(envelope);
|
||||||
session.LastSeenUtc = DateTimeOffset.UtcNow;
|
session.LastSeenUtc = DateTimeOffset.UtcNow;
|
||||||
|
|
||||||
|
_detailedLogger.LogState(nameof(HandleMessageAsync), "SessionId", session.SessionId);
|
||||||
|
_detailedLogger.LogState(nameof(HandleMessageAsync), "SessionKind", session.Kind);
|
||||||
|
|
||||||
if (envelope.IsBinary)
|
if (envelope.IsBinary)
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogStep(nameof(HandleMessageAsync), "ProcessingBinaryAudio", $"bytes={envelope.Binary?.Length ?? 0}");
|
||||||
var replies = await turnFinalizationService.HandleBinaryAudioAsync(session, envelope, cancellationToken);
|
var replies = await turnFinalizationService.HandleBinaryAudioAsync(session, envelope, cancellationToken);
|
||||||
await telemetrySink.RecordTurnEventAsync(envelope, session, "binary_audio_received", new Dictionary<string, object?>
|
await telemetrySink.RecordTurnEventAsync(envelope, session, "binary_audio_received", new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
["bytes"] = envelope.Binary?.Length ?? 0
|
["bytes"] = envelope.Binary?.Length ?? 0
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
_detailedLogger.LogPayload(nameof(HandleMessageAsync), "BinaryAudio", envelope.Binary?.Length ?? 0, null);
|
||||||
|
_detailedLogger.LogExit(nameof(HandleMessageAsync), $"replies={replies.Count}");
|
||||||
return replies;
|
return replies;
|
||||||
}
|
}
|
||||||
|
|
||||||
var parsedType = ReadMessageType(envelope.Text);
|
var parsedType = ReadMessageType(envelope.Text);
|
||||||
|
_detailedLogger.LogDecision(nameof(HandleMessageAsync), "MessageTypeResolved", parsedType);
|
||||||
|
|
||||||
session.LastMessageType = parsedType;
|
session.LastMessageType = parsedType;
|
||||||
WebSocketTurnFinalizationService.ObserveIncomingMessage(session, envelope.Text);
|
WebSocketTurnFinalizationService.ObserveIncomingMessage(session, envelope.Text);
|
||||||
|
_detailedLogger.LogState(nameof(HandleMessageAsync), "LastMessageType", parsedType);
|
||||||
|
|
||||||
switch (parsedType)
|
switch (parsedType)
|
||||||
{
|
{
|
||||||
case "CONTEXT":
|
case "CONTEXT":
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogStep(nameof(HandleMessageAsync), "ProcessingContext", $"transId={session.TurnState.TransId}");
|
||||||
var replies = await turnFinalizationService.HandleContextAsync(session, envelope, cancellationToken);
|
var replies = await turnFinalizationService.HandleContextAsync(session, envelope, cancellationToken);
|
||||||
await telemetrySink.RecordTurnEventAsync(envelope, session, "context_received", new Dictionary<string, object?>
|
await telemetrySink.RecordTurnEventAsync(envelope, session, "context_received", new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
["transID"] = session.TurnState.TransId
|
["transID"] = session.TurnState.TransId
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
_detailedLogger.LogExit(nameof(HandleMessageAsync), $"replies={replies.Count}");
|
||||||
return replies;
|
return replies;
|
||||||
}
|
}
|
||||||
case "LISTEN":
|
case "LISTEN":
|
||||||
{
|
{
|
||||||
var replies = ContainsInlineTurnPayload(envelope.Text)
|
var hasInlinePayload = ContainsInlineTurnPayload(envelope.Text);
|
||||||
|
_detailedLogger.LogDecision(nameof(HandleMessageAsync), "ListenHandlerSelected", hasInlinePayload ? "inline_turn" : "listen_setup");
|
||||||
|
var replies = hasInlinePayload
|
||||||
? await turnFinalizationService.HandleTurnAsync(session, envelope, parsedType, cancellationToken)
|
? await turnFinalizationService.HandleTurnAsync(session, envelope, parsedType, cancellationToken)
|
||||||
: WebSocketTurnFinalizationService.HandleListenSetup(session, envelope);
|
: WebSocketTurnFinalizationService.HandleListenSetup(session, envelope);
|
||||||
await telemetrySink.RecordTurnEventAsync(envelope, session, "turn_processed", new Dictionary<string, object?>
|
await telemetrySink.RecordTurnEventAsync(envelope, session, "turn_processed", new Dictionary<string, object?>
|
||||||
@@ -57,10 +87,12 @@ public sealed class JiboWebSocketService(
|
|||||||
["transcript"] = session.LastTranscript,
|
["transcript"] = session.LastTranscript,
|
||||||
["intent"] = session.LastIntent
|
["intent"] = session.LastIntent
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
_detailedLogger.LogExit(nameof(HandleMessageAsync), $"replies={replies.Count}");
|
||||||
return replies;
|
return replies;
|
||||||
}
|
}
|
||||||
case "CLIENT_NLU" or "CLIENT_ASR":
|
case "CLIENT_NLU" or "CLIENT_ASR":
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogStep(nameof(HandleMessageAsync), "ProcessingTurn", $"type={parsedType}");
|
||||||
var replies = await turnFinalizationService.HandleTurnAsync(session, envelope, parsedType, cancellationToken);
|
var replies = await turnFinalizationService.HandleTurnAsync(session, envelope, parsedType, cancellationToken);
|
||||||
await telemetrySink.RecordTurnEventAsync(envelope, session, "turn_processed", new Dictionary<string, object?>
|
await telemetrySink.RecordTurnEventAsync(envelope, session, "turn_processed", new Dictionary<string, object?>
|
||||||
{
|
{
|
||||||
@@ -69,9 +101,12 @@ public sealed class JiboWebSocketService(
|
|||||||
["transcript"] = session.LastTranscript,
|
["transcript"] = session.LastTranscript,
|
||||||
["intent"] = session.LastIntent
|
["intent"] = session.LastIntent
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
|
_detailedLogger.LogExit(nameof(HandleMessageAsync), $"replies={replies.Count}");
|
||||||
return replies;
|
return replies;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
_detailedLogger.LogDecision(nameof(HandleMessageAsync), "UnknownMessageType", $"type={parsedType}");
|
||||||
|
_detailedLogger.LogExit(nameof(HandleMessageAsync), "empty");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,20 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Jibo.Cloud.Application.Abstractions;
|
using Jibo.Cloud.Application.Abstractions;
|
||||||
|
using Jibo.Cloud.Application.Logging;
|
||||||
using Jibo.Cloud.Domain.Models;
|
using Jibo.Cloud.Domain.Models;
|
||||||
using Jibo.Runtime.Abstractions;
|
using Jibo.Runtime.Abstractions;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Jibo.Cloud.Application.Services;
|
namespace Jibo.Cloud.Application.Services;
|
||||||
|
|
||||||
public sealed partial class WebSocketTurnFinalizationService(
|
public sealed partial class WebSocketTurnFinalizationService(
|
||||||
IConversationBroker conversationBroker,
|
IConversationBroker conversationBroker,
|
||||||
ISttStrategySelector sttStrategySelector,
|
ISttStrategySelector sttStrategySelector,
|
||||||
ITurnTelemetrySink sink
|
ITurnTelemetrySink sink,
|
||||||
)
|
ILogger<WebSocketTurnFinalizationService> logger)
|
||||||
{
|
{
|
||||||
|
private readonly DetailedOperationLogger _detailedLogger = new(logger);
|
||||||
private const int AutoFinalizeMinBufferedAudioBytes = 12000;
|
private const int AutoFinalizeMinBufferedAudioBytes = 12000;
|
||||||
private const int AutoFinalizeMinBufferedAudioChunks = 4;
|
private const int AutoFinalizeMinBufferedAudioChunks = 4;
|
||||||
private static readonly TimeSpan AutoFinalizeMinTurnAge = TimeSpan.FromMilliseconds(1400);
|
private static readonly TimeSpan AutoFinalizeMinTurnAge = TimeSpan.FromMilliseconds(1400);
|
||||||
@@ -36,12 +39,18 @@ public sealed partial class WebSocketTurnFinalizationService(
|
|||||||
WebSocketMessageEnvelope envelope,
|
WebSocketMessageEnvelope envelope,
|
||||||
CancellationToken cancellationToken = default)
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogEntry(nameof(HandleBinaryAudioAsync),
|
||||||
|
("sessionId", session.SessionId),
|
||||||
|
("audioBytes", envelope.Binary?.Length ?? 0));
|
||||||
|
|
||||||
var turnState = session.TurnState;
|
var turnState = session.TurnState;
|
||||||
if (ShouldIgnoreLateAudio(session) || !turnState.AwaitingTurnCompletion &&
|
if (ShouldIgnoreLateAudio(session) || !turnState.AwaitingTurnCompletion &&
|
||||||
!session.FollowUpOpen &&
|
!session.FollowUpOpen &&
|
||||||
!turnState.SawListen &&
|
!turnState.SawListen &&
|
||||||
!string.IsNullOrWhiteSpace(turnState.TransId))
|
!string.IsNullOrWhiteSpace(turnState.TransId))
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogDecision(nameof(HandleBinaryAudioAsync), "IgnoringLateAudio", $"transId={turnState.TransId}");
|
||||||
|
_detailedLogger.LogExit(nameof(HandleBinaryAudioAsync), "empty");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -59,9 +68,14 @@ public sealed partial class WebSocketTurnFinalizationService(
|
|||||||
|
|
||||||
if (ShouldAutoFinalize(session))
|
if (ShouldAutoFinalize(session))
|
||||||
{
|
{
|
||||||
return await FinalizeTurnAsync(session, envelope, "AUTO_FINALIZE", allowFallbackOnMissingTranscript: true, cancellationToken);
|
_detailedLogger.LogDecision(nameof(HandleBinaryAudioAsync), "AutoFinalizing", $"chunks={turnState.BufferedAudioChunkCount}, bytes={turnState.BufferedAudioBytes}");
|
||||||
|
var replies = await FinalizeTurnAsync(session, envelope, "AUTO_FINALIZE", allowFallbackOnMissingTranscript: true, cancellationToken);
|
||||||
|
_detailedLogger.LogExit(nameof(HandleBinaryAudioAsync), $"replies={replies.Count}");
|
||||||
|
return replies;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_detailedLogger.LogStep(nameof(HandleBinaryAudioAsync), "BufferingAudio", $"chunks={turnState.BufferedAudioChunkCount}, bytes={turnState.BufferedAudioBytes}");
|
||||||
|
_detailedLogger.LogExit(nameof(HandleBinaryAudioAsync), "empty-awaiting-more");
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ namespace Jibo.Cloud.Infrastructure.DependencyInjection;
|
|||||||
|
|
||||||
public static class ServiceCollectionExtensions
|
public static class ServiceCollectionExtensions
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddOpenJiboCloud(this IServiceCollection services, IConfiguration? configuration = null)
|
public static IServiceCollection AddOpenJiboCloud(this IServiceCollection services, IConfiguration? configuration = null, int? logLevel = null)
|
||||||
{
|
{
|
||||||
var sttOptions = new BufferedAudioSttOptions();
|
var sttOptions = new BufferedAudioSttOptions();
|
||||||
if (configuration is not null)
|
if (configuration is not null)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Jibo.Cloud.Application.Abstractions;
|
using Jibo.Cloud.Application.Abstractions;
|
||||||
|
using Jibo.Cloud.Application.Logging;
|
||||||
using Jibo.Cloud.Domain.Models;
|
using Jibo.Cloud.Domain.Models;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
@@ -11,6 +12,7 @@ public sealed class FileWebSocketTelemetrySink(
|
|||||||
ILogger<FileWebSocketTelemetrySink> logger,
|
ILogger<FileWebSocketTelemetrySink> logger,
|
||||||
IOptions<WebSocketTelemetryOptions> options) : IWebSocketTelemetrySink
|
IOptions<WebSocketTelemetryOptions> options) : IWebSocketTelemetrySink
|
||||||
{
|
{
|
||||||
|
private readonly DetailedOperationLogger _detailedLogger = new(logger);
|
||||||
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
|
private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
|
||||||
{
|
{
|
||||||
WriteIndented = true
|
WriteIndented = true
|
||||||
@@ -21,8 +23,14 @@ public sealed class FileWebSocketTelemetrySink(
|
|||||||
|
|
||||||
public async Task RecordConnectionOpenedAsync(WebSocketMessageEnvelope envelope, CloudSession session, CancellationToken cancellationToken = default)
|
public async Task RecordConnectionOpenedAsync(WebSocketMessageEnvelope envelope, CloudSession session, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogEntry(nameof(RecordConnectionOpenedAsync),
|
||||||
|
("sessionId", session.SessionId),
|
||||||
|
("host", envelope.HostName),
|
||||||
|
("kind", envelope.Kind));
|
||||||
|
|
||||||
if (!options.Value.Enabled)
|
if (!options.Value.Enabled)
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogStep(nameof(RecordConnectionOpenedAsync), "TelemetryDisabled");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,10 +50,20 @@ public sealed class FileWebSocketTelemetrySink(
|
|||||||
|
|
||||||
public Task RecordInboundAsync(WebSocketMessageEnvelope envelope, CloudSession session, string? messageType, CancellationToken cancellationToken = default)
|
public Task RecordInboundAsync(WebSocketMessageEnvelope envelope, CloudSession session, string? messageType, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
return !options.Value.Enabled
|
_detailedLogger.LogEntry(nameof(RecordInboundAsync),
|
||||||
? Task.CompletedTask
|
("sessionId", session.SessionId),
|
||||||
: WriteRecordAsync(BuildRecord("message_in", envelope, session, messageType, "in", null, null),
|
("messageType", messageType),
|
||||||
cancellationToken);
|
("textLength", envelope.Text?.Length ?? 0),
|
||||||
|
("binaryLength", envelope.Binary?.Length ?? 0));
|
||||||
|
|
||||||
|
if (!options.Value.Enabled)
|
||||||
|
{
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
_detailedLogger.LogPayload(nameof(RecordInboundAsync), "WebSocketMessage", envelope.Text?.Length ?? envelope.Binary?.Length ?? 0, envelope.Text?[..Math.Min(100, envelope.Text?.Length ?? 0)]);
|
||||||
|
|
||||||
|
return WriteRecordAsync(BuildRecord("message_in", envelope, session, messageType, "in", null, null), cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Task RecordTurnEventAsync(WebSocketMessageEnvelope envelope, CloudSession session, string eventType, IReadOnlyDictionary<string, object?> details, CancellationToken cancellationToken = default)
|
public Task RecordTurnEventAsync(WebSocketMessageEnvelope envelope, CloudSession session, string eventType, IReadOnlyDictionary<string, object?> details, CancellationToken cancellationToken = default)
|
||||||
@@ -58,11 +76,17 @@ public sealed class FileWebSocketTelemetrySink(
|
|||||||
|
|
||||||
public async Task RecordOutboundAsync(WebSocketMessageEnvelope envelope, CloudSession session, IReadOnlyList<WebSocketReply> replies, CancellationToken cancellationToken = default)
|
public async Task RecordOutboundAsync(WebSocketMessageEnvelope envelope, CloudSession session, IReadOnlyList<WebSocketReply> replies, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
|
_detailedLogger.LogEntry(nameof(RecordOutboundAsync),
|
||||||
|
("sessionId", session.SessionId),
|
||||||
|
("replyCount", replies.Count));
|
||||||
|
|
||||||
if (!options.Value.Enabled)
|
if (!options.Value.Enabled)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_detailedLogger.LogState(nameof(RecordOutboundAsync), "ReplyCount", replies.Count);
|
||||||
|
|
||||||
var replyTypes = replies
|
var replyTypes = replies
|
||||||
.Select(reply => ReadReplyType(reply.Text))
|
.Select(reply => ReadReplyType(reply.Text))
|
||||||
.Where(type => !string.IsNullOrWhiteSpace(type))
|
.Where(type => !string.IsNullOrWhiteSpace(type))
|
||||||
|
|||||||
Reference in New Issue
Block a user