z8018
2 天以前 d8dc91f9c1fece5711e38edd1b1274cb9e579015
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
using Microsoft.AspNetCore.Http;
using SqlSugar;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using WIDESEA_Core.DB;
using WIDESEA_Core.Helper;
using WIDESEA_Core.HttpContextUser;
using WIDESEA_Core.Seed;
 
namespace WIDESEA_Core.LogHelper
{
    public static class Logger
    {
        public static ConcurrentQueue<dynamic> loggerQueueData = new ConcurrentQueue<dynamic>();
        static Logger()
        {
            Task.Run(() =>
            {
                StartWriteLog();
            });
 
        }
 
        /// <summary>
        /// 日志写入线程的入口方法,负责从队列中取出日志数据并批量写入数据库
        /// </summary>
        /// <remarks>
        /// 该方法会持续运行,每5秒检查一次日志队列:
        /// 1. 当队列有数据且缓存表行数小于500时,将数据移入缓存表
        /// 2. 当缓存表有数据时,使用SqlSugar进行批量插入
        /// 3. 发生异常时会输出错误信息但不会终止线程
        /// </remarks>
        static void StartWriteLog()
        {
            DataTable queueTable = CreateEmptyTable();
            while (true)
            {
                try
                {
                    //如果队列中有数据且队列表中的行数小于500,则将队列中的数据写入队列表
                    if (loggerQueueData.Count() > 0 && queueTable.Rows.Count < 500)
                    {
                        DequeueToTable(queueTable); continue;
                    }
                    //每5秒写一次数据
                    Thread.Sleep(5000);
 
                    //如果队列表中的行数为0,则跳过本次循环
                    if (queueTable.Rows.Count == 0) { continue; }
 
                    string str = DBContext.GetMainConnectionDb().Connection;
                    //创建SqlSugarClient对象,用于连接数据库
                    SqlSugarClient sugarClient = new SqlSugarClient(new ConnectionConfig()
                    {
                        ConnectionString = DBContext.GetMainConnectionDb().Connection,
                        IsAutoCloseConnection = true,
                        DbType = DBContext.DbType,
                    });
 
                    //将队列表中的数据批量插入数据库
                    int rows = sugarClient.Fastest<DataTable>().AS("Sys_Log").BulkCopy(queueTable);
 
                    //清空队列表
                    queueTable.Clear();
                }
                catch (Exception ex)
                {
                    //打印异常信息
                    Console.WriteLine(ex.ToString());
                }
            }
        }
 
        /// <summary>
        /// 从日志队列中取出日志项并添加到数据表中
        /// </summary>
        /// <param name="queueTable">要添加日志数据的目标数据表</param>
        /// <remarks>
        /// 1. 处理日志日期:如果BeginDate为空或早于2010年,则设为当前时间;EndDate为空则设为当前时间
        /// 2. 清理RequestParam和ResponseParam中的换行符
        /// 3. 计算请求耗时(EndDate - BeginDate的毫秒数)
        /// 4. 处理UserId为空的情况,默认设为-1
        /// </remarks>
        private static void DequeueToTable(DataTable queueTable)
        {
            loggerQueueData.TryDequeue(out dynamic log);
            if (log != null)
            {
                DataRow row = queueTable.NewRow();
                // 如果log的BeginDate为空或者BeginDate的年份小于2010,则将BeginDate设置为当前时间
                if (log.BeginDate == null || log.BeginDate?.Year < 2010)
                {
                    log.BeginDate = DateTime.Now;
                }
                // 如果log的EndDate为空,则将EndDate设置为当前时间
                if (log.EndDate == null)
                {
                    log.EndDate = DateTime.Now;
                }
                //  row["Id"] = log.Id;
                // 将RequestParam中的换行符替换为空字符串
                row["RequestParam"] = log.RequestParam?.Replace("\r\n", "");
                // 将ResponseParam中的换行符替换为空字符串
                row["ResponseParam"] = log.ResponseParam?.Replace("\r\n", "");
                //row["Success"] = log.Success ?? -1;
                // 将BeginDate设置为log的BeginDate
                row["BeginDate"] = log.BeginDate;
                // 将EndDate设置为log的EndDate
                row["EndDate"] = log.EndDate;
                // 计算ElapsedTime,即EndDate减去BeginDate的毫秒数
                row["ElapsedTime"] = ((DateTime)log.EndDate - (DateTime)log.BeginDate).TotalMilliseconds;
                // 将UserIP设置为log的UserIP
                row["UserIP"] = log.UserIP;
                // 将Url设置为log的Url
                row["Url"] = log.Url;
                // 如果log的UserId为空,则将UserId设置为-1,否则设置为log的UserId
                row["UserId"] = log.UserId ?? -1;
                // 将UserName设置为log的UserName
                row["UserName"] = log.UserName;
                // 将row添加到queueTable中
                queueTable.Rows.Add(row);
            }
        }
 
        /// <summary>
        /// 创建一个空的DataTable,用于存储日志数据
        /// </summary>
        /// <returns>包含日志字段结构的空DataTable</returns>
        /// <remarks>
        /// 包含以下列:BeginDate, ElapsedTime, EndDate, RequestParam, ResponseParam, 
        /// Url, UserIP, UserName, UserId。其他注释掉的列为预留字段。
        /// </remarks>
        private static DataTable CreateEmptyTable()
        {
            DataTable queueTable = new DataTable();
            queueTable.Columns.Add("BeginDate", Type.GetType("System.DateTime"));
            queueTable.Columns.Add("ElapsedTime", Type.GetType("System.Int32"));
            queueTable.Columns.Add("EndDate", Type.GetType("System.DateTime"));
            queueTable.Columns.Add("RequestParam", typeof(string));
            queueTable.Columns.Add("ResponseParam", typeof(string));
            //queueTable.Columns.Add("Success", Type.GetType("System.Int32"));
            queueTable.Columns.Add("Url", typeof(string));
            queueTable.Columns.Add("UserIP", typeof(string));
            queueTable.Columns.Add("UserName", typeof(string));
            queueTable.Columns.Add("UserId", Type.GetType("System.Int32"));
            //queueTable.Columns.Add("LogType", typeof(string));
            //queueTable.Columns.Add("ExceptionInfo", typeof(string));
            //queueTable.Columns.Add("ServiceIP", typeof(string));
            //queueTable.Columns.Add("BrowserType", typeof(string));
            //queueTable.Columns.Add("Role_Id", Type.GetType("System.Int32"));
            return queueTable;
        }
 
        /// <summary>
        /// 添加系统日志记录
        /// </summary>
        /// <param name="url">请求URL地址</param>
        /// <param name="requestParameter">请求参数</param>
        /// <param name="responseParameter">响应参数</param>
        /// <param name="beginDate">请求开始时间</param>
        /// <remarks>
        /// 该方法会记录请求的URL、参数、响应以及用户信息,并自动计算请求耗时。
        /// 如果获取用户信息时发生异常,仍会记录基本请求信息。
        /// </remarks>
        public static void Add(string url, string requestParameter, string responseParameter, DateTime beginDate)
        {
            dynamic? log = null;
            try
            {
                //获取当前用户
                IUser? user = App.User;
                //创建日志对象
                log = new
                {
                    //请求开始时间
                    BeginDate = beginDate,
                    //请求结束时间
                    EndDate = DateTime.Now,
                    //请求参数
                    RequestParam = requestParameter,
                    //响应参数
                    ResponseParam = responseParameter,
                    //请求URL
                    Url = url,
                    //客户端IP
                    UserIP = "",
                    //用户ID
                    UserId = user?.UserId,
                    //用户名
                    UserName = user?.UserName
                };
            }
            catch (Exception exception)
            {
                //如果发生异常,则创建日志对象
                log = log ?? new
                {
                    //请求开始时间
                    BeginDate = DateTime.Now,
                    //请求结束时间
                    EndDate = DateTime.Now,
                    //请求参数
                    RequestParam = requestParameter,
                    //响应参数
                    ResponseParam = responseParameter,
                };
            }
            //添加系统日志
            loggerQueueData.Enqueue(log);
        }
 
        /// <summary>
        /// 添加请求和响应日志记录
        /// </summary>
        /// <param name="requestParameter">请求参数</param>
        /// <param name="responseParameter">响应参数</param>
        /// <remarks>
        /// 该方法会记录HTTP请求的详细信息,包括请求时间、响应时间、请求URL、客户端IP、用户信息等。
        /// 如果发生异常,仍会记录基本的请求和响应信息。
        /// 对于OPTIONS请求方法或空HttpContext的情况会直接返回不做记录。
        /// </remarks>
        public static void Add(string requestParameter, string responseParameter)
        {
            dynamic? log = null;
            try
            {
                //获取当前HttpContext
                HttpContext? context = App.HttpContext;
                //如果HttpContext为空,则返回
                if (context == null)
                {
                    return;
                }
                //如果请求方法为OPTIONS,则返回
                if (context.Request.Method == "OPTIONS") return;
                //获取RequestLogModel实例
                RequestLogModel logModel = (context.RequestServices.GetService(typeof(RequestLogModel)) as RequestLogModel) ?? new RequestLogModel { RequestDate = DateTime.Now };
 
                //获取当前用户
                IUser? user = App.User;
                //创建日志对象
                log = new
                {
                    //请求开始时间
                    BeginDate = logModel.RequestDate,
                    //请求结束时间
                    EndDate = DateTime.Now,
                    //请求参数
                    RequestParam = requestParameter,
                    //响应参数
                    ResponseParam = responseParameter,
                    //请求URL
                    Url = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase + context.Request.Path,
                    //客户端IP
                    UserIP = GetClientIP(context)?.Replace("::ffff:", ""),
                    //用户ID
                    UserId = user?.UserId,
                    //用户名
                    UserName = user?.UserName
                };
            }
            catch (Exception exception)
            {
                //如果发生异常,则创建日志对象
                log = log ?? new
                {
                    //请求开始时间
                    BeginDate = DateTime.Now,
                    //请求结束时间
                    EndDate = DateTime.Now,
                    //请求参数
                    RequestParam = requestParameter,
                    //响应参数
                    ResponseParam = responseParameter,
                };
            }
            //添加系统日志
            loggerQueueData.Enqueue(log);
        }
 
        /// <summary>
        /// 获取客户端的IP地址
        /// </summary>
        /// <param name="context">Http上下文对象</param>
        /// <returns>客户端的IP地址字符串</returns>
        /// <remarks>
        /// 优先从X-Forwarded-For请求头中获取IP地址,如果不存在则从连接远程IP地址获取
        /// </remarks>
        public static string GetClientIP(HttpContext context)
        {
            // 获取请求头中的X-Forwarded-For字段,并将其转换为字符串
            var ip = context.Request.Headers["X-Forwarded-For"].ObjToString();
            // 如果X-Forwarded-For字段为空,则获取远程IP地址
            if (string.IsNullOrEmpty(ip))
            {
                ip = context.Connection.RemoteIpAddress.ObjToString();
            }
 
            // 返回IP地址
            return ip;
        }
 
        
    }
}