You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.22 08:12:16