Skip to content
Back to Blog
1 min read

Azure Log Analytics Integration with Synapse

I wrote “Azure Log Analytics Integration with Synapse” to share practical, production-minded guidance on this topic.

Exporting Logs to Synapse

Configure diagnostic settings to export to Data Lake:

resource diagnosticSetting 'Microsoft.Insights/diagnosticSettings@2021-05-01-preview' = {
  name: 'export-to-datalake'
  scope: webApp
  properties: {
    workspaceId: logAnalyticsWorkspace.id
    storageAccountId: storageAccount.id
    logs: [
      {
        category: 'AppServiceHTTPLogs'
        enabled: true
        retentionPolicy: {
          enabled: true
          days: 90
        }
      }
      {
        category: 'AppServiceConsoleLogs'
        enabled: true
      }
      {
        category: 'AppServiceAppLogs'
        enabled: true
      }
    ]
    metrics: [
      {
        category: 'AllMetrics'
        enabled: true
      }
    ]
  }
}

Querying Logs in Synapse

Use serverless SQL to query exported logs:

-- Query App Service HTTP logs from Data Lake
SELECT
    time,
    resourceId,
    JSON_VALUE(properties, '$.Method') as HttpMethod,
    JSON_VALUE(properties, '$.Protocol') as Protocol,
    JSON_VALUE(properties, '$.CsHost') as Host,
    JSON_VALUE(properties, '$.CsUriStem') as Path,
    CAST(JSON_VALUE(properties, '$.ScStatus') AS INT) as StatusCode,
    CAST(JSON_VALUE(properties, '$.TimeTaken') AS INT) as TimeTakenMs,
    JSON_VALUE(properties, '$.UserAgent') as UserAgent
FROM OPENROWSET(
    BULK 'https://datalake.blob.core.windows.net/logs/AppServiceHTTPLogs/**/*.json',
    FORMAT = 'CSV',
    FIELDQUOTE = '0x0b',
    FIELDTERMINATOR = '0x0b',
    ROWTERMINATOR = '\n'
)
WITH (doc NVARCHAR(MAX)) AS logs
CROSS APPLY OPENJSON(doc)
WITH (
    time DATETIME2 '$.time',
    resourceId NVARCHAR(500) '$.resourceId',
    properties NVARCHAR(MAX) '$.properties' AS JSON
)
WHERE time > DATEADD(day, -7, GETUTCDATE());

-- Analyze error rates
WITH HttpLogs AS (
    SELECT
        CAST(time AS DATE) as LogDate,
        DATEPART(HOUR, time) as LogHour,
        CAST(JSON_VALUE(properties, '$.ScStatus') AS INT) as StatusCode
    FROM OPENROWSET(
        BULK 'https://datalake.blob.core.windows.net/logs/AppServiceHTTPLogs/**/*.json',
        FORMAT = 'CSV',
        FIELDQUOTE = '0x0b',
        FIELDTERMINATOR = '0x0b'
    ) WITH (doc NVARCHAR(MAX)) AS logs
    CROSS APPLY OPENJSON(doc)
    WITH (
        time DATETIME2 '$.time',
        properties NVARCHAR(MAX) '$.properties' AS JSON
    )
)
SELECT
    LogDate,
    LogHour,
    COUNT(*) as TotalRequests,
    SUM(CASE WHEN StatusCode >= 400 THEN 1 ELSE 0 END) as ErrorCount,
    CAST(SUM(CASE WHEN StatusCode >= 400 THEN 1.0 ELSE 0 END) / COUNT(*) * 100 AS DECIMAL(5,2)) as ErrorRate
FROM HttpLogs
GROUP BY LogDate, LogHour
ORDER BY LogDate DESC, LogHour DESC;

KQL Queries in Log Analytics

Complex log analysis:

// Application performance analysis
requests
| where timestamp > ago(24h)
| summarize
    RequestCount = count(),
    AvgDuration = avg(duration),
    P50 = percentile(duration, 50),
    P95 = percentile(duration, 95),
    P99 = percentile(duration, 99),
    FailureCount = countif(success == false)
    by bin(timestamp, 1h), cloud_RoleName
| render timechart

// Dependency analysis
dependencies
| where timestamp > ago(24h)
| where success == false
| summarize FailureCount = count() by target, type, resultCode
| top 20 by FailureCount
| render barchart

// End-to-end transaction tracing
let operationId = "abc123";
union requests, dependencies, traces, exceptions
| where operation_Id == operationId
| project
    timestamp,
    itemType,
    name = coalesce(name, message, outerMessage),
    duration = coalesce(duration, 0),
    success = coalesce(success, problemId == "")
| order by timestamp asc

// Slow query detection
AppTraces
| where Message contains "SQL"
| parse Message with * "Duration: " Duration:long "ms" *
| where Duration > 1000
| summarize
    SlowQueryCount = count(),
    AvgDuration = avg(Duration),
    MaxDuration = max(Duration)
    by bin(TimeGenerated, 1h)
| render timechart

// Resource utilization correlation
Perf
| where TimeGenerated > ago(1h)
| where ObjectName == "Processor" and CounterName == "% Processor Time"
| summarize AvgCPU = avg(CounterValue) by bin(TimeGenerated, 5m), Computer
| join kind=inner (
    requests
    | where timestamp > ago(1h)
    | summarize RequestCount = count() by bin(timestamp, 5m)
) on $left.TimeGenerated == $right.timestamp
| project TimeGenerated, Computer, AvgCPU, RequestCount
| render timechart

Azure Function for Log Processing

Process logs with Azure Functions:

using Microsoft.Azure.WebJobs;
using Microsoft.Extensions.Logging;
using Azure.Monitor.Query;
using Azure.Identity;

public class LogProcessor
{
    private readonly LogsQueryClient _logsClient;
    private readonly ILogger<LogProcessor> _logger;

    public LogProcessor(ILogger<LogProcessor> logger)
    {
        _logger = logger;
        _logsClient = new LogsQueryClient(new DefaultAzureCredential());
    }

    [FunctionName("ProcessLogs")]
    public async Task Run(
        [TimerTrigger("0 */15 * * * *")] TimerInfo timer)
    {
        var workspaceId = Environment.GetEnvironmentVariable("LOG_ANALYTICS_WORKSPACE_ID");

        var query = @"
            requests
            | where timestamp > ago(15m)
            | where success == false
            | summarize FailureCount = count() by cloud_RoleName, name, resultCode
            | where FailureCount > 10
        ";

        var response = await _logsClient.QueryWorkspaceAsync(
            workspaceId,
            query,
            new QueryTimeRange(TimeSpan.FromMinutes(15)));

        foreach (var row in response.Value.Table.Rows)
        {
            var roleName = row["cloud_RoleName"].ToString();
            var requestName = row["name"].ToString();
            var failureCount = Convert.ToInt32(row["FailureCount"]);

            _logger.LogWarning(
                "High failure rate detected: {RoleName}/{RequestName} - {Count} failures",
                roleName, requestName, failureCount);

            // Send alert
            await SendAlertAsync(roleName, requestName, failureCount);
        }
    }

    private async Task SendAlertAsync(string roleName, string requestName, int failureCount)
    {
        // Send to Teams, PagerDuty, etc.
    }
}

Creating Custom Metrics

Export custom metrics from logs:

// Create custom metric from logs
let BusinessMetrics = AppTraces
| where Message startswith "OrderProcessed"
| parse Message with "OrderProcessed: " OrderId " Total: " Total:real " Items: " ItemCount:int
| summarize
    OrderCount = count(),
    TotalRevenue = sum(Total),
    AvgOrderValue = avg(Total),
    AvgItemsPerOrder = avg(ItemCount)
    by bin(TimeGenerated, 1h);

// Export to custom metrics
BusinessMetrics
| extend
    MetricName = "BusinessMetrics",
    Dimensions = pack("OrderCount", OrderCount, "Revenue", TotalRevenue)

Summary

Log Analytics integration enables:

  • Centralized log analysis
  • Cross-service correlation
  • Performance monitoring
  • Custom metric creation
  • Automated alerting

Build comprehensive observability for your applications.

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.