The job history page currently shows Processing, Succeeded, etc. Is it possible for a job to write arbitrary logging (for instance, periodic "current status" messages or warnings) into it? If not, is that a feature you are interested in?
+1 for the request, I'd like it too
:+1: I like this idea too. But this feature requires deeper investigation – how and where to store this messages in a efficient way. Also there's need to consider the API of this feature.
In the system we're trying to replace with Hangfire, we had two relevant APIs:
// Update the task's status with a user-defined percent complete value and
// some text describing it's current state or the step it's currently processing.
void updateProgress(int percentComplete, string currentStatus) { ... }
Separately, we used our normal logging mechanism to allow logDebug, Info, Warn, etc. and those line items would get written into the database and potentially purged after some time period or number of tasks logged, or whatever.
All of that is of course very useful with complex tasks that may fail for any number of reasons. In our case, actually remoting to the servers is locked down so exposing all that debugging stuff to the web is very useful.
I like this idea. Are you talking about having a callback from within a Hangfire client job to the background job server?
@sjwoodard: I think so. I imagine calls from a hangfire client job:
BackgroundJob.Enqueue(
(taskEngine) => taskEngine.updateProgress(30, "We're 30% done!"));
that would affect the database such that this page would display them: http://hangfire.io/img/succeeded-job-sm.png
@ses4j, I don't think using Enqueue makes sense here, because it will only run if a worker is available. I was thinking of a different approach where you would pass a dependency into the job that handles callbacks, the same way as the JobCancellationToken. This way, everything will be on the same worker thread.
BackgroundJob.Enqueue(() => Run(JobCallback.Null, JobCancellationToken.Null));
The JobCallback interface could have some methods like LogToHistory(...), UpdateProgress(0.3d), etc..., which I think would solve your two specific issues. I'm also think that all the callback methods would be implemented by the JobStorage class.
@sjwoodard Sure. I don't really understand what a JobCallback.Null is but I'll take your word for it. I haven't actually used Hangfire that much. :) But as long as, inside the callback action, I have some interface with UpdateProgress, the goal is met..
@ses4j, the JobCallback.Null would just be a null placeholder implementation of the interface that I'm proposing (i.e. with the LogToHistory(...) and UpdateProgress(...) methods). Then when the job is created, Hangfire will create the actual implementation that gets passed to your job, which will include the Hangfire job ID and the background job server hook. I haven't looked into the JobCancellationToken code before, but I believe it works the same way.
So, again the calling the job:
BackgroundJob.Enqueue(() => Run(JobCallback.Null, JobCancellationToken.Null));
And, now the job implementation:
public void Run(IJobCallback callback, IJobCancellationToken ct)
{
callback.LogToHistory(...);
}
Good feature..I promote it !!
+1
I started an implementation of this you can try here:
https://github.com/mppa-git/Hangfire/tree/master
It is simple but functional. It just writes additional "types" of state to the state table which corresponds to log and status messages. Also, rather than add another type of token, I simply subclassed the existing cancellation token -- the JobCallback is now a feature superset of the CancellationToken. Any future data/methods offered to the background action could be added to the JobCallback, such as access to the Job ID or elapsed time or whatever...
Next week I'll try and flesh out improvements to how the dashboard renders it.
Please let me know if this basic approach will be acceptable into the mainline.
As it includes the cancellation token I would almost be tempted to call it something like JobExecutionContext, rather than a callback.
@yngndrw JobExecutionContext isn't bad.
@odinserj Is this patch of interest as-is? Should I continue with implementation?
yes! Please provide a JobExecutionContext, this way we can interact with hangfire in a job execution.
Things like JobId should be provided.
It is possible now, using a JobFilter, which set's up a threadstatic Context object and tears it down afterwards.
possibly: JobExecutionContext.Current ?
@rikbosch What would you use JobId for? Theoretically, it's just an implementation detail. @odinserj is right to keep his interface small, because once you expose it, you're stuck forever. Exposing JobId, for instance, means you can't switch it out, with, say, a GUID. I'm not saying you're not right, just curious about the use case.
@odinserj is already exposing the JobId, you can get it like this...
string jobId = BackgroundJob.Schedule<CustomTask>(t => t.Run(), TimeSpan.FromMinutes(5));
I don't think you'll be doing anything radical by adding it to the JobExecutionContext as well. But having it there will make it easier for devs to do things like this.
BackgroundJob.Delete(context.JobId);
Otherwise devs will have to roll their own aggregate class which defeats the beauty of what you're doing.
Hey, I looked through ses4j patch, and would like to propose a simpler change. In src/Hangfire.Core/Dashboard/JobHistoryRenderer.cs there already are methods to render each state (as it is serialized to Dictionary with SerializeData method), but the keys being rendered are predefined here (which also intoduces some additional coupling) - Rendering every keyValuePair in that dictionary could make the UI more extensible (optionally, SerializeData could return a sorted collection of KeyValuePair
As for putting some custom Data in a state, it can currently done with subclassing and jobfilters, e.g:
public class GreatSucceededState : SucceededState, IState
{
public GreatSucceededState(object result, long latency, long performanceDuration) : base(result, latency, performanceDuration) {}
public Dictionary<string, string> SerializeData()
{
var ret = base.SerializeData();
ret["AdditionalData"] = "AdditionalData";
return ret;
}
}
public class GreatSuccessAttribute : JobFilterAttribute, IElectStateFilter
{
public void OnStateElection(ElectStateContext context)
{
var state = context.CandidateState as SucceededState;
if (state != null)
{
context.CandidateState = new GreatSucceededState(state.Result, state.Latency, state.PerformanceDuration);
}
}
}
The AdditionalData gets written to DB, but currently, there's no way to render it.
I can work on a patch if you'd like me to.
Do you mind explaining further what you mean, and how it's different from mine?
As far as rendering - I agree that the dashboard ought to render anything user-added that is stuck in SerializeData, but I don't think that takes the place of the hard-coded stuff. Any "generic" rendering of serialized data would be formatted in that heavy-on-the-whitespace two-column format. I felt that it was important to format the logging/status updates densely (as shown below), otherwise the log page would be unreasonably long and with too much whitespace:
Oh, I didn't submit a patch yet, so it's difficult to say how it would be different form yours :) - but I would probably start with just a small modification of only JobHistoryRenderer.cs to iterate over all dictionary (with previously hardcoded values served first). I admit I did not check the display style of this, and how it would render multiline messages.
How can we help on getting this implemented?
+1
+1
+1
+1
+1
+1
Note that pull request #232 (referenced above but maybe lost in the chain) implements this.
+1
+1
Please see: https://github.com/samshiles/hangfire.job.logging for one way to get job output logged to the hangfire console. It requires a very small change to hangfire which should be 100% backwards compatible. PR: #466
+1
+1
@samshiles : first thanks for sharing the idea to insert in the state table directly.
however, you don't need to inject the jobid, using a jobcontext makes it much easier and transparant
this can be used inside your joblogger to get the right jobid. i just did that in my project and works really well.
public class JobContext : IServerFilter
{
[ThreadStatic]
private static string _jobId;
[ThreadStatic]
private static Type _jobType;
public static string JobId { get { return _jobId; } set { _jobId = value; } }
public static Type JobType { get { return _jobType; } set { _jobType = value; } }
public void OnPerforming(PerformingContext context)
{
JobId = context.JobId;
JobType = context.Job.Type;
}
public void OnPerformed(PerformedContext filterContext)
{
}
}
and configure this filter:
GlobalConfiguration.Configuration.UseFilter(new JobContext())
+1
+1
+1
+1
+1
+1
+1
+1
Any update on this?
+1
+1
+1.
Any update on this?
+1
+1
+1
+1
+1
+1
+1
+1
Hey guys, see what @pieceofsummer did in the Hangfire.Console extension, even with colours and live updates! Almost all storages are supporting this with no changes!

@odinserj That looks very cool. Just what we need.
I suppose this also uses the hangfire tables for its storage (SQL server provider). Any recommendation on that, does that scale?
@vip32 it uses Hangfire's Sets to store data.
If you're going to log large amounts of data, or having lots of jobs daily, you can setup clustering (sharding/whatever it is called in your DB engine) of sets table by key. Also decreasing the amount of time console logs are stored may be an option.
P.S. Logging large amounts of data is not recommended, as the browser may just hang while displaying it. After all, the purpose is to track job status, not to dump debug logs. Use it wisely :)
P.P.S. I've also added progress bars in the latest build, so it's even more fun now.
@pieceofsummer Great library...exactly what was needed!!
Most helpful comment
Hey guys, see what @pieceofsummer did in the Hangfire.Console extension, even with colours and live updates! Almost all storages are supporting this with no changes!