4

I have set up hangfire successfully for my ASP.NET project, i.e. the 11 Hangfire tables are created in my database. I tried the following command inside the Application_Start() of my project's Global.asax:

namespace myAPI
{
   public class WebApiApplication : System.Web.HttpApplication
   {
      protected void Application_Start(
      {
         System.Diagnostics.Debug.WriteLine("Recurring job will be set up.");

         RecurringJob.AddOrUpdate(
             "some-id", 
             () => System.Diagnostics.Debug.WriteLine("Job instance started at " +
                                                      DateTime.Now)),
             "*/2 * * * 1-5"); 
      }
   }
}

Sadly, inside Visual Studio's window Output > Debug I only see Reccuring job will be set up. and nothing ever after. However, a SELECT * FROM [myContext].[HangFire].[Set] shows me

Key              Score      Value     ExpireAt
recurring-jobs  1579116240  some-id   NULL

So far so good, this means that the job is indeed set up.

But how do I log inside my DB each and each time when the RecurringJob is executed? Do I assume correctly that Hangfire does not do that out of the box and I have to log it myself within the arrow-function? Or is there a more elegant way?

Question on the side: Why don't I see any output of System.Diagnostics.Debug.WriteLine within my recurring job?

References

B--rian
  • 5,578
  • 10
  • 38
  • 89

4 Answers4

3

You can use SeriLog with Hangfire out of the box. Serilog comes with different sinks, e.g. Serilog.Sinks.MSSqlServer. You can configure it in startup.cs:

using Serilog;
using Serilog.Sinks.MSSqlServer;

Log.Logger = new LoggerConfiguration()
                 .WriteTo
                 .MSSqlServer(
                        connectionString: hangfireConnectionString,
                        tableName: "Logs",
                        autoCreateSqlTable: true
                    ).CreateLogger();
               // will display any issues with Serilog config. comment out in prod.
Serilog.Debugging.SelfLog.Enable(msg => Debug.WriteLine(msg));

GlobalConfiguration.Configuration
                   .UseSqlServerStorage(hangfireConnectionString)
                   .UseSerilogLogProvider();

After you schedule your job, you can log it with

Log.Information(string.Format("Hanfire Job Scheduled at {0}", DateTime.Now));
B--rian
  • 5,578
  • 10
  • 38
  • 89
salli
  • 722
  • 5
  • 10
  • Thanks for your input! Any idea why my simple logging does not work? – B--rian Jan 22 '20 at 16:36
  • 1
    I am not really sure. I think because you are scheduling the job and you are not waiting long enough? I think you are scheduling for every 2 minutes, Monday to Friday. Try to see if this works `BackgroundJob.Enqueue( () => System.Diagnostics.Debug.WriteLine("Job executed at " +DateTime.Now + "."));` – salli Jan 22 '20 at 18:53
  • Is your hangfire server running in the same application? Or just the dashboard? Try what @JohnBabb suggested. `GlobalConfiguration.Configuration.UseConsole();` – salli Jan 24 '20 at 13:19
  • I do not use the dashboard, and my hangfire server is started by my API - I added a bit of code in my post. I am currently installing `Hangfire.Console`, so your hint was helpful. – B--rian Jan 24 '20 at 13:39
3

Hangfire includes a concept of job filters (similar to ASP.NET MVC's Action Filters). For your use case, you would define one that would write to your database (adjust based on your needs):

using Hangfire.Common;
using Hangfire.Server;

class LogCompletionAttribute : JobFilterAttribute, IServerFilter
{
    public void OnPerforming(PerformingContext filterContext)
    {
        // Code here if you care when the execution **has begun**
    }

    public void OnPerformed(PerformedContext context)
    {
        // Check that the job completed successfully
        if (!context.Canceled && context.Exception != null)
        {
            // Here you would write to your database.
            // Example with entity framework:
            using (var ctx = new YourDatabaseContext())
            {
                ctx.Something.Add(/**/);
                ctx.SaveChanges();
            }
        }
    }
}

And then apply the filter to the job method:

namespace myAPI
{
   public class WebApiApplication : System.Web.HttpApplication
   {
      protected void Application_Start(
      {
         System.Diagnostics.Debug.WriteLine("Recurring job will be set up.");

         RecurringJob.AddOrUpdate("some-id", () => MyJob(), "*/2 * * * 1-5"); 
      }

      [LogCompletion]
      public static void MyJob()
      {
          System.Diagnostics.Debug.WriteLine("Job instance started at " + DateTime.Now)
      }
   }
}

Docs: https://docs.hangfire.io/en/latest/extensibility/using-job-filters.html

Xymanek
  • 1,357
  • 14
  • 25
  • The `[LogCompletion]` attribute should be on the method that you are invoking as the job, it doesn't matter in which class the method is located – Xymanek Jan 22 '20 at 17:54
  • What exactly is the purpose of a *job filter*? Is there some definition for that? The documentation you have linked is a bit sparse. – B--rian Jan 24 '20 at 10:29
  • True, the documentation for them is small. Think of a Job Filter as an "event listener" for various actions that happen with the job - it's scheduled, executed, deleted, etc. For your case, you can "listen" to the execution events and log as you want (e.g. to a database of your choosing). Job Filters are applied on per-job basis - on methods that you invoke as the job. You can also apply them to a class, which is simply a shortcut that applies the filter to each method in that class. – Xymanek Jan 24 '20 at 13:12
1

So the cron is set to fire At every 2nd minute on every day-of-week from Monday through Friday. I assume you are waiting for the job to execute and that it is in the right window of time.

Most of the references that I found on the web indicated that you can do.

RecurringJob.AddOrUpdate(() => Console.WriteLine("This job will execute once in every minute"), Cron.Minutely);

Maybe you have to line up the dots a bit better to write to the vs console.

There is also an admin portal that can be configured to see what is begin run and when.

I have the following setup. Global.asax.cs

    protected void Application_Start()
    {
        HangfireJobsConfig.Register();
    }
    public class HangfireJobsConfig
    {
        public static void Register()
        {
            if (App1Config.RunHangfireService)
            {
                JobStorage.Current = new SqlServerStorage(App1Config.DefaultConnectionStringName.Split('=').Last());
                GlobalConfiguration.Configuration.UseConsole();
                RecurringJob.AddOrUpdate("RunJob1", () => RunJob1(null), Cron.MinuteInterval(App1Config.RunJob1Interval));
                RecurringJob.AddOrUpdate("RunJob2", () => RunJob2(null), Cron.MinuteInterval(App1Config.RunJob2Interval));
            }
        }

        [AutomaticRetry(Attempts = 0, Order = 1)]
        public static void RunJob1(PerformContext context)
        {
            //dostuff
        }

        [AutomaticRetry(Attempts = 0, Order = 2)]
        public static void RunJob2(PerformContext context)
        {
            //do stuff
        }
    }

Startup.cs

    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            ConfigureAuth(app);
            ConfigureHangFire(app);
        }
        public void ConfigureHangFire(IAppBuilder app)
        {
            if (App1Config.RunHangfireService)
            {
                GlobalConfiguration.Configuration.UseSqlServerStorage(
                    AppiConfig.DefaultConnectionStringName.Split('=').Last());

                GlobalConfiguration.Configuration.UseConsole();

                app.UseHangfireServer();
                var options = new DashboardOptions
                {
                    AuthorizationFilters = new[]
                    {
                        new AuthorizationFilter { Roles = "Inventory" }         
                    }
                };
                app.UseHangfireDashboard("/hangfire", options);
            }
        }
    }
John Babb
  • 931
  • 10
  • 19
  • Thanks for your explanations. My main problem is not so much the initial setup, but the actual logging: In other words: I want each time when a job is executed (in my example every 2 minutes for debugging purposes), a log entry in my DB. In production, this job should be executed once a (work-)day. – B--rian Jan 21 '20 at 12:31
1

The actual problem was a very trivial one, the initialization of the actual background server was missing BackgroundJobServer();. Here the fully functional code:

namespace myAPI 
{
  public class WebApiApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
       string connString = ConfigurationManager.ConnectionStrings["myContext"].ToString();
       Hangfire.GlobalConfiguration.Configuration.UseConsole();
       Hangfire.GlobalConfiguration.Configuration.UseSqlServerStorage(connString,
       new SqlServerStorageOptions {
             CommandBatchMaxTimeout = TimeSpan.FromMinutes(5),
             SlidingInvisibilityTimeout = TimeSpan.FromMinutes(5),
             QueuePollInterval = TimeSpan.Zero,
             UseRecommendedIsolationLevel = true,
             UsePageLocksOnDequeue = true,
             DisableGlobalLocks = true
           });
        var bgndJS = new BackgroundJobServer(); // <--- this is essential!
        RecurringJob.AddOrUpdate("myRecurringJob", () => HangfireRecurringJob(), "*/2 * * * 1-5");
        System.Diagnostics.Debug.WriteLine("---> RecurringJob 'myHangfireJob' initated.");
    }

    public void HangfireRecurringJob() {
       System.Diagnostics.Debug.WriteLine("---> HangfireRecurringJob() executed at" + DateTime.Now);
       Console.Beep(); // <-- I was really happy to hear the beep
    }
  }
}
B--rian
  • 5,578
  • 10
  • 38
  • 89