How do you track custom events from Azure WebJobs in Application Insights? - c#

I have an Azure WebJobs (v2.2.0) project that I would like to monitor with Application Insights (AI), and there are events that I would like to be able to track. In a normal web app that is configured to use AI you can just use this:
TelemetryClient tc = new TelemetryClient();
tc.TrackEvent("EventName");
However this seems not to work in the context of a WebJob! I have configured my WebJob project as per the instructions on the WebJob SDK repo which ends up looking like this:
Program
using System.Configuration;
using System.Diagnostics;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
namespace WebJobs
{
public class Program
{
public static void Main()
{
JobHostConfiguration config = new JobHostConfiguration();
config.UseTimers();
using (LoggerFactory loggerFactory = new LoggerFactory())
{
string key = ConfigurationManager.AppSettings["webjob-instrumentation-key"];
loggerFactory.AddApplicationInsights(key, null);
loggerFactory.AddConsole();
config.LoggerFactory = loggerFactory;
config.Tracing.ConsoleLevel = TraceLevel.Off;
if (config.IsDevelopment)
config.UseDevelopmentSettings();
JobHost host = new JobHost(config);
host.RunAndBlock();
}
}
}
}
Functions
This is just a test function that will run every minute for half an hour.
using Core.Telemetry;
using Microsoft.ApplicationInsights;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Timers;
using System;
using System.Collections.Generic;
namespace WebJobs.Functions
{
public class TestFunctions
{
public void TelemetryTest([TimerTrigger(typeof(Schedule))] TimerInfo timer)
{
TelemetryClient tc = new TelemetryClient();
tc.TrackEvent("TelemetryTestEvent");
}
// schedule that will run every minute
public class Schedule : DailySchedule
{
private static readonly string[] times =
{
"12:01","12:02","12:03","12:04","12:05","12:06","12:07","12:08","12:09","12:10",
"12:11","12:12","12:13","12:14","12:15","12:16","12:17","12:18","12:19","12:20",
"12:21","12:22","12:23","12:24","12:25","12:26","12:27","12:28","12:29","12:30"
};
public Schedule() : base(times) { }
}
}
}
This seems to partially work in that I can see some telemetry in AI but not the custom events. For example I can see a Request show up each time TestFunctions.TelemetryTest() runs and various Traces during the initialisation of the WebJob.
I have probably not configured something properly or am not getting the TelemetryClient in the correct manner, but I cannot find any documentation on tracking custom events in WebJobs.
Any help would be appreciated.

Try setting the instrumentationkey explicit:
tc.Context.InstrumentationKey = "<your_key>";
According to the docs you should be able to get the key using
System.Environment.GetEnvironmentVariable(
"APPINSIGHTS_INSTRUMENTATIONKEY", EnvironmentVariableTarget.Process)
if you have set up application insights integration.

Related

How to implement a triggerless .NET Core Console App as a continuous Azure WebJob?

All the code samples I've seen so far for Azure WebJobs rely on some kind of trigger (e.g. TimerTrigger or QueueTrigger).
I am looking specifically at WebJobs SDK 3.x, by the way.
So. For a triggerless WebJob (Windows Service-alike one), am I expected to use NoAutomaticTrigger and find a way to kickoff my "main" code manually?
Or should I resort to implementing and registering a class that implements the IHostedService interface?
So far that's the approach I'm taking but it feels more of a hack than a recommended way.
I have not even tried to deploy this code and only ran it on my local machine, so I am afraid that the publishing process will confirm my code is not suitable for Azure WebJobs in its current form.
EntryPoint.cs
This is how the application is being bootstrap when the process is starting.
using Microsoft.Azure.ServiceBus;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace AbcCorp.Jobs
{
public static class Program
{
static async Task Main(string[] args)
{
var config = new ConfigurationBuilder()
.SetBasePath(Directory.GetCurrentDirectory())
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json", false)
.Build();
var hostBuilder = new HostBuilder()
.ConfigureWebJobs(builder => { builder.AddAzureStorageCoreServices(); })
.ConfigureServices(serviceCollection =>
{
ConfigureServices(serviceCollection, config);
serviceCollection.AddHostedService<ConsoleApplication>();
});
using (var host = hostBuilder.Build())
await host.RunAsync();
}
private static IServiceCollection ConfigureServices(IServiceCollection services, IConfigurationRoot configuration)
{
services.AddTransient<ConsoleApplication>();
// ... more DI registrations
return services;
}
}
}
ConsoleApplication.cs
This would normally be implemented as a function with a trigger.
The thing is, I want this code to only run once on the process startup.
It will start listening on the service bus events using the regular Microsoft.Azure.ServiceBus SDK package.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Hosting;
using AbcCorp.Internal.Microsoft.Azure.ServiceBus;
using AbcCorp.Api.Messaging;
namespace AbcCorp.Jobs
{
public sealed class ConsoleApplication: IHostedService
{
private readonly IReceiver<SubmissionNotification> _messageReceiver;
private readonly MessageHandler _messageHandler;
public ConsoleApplication(IReceiver<SubmissionNotification> messageReceiver, MessageHandler messageHandler)
{
_messageReceiver = messageReceiver;
_messageHandler = messageHandler;
}
public Task StartAsync(CancellationToken cancellationToken)
{
_messageReceiver.StartListening(_messageHandler.HandleMessage, _messageHandler.HandleException);
return Task.Delay(Timeout.Infinite);
}
public Task StopAsync(CancellationToken cancellationToken)
{
_messageReceiver.Dispose();
return Task.CompletedTask;
}
}
}
So you want a console application to run in a WebJob and listen to messages. You don't really care about WebJob magic like triggers, it's just a place to run your console app. I've done the exact same thing before.
I found the IHostedService abstraction to be very helpful, but I didn't like their SDK. I found it bloated and hard to use. I didn't want to take a large dependency in order use a large array of special magic Azure stuff, when all I wanted to do was run a console application in a WebJob for now, and maybe move it elsewhere later.
So I ended just deleting that dependency, stealing the Shutdown code from the SDK and writing my own Service Host. The result is on my Github Repo azure-webjob-host. Feel free to use it or raid it for ideas. I don't know, maybe if I did it again I'd have another attempt at getting the SDK to work, but I present this is a bit of an alternative to the SDK.
Basically I wrote an IServiceHost not too different from yours (except that StartAsync exited when stuff started instead of just hanging). Then I wrote my own service host, which is basically just a loop:
await _service.StartAsync(cancellationToken);
while (!token.IsCancellationRequested){await Task.Delay(1000);}
await _service.StopAsync(default);
Then I stole the WebJobsShutdownWatcher code from their repo.
Then I created an IServiceHost that started my message handler. (I was using Rabbit, which has nothing to do with triggers or azure stuff)
public class MessagingService : IHostedService, IDisposable
{
public MessagingService(ConnectionSettings connectionSettings,
AppSubscriberSettings subscriberSettings,
MessageHandlerTypeMapping[] messageHandlerTypeMappings,
ILogger<MessagingService> logger)
{
....
}
public async Task StartAsync(CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
await Task.WhenAll(subscribers.Value.Select(s => s.StartSubscriptionAsync()));
}
public async Task StopAsync(CancellationToken cancellationToken)
{
...
}
public void Dispose()
{
...
}
}
Then I put that all together into something like this:
IHostedService myService = new MyService();
using (var host = new ServiceHostBuilder().HostService(myService))
{
await host.RunAsync(default);
}
I have some workers attached to service bus topics and what we do is the following (ServiceBusClient is a custom Class that contains our Subscription Client):
public override Task StartAsync(CancellationToken cancellationToken)
{
_serviceBusClient.RegisterOnMessageHandlerAndReceiveMessages(MessageReceivedAsync);
_logger.LogDebug($"Started successfully the Import Client. Listening for messages...");
return base.StartAsync(cancellationToken);
}
public void RegisterOnMessageHandlerAndReceiveMessages(Func<Message, CancellationToken, Task> ProcessMessagesAsync)
{
// Configure the message handler options in terms of exception handling, number of concurrent messages to deliver, etc.
var messageHandlerOptions = new MessageHandlerOptions(ExceptionReceivedHandler)
{
// Maximum number of concurrent calls to the callback ProcessMessagesAsync(), set to 1 for simplicity.
// Set it according to how many messages the application wants to process in parallel.
MaxConcurrentCalls = 1,
// Indicates whether MessagePump should automatically complete the messages after returning from User Callback.
// False below indicates the Complete will be handled by the User Callback as in `ProcessMessagesAsync` below.
AutoComplete = false
};
// Register the function that processes messages.
SubscriptionClient.RegisterMessageHandler(ProcessMessagesAsync, messageHandlerOptions);
}
And then you can use DI to instantiate your service bus client and inject on the constructor of your Worker class.
Here i have the initialization of the singleton instance of my custom class Service Bus Client
services.AddSingleton<IServiceBusClient, ServiceBusClient>((p) =>
{
var diagnostics = p.GetService<EventHandling>();
var sbc = new ServiceBusClient(
programOptions.Endpoint,
programOptions.TopicName,
programOptions.Subscriber,
programOptions.SubscriberKey);
sbc.Exception += exception => diagnostics.HandleException(exception);
return sbc;
});
Then on this custom class, i initialize my subscription client
public ServiceBusClient(
string endpoint,
string topicName,
string subscriberName,
string subscriberKey, ReceiveMode mode = ReceiveMode.PeekLock)
{
var connBuilder = new ServiceBusConnectionStringBuilder(endpoint, topicName, subscriberName, subscriberKey);
var connectionString = connBuilder.GetNamespaceConnectionString();
ConnectionString = connectionString;
TopicName = topicName;
SubscriptionName = topicName;
SubscriptionClient = new SubscriptionClient(connectionString, topicName, subscriberName, mode);
}
You can check #george chen's answer from this post How to create service bus trigger webjob?
where instead of creating a receiver and registering a message handler, you can use the in built queue trigger and and write your message handler logic inside it.

use hangfire as windows service by topshelf (.net core 2.2)

I am trying to use hangfire as windows service by using Topshelf in console app .net core 2.2 . I just want to load hangfire dashboard, not adding any job or anything else.
Program.cs
using System;
using Topshelf;
namespace HangfireAsService
{
class Program
{
static void Main(string[] args)
{
HostFactory.Run(config =>
{
config.Service<Bootstrap>(service =>
{
service.ConstructUsing(s => new Bootstrap());
service.WhenStarted(s => s.Start());
service.WhenStopped(s => s.Stop());
});
config.RunAsLocalSystem();
config.SetDescription("Hangfire as windows Service for DataCrawling Project");
config.SetDisplayName("Hangfire Service Custom");
});
}
}
}
Bootstrap.cs
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.Owin.Hosting;
namespace HangfireAsService
{
public class Bootstrap
{
private IDisposable _host;
public void Start()
{
var options = new StartOptions { Port = 8999 };
_host = WebApp.Start<Startup>(options);
Console.WriteLine();
Console.WriteLine("Hangfire has started");
Console.WriteLine("Dashboard is available at http://localhost:8999/hangfire");
Console.WriteLine();
}
public void Stop()
{
_host.Dispose();
}
}
}
Startup.cs
using Hangfire;
using Microsoft.AspNetCore.Builder;
using Owin;
using System;
using System.Collections.Generic;
using System.Text;
namespace HangfireAsService
{
public class Startup
{
public void Configuration(IApplicationBuilder appBuilder)
{
GlobalConfiguration.Configuration
.UseSqlServerStorage("Server=111.111.11.1\\INS2017; Database=Hangfire; user=sa;
password=;");
appBuilder.UseHangfireDashboard();
appBuilder.UseHangfireServer();
}
}
}
As you can see, I created 2 classes for my self-host owin and after reviewing the event viewer I got the error displayed below:
The description for Event ID 0 from source HangfireAsService cannot be
found. Either the component that raises this event is not installed on
your local computer or the installation is corrupted. You can install
or repair the component on the local computer.
If the event originated on another computer, the display information
had to be saved with the event.
The following information was included with the event:
Service cannot be started. System.NullReferenceException: Object
reference not set to an instance of an object. at
Microsoft.Owin.Hosting.Utilities.SettingsLoader.FromConfigImplementation..ctor()
at
Microsoft.Owin.Hosting.Utilities.SettingsLoader.<>c.b__1_0()
at System.Threading.LazyInitializer.EnsureInitializedCore[T](T&
target, Func1 valueFactory) at
Microsoft.Owin.Hosting.Utilities.SettingsLoader.LoadFromConfig(IDictionary2
settings) at
Microsoft.Owin.Hosting.Engine.StartContext..ctor(StartOptions options)
at
Microsoft.Owin.Hosting.Starter.DirectHostingStarter.Start(StartOptions
options) at
Microsoft.Owin.Hosting.Starter.HostingStarter.Start(StartOptions
options) at HangfireAsService.Bootstrap.Start() in
C:\MyWorkSpace\Data
Crawling\dataCrawlingConsole\HangfireAsService\Bootstrap.cs:line 17
at HangfireAsService.Program.<>c.b__0_3(Bootstrap s) in
C:\MyWorkSpace\Data
Crawling\dataCrawlingConsole\HangfireAsService\Program.cs:line 15
at
Topshelf.ServiceConfiguratorExtensions.<>c__DisplayClass2_01.<WhenStarted>b__0(T
service, HostControl control) at
Topshelf.Builders.DelegateServiceBuilder1.DelegateServiceHandle.Start(HostControl
hostControl) at
Topshelf.Runtime.Windows.WindowsServiceHost.OnStart(String[] args)
at System.ServiceProcess.ServiceBase.ServiceQueuedMainCallback(Object
state)
The message resource is present, but the message was not found in the message table.
i used same code inside .net framework instead of .net core and work perfectly.after a test something else i notice this problem because of OWIN happened so after i removed it and use using .net core self-host instead of OWIN everything work perfectly.
below link will help you a lot.
https://medium.com/#tocalai/create-windows-service-using-net-core-console-application-dc2f278bbe42

Azure WebJob BlobTrigger - Output Blob makes trigger not fire

I have a v3 WebJob that successfully fires when my function method signature is as follows:
public static void ProcessQueueMessage(
[BlobTrigger("process/{name}", Connection = "storage-connection")] Stream blob,
ILogger log
)
However when I add an output blob the BlobTrigger never fires.
public static void ProcessQueueMessage(
[BlobTrigger("process/{name}", Connection = "storage-connection")] Stream blob,
[Blob("output/{name}", FileAccess.Write, Connection = "storage-connection")] Stream processedBlob,
ILogger log
)
The documentation I'm following is here: https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-blob#output
If you want to use Azure WebJob BlobTrigger and also use a Output binding, you can follow my steps.
In my side, it works fine.
1.create a console app and install everything needed. You can follow by this doc. This will tell you how to use the WebJob SDK.
This is my code:
Functions.cs:
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using System.IO;
namespace ConsoleApp1
{
public class Functions
{
public static void ProcessQueueMessage(
[BlobTrigger("images/{name}")]Stream myBlob,
[Blob("form/{name}", FileAccess.Write)] Stream imageSmall,
string name,
ILogger log
)
{
log.LogInformation("webjobs blob trigger works!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
}
}
}
Program.cs:
using System;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace ConsoleApp1
{
class Program
{
static void Main(string[] args)
{
var builder = new HostBuilder();
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorage();
b.AddAzureStorageCoreServices();
});
builder.ConfigureLogging((context, b) =>
{
b.AddConsole();
});
builder.ConfigureWebJobs(b =>
{
b.AddAzureStorageCoreServices();
b.AddAzureStorage();
});
var host = builder.Build();
using (host)
{
host.Run();
}
}
}
}
appsettings.json:
{
"AzureWebJobsStorage": "DefaultEndpointsProtocol=https;AccountName=bowmanimagestorage02;AccountKey=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxSAHfYi1d2yc+BU3NG9hkbGEPU/lJP5wtqYZ3pdDq1lGEkdUx7w==;EndpointSuffix=core.windows.net"
}
You can find that I have already add the output binding in the blobtrigger, Just follow the doc you give. I upload a image to the images container and the console show the loginformation, the image also been upload to the form container.
Things works in my side, if you have more questions, please show more details. I hope my answer can give you some help.

how do i make a background worker to update a datastore every 5 minutes in asp.net core

I've followed the getting started tutorial and currently have a TODO CRUD app.
https://learn.microsoft.com/en-us/aspnet/core/tutorials/first-web-api-mac?view=aspnetcore-2.1
I want to add a background worker that updates the todo database every 5 minutes and sets Item 1 to a random value for its Name, and isCompleted properties.
This is pretty easy in Java SpringBoot or Elixir's Phoenix...
Is there a quick and painless way in c# to do this?
The doc I found on Microsoft website was from 2012... so I assume there is a more elegant way to do this by now.
Edit: I went with DNTScheduler.Core and it was relatively painless to set up. Followed the exampleApp setup that was on github repo and here is the task i ended up using:
using System;
using System.Threading.Tasks;
using DNTScheduler.Core.Contracts;
using Microsoft.Extensions.Logging;
using myapp.Models;
namespace MyApp.Tasks
{
public class TaskOne : IScheduledTask
{
private readonly ILogger<DoBackupTask> _logger;
private readonly TodoContext _context; // autoinjects since it was added in startup.cs in configure()
public TaskOne(ILogger<DoBackupTask> logger, TodoContext context)
{
_logger = logger;
_context = context;
}
public Task RunAsync()
{
var todo = _context.TodoItems.Find(1);
if (todo == null)
{
return Task.CompletedTask;
}
string[] names = new string[] { "val1", "val2"};
Random r = new Random();
string random_name = names[r.Next(0, names.Length)];
todo.Name = random_name;
_context.TodoItems.Update(todo);
_context.SaveChanges();
_logger.LogInformation("Ran My Task\n\n ");
return Task.CompletedTask;
}
}
}
I would suggest you use Scheduler, there are some packages that can do that, but the best package that I have seen so far is DNTScheduler.Core
DNTScheduler.Core is a lightweight ASP.NET Core's background tasks runner and scheduler.
for more information, you can visit this link
I would argue that the correct answer is wrong if you want to follow the native practice.
Make use of IHostedService to perform repetitive actions.
Here’s an answer I’ve written that addresses that.
There is a saying i've read somewhere: "If its worth doing, its worth doing right. If its not worth doing right - find something that is."
So, as other colleagues already said, web application is not meant to be a container for background processes for reasons already mentioned.
Depending on your environment, you'll be much better off using:
windows services or even windows scheduler (on classic hosting) or some of the libraries for scheduling but outside of the web app. then, those scheduler, whichever it is, could just trigger API endpoint within your web app so you have business logic at one place
azure scheduler if you work with azure
pretty much anything else is looking for trouble when you don't need one.
In this sample I Insert a record to my database ( Word-Suggestion-Table in here) every hour using UnitOfWork and Repository Pattern. No error on Cannot consume scoped service *IUnitOfWork* from singleton
1- Add work class and IWork interface as below:
using System.Threading;
using System.Threading.Tasks;
namespace Map118.App.BackgroundTasks
{
public interface IWorker
{
Task DoWork(CancellationToken cancellationToken);
}
}
Work.cs :
using Map118.DataLayer.IRepositories;
using Map118.Models;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace Map118.App.BackgroundTasks
{
public class Worker : IWorker
{
private readonly IServiceProvider serviceProvider;
public Worker( IServiceProvider serviceProvider)
{
this.serviceProvider = serviceProvider;
}
public async Task DoWork(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
using (var scope = serviceProvider.CreateScope())
{
var _unitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
while (!stoppingToken.IsCancellationRequested)
{
WordSuggestion wordSuggestion = new WordSuggestion()
{
word = Guid.NewGuid().ToString(),
};
await _unitOfWork.wordSuggestionRepository.Add(wordSuggestion);
await _unitOfWork.Save();
await Task.Delay(1000 * 3600);
}
}
}
}
}
}
2- Add another class as below:
using Microsoft.Extensions.Hosting;
using System.Threading;
using System.Threading.Tasks;
namespace Map118.App.BackgroundTasks
{
public class UserActivitiesCleaner : BackgroundService
{
private readonly IWorker worker;
public UserActivitiesCleaner(IWorker worker)
{
this.worker = worker;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
await worker.DoWork(stoppingToken);
}
}
}
3- Add services to Startup.cs :
services.AddHostedService<UserActivitiesCleaner>();
services.AddSingleton<IWorker, Worker>();

Quartz.Net scheduler works locally but not on remote host

I have a timed quartz.net job working fine on my dev machine, but once deployed to a remote server it is not triggering. I believe the job is scheduled ok, because if I postback, it tells me the job already exists (I normally check for postback however). The email code definitely works, as the 'button1_click' event sends emails successfully.
I understand I have full or medium trust on the remove server. My host says they don't apply restrictions that they know of which would affect it. Any other things I need to do to get it running?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using Quartz;
using Quartz.Impl;
using Quartz.Core;
using Aspose.Network.Mail;
using Aspose.Network;
using Aspose.Network.Mime;
using System.Text;
namespace QuartzTestASP
{
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
ISchedulerFactory schedFact = new StdSchedulerFactory();
IScheduler sched = schedFact.GetScheduler();
JobDetail jobDetail = new JobDetail("testJob2", null, typeof(testJob));
//Trigger trigger = TriggerUtils.MakeMinutelyTrigger(1, 3);
Trigger trigger = TriggerUtils.MakeSecondlyTrigger(10, 5);
trigger.StartTimeUtc = DateTime.UtcNow;
trigger.Name = "TriggertheTest";
sched.Start();
sched.ScheduleJob(jobDetail, trigger);
}
}
protected void Button1_Click1(object sender, EventArgs e)
{
myutil.sendEmail();
}
}
class testJob : IStatefulJob
{
public testJob() { }
public void Execute(JobExecutionContext context)
{
myutil.sendEmail();
}
}
public static class myutil
{
public static void sendEmail()
{
// tested code lives here and works fine when called from elsewhere
}
}
}
The scheduler factory should be global to your application. In other words, declare it in Global.asax or similar that effectively gives you a global instance to operate with. You should start the scheduler in your application start if running ASP.NET.
Beware thought. ASP.NET recycles its processes which effective causes shutdown of scheduler (no jobs will run) until next next request comes in to web server to start the scheduler again. The recommended way is to have a windows service for running Quartz.NET jobs.

Categories