基于Quartz.Net+CronSchedule的任务日志写入文件方案咨询
嘿,我来给你捋捋怎么实现Quartz.Net任务的执行时间、结束时间这些信息的日志记录,其实核心思路就是利用Quartz的扩展点或者在任务执行前后埋点,把关键时间数据写到文件里。下面给你两种靠谱的方案,你可以根据场景选:
方案一:全局统一记录(用Quartz Job监听器)
这个方案最省心,相当于给所有任务装了个统一的“记录仪”,不用每个任务都写重复代码。核心是实现Quartz的IJobListener接口,监听任务的开始和结束事件:
1. 自定义任务监听器
using Quartz; using Serilog; // 这里以Serilog为例,也可以换NLog等框架 public class JobExecutionLogger : IJobListener { // 监听器名称,必须唯一 public string Name => "GlobalJobExecutionLogger"; // 任务即将执行时触发,记录开始时间 public Task JobToBeExecuted(IJobExecutionContext context, CancellationToken cancellationToken) { var jobKey = context.JobDetail.Key; var startTime = DateTime.UtcNow; // 推荐用UTC时间避免时区问题 Log.Information($"任务[{jobKey.Group}.{jobKey.Name}]开始执行 | UTC时间:{startTime:yyyy-MM-dd HH:mm:ss.fff}"); // 把开始时间存到JobDataMap,方便结束时计算耗时 context.JobDetail.JobDataMap["JobStartTimeUtc"] = startTime; return Task.CompletedTask; } // 任务执行完成后触发(无论成功失败),记录结束时间和结果 public Task JobWasExecuted(IJobExecutionContext context, JobExecutionException? jobException, CancellationToken cancellationToken) { var jobKey = context.JobDetail.Key; var startTime = context.JobDetail.JobDataMap["JobStartTimeUtc"] as DateTime? ?? DateTime.UtcNow; var endTime = DateTime.UtcNow; var duration = endTime - startTime; if (jobException == null) { Log.Information($"任务[{jobKey.Group}.{jobKey.Name}]执行完成 | 开始UTC时间:{startTime:yyyy-MM-dd HH:mm:ss.fff} | 结束UTC时间:{endTime:yyyy-MM-dd HH:mm:ss.fff} | 耗时:{duration.TotalMilliseconds:F2}ms"); } else { Log.Error(jobException, $"任务[{jobKey.Group}.{jobKey.Name}]执行失败 | 开始UTC时间:{startTime:yyyy-MM-dd HH:mm:ss.fff} | 结束UTC时间:{endTime:yyyy-MM-dd HH:mm:ss.fff} | 耗时:{duration.TotalMilliseconds:F2}ms"); } return Task.CompletedTask; } // 下面两个方法如果不需要逻辑,留空实现即可 public Task JobExecutionVetoed(IJobExecutionContext context, CancellationToken cancellationToken) { return Task.CompletedTask; } }
2. 注册监听器到调度器
在初始化Quartz调度器的时候,把自定义监听器加进去:
var schedulerFactory = new StdSchedulerFactory(); var scheduler = await schedulerFactory.GetScheduler(); // 注册全局任务监听器 await scheduler.ListenerManager.AddJobListener(new JobExecutionLogger()); // 启动调度器 await scheduler.Start();
方案二:单个任务自定义记录
如果某个任务需要定制化的日志内容(比如额外记录任务参数、执行结果),可以直接在任务的Execute方法里埋点:
using Quartz; using Serilog; public class CustomJob : IJob { public async Task Execute(IJobExecutionContext context) { var jobKey = context.JobDetail.Key; var startTime = DateTime.UtcNow; Log.Information($"自定义任务[{jobKey.Group}.{jobKey.Name}]启动 | UTC时间:{startTime:yyyy-MM-dd HH:mm:ss.fff}"); try { // 这里写你的任务核心逻辑 var taskResult = await RunCustomBusinessLogic(context.MergedJobDataMap); var endTime = DateTime.UtcNow; var duration = endTime - startTime; Log.Information($"自定义任务[{jobKey.Group}.{jobKey.Name}]执行成功 | 结果:{taskResult} | 耗时:{duration.TotalSeconds:F2}s | 结束UTC时间:{endTime:yyyy-MM-dd HH:mm:ss.fff}"); } catch (Exception ex) { var endTime = DateTime.UtcNow; var duration = endTime - startTime; Log.Error(ex, $"自定义任务[{jobKey.Group}.{jobKey.Name}]执行失败 | 耗时:{duration.TotalSeconds:F2}s | 结束UTC时间:{endTime:yyyy-MM-dd HH:mm:ss.fff}"); // 如果需要Quartz重试任务,抛出JobExecutionException throw new JobExecutionException(ex) { RefireImmediately = false }; } } private async Task<string> RunCustomBusinessLogic(JobDataMap jobDataMap) { // 模拟业务逻辑,比如读取任务参数 var param = jobDataMap.GetString("TaskParam"); await Task.Delay(2000); return $"处理参数[{param}]完成"; } }
额外建议:用成熟日志框架代替手写文件操作
别自己写FileStream来写入txt!多任务并发写入时很容易出现文件占用、内容错乱的问题。推荐用Serilog或NLog,它们已经帮你处理了:
- 并发写入安全
- 按天/按大小滚动生成日志文件
- 日志格式自定义
- 自动清理旧日志
比如Serilog配置写入txt的示例:
// 程序启动时初始化Serilog Log.Logger = new LoggerConfiguration() .WriteTo.File( path: "./Logs/job-execution-.txt", // 日志文件前缀,会自动补日期 rollingInterval: RollingInterval.Day, // 按天生成新文件 retainedFileCountLimit: 30, // 保留最近30天的日志 outputTemplate: "{Timestamp:yyyy-MM-dd HH:mm:ss.fff} [{Level:u3}] {Message:lj}{NewLine}{Exception}" ) .CreateLogger();
内容的提问来源于stack exchange,提问作者Harry Tipper
相关产品推荐
相关产品推荐

