How to fetch events from eventhub from a timer enabled azure function? - azure

I am working with Azure Event Hubs. My requirement is to fetch the events from Azure Event Hub, using azure function on a daily basis. Basically my azure function will be timer enabled. It should be able to fetch the data from azure event hubs. Is there a mechanism for this ?
I am aware that we can trigger a azure function whenever an event is received at event hub. This i don't want as the function will execute n number of time. I want to just fetch the events on a daily basis.

You can still create a timer triggered function and create consumer clients in your code to receive events. See sample code below. Let me know if you have any questions.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Azure.Messaging.EventHubs.Consumer;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
namespace FunctionApp7
{
public static class Function1
{
const string EventHubsConnectionString = "your connection string";
const string EventHubName = "evethub name";
const string ConsumerGroupName = "cgname";
[FunctionName("Function1")]
public static void Run([TimerTrigger("0 */5 * * * *")]TimerInfo myTimer, ILogger log)
{
log.LogInformation($"C# Timer trigger function executed at: {DateTime.Now}");
// You better dissover partitions by eventhub client. I am just hardcoding them here for now.
var partitions = new List<string> { "0", "1" };
var receiveTasks = new List<Task>();
foreach(var p in partitions)
{
receiveTasks.Add(ReadEventsFromPartition(p));
}
// Wait until all reads complete.
Task.WhenAll(receiveTasks);
}
public static async Task ReadEventsFromPartition(string partitionId)
{
await using (var consumer = new EventHubConsumerClient(ConsumerGroupName, EventHubsConnectionString, EventHubName))
{
EventPosition startingPosition = EventPosition.FromOffset(CheckpointStore.ReadOffsetForPartition(partitionId));
long lastOffset = -1;
await foreach (PartitionEvent receivedEvent in consumer.ReadEventsFromPartitionAsync(partitionId, startingPosition))
{
// Process received events here.
// Break if no events left.
if (receivedEvent.Data == null)
{
break;
}
lastOffset = receivedEvent.Data.Offset;
}
// Persist last event's offset so we can continue reading from this position next time function is triggered.
if (lastOffset != -1)
{
// Write offset into some durable store.
CheckpointStore.WriteOffsetForPartition(partitionId, lastOffset);
}
}
}
}
}

Related

Returned Azure service bus queue sequence number different in my consumer than what was returned in the producer and shown in the Azure portal?

When I create a scheduled service bus message, both in Azure Portal and in my app using the Service bus producer code (below) and I receive a sequence number. I save it in my db.
Problem - When my Service bus consumer code is triggered by the dequeue of the scheduled message the sequence number is different than the one that was initially given to me by both the service bus producer code and through the Azure portal.
Shown here, where '13' is the sequnce number shown in Azure Portal screen.
Here is the code that receives the scheduled message and you can see the sequence number is different!
Here is my consumer code (don't think it matters)
private async Task MessageHandler(ProcessMessageEventArgs args)
{
string body = args.Message.Body.ToString();
JObject jsonObject = JObject.Parse(body);
var eventStatus = (string)jsonObject["EventStatus"];
await args.CompleteMessageAsync(args.Message);
// fetch row here by sequence number
// edit some data from entity, then save
int result = await dbContext.SaveChangesAsync();
}
Here is my producer code
public async Task<long> SendMessage(string messageBody, DateTimeOffset scheduledEnqueueTimeUtc)
{
await using (ServiceBusClient client = new ServiceBusClient(_config["ServiceBus:Connection"]))
{
ServiceBusSender sender = client.CreateSender(_config["ServiceBus:Queue"]);
ServiceBusMessage message = new ServiceBusMessage(messageBody);
var sequenceNumber = await sender.ScheduleMessageAsync(message, scheduledEnqueueTimeUtc);
return sequenceNumber;
}
}
From the documentation:
The SequenceNumber for a scheduled message is only valid while the message is in this state. As the message transitions to the active state, the message is appended to the queue as if had been enqueued at the current instant, which includes assigning a new SequenceNumber.
This is the code on my side:
using System;
using System.Threading;
using System.Threading.Tasks;
using Azure.Messaging.ServiceBus;
namespace ConsoleApp3
{
class Program
{
static string connectionString = "xxxxxx";
static string queueName = "myqueue";
static ServiceBusClient client;
static ServiceBusProcessor processor;
static async Task Main(string[] args)
{
client = new ServiceBusClient(connectionString);
processor = client.CreateProcessor(queueName, new ServiceBusProcessorOptions());
try
{
processor.ProcessMessageAsync += MessageHandler;
processor.ProcessErrorAsync += ErrorHandler;
await processor.StartProcessingAsync();
Console.WriteLine("Wait for a minute and then press any key to end the processing");
Console.ReadKey();
Console.WriteLine("\nStopping the receiver...");
await processor.StopProcessingAsync();
Console.WriteLine("Stopped receiving messages");
}
finally
{
await processor.DisposeAsync();
await client.DisposeAsync();
}
}
static async Task MessageHandler(ProcessMessageEventArgs args)
{
string body = args.Message.Body.ToString();
Console.WriteLine($"Received: {body}");
Console.WriteLine($"ID: {args.Message.MessageId}");
await args.CompleteMessageAsync(args.Message);
}
static Task ErrorHandler(ProcessErrorEventArgs args)
{
Console.WriteLine(args.Exception.ToString());
return Task.CompletedTask;
}
}
}
And it seems no problem on my side:
Message Id changed should be the message be thrown back by some reasons.

Not able to get Azure function to trigger on Event Hub

I've been trying to get started with Azure Event Hubs & Azure functions.
While following the exact documentation - I'm unable to get the event hub trigger to work.
I have the following setup:
Function I call in an HTTP function (to generate event logs):
#EventHubOutput(name = "message-new", eventHubName = "KCETest1", connection = "KCETest1_all_EVENTHUB", dataType = "string" )
public String sendOrder(ExecutionContext context) {
return "foobar";
}
Snapshot of my event hub showing events are being triggered:
Event Hub Receiving messages
However, I'm not able to get anything to work to trigger an azure function.
I tried all the below just to get at least something written to the logs.
(as you can see - I've been playing around with all variables - thinking this might be the rootcause)
public class EventHubTriggerJava1 {
/**
* This function will be invoked when an event is received from Event Hub.
*/
#FunctionName("EventHubTriggerJava1")
public void run(
#EventHubTrigger(name = "msg", eventHubName = "KCETest1", connection = "KCETest1_all_EVENTHUB", consumerGroup = "$Default", cardinality = Cardinality.ONE , dataType = "string") String message,
final ExecutionContext context
) {
context.getLogger().info("Java Event Hub trigger 1function executed.");
context.getLogger().info("Length:" + message);
}
#FunctionName("EventHubTriggerJava2")
public void run(
#EventHubTrigger(name = "msg2", eventHubName = "testhub1", connection = "KCETest1_all_EVENTHUB", consumerGroup = "$Default", cardinality = Cardinality.ONE) EventData message,
final ExecutionContext context
) {
context.getLogger().info("Java Event Hub trigger 2function executed.");
context.getLogger().info("Length:" + message.toString());
}
#FunctionName("EventHubTriggerJava3")
public void run(
#EventHubTrigger(name = "msg3", eventHubName = "testhub1", connection = "KCETest1_all_EVENTHUB", consumerGroup = "$Default") List<String> message,
final ExecutionContext context
) {
context.getLogger().info("Java Event Hub trigger 3 function executed.");
context.getLogger().info("Length:" + message.get(0).toString());
}
}
I'm running out of ideas - any suggestions from your side?
Thx a lot!

How to set proxy for local environment in EventHubTrigger connection string for Azure Function

I can set proxy through EventHubConnectionOptions when using EventProcessorClient and I am able to receive events. But how can I achieve the same in EventHubTrigger Azure function? In my local enviornment I cannot access azure resources without proxy.
Update:
Azure function use proxies.json to set proxy.
Please have a look of the doc:
https://learn.microsoft.com/en-us/azure/azure-functions/functions-proxies
Original Answer:
In my local [environment] I cannot access azure resources without proxy.
Below code doesn't need proxy:
EventHub Trigger:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Azure.EventHubs;
using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
namespace FunctionApp63
{
public static class Function1
{
[FunctionName("Function1")]
public static async Task Run([EventHubTrigger("test", Connection = "str")] EventData[] events, ILogger log)
{
var exceptions = new List<Exception>();
foreach (EventData eventData in events)
{
try
{
string messageBody = Encoding.UTF8.GetString(eventData.Body.Array, eventData.Body.Offset, eventData.Body.Count);
// Replace these two lines with your processing logic.
log.LogInformation($"C# Event Hub trigger function processed a message: {messageBody}");
await Task.Yield();
}
catch (Exception e)
{
// We need to keep processing the rest of the batch - capture this exception and continue.
// Also, consider capturing details of the message that failed processing so it can be processed again later.
exceptions.Add(e);
}
}
// Once processing of the batch is complete, if any messages in the batch failed processing throw an exception so that there is a record of the failure.
if (exceptions.Count > 1)
throw new AggregateException(exceptions);
if (exceptions.Count == 1)
throw exceptions.Single();
}
}
}
Send message to event hub:
using System;
using System.Text;
using System.Threading.Tasks;
using Azure.Messaging.EventHubs;
using Azure.Messaging.EventHubs.Producer;
namespace ConsoleApp27
{
class Program
{
public static async Task Main(string[] args)
{
string connectionString = "Endpoint=sb://testbowman.servicebus.windows.net/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=QhZeIb7KaCiNLP171jW3chJtIQDQgx0OBAOQNx7wE9g=";
const string eventHubName = "test";
// Create a producer client that you can use to send events to an event hub
await using (var producerClient = new EventHubProducerClient(connectionString, eventHubName))
{
// Create a batch of events
using EventDataBatch eventBatch = await producerClient.CreateBatchAsync();
// Add events to the batch. An event is a represented by a collection of bytes and metadata.
eventBatch.TryAdd(new EventData(Encoding.UTF8.GetBytes("First event")));
eventBatch.TryAdd(new EventData(Encoding.UTF8.GetBytes("Second event")));
eventBatch.TryAdd(new EventData(Encoding.UTF8.GetBytes("Third event")));
// Use the producer client to send the batch of events to the event hub
await producerClient.SendAsync(eventBatch);
Console.WriteLine("A batch of 3 events has been published.");
}
}
}
}

Time trigger function with CosmosDB inputs

I have created a time trigger function in azure functions and Added a CosmosDB input as shown below.
Below is the .csx file
#r "Microsoft.Azure.Documents.Client"
using System;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
public static async Task Run(TimerInfo myTimer, string[] inputDocument, TraceWriter log)
{
log.Info($"C# Timer trigger function executed at: {DateTime.Now}");
// string [] = bindings.inputDocument;
DocumentClient client;
}
How to get the input documents from cosmosDb into this csx file?
I am not familiar with C#, in javascript we will use var Data = context.bindings.DataInput;
How to do the same in c#?
You can use it like the below snippet
public static void Run(TimerInfo myTimer, IEnumerable<dynamic> documents)
{
foreach (var doc in documents)
{
// operate on each document
}
}
More examples in documentation
Questions from comments
If we have more than one cosmos Db input do we need to add as below ?
No if even if you have more than one inputs the IEnumerable<dynamic> documents is used. And you can iterate the list.
How to add if we have a cosmosDB output ?
The out object is used in this which points to your binding.
public static void Run(string myQueueItem, out object employeeDocument, ILogger log)
{
log.LogInformation($"C# Queue trigger function processed: {myQueueItem}");
dynamic employee = JObject.Parse(myQueueItem);
employeeDocument = new {
id = employee.name + "-" + employee.employeeId,
name = employee.name,
employeeId = employee.employeeId,
address = employee.address
};
}
More information on Output

Azure Event Grid - Event Delivery Security at Azure Function

I have been working on a POC related to Azure Event Grid integration with Azure Function. I am stuck on the Event delivery Security as mentioned here.
I am using Event Grid Trigger which is sent by built-in Event grid Subscription in Azure Blob Storage. I have added an access token as a query parameter in WebHook endpoint as mentioned in the above URL.
But I cannot access that parameter in the Function code. Can someone share a sample for doing this?
FYI - Below is the function definition in my code.
[FunctionName("EventGridFunc")]
public static void Run([EventGridTrigger]EventGridEvent eventGridEvent,
TraceWriter log)
{
log.Info("Received a trigger.");
log.Info(eventGridEvent.Data.ToString());
}
The full subscriberUrl for EventGridTrigger function has the following format:
https://{FunctionAppName}.azurewebsites.net/admin/extensions/EventGridExtensionConfig?functionName={EventGridTriggerFunctionName}&code={masterKey}
As you can see, the EventGridTrigger is basically a special HttpTrigger (push) function with a "hidden pre-processing" of the event message for its validation.
Update:
I didn't see a way how to obtain a query string in the EventGridTrigger. However, there are few workarounds for your solution such as:
using an Application settings
using an Azure Key Vault for storing a secret
using a HttpTrigger instead of EventGridTrigger
The following code snippet shows an example of the HttpTrigger function for EventGrid (version 2018-05-01-preview) subscriber:
#r "Newtonsoft.Json"
using System.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System.Linq;
using System.Threading.Tasks;
public static async Task<HttpResponseMessage> Run(HttpRequestMessage req, IDictionary<string, string> query, TraceWriter log)
{
log.Info("C# HTTP trigger function processed an EventGrid request.");
log.Info($"\nHeaders:\n\t{string.Join("\n\t", req.Headers.Where(i => i.Key.StartsWith("aeg-")).Select(i => $"{i.Key}={i.Value.First()}"))}");
log.Info($"\nQuery:\n\t{string.Join("\n\t", query.Select(i => $"{i.Key}={i.Value}"))}");
string eventGridValidationHeader = req.Headers.FirstOrDefault( x => string.Compare(x.Key,"Aeg-Event-Type", true) == 0).Value?.FirstOrDefault().Trim();
// media type = application/json or application/cloudevents+json
string jsontext = null;
var jtoken = JToken.Parse(await req.Content.ReadAsStringAsync());
log.Info($"\n{jtoken.ToString(Newtonsoft.Json.Formatting.Indented)}");
if (jtoken is JArray)
jsontext = jtoken.SingleOrDefault<JToken>().ToString();
else if (jtoken is JObject)
jsontext = jtoken.ToString();
var eventGridEvent = JsonConvert.DeserializeAnonymousType(jsontext, new { EventType = "", Data = new JObject()});
if (string.IsNullOrEmpty(eventGridValidationHeader) || string.IsNullOrEmpty(eventGridEvent?.EventType) || eventGridEvent?.Data == null)
{
return req.CreateErrorResponse(HttpStatusCode.BadRequest, "No EventGrid message.");
}
if (eventGridValidationHeader == "SubscriptionValidation" && eventGridEvent.EventType == "Microsoft.EventGrid.SubscriptionValidationEvent")
{
log.Verbose(#"Event Grid Validation event received.");
return req.CreateResponse(HttpStatusCode.OK, JsonConvert.DeserializeObject(JsonConvert.SerializeObject(new { validationResponse = ((dynamic)eventGridEvent.Data).validationCode })));
}
#region Event Processing
// for testing a retry delivery policy
//return req.CreateResponse(HttpStatusCode.BadRequest, "Testing");
#endregion
return req.CreateResponse(HttpStatusCode.NoContent);
}

Resources