diff --git a/src/Infrastructure/BotSharp.Core/Infrastructures/LogAttributes/FnExceptionLogAttribute.cs b/src/Infrastructure/BotSharp.Core/Infrastructures/LogAttributes/FnExceptionLogAttribute.cs
new file mode 100644
index 000000000..adf7a8fef
--- /dev/null
+++ b/src/Infrastructure/BotSharp.Core/Infrastructures/LogAttributes/FnExceptionLogAttribute.cs
@@ -0,0 +1,187 @@
+using BotSharp.Abstraction.Functions;
+using Microsoft.Extensions.Logging.Abstractions;
+using Rougamo;
+using Rougamo.Context;
+using Rougamo.Metadatas;
+using System.Reflection;
+
+namespace BotSharp.Core.Infrastructures.Log;
+
+///
+/// Use Rougamo-based logging attribute to captures method exception details, parameters, and BotSharp-specific context
+/// This attribute can be used across all BotSharp plugins for consistent function logging.
+///
+[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
+[Advice(Feature.OnException)]
+public class FnExceptionLogAttribute : AsyncMoAttribute
+{
+ private readonly bool _logArguments;
+
+ public FnExceptionLogAttribute(
+ bool logArguments = true)
+ {
+ _logArguments = logArguments;
+ }
+
+ public override async ValueTask OnExceptionAsync(MethodContext context)
+ {
+ var logger = GetLogger(context);
+ var functionContext = GetFunctionContext(context);
+ LogMethodError(logger, context, functionContext, context?.Exception);
+
+ await ValueTask.CompletedTask;
+ }
+
+ private ILogger? GetLogger(MethodContext context)
+ {
+ try
+ {
+ var target = context.Target;
+ var targetType = target?.GetType();
+
+ if (targetType == null) return NullLogger.Instance;
+
+ var loggerField = targetType?.GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
+ .FirstOrDefault(f => f.FieldType == typeof(ILogger) ||
+ (f.FieldType.IsGenericType &&
+ f.FieldType.GetGenericTypeDefinition() == typeof(ILogger<>)));
+
+ if (loggerField != null)
+ {
+ return loggerField.GetValue(target) as ILogger;
+ }
+
+ var serviceProviderField = targetType?.GetFields(System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
+ .FirstOrDefault(f => f.FieldType == typeof(IServiceProvider));
+
+ if (serviceProviderField != null)
+ {
+ var serviceProvider = serviceProviderField.GetValue(target) as IServiceProvider;
+ var loggerFactory = serviceProvider?.GetService();
+ return loggerFactory?.CreateLogger(targetType) ??
+ NullLogger.Instance;
+ }
+
+ return NullLogger.Instance;
+ }
+ catch
+ {
+ return NullLogger.Instance;
+ }
+ }
+
+ private FunctionContext GetFunctionContext(MethodContext context)
+ {
+ var functionContext = new FunctionContext();
+
+ try
+ {
+ var target = context.Target;
+ var targetType = target?.GetType();
+ functionContext.MethodFullName = $"{targetType?.Name}.{context.Method.Name}";
+
+ var serviceProviderField = targetType?.GetFields(BindingFlags.NonPublic | BindingFlags.Instance)
+ .FirstOrDefault(f => f.FieldType == typeof(IServiceProvider));
+
+ if (serviceProviderField != null)
+ {
+ var serviceProvider = serviceProviderField.GetValue(target) as IServiceProvider;
+ var stateService = serviceProvider?.GetService();
+
+ if (stateService != null)
+ {
+ functionContext.ConversationId = stateService.GetConversationId() ?? "unknown";
+ functionContext.Channel = stateService.GetState("channel", "unknown");
+ functionContext.CurrentAgentId = stateService.GetState("current_agent_id", string.Empty);
+ }
+ }
+
+ if (target is IFunctionCallback callback)
+ {
+ functionContext.FunctionName = callback.Name ?? "unknown";
+ }
+
+ // Enhanced message argument processing
+ if (context.Arguments?.FirstOrDefault(arg => arg is RoleDialogModel) is RoleDialogModel messageArg)
+ {
+ functionContext.MessageId = messageArg.MessageId ?? "unknown";
+ functionContext.CurrentAgentId = messageArg.CurrentAgentId ?? functionContext.CurrentAgentId;
+ functionContext.FunctionName = messageArg.FunctionName ?? functionContext.FunctionName;
+ }
+ }
+ catch (Exception ex)
+ {
+ functionContext.ContextError = $"Context extraction failed: {ex.Message}";
+ }
+
+ return functionContext;
+ }
+
+ private void LogMethodError(ILogger? logger, MethodContext context, FunctionContext functionContext, Exception? ex)
+ {
+ if (logger == null || !logger.IsEnabled(LogLevel.Error)) return;
+
+ var argumentsSummary = string.Empty;
+ if (_logArguments && context?.Arguments?.Length > 0)
+ {
+ argumentsSummary = string.Join(", ", context.Arguments.Select((arg, i) =>
+ $"arg{i}: {GetArgumentSummary(arg)}"));
+ }
+
+ logger.LogError(ex,
+ "[FUNCTION_ERROR] {MethodName} | ConvId: {ConversationId} | Channel: {Channel} | AgentId: {AgentId} | Function: {FunctionName} | MsgId: {MessageId} | Exception: {ExceptionType} | Message: {ExceptionMessage}{ArgumentsInfo}",
+ functionContext.MethodFullName,
+ functionContext.ConversationId,
+ functionContext.Channel,
+ functionContext.CurrentAgentId,
+ functionContext.FunctionName,
+ functionContext.MessageId,
+ ex?.GetType().Name ?? "Unknown",
+ ex?.Message ?? "No message",
+ !string.IsNullOrEmpty(argumentsSummary) ? $" | Args: [{argumentsSummary}]" : "");
+ }
+
+ private string GetArgumentSummary(object arg)
+ {
+ if (arg == null) return "null";
+
+ try
+ {
+ if (arg is string str)
+ {
+ return $"\"{str}\"";
+ }
+
+ if (arg is RoleDialogModel message)
+ {
+ return $"RoleDialogModel(Role: {message.Role}, Content: {message.Content})";
+ }
+
+ if (arg.GetType().IsPrimitive || arg is decimal || arg is DateTime)
+ {
+ return arg.ToString();
+ }
+
+ var json = JsonSerializer.Serialize(arg);
+ return json;
+ }
+ catch
+ {
+ return $"{arg.GetType().Name}(...)";
+ }
+ }
+
+ ///
+ /// Function context with additional metadata
+ ///
+ private class FunctionContext
+ {
+ public string ConversationId { get; set; } = string.Empty;
+ public string Channel { get; set; } = string.Empty;
+ public string CurrentAgentId { get; set; } = string.Empty;
+ public string FunctionName { get; set; } = string.Empty;
+ public string MessageId { get; set; } = string.Empty;
+ public string ContextError { get; set; } = string.Empty;
+ public string MethodFullName { get; set; } = string.Empty;
+ }
+}
\ No newline at end of file