{"id":12092,"plugin_id":"plugin_asdk_app_6a3345aed5b081918ae752ac49e4df0e","kind":"skill","collection_source":null,"comparison_source":null,"observed_at":"2026-09-30T23:00:54.799Z","digest":"bd549ef1509fe356e0c58e7b300d55e2fe18a4b5fe2f28d682a3823b904c081e","against":null,"payload":{"description":"Ships Control Plane org logs to external providers. Use when the user asks about log export to S3, CloudWatch, Coralogix, Datadog, Logz.io, Stackdriver, Elastic, syslog, OpenTelemetry, or centralized log forwarding.","included_files":[],"name":"external-logging","skill_md_contents":"---\nname: external-logging\ndescription: \"Ships Control Plane org logs to external providers. Use when the user asks about log export to S3, CloudWatch, Coralogix, Datadog, Logz.io, Stackdriver, Elastic, syslog, OpenTelemetry, or centralized log forwarding.\"\n---\n\n# External Logging\n\nExternal logging lives on the **org** (`spec.logging` plus `spec.extraLogging`) and ships **every workload log in the org** — there is no per-GVC or per-workload filtering. One primary provider plus up to 3 extras (4 total); each logging block holds exactly one provider key. Logs stay queryable in built-in LogQL regardless (separate org retention, `spec.observability.logsRetentionDays`, default 30 days). The recurring failure is credentials: each provider needs a pre-created secret of the exact type below — a wrong-type secret passes configuration and the log router then **silently skips that provider**, so logs simply never arrive.\n\n## Providers\n\n| Key | Secret | Required fields | Worth knowing |\n|---|---|---|---|\n| `s3` | aws | `bucket`, `region`, `credentials` | `prefix` default `/`; region free-form; the IAM user needs `s3:PutObject` on the bucket |\n| `cloudWatch` | aws | `region`, `credentials`, `groupName`, `streamName` | `region` is an 18-region allowlist (below); optional `retentionDays` enum and `extractFields` map |\n| `coralogix` | opaque | `cluster`, `credentials` | `cluster`: `coralogix.com`, `coralogix.us`, `app.coralogix.in`, `app.eu2.coralogix.com`, or `app.coralogixsg.com`; optional `app`/`subsystem` |\n| `datadog` | opaque | `host`, `credentials` | `host` enum: `http-intake.logs.datadoghq.com`, `http-intake.logs.us3.datadoghq.com`, `http-intake.logs.us5.datadoghq.com`, `http-intake.logs.datadoghq.eu` (dashboard `us3.datadoghq.com` pairs with the `us3` intake host) |\n| `logzio` | opaque | `listenerHost`, `credentials` | `listenerHost`: `listener.logz.io` or `listener-nl.logz.io` |\n| `stackdriver` | gcp | `location`, `credentials` | `location` is a 40-region GCP allowlist (a rejection lists it); the service account needs Logging write |\n| `elastic` | aws or userpass | one variant block — see below | |\n| `fluentd` | none | `host` | `port` default 24224; Fluent Bit forward protocol |\n| `syslog` | none | `host`, `port`, `mode`, `format`, `severity` | `mode` tcp/udp/tls (tls enables TLS); `format` rfc3164/rfc5424; `severity` 0-7; the receiver sees gvc as hostname, workload as appname, replica as procid |\n| `opentelemetry` | opaque (optional) | `endpoint` | OTLP over HTTP, not gRPC; an https endpoint turns TLS on; optional `credentials` becomes the Authorization header. Raw header values are intentionally not accepted by the MCP tool. |\n\n- CloudWatch `region` allowlist: us-east-1, us-east-2, us-west-1, us-west-2, ap-south-1, ap-northeast-1, ap-northeast-2, ap-southeast-1, ap-southeast-2, eu-central-1, eu-west-1, eu-west-2, eu-west-3, eu-south-1, eu-north-1, me-south-1, sa-east-1, af-south-1.\n- CloudWatch `retentionDays`: 1, 3, 5, 7, 14, 30, 60, 90, 120, 150, 180, 365, 400, 545, 731, 1827, 3653.\n\n### Elastic variants\n\nIn YAML the variant is a nested object under `elastic`; the MCP tool flattens it into `elasticVariant` + `indexType` (`indexType` maps to the schema field `type`):\n\n| Variant | Required | Secret |\n|---|---|---|\n| `aws` | `host` (must end with `es.amazonaws.com`), `port`, `region`, `index`, `type`, `credentials` | aws |\n| `elasticCloud` | `cloudId`, `index`, `type`, `credentials` | userpass |\n| `generic` | `host`, `index`, `type`, `credentials`; optional `port` (default 443), `path` (must start with `/`) | userpass |\n\n## Configure (MCP first)\n\n1. **Ensure the credential secret exists — but do not pull its value into the chat.** The provider key is the user's own confidential credential: never ask them to paste it here, never pass it as a tool argument, and never invent a placeholder value. Offer to draft the secret manifest with a placeholder for the user to fill and apply (type per the table — usually opaque, `payload` = the raw API key, `encoding: plain`; shapes in `setup-secret`), then **confirm it exists with `get_resource` (kind `secret`) before wiring anything** — referencing a secret that does not exist makes the log router silently skip the provider. No workload identity or policy is needed — the 3-step secret flow applies to workloads consuming secrets, not to org logging.\n2. `get_external_logging` — see what is already configured and where.\n3. `configure_external_logging`, once per provider. Placement is automatic: with no primary it becomes `spec.logging`; additional providers append to `spec.extraLogging`; re-configuring a provider that is already present updates it in place; a 4th extra errors with \"maximum 3 extra logging providers reached\". `credentials` takes a bare secret name or `//secret/NAME`. Syslog `mode`/`format`/`severity` are optional here — the tool fills tcp / rfc5424 / 6.\n4. `remove_external_logging` is **destructive — confirm first**: shipping to that destination stops immediately, a compliance/retention gap until reconfigured. Removing the primary promotes the first extra to primary.\n\n## CLI fallback (manifest shape)\n\nThere is no `cpln` logging subcommand (`cpln org update --set` covers only description and tags). Get, edit, apply — or `cpln org edit ORG`:\n\n```bash\ncpln org get ORG -o yaml-slim > org.yaml    # edit spec.logging / spec.extraLogging\ncpln apply -f org.yaml --org ORG\n```\n\n```yaml\nkind: org\nname: ORG\nspec:\n  logging:\n    s3:\n      bucket: MY_LOG_BUCKET\n      region: us-east-1\n      prefix: /\n      credentials: //secret/AWS_SECRET\n  extraLogging:              # forbidden unless logging is set; max 3\n    - datadog:\n        host: http-intake.logs.us3.datadoghq.com\n        credentials: //secret/DATADOG_KEY\n    - elastic:\n        elasticCloud:        # variant nests in YAML\n          cloudId: DEPLOYMENT:BASE64_ID\n          index: cpln-logs\n          type: logs         # the MCP tool calls this indexType\n          credentials: //secret/ELASTIC_USERPASS\n```\n\nIn raw YAML, `syslog` requires all five fields — the documented defaults do not satisfy the required check, and the API rejects an omitted `mode`/`format`/`severity` (the MCP tool fills them for you).\n\n## Template variables\n\n- **CloudWatch** `groupName`/`streamName` accept Fluent Bit record accessors over the shipped fields: `$org`, `$gvc`, `$workload`, `$container`, `$replica`, `$location`, `$provider`, `$version`, `$stream` (e.g. `groupName: $gvc`, `streamName: $workload`). Adjacent variables must be separated by `.` or `,`.\n- **Coralogix** `app`/`subsystem` accept only `{org}`, `{gvc}`, `{workload}`, `{location}` — any other `{var}` is rejected at validation.\n\n## What ships\n\nEvery entry carries `time` and `log` plus the labels `org`, `gvc`, `workload`, `container`, `replica`, `location`, `provider`, `version`, `stream`. S3 receives gzip-compressed JSONL objects at `PREFIX/ORG/YYYY/MM/DD/HH/MM/UUID.jsonl.gz` (~1 MB chunks). The shipper flushes every 5 seconds; entries appear at the provider within a few minutes.\n\n## Verify\n\n1. `get_external_logging` — primary and extras placed as intended.\n2. Generate some traffic, wait 2-5 minutes, check the provider dashboard or bucket.\n3. Built-in access is unaffected: `cpln logs '{gvc=\"GVC\", workload=\"WORKLOAD\"}' --org ORG`.\n\n## Troubleshooting\n\n| Symptom | Cause / fix |\n|---|---|\n| Logs never arrive, no error anywhere | Credential secret has the wrong type — the log router silently skips the provider. Recreate it with the type from the table. |\n| S3 stays empty with valid keys | The IAM user lacks `s3:PutObject` on the bucket |\n| region/location \"must be one of\" rejection | CloudWatch and Stackdriver take fixed allowlists, not arbitrary regions |\n| `extraLogging` rejected | A primary `spec.logging` must exist first |\n| \"maximum 3 extra logging providers reached\" | 4 providers total is the cap — remove one first |\n| xor validation error on a logging block | Exactly one provider key per block — extra providers are separate `extraLogging` entries |\n| Datadog/Coralogix/Logz.io value rejected | `host`/`cluster`/`listenerHost` are fixed enums — see the table |\n\n## Quick reference — MCP tools\n\n| Tool | Action |\n|---|---|\n| `get_external_logging` | Show primary + extra providers |\n| `configure_external_logging` | Add or update one provider (automatic primary/extra placement) |\n| `remove_external_logging` | Remove a provider (destructive; removing the primary promotes the first extra) |\n\nCLI fallback (CI/CD: `CPLN_TOKEN` + `cpln apply` — read the `cpln` skill first): edit the org manifest as shown above.\n\n## Related skills\n\n| Need | Skill |\n|---|---|\n| Query logs inside Control Plane (LogQL, Grafana) | `logql-observability` |\n| Metrics and tracing export | `metrics-observability` |\n| Creating credential secrets, RBAC | `access-control` |\n| Org settings: retention, tracing, auth | `org-management` |\n\n## Documentation\n\n- [External Logging Overview](https://docs.controlplane.com/external-logging/overview.md)\n- Per provider: [S3](https://docs.controlplane.com/external-logging/s3.md), [CloudWatch](https://docs.controlplane.com/external-logging/cloudwatch.md), [Coralogix](https://docs.controlplane.com/external-logging/coralogix.md), [Datadog](https://docs.controlplane.com/external-logging/datadog.md), [Logz.io](https://docs.controlplane.com/external-logging/logz-io.md), [Stackdriver](https://docs.controlplane.com/external-logging/stackdriver.md), [Syslog](https://docs.controlplane.com/external-logging/syslog.md)\n- Elastic, Fluentd, and OpenTelemetry have no docs pages — the MCP tool description is the reference.\n"},"changes":[],"summary":"First saved snapshot. No earlier version is available for comparison.","summary_kind":"deterministic","summary_metadata":{}}