Skip to content

Monitor Diagnostic Setting

Overview

The monitor_diagnostic_setting module wires Azure resources to one or more diagnostic destinations (Log Analytics Workspace, Storage Account, Event Hub, or a partner solution) so their platform logs and metrics flow into a queryable store. It is intentionally a low-level module: with one exception, it is not configured directly in tfvars. Instead, each supported resource type exposes a diagnostic_settings map on its own input variable, and the module projects those per-resource maps into the diagnostic setting resources. The exception is the subscription-scope Activity Log export, which has no parent resource entry and uses the dedicated subscription_diagnostic_setting root variable (see Subscription Activity Log).

This keeps the user-facing configuration close to the resource being monitored (the diagnostic settings live on the vault entry, the storage account entry, and so on), while a single module creates the actual azurerm_monitor_diagnostic_setting resources.

Each entry in diagnostic_settings produces a separate azurerm_monitor_diagnostic_setting resource, so a single Azure resource can have multiple settings — for example, one routing logs to a workspace and another routing them to a storage account for long-term retention.

Module Structure

Module Azure Resource Purpose
monitor_diagnostic_setting azurerm_monitor_diagnostic_setting Routes platform logs/metrics for a target resource to one or more destinations
log_analytics_workspace azurerm_log_analytics_workspace Workspace destination, referenced by key — see Log Analytics Workspace
storage_account azurerm_storage_account Storage destination, referenced by key

Supported Resource Types

The following root variables accept a diagnostic_settings map on each entry.

Root Variable Module Composite Key Prefix Log Categories
recovery_services_vault recovery_services_vault recovery_services_vault.<vault_key>.<setting_key> CoreAzureBackup, AddonAzureBackupAlerts, AddonAzureBackupJobs, AddonAzureBackupPolicy, AddonAzureBackupProtectedInstance, AddonAzureBackupStorage
key_vault key_vault key_vault.<vault_key>.<setting_key> AuditEvent, AzurePolicyEvaluationDetails
automation_account automation_account automation_account.<account_key>.<setting_key> JobLogs, JobStreams, DscNodeStatus, AuditEvent
firewall firewall firewall.<firewall_key>.<setting_key> AZFWApplicationRule, AZFWNetworkRule, AZFWNatRule, AZFWThreatIntel, AZFWDnsQuery, AZFWFqdnResolveFailure — see Firewall
application_gateway application_gateway application_gateway.<agw_key>.<setting_key> ApplicationGatewayAccessLog, ApplicationGatewayPerformanceLog, ApplicationGatewayFirewallLog — see Application Gateway
network_security_group network_security_group network_security_group.<nsg_key>.<setting_key> NetworkSecurityGroupEvent, NetworkSecurityGroupRuleCounter
public_ip public_ip public_ip.<public_ip_key>.<setting_key> DDoSProtectionNotifications, DDoSMitigationFlowLogs, DDoSMitigationReports
bastion_host bastion_host bastion_host.<bastion_key>.<setting_key> BastionAuditLogs
virtual_network_gateway virtual_network_gateway virtual_network_gateway.<gateway_key>.<setting_key> GatewayDiagnosticLog, TunnelDiagnosticLog, RouteDiagnosticLog, IKEDiagnosticLog, P2SDiagnosticLog — see Virtual Network Gateway
storage_account storage_account storage_account.<account_key>.<setting_key> Transaction (account level); StorageRead, StorageWrite, StorageDelete (per service, set service; see Storage Account Sub-Resource)
mssql_server (databases) mssql_database mssql_database.<server_key>.<database_key>.<setting_key> SQLInsights, AutomaticTuning, QueryStoreRuntimeStatistics, Errors, DatabaseWaitStatistics, Timeouts, Blocks, Deadlocks, DevOpsOperationsAudit, SQLSecurityAuditEvents — see Azure SQL Database and SQL Database Composite Key
subscription_diagnostic_setting — (subscription scope) subscription.current.<setting_key> Activity Log: Administrative, Security, ServiceHealth, Alert, Recommendation, Policy, Autoscale, ResourceHealth — see Subscription Activity Log

The log category names above are representative; the authoritative per-resource list is the Azure Monitor supported categories table or az monitor diagnostic-settings categories list.

When additional resource types are wired through this module, they will be added to this table. See Adding Diagnostic Settings to a New Resource Type for the procedure.

How the Module Resolves Each Type

The module declares all supported types in a single supported_resource_types map in src/modules/monitor_diagnostic_setting/locals.tf. Each entry pairs the resource's tfvars map (var_*) with its module output (mod_*) and declares two per-type behavior flags:

supported_resource_types = {
  recovery_services_vault = {
    module                    = var.mod_recovery_services_vault
    resources                 = var.var_recovery_services_vault
    supports_destination_type = true
    has_service_suffix        = false
  }
  # ... key_vault, automation_account, firewall, application_gateway,
  #     bastion_host, storage_account, mssql_database,
  #     network_security_group, public_ip, virtual_network_gateway,
  #     subscription
}
  • supports_destination_type is false for network_security_group, public_ip, virtual_network_gateway, and subscription — Azure rejects log_analytics_destination_type = "Dedicated" on them (they have no resource-specific tables, so it would re-apply forever), and the generic loop forces the argument to null.
  • has_service_suffix is true only for storage_account — its settings can target a service sub-resource via the per-setting service field (see Storage Account Sub-Resource). The generic loop only reads diag.service when this flag is set.

Types whose targets aren't keyed by a top-level tfvars map key synthesize their module/resources pair inside the map instead of using a dedicated loop:

  • mssql_database pre-flattens each server's databases map into composite "<server>.<database>" keys (see SQL Database Composite Key).
  • subscription wraps the subscription-scope settings in a single dummy current entry (see Subscription Activity Log).
  • mssql_server doesn't consume a user-supplied diagnostic_settings map at all — for each server with log monitoring enabled on its extended_auditing_policy, it synthesizes one audit setting targeting the server's master database, keyed mssql_server.<server_key>.audit (see Azure SQL Database).

A single flatten (diagnostic_settings_flat) walks the map and feeds the resource block — there are no dedicated per-type loops — so adding a type requires only one new map entry plus the matching var_* / mod_* inputs.

How It Is Wired

The root file src/main.monitor_diagnostic_setting.tf only passes inputs into the module — it contains no transform logic. Each supported type contributes a var_* (its tfvars map) and a mod_* (its module output) pair:

module "monitor_diagnostic_setting" {
  source                      = "./modules/monitor_diagnostic_setting"
  var_recovery_services_vault = var.recovery_services_vault
  var_key_vault               = var.key_vault
  var_automation_account      = var.automation_account
  var_firewall                = var.firewall
  mod_recovery_services_vault = module.recovery_services_vault.recovery_services_vault
  mod_key_vault               = module.key_vault.key_vault
  mod_automation_account      = module.automation_account.automation_account
  mod_firewall                = module.firewall.firewall
  mod_log_analytics_workspace = module.log_analytics_workspace.log_analytics_workspace
  mod_storage_account         = module.storage_account.resource

  var_application_gateway     = var.agws
  mod_application_gateway     = module.application_gateway.application_gateway
  var_network_security_group  = var.nsgs
  mod_network_security_group  = module.network_security_group.network_security_group
  var_public_ip               = var.public_ips
  mod_public_ip               = module.public_ip.public_ip
  var_bastion_host            = var.bastion_host
  mod_bastion_host            = module.bastion_host.bastion_host
  var_virtual_network_gateway = var.virtual_network_gateway
  mod_virtual_network_gateway = module.virtual_network_gateway.virtual_network_gateway
  var_storage_account         = var.storage_accounts
  var_mssql_server            = var.mssql_server
  mod_mssql_database          = module.mssql_database.mssql_database
  mod_mssql_server            = module.mssql_server.mssql_server

  var_subscription_diagnostic_setting = var.subscription_diagnostic_setting
  var_subscription_id                 = var.subscription_id
}

Note: The var_* input name is the module's generic label, not always the root variable name. For example var_application_gateway = var.agws and var_network_security_group = var.nsgs — the module input stays singular and descriptive while the root variable keeps its own name.

The projection from each resource's diagnostic_settings map into the shape azurerm_monitor_diagnostic_setting expects lives inside the module at src/modules/monitor_diagnostic_setting/locals.tf. A single generic flatten walks every entry in supported_resource_types, producing a list whose elements key as "<resource_type>.<resource_key>.<setting_key>", then a single resource block iterates the list:

diagnostic_settings_flat = flatten([
  for type_key, type in local.supported_resource_types : [
    for k, v in type.resources : [
      for diag_key, diag in v.diagnostic_settings : {
        key                            = "${type_key}.${k}.${diag_key}"
        target_resource_id             = type.has_service_suffix ? "${type.module[k].id}${diag.service != null ? "/${diag.service}Services/default" : ""}" : type.module[k].id
        log_analytics_destination_type = type.supports_destination_type ? diag.log_analytics_destination_type : null
        # ... remaining fields pass through as diag
      }
    ]
  ]
])

The triple-segment key (<resource_type>.<resource_key>.<setting_key>) keeps diagnostic setting keys unique across resource types and across multiple settings on the same target resource. SQL databases extend this to a four-segment key — see SQL Database Composite Key.

Storage Account Sub-Resource

Storage account diagnostics can target either the account itself or one of its service sub-resources. The Transaction metric lives on the account ID; the per-service logs (StorageRead, StorageWrite, StorageDelete) live on the blob, file, queue, or table service sub-resource. The service field on each diagnostic_settings entry selects the target. When unset it targets the account itself (metrics only); set it to blob, file, queue, or table to target that service. Log categories require service to be set, because the account level exposes no resource logs (this is enforced by a variable validation).

storage_account is the only type flagged has_service_suffix = true in supported_resource_types, which makes the generic flatten map service to the /<service>Services/default suffix on the resolved account ID — so service = "file" targets <account_id>/fileServices/default, while omitting service targets <account_id>. Use one diagnostic setting per service when you need logs from more than one:

storage_accounts = {
  auditarchive = {
    resource_group = "logging"
    diagnostic_settings = {
      # account-level transaction metric
      account = {
        workspace       = "shared"
        enabled_metrics = ["Transaction"]
      }
      # file service read/write/delete logs
      filelogs = {
        workspace = "shared"
        service   = "file"
        enabled_logs = {
          categories = ["StorageRead", "StorageWrite", "StorageDelete"]
        }
      }
    }
  }
}

SQL Database Composite Key

Azure SQL databases are nested under logical servers (one databases map per mssql_server entry), and the mssql_database module is keyed by the composite "<server>.<database>" key. The generic flatten keys resources by a single map key, so the mssql_database entry in supported_resource_types pre-flattens each server's databases map into a composite-keyed map before the generic loop sees it:

mssql_database = {
  module = var.mod_mssql_database
  resources = merge([
    for server_key, server in var.var_mssql_server : {
      for db_key, db in server.databases : "${server_key}.${db_key}" => db
    }
  ]...)
  supports_destination_type = true
  has_service_suffix        = false
}

Because the composite key already contains a dot, the generic key expands to the four-segment mssql_database.<server_key>.<database_key>.<setting_key>, and the matching default name is mssql_database-<server_key>.<database_key>-<setting_key> (the server/database separator stays a dot). Diagnostics are declared on the database entry, not the server:

mssql_server = {
  epic = {
    resource_group = "sql"
    databases = {
      clarity = {
        diagnostic_settings = {
          audit = {
            workspace = "shared"
            enabled_logs = {
              categories = ["SQLSecurityAuditEvents", "Errors", "Deadlocks"]
            }
          }
        }
      }
    }
  }
}

This produces a diagnostic setting keyed mssql_database.epic.clarity.audit. The full server/database configuration lives in Azure SQL Database; only the diagnostic part is shown here.

This module is wired with var_mssql_server = var.mssql_server (to read the nested databases/diagnostic_settings maps) and mod_mssql_database = module.mssql_database.mssql_database (to resolve database IDs by composite key).

Subscription Activity Log

Subscription-scope diagnostic settings export the Activity Log (control-plane events: resource writes/deletes, service health, policy evaluations) to the same destinations as resource diagnostics. Unlike every other supported type, there is no parent resource entry to attach a diagnostic_settings map to — the settings are configured directly in the root subscription_diagnostic_setting variable, and the target is the subscription itself (/subscriptions/<subscription_id>).

Inside the module, the subscription entry in supported_resource_types synthesizes the missing resource level with a single dummy current entry:

1
2
3
4
5
6
subscription = {
  module                    = { current = { id = "/subscriptions/${var.var_subscription_id}" } }
  resources                 = { current = { diagnostic_settings = var.var_subscription_diagnostic_setting } }
  supports_destination_type = false
  has_service_suffix        = false
}

Settings therefore key as subscription.current.<setting_key> with the default name subscription-current-<setting_key>. The Activity Log has no resource-specific tables, so log_analytics_destination_type does not apply (the variable schema omits the field and the module forces it to null), and only log categories are valid — the Activity Log exposes no metrics or category groups:

subscription_diagnostic_setting = {
  activitylog = {
    workspace = "shared"
    enabled_logs = {
      categories = [
        "Administrative",
        "Security",
        "ServiceHealth",
        "Alert",
        "Recommendation",
        "Policy",
        "Autoscale",
        "ResourceHealth",
      ]
    }
  }
}

This produces one azurerm_monitor_diagnostic_setting keyed subscription.current.activitylog targeting the subscription. The module is wired with var_subscription_diagnostic_setting = var.subscription_diagnostic_setting and var_subscription_id = var.subscription_id (the same subscription ID the azurerm provider uses).

Adding Diagnostic Settings to a New Resource Type

Use this procedure to wire diagnostic settings into a module that does not yet support them. The goal is that an operator can opt into diagnostics by adding a diagnostic_settings map to a single entry in tfvars — no other wiring required.

1. Add the diagnostic_settings Map to the Root Variable

In src/variables.<resource>.tf, add a diagnostic_settings field to the object. Use this exact shape so every resource type stays consistent:

diagnostic_settings = optional(map(object({
  name                           = optional(string)
  workspace                      = optional(string)
  log_analytics_destination_type = optional(string)
  storage_account                = optional(string)
  eventhub_authorization_rule_id = optional(string)
  eventhub_name                  = optional(string)
  partner_solution_id            = optional(string)
  enabled_logs                   = optional(object({
    categories      = optional(list(string), [])
    category_groups = optional(list(string), [])
  }), {})
  enabled_metrics                = optional(list(string))
})), {})

2. Pass the New Resource Into the Module

In src/main.monitor_diagnostic_setting.tf, add a var_* input for the new resource's tfvars map and a mod_* input for its module output:

module "monitor_diagnostic_setting" {
  source                          = "./modules/monitor_diagnostic_setting"
  var_recovery_services_vault     = var.recovery_services_vault
  mod_recovery_services_vault     = module.recovery_services_vault.recovery_services_vault
  var_<root_variable>             = var.<root_variable>             # <- new
  mod_<resource_module>           = module.<resource_module>.<resource_output>  # <- new
  mod_log_analytics_workspace     = module.log_analytics_workspace.log_analytics_workspace
  mod_storage_account             = module.storage_account.resource
}

3. Declare the New Inputs Inside the Module

In src/modules/monitor_diagnostic_setting/variables.tf, add matching declarations. var_* inputs hold tfvars maps; mod_* inputs receive module outputs and must be type = any:

variable "var_<root_variable>" {
  type = map
}

variable "mod_<resource_module>" {
  type = any
}

4. Project Entries Into the Diagnostic-Setting Shape

In src/modules/monitor_diagnostic_setting/locals.tf, add an entry to the supported_resource_types map; the single flatten then picks it up automatically and keys each entry as "<resource_type>.<resource_key>.<setting_key>". Set the two behavior flags on the entry: supports_destination_type = false if Azure rejects log_analytics_destination_type on the resource type, and has_service_suffix = true only if the type targets service sub-resources the way storage accounts do. If the type isn't keyed by a top-level tfvars map key — or its settings aren't user-supplied at all — synthesize the module/resources pair inside the entry; the mssql_database, subscription, and mssql_server (audit) entries show the existing patterns.

5. Document the Resource Type

Add a row to the Supported Resource Types table above, including the valid log category names for that Azure resource. The authoritative list is the Azure Monitor supported categories table, or run:

az monitor diagnostic-settings categories list --resource <resource_id>

Per-Entry Diagnostic Setting Block

This is the shape consumed on supported resource entries. At least one destination must be set — either workspace, storage_account, eventhub_authorization_rule_id, or partner_solution_id.

Field Type Description Default
name string Override the diagnostic setting name "<resource_type>-<resource_key>-<setting_key>"
workspace string log_analytics_workspace key (workspace destination) null
log_analytics_destination_type string "Dedicated" (resource-specific tables) or "AzureDiagnostics" (legacy single table). Leave unset for the resource default. Not available on network_security_group, public_ip, virtual_network_gateway, or subscription_diagnostic_setting (Azure rejects Dedicated there). null
storage_account string storage_account key (storage destination for archive/retention) null
eventhub_authorization_rule_id string Full Event Hub namespace authorization rule ID. Pair with eventhub_name. null
eventhub_name string Event Hub name within the namespace referenced by eventhub_authorization_rule_id null
partner_solution_id string Full resource ID of a partner monitoring solution destination null
enabled_logs object Log categories and category groups to enable (see below) {}
enabled_metrics list(string) Metric category names to enable null

enabled_logs

Field Type Description Default
categories list(string) Individual log category names to enable []
category_groups list(string) Category groups to enable (e.g. "audit", "allLogs") []

Note: For a given setting, categories and category_groups are mutually exclusive — pick one approach. Available category names and groups are resource-specific.

Note: A single diagnostic setting can fan out to multiple destination types simultaneously (workspace + storage + event hub on one setting). To send to two workspaces or two storage accounts, define two separate entries in diagnostic_settings.

Tip: Prefer log_analytics_destination_type = "Dedicated" on new workspaces. It writes logs to per-resource tables (for example AddonAzureBackupJobs) instead of the legacy single AzureDiagnostics table, which keeps queries simpler and aligns with current Azure guidance.

Example: Recovery Vault With Multiple Settings

The full vault configuration lives in Backup. The diagnostic part of the entry can define multiple settings — for example, one to a workspace and another to a storage account for long-term retention:

recovery_services_vault = {
  epic = {
    resource_group    = "recoveryvault"
    storage_mode_type = "GeoRedundant"
    diagnostic_settings = {
      law = {
        workspace                      = "shared"
        log_analytics_destination_type = "Dedicated"
        enabled_logs = {
          categories = [
            "CoreAzureBackup",
            "AddonAzureBackupAlerts",
            "AddonAzureBackupJobs",
            "AddonAzureBackupPolicy",
            "AddonAzureBackupProtectedInstance",
            "AddonAzureBackupStorage",
          ]
        }
      }
      archive = {
        storage_account = "auditarchive"
        enabled_logs = {
          category_groups = ["allLogs"]
        }
      }
    }
  }
}

This produces two azurerm_monitor_diagnostic_setting resources keyed recovery_services_vault.epic.law and recovery_services_vault.epic.archive.

Naming Convention

monitor_diagnostic_setting does not use name_prefixes / name_suffixes. The name comes from the setting entry's name, or defaults to "<resource_type>-<resource_key>-<setting_key>" (for example recovery_services_vault-epic-law). The resource-type prefix is included in the default so two resources of different types that share a key (e.g. a vault and a key vault both named epic) don't produce duplicate diagnostic setting names. For composite-keyed types the resource-key segment keeps its internal dot (mssql_database-epic.clarity-audit), and subscription-scope settings use the dummy resource key (subscription-current-activitylog).