Google Chronicle
Synopsis
Creates a target that sends log data to Google Chronicle security analytics platform using either the legacy Chronicle Ingestion API or the Chronicle API. Supports OAuth2 service account authentication, custom labeling, batch processing, and multi-region deployment.
Schema
- name: <string>
description: <string>
type: gcpchronicle
pipelines: <pipeline[]>
status: <boolean>
properties:
api: <string>
project_id: <string>
instance_id: <string>
region: <string>
forwarder: <string>
send_events_as: <string>
udm_type: <string>
default_log_type: <string>
namespace: <string>
log_text_field: <string>
authentication_method: <string>
service_account_credentials: <string>
service_account_credentials_secret: <string>
labels:
- key: <string>
value: <string>
batch_size: <integer>
timeout: <integer>
request_timeout: <integer>
field_format: <string>
debug:
status: <boolean>
dont_send_logs: <boolean>
Configuration
The following fields are used to define the target:
| Field | Required | Default | Description |
|---|---|---|---|
name | Y | Target name | |
description | N | - | Optional description |
type | Y | Must be gcpchronicle | |
pipelines | N | - | Optional post-processor pipelines |
status | N | true | Enable/disable the target |
Chronicle Configuration
| Field | Required | Default | Description |
|---|---|---|---|
api | N | legacy | Google API the target posts to: legacy (Chronicle Ingestion API) or chronicle (Chronicle API). See Migrating to the Chronicle API |
project_id | Y | - | Google Cloud project ID |
instance_id | Y | - | Chronicle instance ID (customer ID) |
region | Y | - | Chronicle region. With api: legacy: exactly us, europe, or asia. With api: chronicle: the same three aliases, or any Chronicle API location such as europe-west2. See Chronicle Regions |
forwarder | N | - | api: chronicle only. Full resource name of a Chronicle forwarder, for example projects/<project>/locations/<location>/instances/<instance>/forwarders/<id>, passed through as written. Ignored with api: legacy |
send_events_as | N | unstructured | api: chronicle only. What the target sends: unstructured (raw logs, ImportLogs) or udm (UDM records). Ignored with api: legacy. See UDM Events and Entities |
udm_type | N | logs | api: chronicle with send_events_as: udm only. logs or events sends UDM events (ImportEvents), entities sends UDM entities (ImportEntities). Ignored otherwise |
default_log_type | Y* | - | Default log type for ingested logs |
namespace | Y | - | Log namespace for organization |
log_text_field | N | - | Event field to use as log text (if empty, uses JSON representation) |
max_bytes | N | 4194304 (4 MB) | Maximum size of a single request body or batch, in bytes |
* = Conditionally required. default_log_type may be empty only with api: chronicle, send_events_as: udm, and udm_type logs or events: ImportEvents carries no log type, because direct UDM ingestion skips parsing. Unstructured logs and udm_type: entities still require it.
max_bytes caps the size of a single request body or batch. It behaves differently from max_size on the file-writing targets, and the difference is easy to get wrong:
- An explicit
max_bytes: 0IS honoured and removes the ceiling on targets that have no ceiling of their own. The value is read withInt64, which returns what you wrote — unlikemax_size, which is read withMustInt64and silently substitutes its default for a zero. Where a target has a fixed ceiling of its own, its page says so and a zero is clamped to that ceiling instead. - A negative value is rejected at configuration time with max_bytes must not be negative.
The ceiling is validated when the target is built and enforced again on every batch, so a value the receiving service will refuse fails early rather than per request.
For this target, max_bytes: 0 does not remove the ceiling. A 0, or any value above 4194304, is clamped to 4194304 bytes, the ceiling this target applies to a single request body, and the clamp is logged as a warning when the target starts. Leave max_bytes unset unless you need a smaller cap.
Authentication
| Field | Required | Default | Description |
|---|---|---|---|
authentication_method | N | credentials | Authentication method: credentials (inline JSON), secret (environment variable) |
service_account_credentials | Y* | - | Google Cloud service account JSON credentials (inline) |
service_account_credentials_secret | Y* | - | Environment variable name containing service account credentials |
* = Conditionally required. When authentication_method is credentials (default), use service_account_credentials. When authentication_method is secret, use service_account_credentials_secret.
Custom Labels
| Field | Required | Default | Description |
|---|---|---|---|
labels | N | - | Array of custom labels to attach to log entries |
labels[].key | Y | - | Label key |
labels[].value | Y | - | Label value |
Batch Configuration
| Field | Required | Default | Description |
|---|---|---|---|
batch_size | N | 1000 | Number of log entries to batch before sending (minimum 1) |
timeout | N | 30 | Request timeout in seconds |
request_timeout | N | 30 | HTTP request timeout in seconds |
Processing
| Field | Required | Default | Description |
|---|---|---|---|
field_format | N | - | Data normalization format. See applicable Normalization section |
Scheduling
See Scheduling and Pool Behavior for interval and cron fields shared by all targets.
Debug Options
| Field | Required | Default | Description |
|---|---|---|---|
debug.status | N | false | Enable debug logging |
debug.dont_send_logs | N | false | Process logs but don't send to target (testing) |
Details
The Google Chronicle target sends log data to Chronicle's security analytics platform. Log events are batched and sent via HTTPS POST requests with OAuth2 authentication. The api property selects the Google API the batches go to:
api | API | Request | OAuth scope |
|---|---|---|---|
legacy (default when api is not set) | Chronicle Ingestion API | …/logTypes/{type}:import on the regional malachiteingestion-pa.googleapis.com host | https://www.googleapis.com/auth/malachite-ingestion |
chronicle | Chronicle API, ImportLogs method, or ImportEvents and ImportEntities for UDM records (see UDM Events and Entities) | https://{location}-chronicle.googleapis.com/v1alpha/projects/{project_id}/locations/{location}/instances/{instance_id}/logTypes/{log_type}/logs:import | https://www.googleapis.com/auth/cloud-platform |
Any other api value is refused when the configuration is validated, with invalid api: <value> (want legacy or chronicle).
Authentication is the same under both APIs: a service account key, inline or by secret. There is no API key mode. max_bytes defaults to and is capped at 4194304 (4 MB) under both APIs. Custom labels and the namespace (namespace, or a per-record __namespace) are sent on every unstructured log under both APIs. Error handling is the same too: 400, 413, and 422 drop the batch as rejected, and every other failure is retried. See Troubleshooting.
Under api: chronicle, two more behaviors apply:
- Per-record log type. For unstructured logs the log type is part of the request URL, and for UDM entities it is part of the request body, so a batch holds records of one log type only. When a record's
__logTypediffers from the batch being built, the target sends that batch first and starts a new one. Interleaved log types therefore produce smaller requests, but nothing is lost. UDM events carry no log type, so their batches never split on__logType. - Times. Each unstructured log's
logEntryTimeandcollectionTimeare both set to the time the target encoded the record. Chronicle parsers usually take the event time from the log itself.
UDM Events and Entities
With api: chronicle, the target can send UDM events and entities as well as unstructured logs. send_events_as and udm_type select what it sends, with the same keys, values, and defaults as the Google SecOps target. With api: legacy, or no api set, both keys are ignored: the target sends unstructured logs and does not refuse a configuration that sets them.
send_events_as | udm_type | Chronicle API method | Request body |
|---|---|---|---|
unstructured (default) | ignored | ImportLogs, …/instances/{instance_id}/logTypes/{log_type}/logs:import | {"inlineSource":{"logs":[…]}} |
udm | logs (default) or events | ImportEvents, …/instances/{instance_id}/events:import | {"inlineSource":{"events":[{"udm":{…}}]}} |
udm | entities | ImportEntities, …/instances/{instance_id}/entities:import | {"inlineSource":{"entities":[{…}],"logType":"…"}} |
The … prefix is https://{location}-chronicle.googleapis.com/v1alpha/projects/{project_id}/locations/{location}, as for ImportLogs.
logsandeventsare synonyms. Both go to ImportEvents.logsis the default, so agcpsecopsUDM configuration moves over unchanged.- Log type. ImportEvents carries no log type, so
default_log_typemay be empty withudm_typelogsorevents. Underentities,inlineSource.logTypeis the batch's log type: a record's__logType, elsedefault_log_type. In both UDM modes,__logTypeis removed from the record before it is sent. - Labels. Configured labels are added to each UDM event as a
labelsarray ofkeyandvaluepairs, as the Google SecOps target does. Entities are sent untouched, with no labels. Thenamespaceis not added to UDM records. - Records must be JSON objects. In either UDM mode, a record that is not a JSON object, such as plain text, an array, or
null, is rejected on its own withudm record is not a JSON object. It is not wrapped as amessagefield the way unstructured records are. - One invalid record rejects its batch. Google accepts or rejects an ImportEvents or ImportEntities request as a whole. One record that is not valid UDM turns its batch into a
400, and the target drops the whole batch as rejected. Validate UDM records in a pipeline before they reach the target. See the400,413, or422entry. - Limits.
max_bytesdefaults to, and is capped at,4194304(4 MB) for ImportEvents and ImportEntities, as for ImportLogs.batch_sizekeeps its existing clamp at10000records.
The target automatically parses JSON log messages and extracts structured data. A message that is not JSON is wrapped as a message field first, and that JSON object becomes the log text, so Chronicle receives {"message":"..."} rather than the original line. Set log_text_field: message to have the raw text arrive unchanged. Custom labels can be attached to all log entries for organizational purposes.
Chronicle Regions
The required region field selects the endpoint. Which values it accepts depends on api:
region | api: legacy host | api: chronicle location and host |
|---|---|---|
us | malachiteingestion-pa.googleapis.com | us - us-chronicle.googleapis.com |
europe | europe-malachiteingestion-pa.googleapis.com | europe - europe-chronicle.googleapis.com |
asia | asia-southeast1-malachiteingestion-pa.googleapis.com | asia-southeast1 - asia-southeast1-chronicle.googleapis.com |
Any other Chronicle API location, for example europe-west2 or me-central2 | Refused with invalid region | Used as written - {location}-chronicle.googleapis.com |
Under both APIs, a value that does not match ^[a-z0-9-]+$, such as EU or Europe West, is refused.
Service Account Permissions
With api: legacy, the service account must have the following IAM role:
- Chronicle API Editor (
roles/chronicle.editor)
Or the following specific permission:
malachite.ingestion.import
For api: legacy, Google documents no IAM role. Its Ingestion API documentation describes the credential as a service account key that your Google SecOps representative provides, and its IAM permission reference does not apply to the malachiteingestion-pa.googleapis.com hosts. Chronicle API Editor is the role the console names for this API.
With api: chronicle, the service account needs the same Chronicle API Editor role, on the Chronicle instance, and the token is requested with the https://www.googleapis.com/auth/cloud-platform scope. The role also covers chronicle.events.import and chronicle.entities.import, the permissions that UDM events and entities need.
Prerequisites
- A Google Cloud project with Chronicle enabled
- Chronicle instance ID (customer ID)
- Service account with the Chronicle API Editor role (
roles/chronicle.editor), under eitherapi - Service account JSON credentials
- Network connectivity to the Chronicle endpoint for your
apiandregion
Migrating to the Chronicle API
Google is retiring the legacy Chronicle ingestion APIs. SecOps instances created after 26 Oct 2026 cannot use them, and Google removes them for everyone on 20 Jul 2027.
A target with api: legacy, or with no api set, keeps sending exactly as before, and logs this warning once when it starts:
Target <target name> uses the legacy Chronicle ingestion API, which Google removes on 20 Jul 2027. Set api: chronicle to send through the Chronicle API.
To move a target to the Chronicle API:
- Enable the Chronicle API (
chronicle.googleapis.com) on the project named inproject_id. A project that already ingests through the legacy API does not have it enabled automatically. - Grant the service account the Chronicle API Editor role on the Chronicle instance.
- Allow outbound
443from Director to{location}-chronicle.googleapis.comfor yourregion. See Chronicle Regions. - Set
api: chronicleon the target. Setforwarderas well if you want the logs attributed to an existing Chronicle forwarder. The target never creates or manages forwarders.
targets:
- name: chronicle_api
type: gcpchronicle
properties:
api: "chronicle"
project_id: "my-gcp-project"
instance_id: "customer-12345678"
region: "europe-west2"
default_log_type: "GENERIC_EVENT"
namespace: "production"
authentication_method: "secret"
service_account_credentials_secret: "CHRONICLE_SA_CREDENTIALS"
The warning is not logged once api: chronicle is set.
A Google SecOps (gcpsecops) target can move to this target as well, including one that uses send_events_as: udm. Keep its send_events_as and udm_type keys and values, change type to gcpchronicle, and set the addressing (project_id, instance_id, region) and api: chronicle. This target also requires three things the Google SecOps target does not: namespace, which is always required here, even for UDM records that do not carry it; default_log_type with udm_type: entities; and a service account key, because this target has no API key mode, so apikey and apikeysecret authentication must change to credentials or secret. See UDM Events and Entities.
The log_text_field parameter specifies which event field to use as the log text. If not specified or the field doesn't exist, the entire JSON event is used as log text.
Service account credentials contain sensitive information. Use the secret authentication method with environment variables for production deployments instead of inline credentials.
Examples
Basic
Minimum configuration for Chronicle ingestion:
targets:
- name: basic_chronicle
type: gcpchronicle
properties:
project_id: "my-gcp-project"
instance_id: "customer-12345678"
region: "us"
default_log_type: "GENERIC_EVENT"
namespace: "production"
service_account_credentials: |
{
"type": "service_account",
"project_id": "my-gcp-project",
"private_key_id": "key-id",
"private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n",
"client_email": "chronicle-writer@my-gcp-project.iam.gserviceaccount.com",
"client_id": "123456789",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token"
}
With Environment Variable
Configuration using environment variable for credentials:
targets:
- name: secure_chronicle
type: gcpchronicle
properties:
project_id: "my-gcp-project"
instance_id: "customer-12345678"
region: "us"
default_log_type: "GENERIC_EVENT"
namespace: "production"
authentication_method: "secret"
service_account_credentials_secret: "CHRONICLE_SA_CREDENTIALS"
Europe Region
Configuration for European Chronicle instance:
targets:
- name: europe_chronicle
type: gcpchronicle
properties:
project_id: "my-gcp-project"
instance_id: "customer-87654321"
region: "europe"
default_log_type: "GENERIC_EVENT"
namespace: "production"
authentication_method: "secret"
service_account_credentials_secret: "CHRONICLE_SA_CREDENTIALS"
With Custom Labels
Configuration with custom labels for log categorization:
targets:
- name: labeled_chronicle
type: gcpchronicle
properties:
project_id: "my-gcp-project"
instance_id: "customer-12345678"
region: "us"
default_log_type: "FIREWALL"
namespace: "production"
authentication_method: "secret"
service_account_credentials_secret: "CHRONICLE_SA_CREDENTIALS"
labels:
- key: "environment"
value: "production"
- key: "application"
value: "firewall"
- key: "datacenter"
value: "us-east-1"
With Log Text Field
Configuration using specific field as log text:
targets:
- name: custom_field_chronicle
type: gcpchronicle
properties:
project_id: "my-gcp-project"
instance_id: "customer-12345678"
region: "us"
default_log_type: "SYSLOG"
namespace: "production"
log_text_field: "message"
authentication_method: "secret"
service_account_credentials_secret: "CHRONICLE_SA_CREDENTIALS"
High Volume
Configuration optimized for high-volume log ingestion:
targets:
- name: highvolume_chronicle
type: gcpchronicle
properties:
project_id: "my-gcp-project"
instance_id: "customer-12345678"
region: "us"
default_log_type: "GENERIC_EVENT"
namespace: "production"
batch_size: 5000
timeout: 60
request_timeout: 60
authentication_method: "secret"
service_account_credentials_secret: "CHRONICLE_SA_CREDENTIALS"
With Normalization
Configuration using field normalization:
targets:
- name: normalized_chronicle
type: gcpchronicle
properties:
project_id: "my-gcp-project"
instance_id: "customer-12345678"
region: "us"
default_log_type: "GENERIC_EVENT"
namespace: "production"
field_format: "ecs"
authentication_method: "secret"
service_account_credentials_secret: "CHRONICLE_SA_CREDENTIALS"
UDM Events
Sending UDM events through the Chronicle API's ImportEvents method. No default_log_type is needed:
targets:
- name: udm_events_chronicle
type: gcpchronicle
properties:
api: "chronicle"
project_id: "my-gcp-project"
instance_id: "customer-12345678"
region: "us"
send_events_as: "udm"
udm_type: "events"
namespace: "production"
authentication_method: "secret"
service_account_credentials_secret: "CHRONICLE_SA_CREDENTIALS"
UDM Entities
Sending UDM entities through the Chronicle API's ImportEntities method. default_log_type is required, and a record's __logType overrides it per batch:
targets:
- name: udm_entities_chronicle
type: gcpchronicle
properties:
api: "chronicle"
project_id: "my-gcp-project"
instance_id: "customer-12345678"
region: "us"
send_events_as: "udm"
udm_type: "entities"
default_log_type: "WINDOWS_AD"
namespace: "production"
authentication_method: "secret"
service_account_credentials_secret: "CHRONICLE_SA_CREDENTIALS"
With Pipeline
Using a pipeline for additional log processing:
targets:
- name: pipeline_chronicle
type: gcpchronicle
pipelines:
- enrich_logs
properties:
project_id: "my-gcp-project"
instance_id: "customer-12345678"
region: "us"
default_log_type: "GENERIC_EVENT"
namespace: "production"
authentication_method: "secret"
service_account_credentials_secret: "CHRONICLE_SA_CREDENTIALS"
Troubleshooting
This section covers the errors you are most likely to see with the gcpchronicle target, what causes each one, and how to fix it.
Where to look:
- Director logs. Target errors are tagged with the target name and carry
"Section":"SenderPool". The part afterReason:or after the last colon is the actual cause. - The target's connection status in the web interface. It shows the same reason as the log line, prefixed with
connection failed for <target name>:.
See Target Delivery Errors for how Director logs and retries target failures.
Two facts about this target shape every entry below. The target asks Google for a token while it starts, so a missing, malformed, or revoked service account key is reported at startup and shows in the connection status. Your ingestion permission is not checked then. Google checks it on the first batch, so the target can report a healthy connection while every flush fails.
Which permission is missing?
Match the error you see against this table first.
| Error text | Missing role or permission | Resource |
|---|---|---|
chronicle api returned status 403, with api: legacy | The Chronicle API Editor role roles/chronicle.editor, or the single permission malachite.ingestion.import | The Chronicle instance named in instance_id |
chronicle api returned status 403, with api: chronicle | The Chronicle API Editor role, or the Chronicle API is not enabled on the project | The Chronicle instance named in instance_id, and the project named in project_id |
chronicle api returned status 403 or status 404 with the role already granted | None. The Chronicle ingestion API is not enabled on the project, or project_id, instance_id, and region do not describe one tenant | The project named in project_id |
chronicle api returned status 401, with api: legacy | The OAuth2 scope https://www.googleapis.com/auth/malachite-ingestion for the service account, or the account is disabled | The service account |
chronicle api returned status 401, with api: chronicle | The OAuth2 scope https://www.googleapis.com/auth/cloud-platform for the service account, or the account is disabled | The service account |
failed to get oauth2 token: ... | None. The key itself cannot mint a token | The service account key |
The console names the same role for both APIs, Chronicle API Editor (roles/chronicle.editor), although Google documents it only for api: chronicle. See Service Account Permissions. Google has no Chronicle API Writer role (roles/chronicle.writer), which earlier versions of this page named. With api: legacy the target only ever calls the log import operation, so any role that allows that import is enough.
Director keeps retrying while a role is missing, so delivery resumes on the next attempt once the grant is in place. No restart is needed.
"failed to get oauth2 token"
[Error] [director] [target-<target id>] [basic_chronicle] Failed to reinitialize target "basic_chronicle" (attempt 1). Reason: failed to get oauth2 token: oauth2: cannot fetch token: 400 Bad Request
Response: ...
Cause: the target asks Google for a token before it accepts any data, and that request was refused. The response body on the second line typically carries invalid_grant, which means the key was rotated, deleted, or disabled, or the service account itself is disabled. A badly skewed host clock typically produces the same body. The same wrapper with private key should be a PEM or plain PKCS1 or PKCS8 or private key is invalid means the private_key value in the key JSON is corrupt, usually because YAML quoting broke its line breaks.
Fix: download a fresh key for the service account and paste it with a YAML block scalar, as the Basic example on this page does. Confirm the account is enabled, and check that the host clock is synchronized.
The same text appears as Sender worker N Finalize failed on flush for target "basic_chronicle": failed to get oauth2 token: ... when the key is revoked after startup. The cached token is used until it expires, so the failure can surface up to an hour after the change. Nothing is lost either way. Incoming data waits in the Director queue and is delivered once the token request succeeds.
"failed to create credentials"
Failed to reinitialize target "basic_chronicle" (attempt 2). Reason: failed to create credentials: invalid character '/' looking for beginning of value
Cause: the value is not a complete Google service account key. The text after the colon says which way it is wrong.
| Text | Meaning |
|---|---|
invalid character '/' looking for beginning of value | A file path was pasted instead of the key. Neither authentication method reads files |
unexpected end of JSON input | The value is empty, or only part of the key was pasted |
invalid character '\n' in string literal | The line breaks inside private_key were not preserved |
missing 'type' field in credentials, or unknown credential type: ... | The JSON parses but is not a service account key, for example a user credential or a client secret file |
Fix: paste the whole key JSON under service_account_credentials using a block scalar, or point secret mode at an environment variable that holds the whole key. Only a key whose type is service_account works.
Nothing is sent while this persists. It is retried until you fix it, and queued data waits.
"failed to resolve service account credentials" or "environment variable ... not found or empty"
Failed to reinitialize target "secure_chronicle" (attempt 4). Reason: failed to resolve service account credentials secret: environment variable "CHRONICLE_SA_CREDENTIALS" not found or empty
Failed to reinitialize target "basic_chronicle" (attempt 7). Reason: failed to resolve service account credentials: credential: env variable "CHRONICLE_SA_CREDENTIALS" is not set
Cause: the first form belongs to secret mode. When the property value is not JSON, it is read as the name of an environment variable, and that variable is unset or empty for the Director service. A file path put in the property ends here too, because a path is not JSON either. The second form means a ${VAR} or $secret{...} reference in either property could not be resolved. The part after credential: says why.
| Text | Meaning |
|---|---|
env variable "..." is not set | The variable is not exported to the Director service user. A variable set in your own shell is not visible to the service |
failed to parse token "...": ... | The $secret{...} syntax is malformed |
store "..." not found in configuration | The store name in $secret{...} does not match a configured credential store |
provider type "..." is not registered, or no credentials configured | The store's provider type is misspelled, or no credential store is defined at all |
path reference "..." names no path | A $path{...} reference points at nothing. The resolved value is still expected to be the key JSON, not a file to read |
Fix: for an unset environment variable, export it for the Director service user with the full key JSON as its value, then restart Director so it inherits it, because a running process does not see a variable exported after it started. For a $secret{...} reference or a store definition, correct the reference or the store and the next retry picks the value up, with no restart. Nothing is sent while this persists, and nothing is lost.
"chronicle api returned status 403"
Sender worker 2 Finalize failed on flush for target "basic_chronicle": chronicle api returned status 403: ...
Cause: Google accepted your token and refused the request. The body typically names PERMISSION_DENIED. Either the service account has no ingestion role on the instance, or the Chronicle ingestion API is not enabled on project_id, or the account belongs to a different Chronicle tenant than instance_id. A wrong region looks the same: all three regional hosts resolve and accept connections, so you get a refusal rather than a network error.
Fix: grant the role from the table above, enable the Chronicle ingestion API on the project, and confirm that project_id, instance_id, and region all describe the same Chronicle tenant.
No data is lost. Director discards the failed batch from memory, keeps the payloads queued, and redelivers them about every five seconds after the target reinitializes. Expect the queue to grow until the role is in place.
"chronicle api returned status 400", "413", or "422"
Sender worker 1 Finalize failed on flush for target "basic_chronicle": record rejected by target: chronicle api returned status 400: ...
Sender worker 1 dropping <payload> for target "basic_chronicle" after 4 rejected flush attempts — the endpoint refuses this batch deterministically, so its records are given up on
Cause: these three statuses are treated as a permanent rejection of the batch content, which the record rejected by target: prefix marks. A 400 typically names INVALID_ARGUMENT, and the usual trigger is a default_log_type your Chronicle instance does not know, or a namespace or label value it refuses. A wrong log type typically appears here as a 400, but it can also come back as a 404, which is retried instead of dropped. A 413 means the request body was larger than the endpoint accepts.
With send_events_as: udm, Google accepts or rejects an ImportEvents or ImportEntities request as a whole, so a single record that is not valid UDM turns its whole batch into a 400, and the whole batch is dropped. A record that is not a JSON object at all never reaches Google: it is rejected on its own with record rejected by target: udm record is not a JSON object, and the rest of the batch is unaffected.
Fix: check default_log_type against the log types your Chronicle instance accepts, and read the response body in the log line for the field it names. With send_events_as: udm, validate records against the UDM schema in a pipeline before they reach the target. On a 413, lower batch_size or max_bytes.
This is the one case where this target loses data. After four flush attempts the whole batch, up to batch_size events, is given up on. The records are not counted in the target's dropped statistics, so watch the log for the dropping ... after 4 rejected flush attempts line. Every other status, 401, 404, 429, and 5xx included, is retried until it succeeds.
"record size ... exceeds configured max_bytes"
Sender worker 3 execute() failed for basic_chronicle: record rejected by target: record size 4210012 exceeds configured max_bytes 4194229
Cause: one event on its own is too large for a request. The second number is the room left for entries once the request envelope is accounted for, so it is slightly smaller than the max_bytes you configured. Two things make an event larger than it looks. A JSON event is embedded as a string, so every quotation mark and backslash costs one extra byte, and an event that is not JSON is wrapped before it is measured.
Fix: trim or split the event in a pipeline. max_bytes cannot be raised past 4194304, so a single event above that ceiling can never be delivered.
That one record is dropped after four deliveries. The rest of the batch is unaffected.
Two related messages appear when the target starts. max_bytes must exceed the 75-byte chronicle request envelope, got 64 means max_bytes is positive but smaller than the request envelope, so raise it or remove the setting. A warning ending and was clamped to it; set a value between 1 and 4194304 to silence this means max_bytes is 0 or above 4194304. The value is then clamped to 4194304 bytes, the ceiling the target applies to one request body, because a 0 does not remove that ceiling here. batch_size is clamped the same way, at 10000 records. Neither clamp drops anything.
"chronicle api returned status 429"
Sender worker 0 Finalize failed on flush for target "basic_chronicle": chronicle api returned status 429: ...
Cause: Google is throttling your ingestion. The body typically names RESOURCE_EXHAUSTED. The target neither limits its own rate nor retries by itself, so every 429 becomes a failed flush.
Fix: nothing in the target needs changing for a short burst. If it persists, raise the ingestion quota on the Chronicle instance or spread the load over more targets.
No data is lost. Payloads are redelivered about every five seconds until Google accepts them, so the queue grows while the throttling lasts.
"failed to send request" or "failed to read response"
Sender worker 0 Finalize failed on flush for target "basic_chronicle": failed to send request: Post "https://malachiteingestion-pa.googleapis.com/v1alpha/projects/my-gcp-project/instances/customer-12345678/logTypes/GENERIC_EVENT:import": dial tcp: lookup malachiteingestion-pa.googleapis.com: no such host
Cause: Director could not complete the HTTPS request. The hostname in the message tells you which endpoint failed: oauth2.googleapis.com for the token, or the regional ingestion host for the batch.
| Text | Meaning |
|---|---|
dial tcp: lookup ...: no such host | DNS on the Director host cannot resolve the endpoint |
i/o timeout, or connection refused | A firewall is blocking outbound 443 to the endpoint |
context deadline exceeded | The request did not finish within timeout, 30 seconds by default |
context deadline exceeded (Client.Timeout exceeded while awaiting headers) | The same, bounded by request_timeout |
tls: failed to verify certificate: x509: certificate signed by unknown authority | A TLS-inspecting proxy is in the path. The target has no TLS options of its own and trusts the operating system store |
proxyconnect tcp: ... | The proxy named in HTTPS_PROXY refused the connection or does not answer |
failed to read response: read tcp ...: connection reset by peer | The connection dropped while the response was being read |
Fix: allow outbound 443 from the Director host to oauth2.googleapis.com and to the regional host for your api and region, listed under Chronicle Regions above. With api: chronicle that host is {location}-chronicle.googleapis.com. Exempt both hosts from TLS inspection, or install the proxy's CA certificate at the operating system level. Set HTTPS_PROXY and NO_PROXY for the Director service user, not in your own shell. Raise timeout and request_timeout together if the link is slow.
All of these are retried until they succeed, so nothing is lost.
"ValidateConfig failed for target ..."
ValidateConfig failed for target "basic_chronicle": invalid region: EU
Cause: the configuration was rejected before anything was sent. Director re-checks it about every 30 seconds, so the message repeats until you change the configuration.
| Reason | Fix |
|---|---|
project_id is required, instance_id is required, default_log_type is required, or namespace is required | Set the field. All four are required, and namespace has no default. The one exception is default_log_type, which may be empty with api: chronicle, send_events_as: udm, and udm_type logs or events |
invalid send_events_as: ... (want unstructured or udm) | send_events_as accepts exactly unstructured or udm. The match is case-sensitive, so UDM is rejected |
invalid udm_type: ... (want logs, events or entities) | udm_type accepts exactly logs, events, or entities. The match is case-sensitive |
invalid region: ... | With api: legacy, region accepts exactly us, europe, or asia. The match is case-sensitive, so US, EU, and europe-west1 are all rejected. With api: chronicle, any location in lower-case letters, digits, and hyphens is accepted, so europe-west1 works there but EU and Europe West do not. See Chronicle Regions |
invalid api: ... (want legacy or chronicle) | api accepts exactly legacy or chronicle. Remove the setting to keep the legacy API |
invalid authentication_method: ... | authentication_method accepts exactly credentials or secret, in lower case |
service_account_credentials is required when using credentials authentication | Set the inline key, or a ${VAR} or $secret{...} reference to it |
service_account_credentials_secret is required when using secret authentication | Set the environment variable name, or a reference that resolves to the key JSON |
batch_size must be greater than 0, got ... | Remove the setting to use the default of 1000, or set a value from 1 to 10000 |
max_bytes must not be negative, got ... | Remove the setting, or use a value from 1 to 4194304 |
Nothing is sent while the configuration is invalid, and queued data waits. Fix the reason shown and the next check picks it up. Restarting Director is not required.
The target is healthy but nothing arrives in Chronicle
Check these in order.
-
debug.dont_send_logsis enabled. Events are processed but never posted. No error is logged and no delivery counter moves. The noticeLog sending is disabled for this targetis written only whendebug.statusis alsotrue, so withdont_send_logson its own there is nothing in the log at all. Remove the flag. -
Batches are being dropped. Search the log for
dropping ... after 4 rejected flush attemptswith the target name. See the entry on400,413, and422above. -
The data is there but does not look like what you sent. A message that is not JSON is wrapped before it is sent, so Chronicle parsers that expect raw syslog or CEF do not match it. Set
log_text_field: messagefor raw text sources. -
log_text_fieldnames a field the event does not carry. The whole JSON event is used as the log text instead, and the delivery still counts as a success. Field names are matched case-sensitively. -
A label is missing from the entries. A label whose
keyorvalueis empty is dropped from the entry, and nothing is logged. -
Events carry their own
__logType. Withapi: legacy, the entry's log type is overridden, but the request is still sent to the log type indefault_log_type. Whether Chronicle honours the per-entry value is not something to rely on. Route different log types to separate targets instead, or setapi: chronicle, which sends each record to its own log type. Withsend_events_as: udm,__logTypeis removed from every record before it is sent: UDM events carry no log type, and UDM entities carry it as the batch'sinlineSource.logType.