F# on Azure: using Table Storage for logging
Windows Azure finally has a good F# support. Creating F# Worker Roles is supported right from the wizzard in Visual Studio and you can create a F# Web Role using the F# C# MVC template. I decided to try it out and the first thing I needed to implement was logging. I decided for logging to Azure Table Storage.
I assume you have a basic idea of how Azure Table Storage works. If not, there is a good guide on the Windows Azure website.
The first thing you need to do is to define your log entry class. You need to create a class, Azure Table Storage does not work with F# records. In my case I want to store a timestamp, message and severity.
[<DataServiceKey("PartitionKey", "RowKey")>]
type LogEntity() =
let mutable partitionKey = ""
let mutable rowKey = ""
let mutable message = ""
let mutable timestamp = DateTime.Now
let mutable severity = ""
member x.PartitionKey with get() = partitionKey and set v = partitionKey <- v
member x.RowKey with get() = rowKey and set v = rowKey <- v
member x.Message with get() = message and set v = message <- v
member x.Timestamp with get() = timestamp and set v = timestamp <- v
member x.Severity with get() = severity and set v = severity <- v
Alternatively you can make your class inherit from TableEntity that already contains the PartitionKey and RowKey properties. The Severity property is in my case just a simple discriminated union
type Severity =
| Debug
| Information
| Error
with
member this.toString() =
match this with
| Debug -> "Debug"
| Information -> "Information"
| Error -> "Error"
You can access the Azure API in C# way but you do not need to, there is a great library called Fog by Dan Mohl that makes using Azure API from F# more comfortable.
First you create a Azure Table Storage client using a connection string defined in the Windows Azure Cloud Service Configuration file for a role
let client = BuildTableClientWithConnStr "TableStorageConnectionString"
Saving a log entry is then very simple thanks to Fog
let log (severity:Severity) message =
let entry = LogEntity(PartitionKey = DateTime.Now.ToString("yyyy-MM-dd"), RowKey = Guid.NewGuid().ToString(), Timestamp = DateTime.Now, Message = message, Severity = string severity)
CreateEntityWithClient client "LogEntity" entry
Saving data to Azure Table Storage may be a slow operation if you do it a lot, so you may want to log asynchronously
let log (severity:Severity) message =
async {
let entry = LogEntity(PartitionKey = DateTime.Now.ToString("yyyy-MM-dd"), RowKey = Guid.NewGuid().ToString(), Timestamp = DateTime.Now, Message = message, Severity = severity.ToString())
CreateEntityWithClient client "LogEntity" entry
} |> Async.Start
As you may have noticed, I use the date as the partiotion key. The thing with Azure Table Storage is, that you can get the data by partition key, row key, or all the data. The log date seems like a reasonable partition key that allows you to get log by days
let getLogs (date:DateTime) = seq {
for e in client.GetDataServiceContext().CreateQuery<LogEntity>("LogEntity") do
if e.PartitionKey = date.ToString("yyyy-MM-dd") then
yield e
}
Update: there is a more way to create the LogEntity using the CLIMuttableAttribte
[<CLIMutable>]
[<DataServiceKey("PartitionKey", "RowKey")>]
type LogEntity =
{
Message: string
Timestamp: DateTime
Severity: string
PartitionKey: string
RowKey: string
}