Use Function and eventgrid to realize automatic analysis and conversion of storage account log

This article is still the previous idea. In the previous article, we shared an idea. You can download the access log of the storage account through the PowerShell script, and then automatically upload it to Log Analytics, so that it can be directly in LA Analyze and query the log. Although this method is simple and convenient, it is not an automated method after all. After expanding our ideas, we might as well try to use eventgrid combined with function to convert this method into an automated solution.

First, let's first popularize what EventGrid and Function are

EventGrid

With Azure Event Grid, applications can be easily generated using an event-based architecture. First, select the Azure resource you want to subscribe to, and then provide an event handler or WebHook endpoint to send events to. The event grid includes built-in support for events from Azure services, such as storage blobs and resource groups. The event grid also supports its own events using custom themes.

Simply put, it is like a trigger, which can trigger various events and then make targeted responses. It sounds a bit like triggers in logic apps, but eventgrid is just a simple event transfer tool, downstream processing methods Completed by other products, the following picture can also be seen where the eventgrid is located, similar to a message queue for event transmission

https://docs.microsoft.com/en-us/azure/event-grid/?WT.mc_id=AZ-MVP-5001235

Picture 1.png

Function

Azure Functions is a serverless solution that allows users to reduce code writing, reduce infrastructure that needs to be maintained, and save costs. No need to worry about deploying and maintaining servers, the cloud infrastructure provides all the latest resources needed to keep applications running.

Function is actually easier to understand. Now all clouds basically have similar products. If you compare it horizontally, it is AWS lambda, a fully managed code running platform.

https://docs.microsoft.com/zh-cn/azure/azure-functions/functions-overview?WT.mc_id=AZ-MVP-5001235

Combining the above figure, we can actually see our thinking. Eventgird has built-in blob triggers, which means that when a new blob appears, it will automatically trigger eventgrid, and downstream processing procedures, we can combine function to Do, the code is actually ready-made, just use the previous one, it just needs to be changed slightly, the overall workload is very small

Of course, there is actually a hidden problem here, because the storage account log is stored in the $logs container, and this container will not trigger the eventgrid in the Azure backend. This is a bit tricky. The method we use is to create a function , And then use azcopy to periodically sync log sync to another container. Just one line of code is easy, so I won’t write it out for now.

Implementation steps

Let's look at the specific implementation steps below. First, create a function app. The relationship between function app and function is very simple. Function app is equivalent to the platform on which the function runs. The code runs on this platform. The function app can contain many Function

Create Function App

The process of creating function app is very simple, we choose runtime as PowerShell Core

Picture 2.png

Create Function 

After the Function app is created, you can create functions in it. Azure actually has built-in evenr grid trigger function, just select it when creating it

Picture 4.png


WeChat screenshot_20210112104638.png


Create Event Grid Subscription

The function is ready, then you can prepare eventgrid, you can create event grid subscription directly in the function

Picture 5.png

When creating event grid subscription, there are many types that can be selected. Here, please choose the storage type.

Picture 6.png


Configure event type of Storage account

Picture 7.png


Then pay attention to the need to configure the filter, because we have to limit the path to a specific range, not all blobs will trigger events

Picture 8.png

The trigger is also ready, and then you can prepare the code to be processed in the function

Write Code

Because the previous code is processed in a loop, and eventgrid is actually pushed one by one, the logic here needs to be adjusted slightly

Function Build-Signature ($customerId, $sharedKey, $date, $contentLength, $method, $contentType, $resource)
{
    $xHeaders = "x-ms-date:" + $date
    $stringToHash = $method + "`n" + $contentLength + "`n" + $contentType + "`n" + $xHeaders + "`n" + $resource

    $bytesToHash = [Text.Encoding]::UTF8.GetBytes($stringToHash)
    $keyBytes = [Convert]::FromBase64String($sharedKey)

    $sha256 = New-Object System.Security.Cryptography.HMACSHA256
    $sha256.Key = $keyBytes
    $calculatedHash = $sha256.ComputeHash($bytesToHash)
    $encodedHash = [Convert]::ToBase64String($calculatedHash)
    $authorization = 'SharedKey {0}:{1}' -f $customerId,$encodedHash
    return $authorization
}

Function Post-LogAnalyticsData($customerId, $sharedKey, $body, $logType)
{

    

    $method = "POST"
    $contentType = "application/json"
    $resource = "/api/logs"
    $rfc1123date = [DateTime]::UtcNow.ToString("r")
    $contentLength = $body.Length
    $signature = Build-Signature `
        -customerId $customerId `
        -sharedKey $sharedKey `
        -date $rfc1123date `
        -contentLength $contentLength `
        -method $method `
        -contentType $contentType `
        -resource $resource
    $uri = "https://" + $customerId + ".ods.opinsights.azure.com" + $resource + "?api-version=2016-04-01"

    $headers = @{
        "Authorization" = $signature;
        "Log-Type" = $logType;
        "x-ms-date" = $rfc1123date;
        "time-generated-field" = $TimeStampField;
    }

    $response = Invoke-WebRequest -Uri $uri -Method $method -ContentType $contentType -Headers $headers -Body $body -UseBasicParsing
    return $response.StatusCode
}

Function ConvertSemicolonToURLEncoding([String] $InputText)
{
    $ReturnText = ""
    $chars = $InputText.ToCharArray()



    $StartConvert = $false



    foreach($c in $chars)
    {
        if($c -eq '"') {
            $StartConvert = ! $StartConvert
        }


        if($StartConvert -eq $true -and $c -eq ';')
        {
            $ReturnText += "%3B"
        } else {
            $ReturnText += $c
        }
    }

    return $ReturnText
}

Function FormalizeJsonValue($Text)
{
    $Text1 = ""
    if($Text.IndexOf("`"") -eq 0) { $Text1=$Text } else {$Text1="`"" + $Text+ "`""}
    if($Text1.IndexOf("%3B") -ge 0) {
        $ReturnText = $Text1.Replace("%3B", ";")
    } else {
        $ReturnText = $Text1
    }
    return $ReturnText
}

Function ConvertLogLineToJson([String] $logLine)
{


    $logLineEncoded = ConvertSemicolonToURLEncoding($logLine)

    $elements = $logLineEncoded.split(';')

    

    $FormattedElements = New-Object System.Collections.ArrayList
                
    foreach($element in $elements)
    {

        $NewText = FormalizeJsonValue($element)
        $FormattedElements.Add($NewText) > null
    }
    $Columns = 
    (   "version-number",
        "request-start-time",
        "operation-type",
        "request-status",
        "http-status-code",
        "end-to-end-latency-in-ms",
        "server-latency-in-ms",
        "authentication-type",
        "requester-account-name",
        "owner-account-name",
        "service-type",
        "request-url",
        "requested-object-key",
        "request-id-header",
        "operation-count",
        "requester-ip-address",
        "request-version-header",
        "request-header-size",
        "request-packet-size",
        "response-header-size",
        "response-packet-size",
        "request-content-length",
        "request-md5",
        "server-md5",
        "etag-identifier",
        "last-modified-time",
        "conditions-used",
        "user-agent-header",
        "referrer-header",
        "client-request-id"
    )

    $logJson = "[{";
    For($i = 0;$i -lt $Columns.Length;$i++)
    {
        $logJson += "`"" + $Columns[$i] + "`":" + $FormattedElements[$i]
        if($i -lt $Columns.Length - 1) {
            $logJson += ","
        }
    }
    $logJson += "}]";

    return $logJson
}

$storageAccount = Get-AzStorageAccount -ResourceGroupName $ResourceGroup -Name $StorageAccountName -ErrorAction SilentlyContinue
if($null -eq $storageAccount)
{
    throw "The storage account specified does not exist in this subscription."
}

$storageContext = $storageAccount.Context


$token = $Null
$maxReturn = 5000
$successPost = 0
$failedPost = 0

$subject=$eventGridEvent.subject.ToString()
$BlobArray=$subject.Split('/')
$container=$BlobArray[$BlobArray.indexof('containers')+1]
$BlobIndex=$subject.indexof('blobs/')+6
$Blob=$subject.substring($BlobIndex,$subject.length - $BlobIndex)

Write-Output("> Downloading blob: {0}" -f $blob)
$filename = ".\log.txt"
Get-AzStorageBlobContent -Context $storageContext -Container $container -Blob $blob -Destination $filename -Force > Null
Write-Output("> Posting logs to log analytic workspace: {0}" -f $blob)

$lines = Get-Content $filename

foreach($line in $lines)
        {
            $json = ConvertLogLineToJson($line)
           

            $response = Post-LogAnalyticsData -customerId $customerId -sharedKey $sharedKey -body ([System.Text.Encoding]::UTF8.GetBytes($json)) -logType $logType

            if($response -eq "200") {
                $successPost++
            } else { 
                $failedPost++
                Write-Output "> Failed to post one log to Log Analytics workspace"
            }
        }

        remove-item $filename -Force

    


Write-Output "> Log lines posted to Log Analytics workspace: success = $successPost, failure = $failedPost"


The last step is to authorize the function app to access storage. This step will not be discussed in detail. You can also use storage account key to do it. Of course, this method is not very recommended.

Finally, you can see the completed effect in the monitoring in the function

Picture 10.png

to sum up

Generally speaking, it has not changed much compared to the way of simply using PowerShell scripts, but because of the addition of eventgrid and functions, the entire solution has become more flexible. Similar ideas can also be extended to many other tasks to be more cloud-based to look at and deal with problems in a native way

Guess you like

Origin blog.51cto.com/mxyit/2588527