Configures the service-wide diagnostic on an Azure API Management service β which logger receives telemetry, how much is sampled, and how much of each message is captured. Targets
hashicorp/azurerm ~> 4.0.
- π Creates one
azurerm_api_management_diagnosticβ the logging configuration that applies to every API in the service. - ποΈ Controls sampling, verbosity, correlation protocol, client-IP recording and the four pipeline stages (frontend/backend, request/response).
- π‘οΈ Defaults
log_client_ipto false β the closed choice for a field that records personal data β and leaves body logging off. - π¨ Surfaces the two settings the provider silently discards:
sampling_percentage = 0andalways_log_errors = false. - π§― States what data masking cannot do: it names headers and query parameters, and never reaches a captured body.
- π·οΈ Carries no
tagsβ the resource has none. The universal tail istimeoutsonly.
π‘ Why it matters: this one record decides how much of every API call is written to a telemetry store. Two of its settings do nothing when you set them, one of its enums is lower-case while its neighbours are not, and the redaction feature does not cover the field most likely to hold customer data. All four are invisible in a plan.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- β Star this repository to help others discover this Terraform module.
- π€ Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- β Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
flowchart TB
rg["terraform-azurerm-resource-group"]
apim["terraform-azurerm-api-management"]
logger["terraform-azurerm-api-management-logger"]
this["terraform-azurerm-api-management-diagnostic"]
api["terraform-azurerm-api-management-api"]
mds["terraform-azurerm-monitor-diagnostic-setting"]
appi["terraform-azurerm-application-insights"]
ehns["terraform-azurerm-eventhub-namespace"]
rg -->|"name"| apim
apim -->|"name"| logger
apim -->|"name"| this
logger -->|"id"| this
logger -->|"id, per-API override"| api
api -->|"overrides this record for its own API"| this
appi -->|"sink behind an applicationinsights logger"| logger
ehns -->|"sink behind an eventhub logger"| logger
mds -->|"routes the gateway logs the azuremonitor identifier fills"| apim
classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
classDef keystone fill:#004578,stroke:#002b4d,color:#ffffff;
classDef sib fill:#eef3f8,stroke:#b9c7d6,color:#1b2a3a;
class this me;
class apim keystone;
class rg,logger,api,mds,appi,ehns sib;
The API Management family is large, so this diagram names only this module's direct neighbours rather than every api-management-* module in the library. Note the edge from the API module back to this one: an API carrying its own diagnostic overrides what is configured here, and nothing in this record lists which APIs those are.
flowchart TB
subgraph inputs["Inputs"]
ident["identifier plus api_management_name plus resource_group_name"]
lg["api_management_logger_id, not force-new"]
coll["sampling_percentage, always_log_errors, verbosity, log_client_ip"]
stages["frontend_request, frontend_response, backend_request, backend_response"]
end
this["azurerm_api_management_diagnostic.this"]
subgraph outputs["Outputs"]
oid["id, identifier, api_management_logger_id"]
oset["sampling_percentage, verbosity, log_client_ip, operation_name_format"]
oexp["logs_message_bodies, headers_logged_by_stage, masked_entries_by_stage"]
ofact["sampling_percentage_zero_is_silently_ignored, this_is_not_an_audit_trail"]
end
ident --> this
lg --> this
coll --> this
stages -->|"data masking reaches headers and query params, never a body"| this
this --> oid
this --> oset
this --> oexp
this --> ofact
classDef me fill:#0078D4,stroke:#004578,color:#ffffff;
classDef sib fill:#eef3f8,stroke:#b9c7d6,color:#1b2a3a;
class this me;
class ident,lg,coll,stages,oid,oset,oexp,ofact sib;
Resource inventory
| Resource | Count | Notes |
|---|---|---|
azurerm_api_management_diagnostic.this |
1 | The keystone, and the only resource. Renders up to four pipeline blocks, each with nested data-masking rules. |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/azurerm |
~> 4.0 |
| Provider block | None in this module β the caller configures provider "azurerm" { features {} }, auth and subscription |
Schema notes that bite
sampling_percentage = 0is silently discarded. Microsoft documents 0 as meaningful β "0% reflects no logging" β and the provider's create-and-update path reads the field with a helper that treats zero as absent, so nothing is sent and Azure keeps what it had. The API-level sibling resource reads the raw configuration and does send zero.always_log_errors = falseis never sent. The flag is transmitted only when present and true, and the field is Computed, so a configuration saying false against a resource that has it on shows no diff, indefinitely.verbosityis lower case βverbose,information,errorβ whilehttp_correlation_protocol(None,Legacy,W3C) andoperation_name_format(Name,Url) are capitalised.Urlcarries a single capital;URLis rejected.operation_name_formatis legal only on theapplicationinsightsidentifier, enforced by the provider in a diff-time check that runs at plan and therefore needs credentials. This module refuses the combination at plan, offline.identifier,resource_group_nameandapi_management_nameare force-new; the last two take that from the provider's shared schema helpers rather than from this resource's own field list.api_management_logger_idis not force-new β the sink can be repointed in place.- Nine fields are Optional and Computed, including all four pipeline blocks. Leaving one null adopts Azure's current value into state rather than clearing it, which is how an out-of-band change is absorbed instead of reported.
data_maskingcovers headers and query parameters only. There is no body rule, no field path and no pattern matching.- No
tags, nolocation. The universal tail istimeoutsonly.
Microsoft.ApiManagement/service/diagnostics/*β write and delete, scoped to the parent API Management service. A custom role at the service is enough; API Management Service Contributor on the service, or Contributor on its resource group, both cover it more broadly than necessary.Microsoft.ApiManagement/service/readon the service, for the existence check the provider performs before creating.Microsoft.ApiManagement/service/loggers/readis not required β the logger is referenced by ID and never read, which is also why nothing here validates that the logger suits the identifier.
π This resource holds no credential, so plan access here is not credential access. The credential lives on the logger; see that module's permissions section.
- An existing API Management service.
- An existing logger on that same service, of a kind that matches the identifier. Nothing validates the pairing β an
applicationinsightsdiagnostic pointed at an Event Hub logger applies cleanly and delivers nothing. - For the
azuremonitoridentifier, Azure Monitor diagnostic settings on the API Management service that routeGatewayLogssomewhere. This record decides what the gateway log contains, not where it goes. - The caller configures
provider "azurerm" { features {} }, authentication and subscription.
terraform-azurerm-api-management-diagnostic/
βββ providers.tf # required_version + pinned azurerm; no provider block
βββ variables.tf # deeply-typed inputs; every enum and range mirrored from the provider
βββ main.tf # the single keystone resource and its four pipeline blocks
βββ outputs.tf # id first, then settings, exposure posture, and the silent behaviours
βββ README.md # this document
βββ SCOPE.md # the cross-module contract
βββ LICENSE # MIT
βββ .gitignore # the canonical library ignore set
provider "azurerm" {
features {}
}
module "apim_diagnostic" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-diagnostic.git?ref=v1.0.0"
identifier = "applicationinsights"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
api_management_logger_id = module.apim_logger.id
}βΉοΈ The caller configures the provider, including the mandatory
features {}block. This module declares none.π This call captures no message bodies and records no client IP addresses. Everything else is left to Azure's own values rather than being chosen for you.
Consumes
| Input | Type | Source |
|---|---|---|
api_management_name |
string |
terraform-azurerm-api-management output name |
resource_group_name |
string |
terraform-azurerm-resource-group output name |
api_management_logger_id |
string |
terraform-azurerm-api-management-logger output id |
Emits
| Output | Consumed by |
|---|---|
id |
review; a management lock, where destroy protection is wanted |
logs_message_bodies, stages_logging_message_bodies |
data-protection review and alerting |
logged_headers_that_commonly_carry_credentials, masked_entries_by_stage |
control review |
sampling_percentage_zero_is_silently_ignored |
troubleshooting a sampling change that did nothing |
verbosity |
trace policy review β a policy below this severity emits nothing |
1 Β· Minimal Application Insights diagnostic
module "diagnostic" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-diagnostic.git?ref=v1.0.0"
identifier = "applicationinsights"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
api_management_logger_id = module.apim_logger.id
}π‘ Everything except
log_client_ipis left to Azure. The module sendslog_client_ip = falseexplicitly, because that field is the one on this resource that distinguishes "set to false" from "not set".
2 Β· Full sampling with errors always logged
module "diagnostic" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-diagnostic.git?ref=v1.0.0"
identifier = "applicationinsights"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
api_management_logger_id = module.apim_logger.id
sampling_percentage = 100
always_log_errors = true
verbosity = "information"
}
β οΈ Microsoft's published load testing puts full logging at a 40%β50% reduction in throughput above 1,000 requests per second.always_log_errorsis the cheap half of this: it exempts errors from sampling without logging everything.
3 Β· Low sampling on a high-volume gateway
module "diagnostic" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-diagnostic.git?ref=v1.0.0"
identifier = "applicationinsights"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
api_management_logger_id = module.apim_logger.id
sampling_percentage = 5
always_log_errors = true
verbosity = "error"
}
β οΈ Sampling discards requests silently and by design. Microsoft: Application Insights "is not intended to be an audit system". Where an evidential record of API access is required, it does not come from here.βΉοΈ
verbosity = "error"also gatestracepolicies β a policy written atinformationseverity emits nothing while this is set, with no error on either side.
4 Β· W3C trace correlation to backends
module "diagnostic" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-diagnostic.git?ref=v1.0.0"
identifier = "applicationinsights"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
api_management_logger_id = module.apim_logger.id
http_correlation_protocol = "W3C"
}π‘
W3Cis what modern OpenTelemetry SDKs speak.Legacyleaves the trace broken at the gateway boundary β both halves work, they simply never correlate, and nothing reports the mismatch.
5 Β· Logging selected headers, with the credential masked
module "diagnostic" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-diagnostic.git?ref=v1.0.0"
identifier = "applicationinsights"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
api_management_logger_id = module.apim_logger.id
frontend_request = {
headers_to_log = ["Content-Type", "Accept", "Ocp-Apim-Subscription-Key"]
data_masking = {
headers = [
{ mode = "Mask", value = "Ocp-Apim-Subscription-Key" },
]
}
}
}π The subscription key is logged and masked. Logging it without the masking rule writes the credential into telemetry in clear β
logged_headers_that_commonly_carry_credentialsexists to find exactly that combination.
6 Β· Masking a query parameter
module "diagnostic" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-diagnostic.git?ref=v1.0.0"
identifier = "applicationinsights"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
api_management_logger_id = module.apim_logger.id
frontend_request = {
data_masking = {
query_params = [
{ mode = "Hide", value = "access_token" },
{ mode = "Mask", value = "account_id" },
]
}
}
}βΉοΈ
Maskreplaces the value;Hideremoves the entry entirely. The published documentation restrictsHideto query parameters, while the provider accepts it on headers too β this module reports such entries throughheader_masking_entries_using_hide_moderather than refusing them.
7 Β· Capturing request and response bodies
module "diagnostic" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-diagnostic.git?ref=v1.0.0"
identifier = "applicationinsights"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
api_management_logger_id = module.apim_logger.id
frontend_request = { body_bytes = 1024 }
frontend_response = { body_bytes = 1024 }
}π Data masking does not reach a body. Whatever these 1024 bytes contain is stored verbatim β on a financial API that means account identifiers, balances and personal detail. Microsoft's own portal guidance adds that overriding the default of 0 "might significantly decrease the performance of your APIs". Treat this as a change with a named owner.
8 Β· All four pipeline stages
module "diagnostic" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-diagnostic.git?ref=v1.0.0"
identifier = "applicationinsights"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
api_management_logger_id = module.apim_logger.id
frontend_request = { headers_to_log = ["Content-Type"] }
frontend_response = { headers_to_log = ["Content-Type"] }
backend_request = {
headers_to_log = ["Content-Type", "Authorization"]
data_masking = { headers = [{ mode = "Mask", value = "Authorization" }] }
}
backend_response = { headers_to_log = ["Content-Type"] }
}βΉοΈ The backend pair is recorded as dependency telemetry, not as requests β a query written over requests will not see it. The backend request also commonly carries a credential the caller never sent, attached by policy, which is why its masking rule differs from the frontend's.
9 Β· The azuremonitor diagnostic
module "gateway_logs" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-diagnostic.git?ref=v1.0.0"
identifier = "azuremonitor"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
api_management_logger_id = module.apim_logger.id
sampling_percentage = 100
always_log_errors = true
verbosity = "information"
}
β οΈ operation_name_formatis not legal here β it belongs to theapplicationinsightsidentifier only, and this module rejects the combination at plan.βΉοΈ API Management enforces a 32 KB ceiling on an Azure Monitor log entry and, above it, "trims the entry by removing all body and trace content". A body-logging configuration honoured on the Application Insights path can be silently dropped on this one.
10 Β· Both diagnostics on one service
module "logger_appi" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-logger.git?ref=v1.0.0"
name = "appinsights"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
application_insights = {
connection_string = data.azurerm_application_insights.platform.connection_string
identity_client_id = "SystemAssigned"
}
}
module "logger_stream" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-logger.git?ref=v1.0.0"
name = "eventhub-audit"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
eventhub = {
name = "apim-audit"
endpoint_uri = "contoso-events.servicebus.windows.net"
}
}
module "diagnostic_appi" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-diagnostic.git?ref=v1.0.0"
identifier = "applicationinsights"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
api_management_logger_id = module.logger_appi.id
sampling_percentage = 10
operation_name_format = "Name"
}
module "diagnostic_monitor" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-diagnostic.git?ref=v1.0.0"
identifier = "azuremonitor"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
api_management_logger_id = module.logger_stream.id
sampling_percentage = 100
}π‘ A service holds at most two of these records, one per identifier, and they are fully independent β different loggers, different sampling, different settings. Running both is two instances of this module.
11 Β· Explicitly recording client IP addresses
module "diagnostic" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-diagnostic.git?ref=v1.0.0"
identifier = "applicationinsights"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
api_management_logger_id = module.apim_logger.id
log_client_ip = true
}π A client IP address is identifying information in most privacy regimes, and once in a telemetry store it inherits that store's retention, access model and export paths β none of which this module can see. Defensible for abuse investigation on an externally exposed gateway; it should be a decision with an owner rather than a default, which is why this module's default is
false.
12 Β· Custom timeouts
module "diagnostic" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-diagnostic.git?ref=v1.0.0"
identifier = "applicationinsights"
resource_group_name = "rg-platform-apim"
api_management_name = "apim-platform-prod"
api_management_logger_id = module.apim_logger.id
timeouts = {
create = "45m"
read = "10m"
update = "45m"
delete = "45m"
}
}
β οΈ A key that is notcreate,read,updateordeleteis silently discarded by Terraform's type conversion β no error, and no timeout.
13 Β· Reviewing the exposure posture
output "gateways_logging_bodies" {
value = [for k, m in module.diagnostics : k if m.logs_message_bodies]
}
output "credential_headers_logged" {
value = {
for k, m in module.diagnostics : k => m.logged_headers_that_commonly_carry_credentials
}
}
output "sampling_changes_that_did_nothing" {
value = [for k, m in module.diagnostics : k if m.sampling_percentage_zero_is_silently_ignored]
}π‘ The third output finds the specific mistake this resource makes easy: someone set sampling to zero to stop telemetry, the plan showed the change, and Azure kept logging at whatever rate it already had.
14 Β· ποΈ End-to-end composition
provider "azurerm" {
features {}
}
module "rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-platform-apim"
location = "eastus2"
}
module "logs" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-log-analytics-workspace.git?ref=v1.0.0"
name = "log-platform-apim"
resource_group_name = module.rg.name
location = module.rg.location
}
module "app_insights" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-application-insights.git?ref=v1.0.0"
name = "appi-platform-apim"
resource_group_name = module.rg.name
location = module.rg.location
workspace_id = module.logs.id
}
module "apim" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management.git?ref=v1.0.0"
name = "apim-platform-prod"
resource_group_name = module.rg.name
location = module.rg.location
publisher_name = "Platform Engineering"
publisher_email = "platform-engineering@example.com"
sku_name = "Developer_1"
}
# The application-insights module deliberately does not emit the connection
# string, so the configuration that needs it declares its own data source.
data "azurerm_application_insights" "platform" {
name = module.app_insights.name
resource_group_name = module.app_insights.resource_group_name
}
module "apim_logger" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-logger.git?ref=v1.0.0"
name = "appinsights"
resource_group_name = module.rg.name
api_management_name = module.apim.name
resource_id = module.app_insights.id
application_insights = {
connection_string = data.azurerm_application_insights.platform.connection_string
identity_client_id = "SystemAssigned"
}
}
module "apim_diagnostic" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-api-management-diagnostic.git?ref=v1.0.0"
identifier = "applicationinsights"
resource_group_name = module.rg.name
api_management_name = module.apim.name
api_management_logger_id = module.apim_logger.id
sampling_percentage = 10
always_log_errors = true
verbosity = "information"
http_correlation_protocol = "W3C"
operation_name_format = "Name"
frontend_request = {
headers_to_log = ["Content-Type", "Ocp-Apim-Subscription-Key"]
data_masking = { headers = [{ mode = "Mask", value = "Ocp-Apim-Subscription-Key" }] }
}
}π No body is captured, no client IP is recorded, and the one credential-bearing header that is logged is masked.
operation_name_format = "Name"rather than"Url"also keeps path-embedded identifiers out of the operation name, where no masking rule reaches them.
β οΈ The API Management service must have a system-assigned identity holding Monitoring Metrics Publisher on the Application Insights component for the logger'sidentity_client_id = "SystemAssigned"to work. That identity is configured on theapi-managementmodule, and the role assignment belongs toterraform-azurerm-role-assignments.
| Group | Variables |
|---|---|
| Identity (force-new) | identifier, resource_group_name, api_management_name |
| The sink (mutable) | api_management_logger_id |
| Collection settings | sampling_percentage, always_log_errors, verbosity, log_client_ip, http_correlation_protocol, operation_name_format |
| Pipeline stages | frontend_request, frontend_response, backend_request, backend_response |
| Universal tail | timeouts |
Full input schemas
identifier = string # force-new; "applicationinsights" or "azuremonitor", lower case
resource_group_name = string # force-new; the SERVICE's resource group
api_management_name = string # force-new; the service NAME, not its id
api_management_logger_id = string # NOT force-new; the logger's full Resource ID
sampling_percentage = optional(number) # 0.0-100.0; 0 is accepted and then discarded
always_log_errors = optional(bool) # false is never sent
verbosity = optional(string) # "verbose" | "information" | "error" (lower case)
log_client_ip = optional(bool, false) # closed default; personal data
http_correlation_protocol = optional(string) # "None" | "Legacy" | "W3C"
operation_name_format = optional(string) # "Name" | "Url"; applicationinsights only
# All four stages share this shape:
frontend_request = optional(object({
body_bytes = optional(number) # 0-8192; NOT covered by data_masking
headers_to_log = optional(set(string), []) # bare header names, no wildcards
data_masking = optional(object({
headers = optional(list(object({ mode = string, value = string })), [])
query_params = optional(list(object({ mode = string, value = string })), [])
}))
}))
timeouts = optional(object({ create, read, update, delete }))See variables.tf for the full descriptions and every validation, including the exact casing of each enum.
| Output | Description | Notes |
|---|---|---|
id |
Resource ID of the API Management diagnostic | Emitted first; primary cross-module reference |
identifier |
applicationinsights or azuremonitor |
Also the record's name |
api_management_name |
Name of the parent API Management service | Rebuilt from the Resource ID on read |
resource_group_name |
Resource group holding the parent service | Rebuilt from the Resource ID on read |
api_management_logger_id |
The logger this diagnostic publishes to | Mutable; not force-new |
sampling_percentage |
The sampling rate Azure holds | Computed |
always_log_errors |
Whether errors bypass sampling, as Azure holds it | Computed; may disagree with the configuration |
verbosity |
verbose, information or error |
Computed; gates trace policies |
log_client_ip |
Whether the caller's IP is recorded | Defaults to false in this module |
http_correlation_protocol |
None, Legacy or W3C |
Computed |
operation_name_format |
Name, Url, or an empty string when unset |
Provider sets an empty string, not null |
body_bytes_by_stage |
Payload bytes captured at each of the four stages | Derived |
stages_logging_message_bodies |
The stages that capture bodies at all | Derived |
logs_message_bodies |
True when any stage captures a body | Derived; the flag to alert on |
data_masking_does_not_apply_to_bodies |
Constant true | |
headers_logged_by_stage |
The header names captured at each stage | Derived |
logged_headers_that_commonly_carry_credentials |
Logged headers that usually hold a credential | Derived |
masked_entries_by_stage |
The names redacted at each stage, with the mode | Derived |
header_masking_entries_using_hide_mode |
Header rules using Hide |
Derived; reported, not refused |
sampling_percentage_zero_is_silently_ignored |
True when 0 was configured and discarded | Derived |
always_log_errors_false_is_never_sent |
Constant true | |
an_api_level_diagnostic_overrides_this_one |
Constant true | |
this_is_not_an_audit_trail |
Constant true | |
full_logging_costs_throughput |
Constant true | |
azure_monitor_entries_over_32kb_lose_their_bodies |
True on the azuremonitor identifier |
Derived |
only_two_diagnostics_can_exist_per_service |
Constant true | |
the_logger_kind_is_never_checked_against_the_identifier |
Constant true | |
repointing_the_logger_is_an_in_place_update |
Constant true | |
force_new_fields |
The fields whose change destroys and recreates the record | |
computed_fields_that_mask_drift |
The Optional+Computed fields that absorb out-of-band change | |
this_resource_supports_no_azure_resource_tags |
Constant true |
Nothing here is a secret: a diagnostic holds no credential.
Two of these settings do nothing when you set them. sampling_percentage = 0 is read with a helper that treats zero as absent, so no sampling settings are sent and Azure keeps what it had β despite Microsoft documenting 0 as "no logging". always_log_errors = false is dropped before the request is built, and because the field is Computed the resulting no-op shows no diff forever. Both are reported through outputs because neither is visible in a plan.
The same field behaves differently on the API-level sibling. azurerm_api_management_api_diagnostic reads sampling_percentage from the raw configuration and does send zero. Two resources that read identically in the documentation disagree on this exact value.
Data masking is narrower than it looks. A masking rule names a header or a query parameter. A body captured by body_bytes is stored verbatim β there is no body rule, no field path and no pattern matching. A configuration that masks the subscription key and then logs 8192 bytes of a response has protected the metadata and published the record.
This record is a default, not a floor. Microsoft: "By default, the single API logger (more granular level) overrides the one for all APIs." An API carrying its own diagnostic ignores everything here, and nothing in this resource, its state or the portal lists which APIs those are.
Nine fields are Optional and Computed. Leaving one null adopts Azure's value rather than clearing it, which is convenient until someone changes a sampling rate or a client-IP setting in the portal and no plan ever mentions it.
The logger pairing is unchecked. The ID does not encode the logger's type, so nothing β not the provider, not this module β can confirm that an applicationinsights diagnostic points at an Application Insights logger. A mismatch applies cleanly and delivers nothing.
Only the sink is mutable. api_management_logger_id updates in place; the identifier, the service name and the resource group all force replacement.
| Concern | Default in this module | Opt-out |
|---|---|---|
| Client IP recording | log_client_ip = false β the closed choice for a field holding personal data, sent explicitly rather than omitted |
set log_client_ip = true |
| Message bodies | not captured β all four stage blocks are null | set body_bytes on a stage |
| Header logging | none β headers_to_log is empty on every stage |
list header names per stage |
| Sampling and verbosity | left null so Azure's own value stands; there is no defensible default rate, and inventing one would be a silent policy decision | set sampling_percentage / verbosity |
Hide on header masking rules |
reported through an output, not refused β the provider accepts it while the documentation restricts it to query parameters, and a validation failure would block terraform destroy too |
none |
| Enum casing | every enum is enforced exactly as the provider compares it, with the casing named in the error message | none |
| Tagging | no tags variable β the resource exposes none |
tag the parent API Management service |
terraform init -backend=false
terraform validate
terraform fmt -checkPin the module by immutable tag β ?ref=v1.0.0 β never a branch. This module is plan-only from the library's point of view: a human applies from CI against a reviewed plan.
terraform validate covers every enum with its exact casing, the sampling_percentage and body_bytes ranges, the logger ID shape, the header-name shape, and the operation_name_format / identifier pairing. That last one is worth noting: the provider enforces it in a diff-time check, which runs at plan and therefore needs credentials β this module refuses it offline instead.
terraform console fires the same validations against a .tfvars file. Terraform skips a validation whose referenced variable has already failed, so a short error list is not proof a check is missing β fix the first failure and re-run.
What only a real plan or apply reaches: whether the service exists, whether the logger ID resolves, and whether the identifier already has a diagnostic. What nothing reaches, at any stage: whether the logger is of a kind that can receive this telemetry, and whether any API has overridden this configuration with its own.
id = "/subscriptions/.../resourceGroups/rg-platform-apim/providers/Microsoft.ApiManagement/service/apim-platform-prod/diagnostics/applicationinsights"
identifier = "applicationinsights"
api_management_name = "apim-platform-prod"
resource_group_name = "rg-platform-apim"
api_management_logger_id = "/subscriptions/.../service/apim-platform-prod/loggers/appinsights"
sampling_percentage = 10
always_log_errors = true
verbosity = "information"
log_client_ip = false
http_correlation_protocol = "W3C"
operation_name_format = "Name"
logs_message_bodies = false
stages_logging_message_bodies = []
headers_logged_by_stage = {
"backend_request" = []
"backend_response" = []
"frontend_request" = ["Content-Type", "Ocp-Apim-Subscription-Key"]
"frontend_response" = []
}
logged_headers_that_commonly_carry_credentials = {
"backend_request" = []
"backend_response" = []
"frontend_request" = ["Ocp-Apim-Subscription-Key"]
"frontend_response" = []
}
sampling_percentage_zero_is_silently_ignored = false
force_new_fields = [
"identifier",
"resource_group_name",
"api_management_name",
]
this_is_not_an_audit_trail = true
| Symptom | Cause | Fix |
|---|---|---|
| Sampling was set to 0 and telemetry keeps arriving | The provider treats a zero here as absent and sends no sampling settings at all, so Azure keeps its previous value | Destroy the diagnostic to stop telemetry. sampling_percentage_zero_is_silently_ignored reports the condition |
always_log_errors = false produces no diff and no change |
The flag is only transmitted when present and true, and the field is Computed | There is no way to turn the flag off through this field. Recreate the diagnostic without it |
Plan rejects verbosity = "Verbose" |
This enum is lower case, unlike its two neighbours on the same resource | Use "verbose", "information" or "error" |
Plan rejects operation_name_format = "URL" |
The SDK constant is Url, with a single capital, and the comparison is case-sensitive |
Use "Url" |
Plan rejects operation_name_format on an azuremonitor diagnostic |
It is legal only on applicationinsights. The provider raises the same error at plan; this module raises it offline |
Remove the field, or move it to the applicationinsights diagnostic |
| Everything applies and no telemetry arrives | The logger does not exist, is of the wrong kind for this identifier, or its credential is wrong. None of these is checked here | Confirm the logger resolves and matches the identifier; check the logger module's own outputs |
| Telemetry arrives for some APIs and not others | An API carries its own diagnostic, which overrides this record | Check each API's own diagnostic. Multiplexing both levels requires different loggers and a Microsoft support request |
Bodies are configured but never appear on the azuremonitor path |
API Management enforces a 32 KB ceiling per entry and trims body and trace content above it | Reduce what else is logged, or use the applicationinsights identifier for body capture |
| A setting changed in the portal never appears as drift | The field is Optional and Computed, so Azure's value is adopted into state | See computed_fields_that_mask_drift; set the field explicitly to assert a value |
A trace policy emits nothing |
Its severity is below this diagnostic's verbosity |
Raise the policy's severity, or lower verbosity |
azurerm_api_management_diagnosticβ provider resource reference- How to integrate Azure API Management with Azure Application Insights β sampling, the throughput cost, and the per-API override
- Monitor API Management β the Azure Monitor path and the 32 KB entry ceiling
- Sibling modules:
terraform-azurerm-api-management,terraform-azurerm-api-management-logger,terraform-azurerm-api-management-api,terraform-azurerm-monitor-diagnostic-setting,terraform-azurerm-application-insights - This module's
SCOPE.mdβ the cross-module contract, permissions and prerequisites
π "Infrastructure as Code should be standardized, consistent, and secure."