咨询:WCF+XPO+DevExpress项目中客户端操作服务端统一日志方案
解决方案:基于XPO的WCF服务统一日志系统搭建
针对你遇到的XPO非契约方法(如Session.Save)无法通过WCF的IOperationInvoker捕获、客户端操作日志无法同步到服务端的问题,可以从XPO扩展拦截和WCF日志联动两个方向入手,以下是具体实现方案:
一、服务端:捕获WCF方法调用与XPO数据库操作日志
1. WCF方法调用日志(含耗时)
通过实现IDispatchMessageInspector拦截WCF请求,记录方法名称、调用时间、耗时及上下文信息:
public class ServiceCallInspector : IDispatchMessageInspector { public object AfterReceiveRequest(ref Message request, IClientChannel channel, InstanceContext instanceContext) { // 获取当前调用的方法名 var operationName = OperationContext.Current.IncomingMessageHeaders.Action.Split('/').Last(); var startTime = DateTime.UtcNow; // 将上下文信息存入OperationContext,用于后续关联XPO日志 OperationContext.Current.Items["CorrelationId"] = Guid.NewGuid().ToString(); OperationContext.Current.Items["OperationName"] = operationName; OperationContext.Current.Items["StartTime"] = startTime; return null; } public void BeforeSendReply(ref Message reply, object correlationState) { var startTime = (DateTime)OperationContext.Current.Items["StartTime"]; var elapsedMs = (DateTime.UtcNow - startTime).TotalMilliseconds; var operationName = (string)OperationContext.Current.Items["OperationName"]; var correlationId = (string)OperationContext.Current.Items["CorrelationId"]; // 记录WCF方法调用日志 var logEntry = new LogEntry { CorrelationId = correlationId, OperationType = "WcfMethodCall", Details = operationName, ElapsedMilliseconds = (long)elapsedMs, Timestamp = DateTime.UtcNow, Source = "Server" }; LogManager.PersistLog(logEntry); } }
将该拦截器注册到WCF服务行为中,即可自动捕获所有契约方法的调用日志。
2. XPO数据库操作日志(含Session.Save等内部操作)
XPO的核心操作无法通过WCF拦截,需从XPO本身扩展:
方案A:自定义Session子类,重写关键方法
通过继承Session并重写Save、CommitTransaction等方法,嵌入日志逻辑:
public class LoggingSession : Session { public LoggingSession(IDataStore dataStore) : base(dataStore) { Committed += OnSessionCommitted; } private string GetCorrelationId() { return OperationContext.Current?.Items["CorrelationId"] as string ?? Guid.NewGuid().ToString(); } public override void Save(object theObject) { var stopwatch = Stopwatch.StartNew(); base.Save(theObject); stopwatch.Stop(); var classInfo = GetClassInfo(theObject.GetType()); var logEntry = new LogEntry { CorrelationId = GetCorrelationId(), OperationType = "XpoSave", ObjectType = classInfo.FullName, ObjectId = classInfo.KeyProperty.GetValue(theObject)?.ToString(), ElapsedMilliseconds = stopwatch.ElapsedMilliseconds, Timestamp = DateTime.UtcNow, Source = "Server" }; LogManager.PersistLog(logEntry); } private void OnSessionCommitted(object sender, EventArgs e) { var logEntry = new LogEntry { CorrelationId = GetCorrelationId(), OperationType = "XpoTransactionCommit", Timestamp = DateTime.UtcNow, Source = "Server" }; LogManager.PersistLog(logEntry); } }
服务端所有WCF服务实例统一使用LoggingSession作为工作单元,即可捕获所有XPO操作。
方案B:自定义DataStore拦截SQL语句
若需记录底层SQL执行细节,可实现IDataStore包装原生DataStore,拦截所有SQL操作:
public class LoggingDataStore : IDataStore { private readonly IDataStore _innerStore; public LoggingDataStore(IDataStore innerStore) => _innerStore = innerStore; public ModificationResult ModifyData(params ModificationStatement[] dmlStatements) { var stopwatch = Stopwatch.StartNew(); var result = _innerStore.ModifyData(dmlStatements); stopwatch.Stop(); var correlationId = OperationContext.Current?.Items["CorrelationId"] as string ?? Guid.NewGuid().ToString(); foreach (var stmt in dmlStatements) { var logEntry = new LogEntry { CorrelationId = correlationId, OperationType = "SqlDml", Details = stmt.ToString(), ElapsedMilliseconds = stopwatch.ElapsedMilliseconds, Timestamp = DateTime.UtcNow, Source = "Server" }; LogManager.PersistLog(logEntry); } return result; } // 实现SelectData、UpdateSchema等方法,转发给_innerStore并按需记录日志 public SelectedData SelectData(params SelectStatement[] selectStatements) { var stopwatch = Stopwatch.StartNew(); var result = _innerStore.SelectData(selectStatements); stopwatch.Stop(); var correlationId = OperationContext.Current?.Items["CorrelationId"] as string ?? Guid.NewGuid().ToString(); foreach (var stmt in selectStatements) { var logEntry = new LogEntry { CorrelationId = correlationId, OperationType = "SqlSelect", Details = stmt.ToString(), ElapsedMilliseconds = stopwatch.ElapsedMilliseconds, Timestamp = DateTime.UtcNow, Source = "Server" }; LogManager.PersistLog(logEntry); } return result; } public void UpdateSchema(bool dontCreateIfFirstTableNotExist, params DBTable[] tables) { _innerStore.UpdateSchema(dontCreateIfFirstTableNotExist, tables); } public SchemaCorrectionResult[] UpdateSchema(bool dontCreateIfFirstTableNotExist, params SchemaCorrectionInfo[] schemaCorrectionInfos) { return _innerStore.UpdateSchema(dontCreateIfFirstTableNotExist, schemaCorrectionInfos); } }
初始化XPO时使用该包装类:
var connectionString = "..."; var nativeStore = XpoDefault.GetConnectionProvider(connectionString, AutoCreateOption.DatabaseAndSchema); var loggingStore = new LoggingDataStore(nativeStore); var session = new LoggingSession(loggingStore);
二、客户端:同步XPO操作日志到服务端
客户端WinForm应用中的XPO操作(如Session.Save),可复用服务端的LoggingSession逻辑,将日志通过WCF发送到服务端:
1. 定义WCF日志提交契约
新增一个专门用于接收客户端日志的WCF服务:
[ServiceContract] public interface ILogService { [OperationContract] void SubmitClientLogs(List<LogEntry> logEntries); } public class LogService : ILogService { public void SubmitClientLogs(List<LogEntry> logEntries) { foreach (var entry in logEntries) { entry.Source = "Client"; LogManager.PersistLog(entry); } } }
2. 客户端自定义LoggingSession修改
客户端的LoggingSession不直接持久化日志,而是缓存日志条目,批量提交到服务端:
public class ClientLoggingSession : Session { private static readonly List<LogEntry> _logCache = new List<LogEntry>(); private static readonly object _cacheLock = new object(); public ClientLoggingSession(IDataStore dataStore) : base(dataStore) { Committed += OnSessionCommitted; } public override void Save(object theObject) { var stopwatch = Stopwatch.StartNew(); base.Save(theObject); stopwatch.Stop(); var classInfo = GetClassInfo(theObject.GetType()); var logEntry = new LogEntry { CorrelationId = Guid.NewGuid().ToString(), OperationType = "XpoSave", ObjectType = classInfo.FullName, ObjectId = classInfo.KeyProperty.GetValue(theObject)?.ToString(), ElapsedMilliseconds = stopwatch.ElapsedMilliseconds, Timestamp = DateTime.UtcNow }; lock (_cacheLock) { _logCache.Add(logEntry); // 每10条日志批量提交,或在UI空闲时提交 if (_logCache.Count >= 10) { SubmitLogsToServer(); } } } private void OnSessionCommitted(object sender, EventArgs e) { var logEntry = new LogEntry { CorrelationId = Guid.NewGuid().ToString(), OperationType = "XpoTransactionCommit", Timestamp = DateTime.UtcNow }; lock (_cacheLock) { _logCache.Add(logEntry); } } private void SubmitLogsToServer() { List<LogEntry> logsToSubmit; lock (_cacheLock) { logsToSubmit = new List<LogEntry>(_logCache); _logCache.Clear(); } // 异步调用WCF日志服务,避免阻塞UI Task.Run(() => { using (var client = new LogServiceClient()) { client.SubmitClientLogs(logsToSubmit); } }); } }
三、统一日志管理与关联
定义通用的LogEntry实体类,包含所有日志所需字段:
public class LogEntry { public string CorrelationId { get; set; } public string OperationType { get; set; } public string ObjectType { get; set; } public string ObjectId { get; set; } public string Details { get; set; } public long ElapsedMilliseconds { get; set; } public DateTime Timestamp { get; set; } public string Source { get; set; } // Server/Client }
实现LogManager统一处理日志持久化(如写入数据库、文件等),确保服务端和客户端的日志存储到同一位置。
内容的提问来源于stack exchange,提问作者phpain
相关产品推荐
相关产品推荐

