Datadog Browser Logs
Send logs from the Datadog browser logs SDK to KubeSense by pointing its proxy at the KubeSense RUM endpoint.
Web apps that already use the Datadog browser logs SDK (@datadog/browser-logs) can send their logs to KubeSense without changing a single logging call. The SDK posts to the KubeSense RUM endpoint through its proxy option instead of Datadog's intake, and the logs are stored with your other logs, so they show up on the Logs page.
The app team changes only the SDK's init (and, optionally, names a cluster). KubeSense runs the collector, the aggregator and ClickHouse.
How it flows
Browser (Datadog logs SDK)
│ POST https://<KUBESENSE_RUM_HOST>/api/v2/proxy?ddforward=/api/v2/logs...
▼
Ingress / load balancer (HTTPS)
▼
KubeSense collector (kubecol, RUM port 34443)
│ checks the client token, adds browser / OS / geo, builds kube_logs rows
▼
KubeSense aggregator (rum_logs source) ── or straight to ClickHouse (destination: datastore)
│ parsing and block rules, like every other log
▼
ClickHouse (kube_logs.logs) → Logs pageThe collector answers every accepted upload with 202 right away and forwards the rows in the background, in batches.
1. What the app team changes
Two settings point the Datadog logs SDK at KubeSense: proxy and a KubeSense clientToken. The rest is standard Datadog SDK code, and logging calls stay as they are.
| Option | Set it to | Why |
|---|---|---|
proxy | https://<KUBESENSE_RUM_HOST>/api/v2/proxy | Sends logs to KubeSense instead of Datadog's intake |
clientToken | The KubeSense client token of your RUM application | Authenticates the upload; KubeSense files the logs under that application |
site | datadoghq.com | Required by the SDK; not used when proxy is set |
service, env, version | Your app's values | Become the log's service, environment and app version |
sessionSampleRate | 100 | Logs from a sampled-out session are never sent |
forwardErrorsToLogs | true (the default) | Sends uncaught errors, unhandled rejections and failed requests |
forwardConsoleLogs | e.g. ['error', 'warn'] (default: none) | Sends console output |
forwardReports | optional, e.g. 'all' | Sends browser reports (CSP violations, deprecations, interventions) |
npm (recommended)
Use npm rather than the CDN: ad blockers often block Datadog's CDN.
npm install @datadog/browser-logsimport { datadogLogs } from '@datadog/browser-logs'
datadogLogs.init({
clientToken: '<KUBESENSE_CLIENT_TOKEN>',
site: 'datadoghq.com',
proxy: 'https://<KUBESENSE_RUM_HOST>/api/v2/proxy',
service: 'web-shop',
env: 'prod',
version: '1.4.2',
sessionSampleRate: 100,
forwardErrorsToLogs: true,
forwardConsoleLogs: ['error', 'warn'],
})
// Optional: file the logs under one of your Kubernetes clusters
datadogLogs.setGlobalContextProperty('cluster', 'prod-eu')
datadogLogs.logger.info('checkout started', { cartId: 'c-42' })CDN
Load https://www.datadoghq-browser-agent.com/us1/v6/datadog-logs.js and call DD_LOGS.init({ ... }) with the same options.
Cluster (optional)
The cluster context property names the Kubernetes cluster the logs belong to. It must be a cluster KubeSense already knows: one whose pods its sensors report, as listed in the Logs page's cluster selector. Any other name is ignored: the logs go under rum, and the name sent is kept in the cluster attribute. Without it, the logs go under rum.
Also check
- Content Security Policy: allow
https://<KUBESENSE_RUM_HOST>inconnect-src. - One destination per init: with
proxyset, logs go only to KubeSense, not to Datadog as well. - Datadog RUM on the same page: point it at the same
proxyURL, so its sessions and views reach KubeSense too and the logs link to them. - SDK version: this was checked against the Datadog browser SDK v6.
note: forwardConsoleLogs: 'all' also sends console.log, console.info and console.debug, which is usually far more volume than ['error', 'warn']. console.log is stored with level INFO.
2. What KubeSense sets up first
Three things must be in place before the app team starts. Without the last two, uploads are still answered 202, but the logs are discarded.
2.1 A RUM application and its client token
Create the application in Settings → RUM Application Management and give its client token to the app team. Its name becomes the logs' app name.
2.2 A public RUM endpoint with the proxy path
The RUM endpoint must be reachable from browsers over HTTPS. On the Ingress that already routes /rum/api/v1 to the collector, add /api/v2/proxy to the same backend:
- path: /api/v2/proxy
pathType: Prefix
backend:
service:
name: kubesense-kubecol
port:
number: 34443 # kubecol.rum.service.portwarning: Add only the exact path /api/v2/proxy. Routing all of /api/v2 or /api to the collector would take paths away from the web app.
To check the route, send a request without a token:
curl -s -X POST "https://<KUBESENSE_RUM_HOST>/api/v2/proxy?ddforward=%2Fapi%2Fv2%2Flogs%3Fddsource%3Dbrowser"
# {"error":"Api Key header is missing"} → 401: the collector answered, the route worksA 404, or the web app's HTML, means the path is not routed to the collector yet.
2.3 Tell the collector where the logs go
Enable RUM logs in the kubesense-server Helm values. Pick the destination:
aggregator(recommended): the Vector aggregator writeskube_logs.logs, applying its parsing and block rules, like every other log.datastore: the collector writeskube_logs.logsitself.
Through the aggregator:
kubecol:
rum:
enabled: true
logs:
enabled: true
destination: aggregator
aggregator:
sources:
rum_logs:
enabled: true
port: 6012 # the aggregator's rum_logs source; kubecol follows itStraight to ClickHouse:
kubecol:
rum:
enabled: true
logs:
enabled: true
destination: datastoreThen run helm upgrade as usual.
With destination: aggregator, the chart:
- adds a
rum_logsHTTP source on the aggregator (port6012by default) and opens it on the aggregator Service (in-cluster only), - adds a
logs_rum_table_transformthat feeds the existingunparsed_logspipeline, - keeps the attributes a browser log arrives with when its message also parses as JSON or logfmt,
- writes the collector's
aggregator.urlandrum_collector_config.logs.aggregator_portto match.
The render fails with a clear message if destination: aggregator is set but aggregator.sources.rum_logs.enabled is not, so the two sides cannot drift apart.
info: The default port is 6012 because 6004, 6010 and 6011 are commonly used by custom aggregator sources. Change it with aggregator.sources.rum_logs.port; the collector picks up the new port automatically. For an aggregator outside the chart, set kubecol.rum.logs.aggregatorPort and kubecol.config.aggregator.url instead.
Collector tuning (optional)
The collector sends to the aggregator in batches of up to 1000 rows. The defaults suit most installs; override them under kubecol.config.aggregator:
| Value | Default | What it does |
|---|---|---|
url | http://kubesense-aggregator | Aggregator base URL, without a port |
workers | 4 | Batches in flight at once; raise for a remote or slow aggregator |
batch_size | 1000 | Rows per batch |
flush_interval_ms | 1000 | Longest wait before a partial batch is sent |
queue_size / max_queued_mb | 20000 / 64 | Rows and memory that may wait for the aggregator |
enqueue_timeout_ms | 2000 | How long an upload waits for room before its rows are dropped and counted |
max_retries / retry_backoff_ms | 2 / 500 | Retries after a connection error, 429 or 5xx |
timeout_seconds | 10 | Per-request timeout |
close_timeout_ms | 5000 | How long shutdown waits for queued rows |
warning: The collector image must support the rum_collector_config.logs settings. A collector config that still sets the old rum_collector_config.logs_aggregator_url gets an error at startup and discards the logs until it is moved to this shape.
3. Where the logs show up
Browser logs appear on the Logs page beside pod and OTel logs. Filter on Source = rum to see only them.
| Logs page field | Comes from |
|---|---|
| Source | Always rum |
| Cluster | The cluster the SDK names, if KubeSense knows it; otherwise rum |
| App name | The RUM application's name in KubeSense |
| Environment type | The SDK that sent it: browser |
| Namespace | The RUM application's id, from the client token |
| Service, workload | The service init option |
| Level | The log's status: debug, info, warn, error; critical, alert and emerg show as FATAL |
| Environment, app version | The env and version init options |
| Body | The log message |
Everything else is in the log's attributes. Numbers are stored as numbers, so they can be compared, except ids (id, *.id, *_id, *Id, *ID) and very large whole numbers. Those are kept as text, exactly as sent.
- Origin (
origin):logger,console,source(uncaught errors),network(failed requests) orreport. - Context:
session.id,view.url,view.id,usr.*,account.*. - Errors and requests:
error.kind,error.message,error.stack,http.method,http.status_code,http.url. - Added by KubeSense: browser, OS, device, IP, country and city.
- Your own context: what you pass to
logger.info(message, {...})orsetGlobalContextProperty, with nested keys flattened, e.g.cart.id.
Timestamps
A log keeps its own date when it lies within the last 24 hours (or up to 1 hour ahead). One dated outside that window, or with a date that can't be read, is stored at the time KubeSense received it, and the date it was sent with is kept in the date attribute: check the device clock if logs show up at an unexpected time.
4. Troubleshooting
| Symptom | Check |
|---|---|
Browser shows 401 from /api/v2/proxy | The clientToken is not a KubeSense RUM client token, or the application is disabled |
Browser shows 403 | The RUM application has allowed origins, and the page's origin is not one of them |
Browser shows 404 | The Ingress does not route /api/v2/proxy to the collector (see 2.2) |
Uploads get 202 but no logs appear | kubecol.rum.logs.enabled is off, or (with aggregator) aggregator.sources.rum_logs.enabled is off |
| Logs appear without attributes | The collector image predates attributes in string_attributes / float_attributes; upgrade the collector |
Logs appear under cluster rum | The SDK named no cluster, or one KubeSense does not know; the name sent is in the cluster attribute |
| Nothing is sent at all | sessionSampleRate sampled the session out, or the page's CSP blocks connect-src to the RUM host |
To query them directly:
SELECT timestamp, cluster, app_name, workload, level, body,
string_attributes['origin'] AS origin
FROM kube_logs.logs
WHERE source = 'rum' AND timestamp > now() - INTERVAL 15 MINUTE
ORDER BY timestamp DESC
LIMIT 20;