references/android.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Android Logs installation - Docs
Copy page
# Android Logs installation - Docs
The PostHog Android SDK has built-in support for capturing structured Logs from Android apps. The SDK handles the OTLP encoding, batching, on-disk persistence across app restarts, and lifecycle integration. You just call `PostHog.logger.{trace,debug,info,warn,error,fatal}(...)`.
> **Manual capture only.** Logs are emitted by your code. The SDK does not autocapture system log streams (`Log.d`, `Logcat`, `Timber`).
> **Minimum version:** `com.posthog:posthog-android@3.46.0` or later. Bump the dependency in your `build.gradle` (or `build.gradle.kts`) and re-sync.
1. 1
## Install posthog-android
Required
If you haven't installed `posthog-android` yet, follow the [Android SDK installation guide](/docs/libraries/android.md#installation).
2. 2
## Configure logs in your PostHogAndroidConfig
Required
Configure Logs through `config.logs` before calling `PostHogAndroid.setup(...)`. All fields are optional; defaults are tuned for mobile (cellular bandwidth, battery, app lifecycle).
Kotlin
PostHog AI
```kotlin
val config = PostHogAndroidConfig(
apiKey = "<ph_project_token>",
host = "https://us.i.posthog.com",
).apply {
logs.serviceName = "my-app" // OTLP service.name – shown in the Logs UI
logs.environment = "production" // OTLP deployment.environment
logs.serviceVersion = "1.2.3" // OTLP service.version
}
```
These resource attributes are captured at `setup(...)` and apply to every batch. Mutating `config.logs.serviceName`, `environment`, `serviceVersion`, or `resourceAttributes` after setup has no effect.
3. 3
## Capture logs
Required
Use `PostHog.logger` for the per-level convenience API.
Kotlin
PostHog AI
```kotlin
import com.posthog.PostHog
import com.posthog.logs.PostHogLogSeverity
// Per-level convenience methods
PostHog.logger.info("checkout completed", mapOf("order_id" to "ord_789", "amount_cents" to 4999))
PostHog.logger.warn("payment retry", mapOf("attempt" to 2))
PostHog.logger.error("payment failed", mapOf("code" to "E001"))
// Generic entry point for a runtime severity (e.g. mapping a Timber priority)
PostHog.logger.log("rendered cart", severity = PostHogLogSeverity.DEBUG)
```
Available severity levels: `TRACE`, `DEBUG`, `INFO`, `WARN`, `ERROR`, `FATAL`.
If you need W3C trace correlation, call `PostHog.captureLog(...)` directly and pass `traceId`, `spanId`, and `traceFlags`.
Kotlin
PostHog AI
```kotlin
PostHog.captureLog(
"payment failed",
severity = PostHogLogSeverity.ERROR,
attributes = mapOf("code" to "PAY_3001"),
traceId = "4bf92f3577b34da6a3ce929d0e0e4736",
spanId = "00f067aa0ba902b7",
traceFlags = 0x01,
)
```
Records are buffered, batched, persisted to disk, and flushed automatically – every 30 seconds, when the buffer hits the threshold, when the app moves to the background, or on `PostHog.flush()`. `flush()` drains events, Session Replay, and Logs together.
Each record is automatically tagged with the current distinct ID, session ID, current screen, app foreground/background state, and active Feature Flags at the moment of capture.
From Java:
Java
PostHog AI
```java
import com.posthog.PostHog;
import com.posthog.logs.PostHogLogSeverity;
import java.util.Map;
PostHog.Companion.getLogger().info("checkout opened", null);
PostHog.Companion.getLogger().error(
"payment failed",
Map.of("amount_cents", 1999, "currency", "USD")
);
```
4. 4
## Test your setup
Recommended
1. Capture a test log from your app:
Kotlin
PostHog AI
```kotlin
PostHog.logger.info("hello from Android")
PostHog.flush()
```
2. Open the [PostHog Logs UI](https://app.posthog.com/logs).
3. Filter by `service.name = 'my-app'` (or whatever value you set above).
You should see your record arrive within a few seconds.
[View your Logs in PostHog](https://app.posthog.com/logs)
5. 5
## Tune buffering, rate cap, and resource attributes
Optional
The `logs` config has knobs for high-volume apps:
Kotlin
PostHog AI
```kotlin
val config = PostHogAndroidConfig(apiKey = "<ph_project_token>").apply {
logs.serviceName = "my-app"
logs.flushIntervalSeconds = 5 // default 30
logs.maxBufferSize = 200 // default 1000
logs.maxBatchSize = 50 // default 50
logs.flushAt = 20 // default 20
logs.rateCapMaxLogs = 5000 // default 500
logs.rateCapWindowSeconds = 60 // default 10
logs.resourceAttributes = mapOf("host.name" to "device-01")
}
PostHogAndroid.setup(this, config)
```
Full configuration reference:
| Field | Default | What it does |
| --- | --- | --- |
| serviceName | app package id | OTLP service.name resource attribute |
| serviceVersion | BuildConfig.VERSION_NAME | OTLP service.version resource attribute |
| environment | null | OTLP deployment.environment resource attribute |
| resourceAttributes | {} | Extra OTLP resource attributes (SDK keys win on collision) |
| flushIntervalSeconds | 30 | Periodic flush interval |
| flushAt | 20 | Buffer threshold that triggers an automatic flush |
| maxBatchSize | 50 | Max records per outbound POST (halved on 413) |
| maxBufferSize | 1000 | Max records held on disk before FIFO eviction |
| rateCapMaxLogs | 500 | Max records per rateCapWindowSeconds window. Set to 0 to disable. |
| rateCapWindowSeconds | 10 | Rate-cap tumbling window length |
`serviceName`, `serviceVersion`, `environment`, `resourceAttributes`, `flushAt`, and `maxBatchSize` are captured at `setup(...)`; mutating them later has no effect. `flushIntervalSeconds`, `maxBufferSize`, and rate-cap fields are re-read at runtime. Defaults are tuned for cellular-aware mobile apps. Raise `rateCapMaxLogs` and `maxBufferSize` for high-volume scenarios.
6. 6
## Filter or redact with beforeSend
Optional
`beforeSend` runs synchronously before the rate cap, so dropped records don't consume the per-window budget. Use it for redaction, sampling, or filtering by level. Each hook receives an immutable `PostHogLogRecord` and returns either a (possibly modified) record or `null` to drop it.
Kotlin
PostHog AI
```kotlin
config.logs.addBeforeSend { record ->
// Drop debug logs in production
if (record.level == PostHogLogSeverity.DEBUG) return@addBeforeSend null
// Redact secrets in the body
record.copy(body = record.body.replace(Regex("api_key=\\S+"), "api_key=[REDACTED]"))
}
```
Call `addBeforeSend` multiple times to compose a chain – hooks are evaluated left-to-right (registration order). Returning `null` from any hook short-circuits and drops the record. A hook that throws is treated the same as returning `null` (the record is dropped, the exception is logged via the SDK's internal debug logger). Returning a record with a blank body also drops the record.
`addBeforeSend` and `removeBeforeSend` are live – added or removed hooks take effect on the next `captureLog` call.
From Java, register a `PostHogBeforeSendLog` SAM:
Java
PostHog AI
```java
config.getLogs().addBeforeSend(record ->
record.getBody().contains("secret") ? null : record
);
```
8. ## Next steps
Checkpoint
*What you can do with your logs*
| Action | Description |
| --- | --- |
| [Why you need logs](/docs/logs/basics.md) | What logs show you that nothing else does |
| [Search logs](/docs/logs/search.md) | Use the search interface to find specific log entries |
| Filter by level | Filter by INFO, WARN, ERROR, etc. |
| [Link session replay](/docs/logs/link-session-replay.md) | Connect logs to users and session replays by passing posthogDistinctId and sessionId |
| [Link logs to a person](/docs/logs/link-person.md) | Surface every log emitted on behalf of a user on their PostHog person profile |
| [Logging best practices](/docs/logs/best-practices.md) | Learn what to log, how to structure logs, and patterns that make logs useful in production |
[Troubleshoot common issues](/docs/logs/troubleshooting.md)
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be betterreferences/best-practices.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Logging best practices - Docs
Copy page
# Logging best practices - Docs
Most logging is bad. Not because people don't log enough. They log too much of the wrong things and too little of the right things. The result is millions of lines that are expensive to store and useless to query.
This guide covers what actually makes logs useful in production systems. PostHog ingests logs via [OpenTelemetry (OTLP)](/docs/logs/installation.md), so the patterns here are built around OTel's structured logging model: resource attributes, log attributes, and trace context.
This guide covers:
- [Centralize your logs](#centralize-your-logs)
- [Logging requests, not code](#log-what-happened-to-requests-not-what-your-code-is-doing)
- [Structured logging](#use-structured-logging)
- [Cardinality and dimensionality](#think-in-cardinality-and-dimensionality)
- [Business context and OTel attributes](#include-business-context)
- [Building wide events](#build-events-throughout-the-request-lifecycle)
- [Log levels](#use-log-levels-correctly)
- [Sampling](#sample-strategically)
- [Trace and session context](#add-trace-and-session-context)
- [Schema evolution](#treat-your-log-schema-like-an-api-contract)
- [What not to log](#what-not-to-log)
- [Automatic PII scrubbing](#automatic-pii-scrubbing)
- [Checklist](#logging-checklist)
## Centralize your logs
Centralizing your logs makes it possible to search across all your services in one place.
With PostHog, your logs live alongside your [Product Analytics](/docs/product-analytics.md), [Session Replays](/docs/session-replay.md), and [Feature Flags](/docs/feature-flags.md), so you can go from a log line to a user's session to the flag variant they were on without switching tools.
If you're already using `posthog.capture()`, you might wonder how logs differ from events. The key distinction is:
- Events track what the user did (e.g. clicks, signups, purchases, feature usage)
- Logs track what the system did (e.g. API requests, errors, retries, timeouts, configuration failures)
If you've been capturing things like `database_connection_failed` or `stripe_api_timeout` as PostHog events, those belong in logs instead.
## Log what happened to requests, not what your code is doing
This is the single most important shift you can make.
PostHog AI
```
logger.info("Entering payment processing")
logger.info("Validating card details")
logger.info("Calling Stripe API")
logger.info("Stripe API returned")
logger.info("Updating database")
logger.info("Payment complete")
```
Six log lines, none of them useful in production at `INFO` level. They tell you what the code *does* (you already know that, you wrote it), not what happened to a specific request.
Step-level logs aren't universally wrong. They're valuable at `DEBUG` level for diagnosing race conditions, understanding ordering in concurrent systems, or tracing through complex state machines. The point is: don't make them your default.
Your `INFO`\-level logs should be wide events. Your `DEBUG`\-level logs can be as granular as you need, turned on selectively when you're actively investigating.
Instead, emit one rich log per request per service:
JSON
PostHog AI
```json
{
"event": "payment.completed",
"duration_ms": 342,
"posthogDistinctId": "user_abc123",
"order_id": "ord_789",
"amount_cents": 4999,
"currency": "USD",
"payment_method": "card",
"provider": "stripe",
"provider_latency_ms": 287,
"retry_count": 0,
"feature_flags": ["new_checkout_flow"],
"subscription_tier": "pro"
}
```
One line. Everything you need to debug, alert on, or analyze, all in one place. This is a **wide event** (sometimes called a canonical log line), and it's the foundation of useful logging.
This pattern works cleanly for request-response services. For long-running processes, event-driven architectures, or workflows that span multiple services over minutes or hours, a pure single-event approach is less practical.
In those cases, use a hybrid: emit a wide event at each meaningful stage boundary (job started, stage completed, job finished), with each event carrying the full accumulated context up to that point. You still get the benefits of wide events without relying on a single emit that might never fire.
## Use structured logging
Plain text logs are optimized for writing, not querying. Structured logs (JSON key-value pairs) are the opposite. They're queryable, filterable, and machine-readable.
**Bad:**
PostHog AI
```
Payment failed for user abc123 - Stripe error: card_declined (amount: $49.99)
```
**Good:**
JSON
PostHog AI
```json
{
"event": "payment.failed",
"posthogDistinctId": "user_abc123",
"error_type": "card_declined",
"provider": "stripe",
"amount_cents": 4999
}
```
The structured version lets you query "all card\_declined errors for pro-tier users in the last hour" without regex. The plain text version requires you to hope your string parsing doesn't break on edge cases.
**Structured logs in PostHog**
PostHog's log search works across all fields in structured logs, so the more context you include, the more useful your [search and filtering](/docs/logs/search.md) becomes. Every key-value pair is a field you can filter on.
## Think in cardinality and dimensionality
Two concepts that separate useful logs from noise. You want both high cardinality *and* high dimensionality. One wide event with 50 fields tells you more than 50 separate log lines with three fields each.
What is cardinality?
Cardinality is the number of unique values a field has. `posthogDistinctId` has high cardinality (millions of unique values). `log_level` has low cardinality (5 values). High-cardinality fields are what enable you to debug specific requests and users.
Some teams avoid high-cardinality fields because older logging tools can't handle them efficiently. Modern columnar databases (like ClickHouse, which PostHog uses under the hood) handle high cardinality just fine. Don't let outdated tooling concerns stop you from logging the fields that matter.
What is dimensionality?
Dimensionality is the number of fields per log event. A log with three fields (`timestamp`, `level`, `message`) has low dimensionality. A wide event with 30+ fields has high dimensionality.
High dimensionality is what makes wide events powerful. Instead of scattering context across dozens of log lines, you pack it all into one event. This means every query can filter, group, and correlate across all those fields simultaneously.
## Include business context
Technical context (status codes, latency, error types) is necessary but insufficient. Add the business context that turns debugging into understanding:
- **Who:** user ID, account type, subscription tier, organization
- **What:** order ID, cart contents, item count, Feature Flags
- **Where:** service name, deployment version, region
- **How:** payment method, auth provider, API version
- **How much:** amount, quantity, retry count
This lets you move from "500 errors spiked" to "500 errors spiked for enterprise users using the new checkout flow with coupon codes."
In OpenTelemetry, this context splits into two layers.
1. **Resource attributes** are set once when your service starts. They describe the service itself: `service.name`, `deployment.environment`, `service.version`, `cloud.region`. Every log from that process automatically includes them.
2. **Log attributes** are set per event. They describe what happened in that specific request: `posthogDistinctId`, `order_id`, `payment_method`, `duration_ms`.
**Correlate with Product Analytics**
If you're using PostHog for [Product Analytics](/docs/product-analytics.md), the business context in your logs can match the properties on your events. This means you can go from a log search result straight to seeing how that user behaves in your product, and vice versa.
## Build events throughout the request lifecycle
Don't emit 15 separate logs as a request moves through your code. Instead, accumulate context onto a single event and emit it once when the request completes.
The implementation details vary by language, but the pattern is always the same. These examples use the OpenTelemetry APIs from the [installation guide](/docs/logs/installation.md):
## Python
Python's standard `logging` module with the `extra` parameter. The OpenTelemetry SDK (configured in the [installation guide](/docs/logs/installation/python.md)) picks up these attributes automatically.
Python
PostHog AI
```python
import logging
logger = logging.getLogger(__name__)
def handle_checkout(request):
attrs = {
"event": "checkout",
"posthogDistinctId": request.user.id,
"subscription_tier": request.user.tier,
}
cart = get_cart(request.user)
attrs.update({
"item_count": len(cart.items),
"cart_total_cents": cart.total_cents,
})
try:
payment = process_payment(cart)
attrs.update({
"payment_method": payment.method,
"provider": payment.provider,
"provider_latency_ms": payment.latency_ms,
"status": "success",
})
logger.info("checkout completed", extra=attrs)
except PaymentError as e:
attrs.update({"status": "failed", "error_type": e.code})
logger.error("checkout completed", extra=attrs)
raise
```
## Node.js
The OpenTelemetry Logs API with `logger.emit()`. Attributes are passed as a dictionary on each log record. See the [installation guide](/docs/logs/installation/nodejs.md) for SDK setup.
JavaScript
PostHog AI
```javascript
import { logs } from "@opentelemetry/api-logs";
const logger = logs.getLogger("my-app");
function handleCheckout(req, res) {
const attrs = {
event: "checkout",
posthogDistinctId: req.user.id,
subscription_tier: req.user.tier,
};
const cart = getCart(req.user);
Object.assign(attrs, { item_count: cart.items.length, cart_total_cents: cart.totalCents });
try {
const payment = processPayment(cart);
Object.assign(attrs, {
payment_method: payment.method,
provider: payment.provider,
provider_latency_ms: payment.latencyMs,
status: "success",
});
logger.emit({ severityText: "INFO", body: "checkout completed", attributes: attrs });
} catch (e) {
Object.assign(attrs, { status: "failed", error_type: e.code });
logger.emit({ severityText: "ERROR", body: "checkout completed", attributes: attrs });
throw e;
}
}
```
## Go
Go's standard `slog` package, bridged to OpenTelemetry via `otelslog` (configured in the [installation guide](/docs/logs/installation/go.md)). Each `slog.With()` call returns a new logger with additional attributes.
Go
PostHog AI
```go
func HandleCheckout(w http.ResponseWriter, r *http.Request) {
log := slog.With(
"event", "checkout",
"posthogDistinctId", r.Context().Value("posthogDistinctId"),
)
cart, _ := getCart(r.Context())
log = log.With(
"item_count", len(cart.Items),
"cart_total_cents", cart.TotalCents,
)
payment, err := processPayment(r.Context(), cart)
if err != nil {
log.With(
"status", "failed",
"error_type", err.Code,
).ErrorContext(r.Context(), "checkout completed")
return
}
log.With(
"payment_method", payment.Method,
"provider", payment.Provider,
"provider_latency_ms", payment.LatencyMs,
"status", "success",
).InfoContext(r.Context(), "checkout completed")
}
```
One log line at the end, containing everything. Each step accumulates attributes, and the final emit carries them all.
**Watch out for context bloat**
Only bind scalar values (strings, numbers, booleans) to your log context. If you accidentally attach a full API response, a large query result, or a serialized object, you'll hit payload size limits or memory issues. Log the fields you need for debugging, not entire data structures.
**What if the process crashes?**
If your application crashes before reaching the end of a request (segfault, OOM, power failure), the accumulated context never gets emitted. Make sure you have a global exception handler or `finally` block that flushes whatever context has been collected. For long-running background jobs, consider emitting a "started" log at the beginning and "checkpoint" logs at key milestones, so a crash doesn't mean total data loss.
## Use log levels correctly
Log levels exist to control signal-to-noise ratio. Use them consistently:
| Level | Use for | Example |
| --- | --- | --- |
| ERROR | Something failed and needs attention | Payment processing failed, database connection lost |
| WARN | Something unexpected that didn't cause failure | Retry succeeded on third attempt, deprecated API version used |
| INFO | Normal operations worth recording | Request completed, user signed up, deployment finished |
| DEBUG | Detailed info for active debugging | Cache hit/miss ratios, query plans, intermediate state |
Two rules of thumb:
**The noisy ERROR trap**
1. If you're logging at `ERROR`, someone should eventually act on it. If no one ever looks at an error log, it's not an error. It's noise.
2. `DEBUG` logs should be off in production by default. Turn them on for specific services or requests when actively investigating.
## Sample strategically
At scale, logging everything is expensive and unnecessary. Use **tail sampling**. Make sampling decisions after a request completes, based on the outcome:
- **Keep 100%** of errors and exceptions
- **Keep 100%** of requests that exceeded your p99 latency threshold
- **Keep 100%** of requests from important accounts or flagged sessions
This gives you full visibility into problems while keeping costs manageable. You lose nothing useful. The sampled successful requests are statistically representative.
Tail sampling is the ideal, but it's genuinely hard to implement well. Your logging pipeline needs to buffer data in memory until a request completes, and in distributed systems you need consistent sampling decisions across services for the same trace. This is typically handled by an OpenTelemetry Collector with a [tail sampling processor](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/tailsamplingprocessor), but configuring it correctly takes real effort.
If your infrastructure doesn't support tail sampling yet, **head sampling** (randomly keeping a fixed percentage of requests up front) is a pragmatic starting point. It's less precise (you'll drop some errors and keep some boring requests), but it's better than logging everything or nothing. You can always move to tail sampling later.
How much does log storage cost in PostHog?
PostHog Logs is billed by GB ingested per month with volume-based pricing. Use the calculator on the [pricing page](/docs/logs/pricing.md) for a full breakdown.
## Drop and rate-limit logs in PostHog
The strategies above reduce volume in your own logging pipeline, before logs ever leave your infrastructure. When you can't change the source — a third-party service, a noisy dependency, or a team that hasn't adopted sampling yet — you can reduce volume in PostHog instead, with **drop rules**.
Drop rules run during ingestion, before logs are stored, so noisy or sensitive lines never reach your storage. Find them under **Logs → Configuration → Drop rules**. There are two actions:
- **Drop** — remove every log line matching the rule. Use it for high-frequency, low-value logs like health checks, load balancer pings, and liveness probes.
- **Rate limit** — cap a service's throughput in KB/s. Lines above the limit are dropped, so a single chatty service can't dominate your volume while quieter services keep flowing.
Match rules by service name, severity, or any log attribute. Rules run top to bottom in ingestion order (after optional PII scrubbing and JSON parsing), so order them from most to least specific — drag the handle on each row to reorder.
Because drop rules apply before storage, they're the fastest way to cut volume without shipping a code change. Pair them with the client-side practices above: sample at the source where you can, and use drop rules to catch the noise you can't.
If you still want the trend from the lines you drop, add a [log-based metric](/docs/logs/metrics.md) first. Metric rules are evaluated before drop rules, so the count survives even though the logs don't.
## Add trace and session context
Isolated logs are hard to correlate. Adding trace IDs and session IDs connects individual log events to the broader request journey.
Pick a library that supports structured output, async/buffered writes, and low per-call overhead. If you're using the OpenTelemetry SDK, the OTel log bridge adds minimal overhead on top of your chosen library, so the library itself is the bottleneck, not the export pipeline.
When in doubt, benchmark your logging path under realistic load before shipping to production.
Since PostHog uses OpenTelemetry, trace context propagation is automatic. Your logs are already correlated by trace ID if you have the OTel SDK configured. If you're also using PostHog for Product Analytics or Session Replay, you can go further and [link your logs to Session Replays](/docs/logs/link-session-replay.md), giving you the user's full experience alongside your backend logs.
**Link logs to Session Replays**
By adding a PostHog session ID and distinct ID to your log attributes, you can jump directly from a log line to the user's Session Replay. See the [Session Replay linking guide](/docs/logs/link-session-replay.md) to set this up.
## Treat your log schema like an API contract
Once you adopt wide events, your field names and value formats become dependencies. Dashboards, alerts, and saved searches all break silently when someone renames `error_type` to `err_code` or changes `duration_ms` from an integer to a string. Treat changes to your log schema the same way you'd treat changes to a public API: communicate them, deprecate before removing, and avoid breaking existing consumers.
## What not to log
Some things should never appear in your logs:
- **Secrets:** API keys, passwords, tokens, credit card numbers. If you log these by accident, you now have a security incident *and* a logging problem.
- **Request and response bodies:** Logging full payloads is one of the fastest ways to blow up storage costs and accidentally capture PII, auth tokens, or sensitive user data. Log the metadata (status code, content length, duration), not the body.
- **Personal data you don't need:** Full email addresses, IP addresses, or other PII beyond what's required for debugging. If you need to correlate logs to a user but can't store raw identifiers, hash or tokenize them. Check your GDPR, HIPAA, or other compliance requirements, as even fields like `posthogDistinctId` or `email` may need masking depending on your jurisdiction.
- **High-frequency health checks:** Load balancer pings and liveness probes generate massive volume with zero debugging value. Exclude them.
- **Unnecessary duplication:** If a downstream service logs the same event, you don't always need to log it again upstream. That said, when you're debugging a production incident at 2am, having key context from downstream calls in your own service's logs can save you from correlating across multiple systems under pressure. The rule of thumb: don't log a play-by-play of every call you make, but do include the outcome and any data you'd need to debug without switching to another service's logs.
### Automatic PII scrubbing
Even with good practices, sensitive data can slip into logs accidentally. PostHog can automatically redact a small set of common patterns from your log payloads at ingestion time, before anything is stored.
**Automatic PII scrubbing is in closed beta**
Automatic PII scrubbing is currently available to internal PostHog teams only while we measure its ingestion overhead. If you'd like early access, please [reach out to us](https://us.posthog.com/project/2/settings/#panel=support%3Asupport%3A%3A%3Afalse) via in-app support.
Once you have access, enable it under [**Project settings** → **Logs** → **PII scrubbing**](https://app.posthog.com/settings/environment-logs#logs-pii-scrub). The toggle is off by default.
When enabled, the following patterns are detected in each log record's `body` and string-valued `attributes`, and replaced with `{{REDACTED}}`:
- **Bearer tokens** – `Bearer <token>` style credentials. The `Bearer` prefix is preserved, so the redacted output looks like `Bearer {{REDACTED}}`.
- **Stripe secret keys** – values matching `sk_live_*` or `sk_test_*` followed by at least 20 alphanumeric characters.
- **Email addresses** – standard `local@domain.tld` shape.
Scrubbing runs as a single regex pass over the raw `body` string and over each string-valued attribute. It does not parse JSON, does not walk nested structures, and does not redact based on attribute or JSON key names – a value only gets scrubbed if it matches one of the three patterns above. `resource_attributes`, `service_name`, `severity_text`, trace IDs, and other metadata fields are not touched.
**Scrubbing is permanent**
Redaction happens at ingestion and cannot be reversed. Original values are not retained anywhere – this is not reversible hashing.
A few things this feature explicitly **does not** catch today:
- **Payment card numbers / PANs.** Raw or hyphenated digit runs (for example `4242 4242 4242 4242`) are not redacted.
- **Secrets identified only by key name.** A value under a key like `password`, `api_key`, or `authorization` is only redacted if the value itself matches a pattern above. The key name alone is not enough.
- **Numbers or booleans inside JSON.** Only string content is scanned; JSON number and boolean leaves are not redacted.
- **Anything that doesn't look like one of the three patterns.** Custom token formats, opaque session IDs, addresses, phone numbers, names, IPs, and so on pass through unchanged.
Treat automatic PII scrubbing as a safety net for accidental leaks, not as a substitute for avoiding sensitive data in your logs in the first place.
## Logging checklist
Use this to audit your existing logging or as a starting point for a new service.
### Structural requirements
- Logs are structured JSON key-value pairs, not plain text strings
- Each request emits one wide event at the end, not a trail of step-by-step messages
- Only scalar values (strings, numbers, booleans) are logged. No raw objects, large arrays, or full API response bodies
- Context is accumulated throughout the request lifecycle (e.g., `cart_total` added once calculated, `payment_id` added later)
### Business and trace context
- **The "Who":** `posthogDistinctId`, `org_id`, `account_tier`, or equivalent
- **The "What":** `order_id`, `transaction_id`, `feature_flag_variants`, or equivalent
- **The "Where":** `service.name`, `service.version`, `deployment.environment` set as OTel resource attributes
- **Trace IDs:** OpenTelemetry `trace_id` is attached so you can jump from logs to traces
- **Session IDs:** PostHog `session_id` is included to enable [Session Replay linking](/docs/logs/link-session-replay.md)
### Levels and sampling
- Log levels are correct: INFO for request completion, WARN for retries or non-breaking issues, ERROR only if someone needs to act
- Health checks (`/healthz`) and load balancer pings are excluded or sampled down
- A sampling strategy is in place (or planned) for high-traffic services
- A `try`/`finally` or global error handler flushes log context if the process dies mid-request
### Security and compliance
- No secrets: API keys, Bearer tokens, and passwords are scrubbed
- PII is masked: emails, physical addresses, and credit card numbers are hashed or removed per GDPR/HIPAA requirements
- Request/response bodies are not logged (to avoid capturing sensitive user data)
- Field names and value types are treated as a stable schema (changes are communicated)
- Consider enabling [automatic PII scrubbing](#automatic-pii-scrubbing) as a safety net for accidental leaks
- [Link logs to Session Replays](/docs/logs/link-session-replay.md) for full user context
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be betterreferences/COMMANDMENTS.md
# Framework rules
Follow these when integrating PostHog into this framework.
- A missing PostHog configuration must never break the app — read keys optionally (never a required setting), guard init and capture behind their presence, and keep build and boot working with no PostHog environment set — but never silently: in development or debug builds fail loudly, using the language's idiomatic error, with the message "<VAR> variable required by PostHog is missing or un-configured, this causes events to be silently missed. This error stops appearing once <VAR> is configured" (substituting the actual variable name); production stays a no-op
references/datadog.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Datadog Logs installation - Docs
Copy page
# Datadog Logs installation - Docs
If you're already using Datadog to collect logs, you can forward them to PostHog by configuring your existing Datadog log exporters (like the Datadog Agent) to send logs to PostHog's Datadog-compatible endpoint.
1. 1
## Get your project token
Required
You'll need your PostHog project token to authenticate log requests. This is the same key you use for capturing events and exceptions with the PostHog SDK.
> **Important:** Use your **project token** which starts with `phc_`. Do **not** use a personal token (which starts with `phx_`).
You can find your project token in [Project Settings](https://app.posthog.com/settings/project).
2. 2
## Configure Datadog Agent
Required
Set the Datadog logs URL to point to PostHog's Datadog-compatible endpoint. The endpoint format is:
PostHog AI
```
https://us.i.posthog.com/i/v1/logs/datadog/<ph_project_token>
```
For the **Datadog Agent**, set the `DD_LOGS_CONFIG_LOGS_DD_URL` environment variable:
Terminal
PostHog AI
```bash
export DD_LOGS_CONFIG_LOGS_DD_URL="https://us.i.posthog.com/i/v1/logs/datadog/<ph_project_token>"
```
Alternatively, you can set this in your `datadog.yaml` configuration file:
YAML
PostHog AI
```yaml
logs_config:
logs_dd_url: "https://us.i.posthog.com/i/v1/logs/datadog/<ph_project_token>"
```
3. 3
## Other Datadog log exporters
Optional
If you're using other Datadog log exporters or forwarders, configure them to send logs to the same endpoint:
PostHog AI
```
https://us.i.posthog.com/i/v1/logs/datadog/<ph_project_token>
```
The endpoint accepts logs in the standard Datadog log format, so existing integrations should work without additional changes.
4. 4
## Test your setup
Recommended
Once everything is configured, test that logs are flowing into PostHog:
1. Restart the Datadog Agent (or your log forwarder) to apply the configuration
2. Generate some log entries in your application
3. Check the PostHog Logs interface for your log entries
4. Verify the logs appear in your project
[View your logs in PostHog](https://app.posthog.com/logs)
6. ## Next steps
Checkpoint
*What you can do with your logs*
| Action | Description |
| --- | --- |
| [Why you need logs](/docs/logs/basics.md) | What logs show you that nothing else does |
| [Search logs](/docs/logs/search.md) | Use the search interface to find specific log entries |
| Filter by level | Filter by INFO, WARN, ERROR, etc. |
| [Link session replay](/docs/logs/link-session-replay.md) | Connect logs to users and session replays by passing posthogDistinctId and sessionId |
| [Link logs to a person](/docs/logs/link-person.md) | Surface every log emitted on behalf of a user on their PostHog person profile |
| [Logging best practices](/docs/logs/best-practices.md) | Learn what to log, how to structure logs, and patterns that make logs useful in production |
[Troubleshoot common issues](/docs/logs/troubleshooting.md)
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be betterreferences/flutter.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Flutter Logs installation - Docs
Copy page
# Flutter Logs installation - Docs
The PostHog Flutter SDK has built-in support for capturing structured Logs from your Flutter app across mobile and web. The SDK handles the OTLP encoding, batching, and flushing — and on mobile, on-disk persistence across app restarts and app-lifecycle integration (web buffers in memory via `posthog-js`). You just call `Posthog().captureLog(...)` or `Posthog().logger.{trace,debug,info,warn,error,fatal}(...)`.
> **Manual capture only.** Logs are emitted by your code. The SDK does not autocapture system log streams (`print`, `debugPrint`, or `dart:developer`'s `log`).
> **Minimum version:** `posthog_flutter` `5.27.0` or later (the release that adds Logs support). On mobile it pulls in `posthog-android` `3.48.0` or later automatically.
1. 1
## Install posthog\_flutter
Required
If you haven't installed `posthog_flutter` yet, follow the steps below. For full details, see the [Flutter SDK guide](/docs/libraries/flutter.md).
PostHog is available for install via [Pub](https://pub.dev/packages/posthog_flutter).
### Configuration
Set your PostHog project token and enable automatic event tracking if you want the library to capture lifecycle events for you.
Remember that the application lifecycle events won't have any special context set for you by the time it is initialized. If you are using a self-hosted instance of PostHog you will need to have the public hostname or IP for your instance as well.
To start, add `posthog_flutter` to your `pubspec.yaml`:
pubspec.yaml
PostHog AI
```yaml
# rest of your code
dependencies:
flutter:
sdk: flutter
posthog_flutter: ^5.26.0
# rest of your code
```
Then complete the setup for each platform:
> For Session Replay and Surveys, you must set up the SDK manually by disabling the `com.posthog.posthog.AUTO_INIT` mode.
#### Android setup
There are 2 ways of initializing the SDK, automatically and manually.
Automatically:
Add your PostHog configuration to your `AndroidManifest.xml` file located in the `android/app/src/main`:
android/app/src/main/AndroidManifest.xml
PostHog AI
```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="your.package.name">
<application>
<!-- ... other configuration ... -->
<meta-data android:name="com.posthog.posthog.PROJECT_TOKEN" android:value="<ph_project_token>" />
<meta-data android:name="com.posthog.posthog.POSTHOG_HOST" android:value="https://us.i.posthog.com" /> <!-- usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com' -->
<!-- com.posthog.posthog.CAPTURE_APPLICATION_LIFECYCLE_EVENTS is enabled by default since version 5.23.0 (previously named TRACK_APPLICATION_LIFECYCLE_EVENTS, which still works as an alias) -->
<meta-data android:name="com.posthog.posthog.DEBUG" android:value="true" />
</application>
</manifest>
```
Or manually (more control and more configurations available):
Add your PostHog configuration to your `AndroidManifest.xml` file located in the `android/app/src/main`:
android/app/src/main/AndroidManifest.xml
PostHog AI
```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="your.package.name">
<application>
<!-- ... other configuration ... -->
<meta-data android:name="com.posthog.posthog.AUTO_INIT" android:value="false" />
</application>
</manifest>
```
In both cases, you'll also need to update the minimum Android SDK version to `23` in `android/app/build.gradle`:
android/app/build.gradle
PostHog AI
```kotlin
// rest of your config
defaultConfig {
minSdkVersion 23
// rest of your config
}
// rest of your config
```
#### iOS setup
There are 2 ways of initializing the SDK, automatically and manually.
The SDK supports both [CocoaPods](https://guides.cocoapods.org/using/getting-started.html) and [Swift Package Manager (SPM)](https://docs.flutter.dev/packages-and-plugins/swift-package-manager/for-app-developers). Flutter 3.44 and later enable SPM by default. On earlier versions, or if you disabled SPM, enable it with `flutter config --enable-swift-package-manager`.
Automatically:
Add your PostHog configuration to the `Info.plist` file located in the `ios/Runner` directory:
ios/Runner/Info.plist
PostHog AI
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- rest of your configuration -->
<key>com.posthog.posthog.PROJECT_TOKEN</key>
<string><ph_project_token></string>
<key>com.posthog.posthog.POSTHOG_HOST</key>
<string>https://us.i.posthog.com</string>
<!-- com.posthog.posthog.CAPTURE_APPLICATION_LIFECYCLE_EVENTS is enabled by default since version 5.23.0 -->
<key>com.posthog.posthog.DEBUG</key>
<true/>
</dict>
</plist>
```
Or manually (more control and more configurations available):
Add your PostHog configuration to the `Info.plist` file located in the `ios/Runner` directory:
ios/Runner/Info.plist
PostHog AI
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<!-- rest of your configuration -->
<key>com.posthog.posthog.AUTO_INIT</key>
<false/>
</dict>
</plist>
```
In both cases, you'll need to set the minimum platform version to iOS 13.0.
For CocoaPods projects, set it in your `Podfile`:
ios/Podfile
PostHog AI
```yaml
platform :ios, '13.0'
# rest of your config
```
For Swift Package Manager projects without a `Podfile`, set the **Minimum Deployments** version to iOS 13.0 for the `Runner` target in Xcode (**Runner > General > Minimum Deployments**). After you change **Minimum Deployments**, regenerate the iOS project's configuration files:
Terminal
PostHog AI
```bash
flutter build ios --config-only
```
#### Dart setup (For manual step only)
If you followed the automatic SDK setup, then there's no more configuration needed in Dart.
If you followed the manual SDK setup:
Dart
PostHog AI
```dart
import 'package:flutter/material.dart';
import 'package:posthog_flutter/posthog_flutter.dart';
Future<void> main() async {
// init WidgetsFlutterBinding if not yet
WidgetsFlutterBinding.ensureInitialized();
final config = PostHogConfig('<ph_project_token>');
config.debug = true;
// captureApplicationLifecycleEvents is enabled by default since version 5.23.0
config.host = 'https://us.i.posthog.com';
await Posthog().setup(config);
runApp(MyApp());
}
```
#### Web setup
If your project has a `web/` directory, this step is required. `Posthog().setup()` is a no-op on web, so a web build without the snippet below captures nothing.
Add your `Web snippet` (which you can find in [your project settings](https://us.posthog.com/settings/project#snippet)) in the `<header>` of your `web/index.html` file. Write your project token into the snippet as a literal string. It's public, the same token ships to every visitor, and it needs no build-time or deploy-time injection:
web/index.html
PostHog AI
```html
<!DOCTYPE html>
<html>
<head>
<!-- ... other head elements ... -->
<script async>
!(function (t, e) {
var o, n, p, r;
e.__SV ||
((window.posthog = e),
(e._i = []),
(e.init = function (i, s, a) {
function g(t, e) {
var o = e.split(".");
(2 == o.length && ((t = t[o[0]]), (e = o[1])),
(t[e] = function () {
t.push([e].concat(Array.prototype.slice.call(arguments, 0)));
}));
}
(((p = t.createElement("script")).type = "text/javascript"),
(p.crossOrigin = "anonymous"),
(p.async = !0),
(p.src = s.api_host + "/static/array.js"),
(r = t.getElementsByTagName("script")[0]).parentNode.insertBefore(p, r));
var u = e;
for (
void 0 !== a ? (u = e[a] = []) : (a = "posthog"),
u.people = u.people || [],
u.toString = function (t) {
var e = "posthog";
return ("posthog" !== a && (e += "." + a), t || (e += " (stub)"), e);
},
u.people.toString = function () {
return u.toString(1) + ".people (stub)";
},
o =
"capture identify alias people.set people.set_once set_config register register_once unregister opt_out_capturing has_opted_out_capturing opt_in_capturing reset isFeatureEnabled onFeatureFlags getFeatureFlag getFeatureFlagResult reloadFeatureFlags group updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures getActiveMatchingSurveys getSurveys getNextSurveyStep onSessionId".split(
" ",
),
n = 0;
n < o.length;
n++
)
g(u, o[n]);
e._i.push([i, s, a]);
}),
(e.__SV = 1));
})(document, window.posthog || []);
posthog.init("<ph_project_token>", {
api_host: "https://us.i.posthog.com", // 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'
defaults: "2026-05-30",
});
</script>
</head>
<!-- other elements -->
</html>
```
For more information please check: /docs/libraries/js
2. 2
## Configure logs in your PostHogConfig
Required
Configure Logs through `config.logsConfig` before calling `Posthog().setup(...)`. All fields are optional; unset fields fall back to the native defaults, which are tuned for mobile (cellular bandwidth, battery, app lifecycle).
Dart
PostHog AI
```dart
final config = PostHogConfig('<ph_project_token>');
config.host = 'https://us.i.posthog.com';
config.logsConfig.serviceName = 'my-app'; // OTLP service.name – shown in the Logs UI
config.logsConfig.environment = 'production'; // OTLP deployment.environment
config.logsConfig.serviceVersion = '1.2.3'; // OTLP service.version
await Posthog().setup(config);
```
These resource attributes are captured at `setup(...)` and apply to every batch.
> **Web behavior.** On Flutter Web, the SDK attaches to an already-initialized [`posthog-js`](/docs/libraries/js.md) instance, so `config.logsConfig` is **not** applied on web. Configure your log options in the `posthog.init({...})` call in your `web/index.html` instead. `captureLog` and `logger` still work on web (they are forwarded to `posthog-js`), and `beforeSend` still runs (in Dart) on web. Web also requires a recent `posthog-js` build that exposes `captureLog`.
For example, set the same service identity on the `posthog-js` snippet in `web/index.html`:
HTML
PostHog AI
```html
<script>
posthog.init('<ph_project_token>', {
api_host: 'https://us.i.posthog.com',
logs: {
serviceName: 'my-app',
environment: 'production',
serviceVersion: '1.2.3',
},
})
</script>
```
See the [JavaScript Logs installation guide](/docs/logs/installation/javascript.md) for the full `posthog-js` logs config.
3. 3
## Capture logs
Required
Use `Posthog().logger` for the per-level convenience API, or `Posthog().captureLog` for full control over level, attributes, and trace context.
Dart
PostHog AI
```dart
import 'package:posthog_flutter/posthog_flutter.dart';
// Per-level convenience methods
Posthog().logger.info('checkout completed', {'order_id': 'ord_789', 'amount_cents': 4999});
Posthog().logger.warn('payment retry', {'attempt': 2});
Posthog().logger.error('payment failed', {'code': 'E001'});
// Lower-level API for custom severity / trace context
Posthog().captureLog(
body: 'checkout failed',
level: PostHogLogSeverity.error,
attributes: {'order_id': 'ord_789', 'step': 'auth'},
traceId: '4bf92f3577b34da6a3ce929d0e0e4736', // optional W3C trace context (32 hex chars)
spanId: '00f067aa0ba902b7', // optional W3C span (16 hex chars)
traceFlags: 1,
);
```
The per-level facade methods are `trace`, `debug`, `info`, `warn`, `error`, and `fatal`, each taking a `String body` and an optional `Map<String, Object>` of attributes. Available severity levels for `captureLog` are `PostHogLogSeverity.trace`, `.debug`, `.info`, `.warn`, `.error`, and `.fatal`.
The optional W3C trace fields (`traceId`, `spanId`, `traceFlags`) are available on `captureLog` only, not on the `logger` facade.
Records are buffered, batched, persisted to disk, and flushed automatically – every 30 seconds, when the buffer hits the threshold, when the app moves to the background, or on `Posthog().flush()`. `flush()` drains events, Session Replay, and Logs together.
Each record is automatically tagged with the current distinct ID, session ID, active feature flags, and (on mobile) the current screen and app foreground/background state at the moment of capture. On web, `url.full` is tagged instead of screen name and app state.
4. 4
## Test your setup
Recommended
1. Capture a test log from your app:
Dart
PostHog AI
```dart
Posthog().logger.info('hello from Flutter');
Posthog().flush();
```
2. Open the [PostHog Logs UI](https://app.posthog.com/logs).
3. Filter by `service.name = 'my-app'` (or whatever value you set above).
You should see your record arrive within a few seconds.
[View your Logs in PostHog](https://app.posthog.com/logs)
5. 5
## Tune buffering, rate cap, and resource attributes
Optional
The `logsConfig` has knobs for high-volume apps:
Dart
PostHog AI
```dart
final config = PostHogConfig('<ph_project_token>');
config.logsConfig.serviceName = 'my-app';
config.logsConfig.flushInterval = Duration(seconds: 5); // default 30s
config.logsConfig.maxBufferSize = 200; // default 1000
config.logsConfig.maxBatchSize = 50; // default 50
config.logsConfig.flushAt = 20; // default 20
config.logsConfig.rateCapMaxLogs = 5000; // default 500
config.logsConfig.rateCapWindow = Duration(seconds: 60); // default 10s
config.logsConfig.resourceAttributes = {'host.name': 'device-01'};
await Posthog().setup(config);
```
Full configuration reference:
| Field | Default | What it does |
| --- | --- | --- |
| serviceName | app bundle id (iOS) / app namespace (Android) | OTLP service.name resource attribute |
| serviceVersion | app version | OTLP service.version resource attribute |
| environment | none | OTLP deployment.environment resource attribute |
| resourceAttributes | {} | Extra OTLP resource attributes |
| flushInterval | 30s | Periodic flush interval |
| flushAt | 20 | Buffer threshold that triggers an automatic flush |
| maxBatchSize | 50 | Max records per outbound POST |
| maxBufferSize | 1000 | Max records held on disk before FIFO eviction |
| rateCapMaxLogs | 500 | Max records per rateCapWindow. Set to 0 to disable. |
| rateCapWindow | 10s | Rate-cap window length |
Defaults are tuned for cellular-aware mobile apps. Raise `rateCapMaxLogs` and `maxBufferSize` for high-volume scenarios.
> On web, these fields are not applied – configure them in your `posthog.init({...})` call in `web/index.html` instead.
6. 6
## Filter or redact with beforeSend
Optional
Use `config.logsConfig.beforeSend` for redaction, sampling, or filtering by level. It is a `List<BeforeSendLogCallback>`, where each callback is a `FutureOr<PostHogLogRecord?> Function(PostHogLogRecord)`. Callbacks run **in Dart on all platforms (including web)**, evaluated left-to-right. Each callback receives a mutable `PostHogLogRecord` (with mutable `body`, `level`, and `attributes`) and returns either the (possibly mutated) record or `null` to drop it. Callbacks can be synchronous or asynchronous.
Dart
PostHog AI
```dart
config.logsConfig.beforeSend = [
(record) {
// Drop debug logs in production
if (record.level == PostHogLogSeverity.debug) return null;
// Redact a sensitive attribute
record.attributes?.remove('password');
return record;
},
// Compose a chain – callbacks run left-to-right
(record) => record.body.contains('secret') ? null : record,
];
```
Returning `null` from any callback short-circuits and drops the record. Setting `record.body` to an empty or whitespace-only string also drops the record. A callback that throws is logged and the record is dropped (fail-closed).
8. ## Next steps
Checkpoint
*What you can do with your logs*
| Action | Description |
| --- | --- |
| [Why you need logs](/docs/logs/basics.md) | What logs show you that nothing else does |
| [Search logs](/docs/logs/search.md) | Use the search interface to find specific log entries |
| Filter by level | Filter by INFO, WARN, ERROR, etc. |
| [Link session replay](/docs/logs/link-session-replay.md) | Connect logs to users and session replays by passing posthogDistinctId and sessionId |
| [Link logs to a person](/docs/logs/link-person.md) | Surface every log emitted on behalf of a user on their PostHog person profile |
| [Logging best practices](/docs/logs/best-practices.md) | Learn what to log, how to structure logs, and patterns that make logs useful in production |
[Troubleshoot common issues](/docs/logs/troubleshooting.md)
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be betterreferences/go.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Go Logs installation - Docs
Copy page
# Go Logs installation - Docs
1. 1
## Install OpenTelemetry packages
Required
Terminal
PostHog AI
```bash
go get go.opentelemetry.io/otel/sdk/log
go get go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp
```
2. 2
## Get your project token
Required
You'll need your PostHog project token to authenticate log requests. This is the same key you use for capturing events and exceptions with the PostHog SDK.
> **Important:** Use your **project token** which starts with `phc_`. Do **not** use a personal API key (which starts with `phx_`).
You can find your project token in [Project Settings](https://app.posthog.com/project/settings).
3. 3
## Configure the SDK
Required
Set up the OpenTelemetry SDK to send logs to PostHog.
Go
PostHog AI
```go
package main
import (
"os"
"context"
"log"
"log/slog"
"go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp"
"go.opentelemetry.io/otel/exporters/stdout/stdoutlog"
"go.opentelemetry.io/contrib/bridges/otelslog"
otellog "go.opentelemetry.io/otel/sdk/log"
"go.opentelemetry.io/otel/log/global"
)
func main() {
ctx := context.Background()
// Create OTLP HTTP exporter
exporter, err := otlploghttp.New(ctx,
otlploghttp.WithEndpoint("us.i.posthog.com"),
otlploghttp.WithURLPath("/i/v1/logs"),
otlploghttp.WithHeaders(map[string]string{
"Authorization": "Bearer <ph_project_token>",
}),
)
if err != nil {
panic(err)
}
// you could also set this outside your application
os.Setenv("OTEL_SERVICE_NAME", "my-service")
stdoutExporter, _ := stdoutlog.New()
// Create logger provider
loggerProvider := otellog.NewLoggerProvider(
otellog.WithProcessor(otellog.NewBatchProcessor(exporter)),
// optional, also log to stdout
otellog.WithProcessor(otellog.NewSimpleProcessor(stdoutExporter)),
)
defer func() {
loggerProvider.Shutdown(context.Background())
}()
global.SetLoggerProvider(loggerProvider)
slog.SetDefault(otelslog.NewLogger(""))
log.Println("this is a log line")
}
```
Alternatively, you can pass the API key as a query parameter by modifying the URL path:
Go
PostHog AI
```go
otlploghttp.WithURLPath("/i/v1/logs?token=<ph_project_token>")
```
4. 4
## Use OpenTelemetry logging
Required
Now you can start logging with OpenTelemetry:
Go
PostHog AI
```go
import (
"go.opentelemetry.io/otel/log"
)
logger := otel.GetLoggerProvider().Logger("my-app")
logger.Info(ctx, "User action",
log.String("userId", "123"),
log.String("action", "login"),
)
```
5. 5
## Test your setup
Recommended
Once everything is configured, test that logs are flowing into PostHog:
1. Send a test log from your application
2. Check the PostHog Logs interface for your log entries
3. Verify the logs appear in your project
[View your logs in PostHog](https://app.posthog.com/logs)
7. ## Next steps
Checkpoint
*What you can do with your logs*
| Action | Description |
| --- | --- |
| [Why you need logs](/docs/logs/basics.md) | What logs show you that nothing else does |
| [Search logs](/docs/logs/search.md) | Use the search interface to find specific log entries |
| Filter by level | Filter by INFO, WARN, ERROR, etc. |
| [Link session replay](/docs/logs/link-session-replay.md) | Connect logs to users and session replays by passing posthogDistinctId and sessionId |
| [Link logs to a person](/docs/logs/link-person.md) | Surface every log emitted on behalf of a user on their PostHog person profile |
| [Logging best practices](/docs/logs/best-practices.md) | Learn what to log, how to structure logs, and patterns that make logs useful in production |
[Troubleshoot common issues](/docs/logs/troubleshooting.md)
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be betterreferences/ios.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# iOS Logs installation - Docs
Copy page
# iOS Logs installation - Docs
The PostHog iOS SDK has built-in support for capturing structured Logs from iOS, macOS, tvOS, watchOS, and visionOS apps. The SDK handles OTLP encoding, batching, on-disk persistence across app restarts, and lifecycle integration. You just call `PostHogSDK.shared.captureLog(...)` or `PostHogSDK.shared.logger?.{trace,debug,info,warn,error,fatal}(...)`.
> **Manual capture only.** Logs are emitted by your code. The SDK does not autocapture system log streams (`os_log`, `Logger`, `print`).
> **Minimum version:** `posthog-ios@3.58.0` or later. Run `pod update PostHog` (CocoaPods) or update the package version in Xcode (Swift Package Manager).
1. 1
## Install posthog-ios
Required
If you haven't installed `posthog-ios` yet, follow the steps below. For full details, see the [iOS SDK guide](/docs/libraries/ios.md).
PostHog is available through [CocoaPods](http://cocoapods.org) or you can add it as a Swift Package Manager based dependency.
### CocoaPods
Podfile
PostHog AI
```ruby
pod "PostHog", "~> 3.59.3"
```
### Swift Package Manager
Add PostHog as a dependency in your Xcode project "Package Dependencies" and select the project target for your app, as appropriate.
For a Swift Package Manager based project, add PostHog as a dependency in your `Package.swift` file's Package dependencies section:
Package.swift
PostHog AI
```swift
dependencies: [
.package(url: "https://github.com/PostHog/posthog-ios.git", from: "3.59.3")
],
```
and then as a dependency for the Package target utilizing PostHog:
Package.swift
PostHog AI
```swift
.target(
name: "myApp",
dependencies: [.product(name: "PostHog", package: "posthog-ios")]),
```
### Configuration
Configuration is done through the `PostHogConfig` object. Here's a basic configuration example to get you started.
You can find more advanced configuration options in the [configuration page](/docs/libraries/ios/configuration.md).
## UIKit
Swift
PostHog AI
```swift
import Foundation
import PostHog
import UIKit
class AppDelegate: NSObject, UIApplicationDelegate {
func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
let POSTHOG_PROJECT_TOKEN = "<ph_project_token>"
// usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'
let POSTHOG_HOST = "https://us.i.posthog.com"
let config = PostHogConfig(projectToken: POSTHOG_PROJECT_TOKEN, host: POSTHOG_HOST)
PostHogSDK.shared.setup(config)
return true
}
}
```
## SwiftUI
Swift
PostHog AI
```swift
import SwiftUI
import PostHog
@main
struct YourGreatApp: App {
// Add PostHog to your app's initializer.
// If using UIApplicationDelegateAdaptor, see the UIKit tab.
init() {
let POSTHOG_PROJECT_TOKEN = "<ph_project_token>"
// usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'
let POSTHOG_HOST = "https://us.i.posthog.com"
let config = PostHogConfig(projectToken: POSTHOG_PROJECT_TOKEN, host: POSTHOG_HOST)
PostHogSDK.shared.setup(config)
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
```
2. 2
## Configure logs in your PostHogConfig
Required
Configure Logs through `config.logs` before calling `setup(_:)`. All fields are optional; defaults are tuned for mobile (cellular bandwidth, battery, OS lifecycle).
Swift
PostHog AI
```swift
import PostHog
let config = PostHogConfig(projectToken: "<ph_project_token>", host: "https://us.i.posthog.com")
config.logs.serviceName = "my-app" // OTLP service.name – shown in the Logs UI
config.logs.environment = "production" // OTLP deployment.environment
config.logs.serviceVersion = "1.2.3" // OTLP service.version
PostHogSDK.shared.setup(config)
```
These resource attributes are captured at `setup(_:)` and apply to every batch. Mutating `config.logs` after `setup` has no effect.
3. 3
## Capture logs
Required
Use `PostHogSDK.shared.logger` for the per-level convenience API, or `PostHogSDK.shared.captureLog` for full control over level, attributes, and trace context.
Swift
PostHog AI
```swift
// Per-level convenience methods – Optional, since logger is created at setup
PostHogSDK.shared.logger?.info("checkout completed", attributes: ["order_id": "ord_789", "amount_cents": 4999])
PostHogSDK.shared.logger?.warn("payment retry", attributes: ["attempt": 2])
PostHogSDK.shared.logger?.error("payment failed", attributes: ["code": "E001"])
// Lower-level API for custom severity / trace context
PostHogSDK.shared.captureLog(
"checkout failed",
level: .error,
attributes: ["order_id": "ord_789", "step": "auth"],
traceId: "4bf92f3577b34da6a3ce929d0e0e4736", // optional W3C trace context (32 hex chars)
spanId: "00f067aa0ba902b7" // optional W3C span (16 hex chars)
)
```
Available severity levels: `.trace`, `.debug`, `.info`, `.warn`, `.error`, `.fatal`.
Records are buffered, batched, persisted to disk, and flushed automatically – every 30 seconds, when the buffer hits the threshold, when the app moves to the background, or on `PostHogSDK.shared.flush()`. `flush()` drains events, Session Replay, and Logs together.
Each record is automatically tagged with the current distinct ID, session ID, current screen, app foreground/background state, and active Feature Flags at the moment of capture.
4. 4
## Test your setup
Recommended
1. Capture a test log from your app:
Swift
PostHog AI
```swift
PostHogSDK.shared.logger?.info("hello from iOS")
PostHogSDK.shared.flush()
```
2. Open the [PostHog Logs UI](https://app.posthog.com/logs).
3. Filter by `service.name = 'my-app'` (or whatever value you set above).
You should see your record arrive within a few seconds.
[View your Logs in PostHog](https://app.posthog.com/logs)
5. 5
## Tune buffering, rate cap, and resource attributes
Optional
The `logs` config has knobs for high-volume apps:
Swift
PostHog AI
```swift
let config = PostHogConfig(projectToken: "<ph_project_token>")
config.logs.serviceName = "my-app"
config.logs.flushIntervalSeconds = 5 // default 30
config.logs.maxBufferSize = 200 // default 1000
config.logs.maxBatchSize = 50 // default 50
config.logs.flushAt = 20 // default 20
config.logs.rateCapMaxLogs = 5000 // default 500
config.logs.rateCapWindowSeconds = 60 // default 10
config.logs.resourceAttributes = ["host.name": "device-01"]
PostHogSDK.shared.setup(config)
```
Full configuration reference:
| Field | Default | What it does |
| --- | --- | --- |
| serviceName | bundle identifier | OTLP service.name resource attribute |
| serviceVersion | CFBundleShortVersionString | OTLP service.version resource attribute |
| environment | nil | OTLP deployment.environment resource attribute |
| resourceAttributes | [:] | Extra OTLP resource attributes (SDK keys win on collision) |
| flushIntervalSeconds | 30 | Periodic flush interval |
| flushAt | 20 | Buffer threshold that triggers an automatic flush |
| maxBatchSize | 50 | Max records per outbound POST (halved on 413) |
| maxBufferSize | 1000 | Max records held on disk before FIFO eviction |
| rateCapMaxLogs | 500 | Max records per rateCapWindowSeconds window. Set to 0 to disable. |
| rateCapWindowSeconds | 10 | Rate-cap tumbling window length |
All of the above are captured at `setup(_:)`; mutating them later has no effect. Defaults are tuned for cellular-aware mobile apps. Raise `rateCapMaxLogs` and `maxBufferSize` for high-volume scenarios.
6. 6
## Filter or redact with beforeSend
Optional
`beforeSend` runs synchronously before the rate cap, so dropped records don't consume the per-window budget. Use it for redaction, sampling, or filtering by level. Each block receives a mutable `PostHogLogRecord` and returns either the (possibly mutated) record or `nil` to drop it.
Swift
PostHog AI
```swift
config.logs.setBeforeSend({ record in
// Drop debug logs in production
if record.level == .debug { return nil }
// Redact secrets in the body
record.body = record.body.replacingOccurrences(
of: #"api_key=\S+"#,
with: "api_key=[REDACTED]",
options: .regularExpression
)
return record
})
```
Pass an array (or a comma-separated list) of blocks to compose a chain – evaluated left-to-right. Returning `nil` from any block short-circuits and drops the record. Setting `record.body` to an empty string also drops the record.
From Objective-C, wrap each closure in a `BoxedBeforeSendLogBlock`:
objc
PostHog AI
```objc
[posthogConfig.logs setBeforeSend:@[
[[BoxedBeforeSendLogBlock alloc] initWithBlock:^PostHogLogRecord * _Nullable(PostHogLogRecord * record) {
return [record.body containsString:@"secret"] ? nil : record;
}]
]];
```
8. ## Next steps
Checkpoint
*What you can do with your logs*
| Action | Description |
| --- | --- |
| [Why you need logs](/docs/logs/basics.md) | What logs show you that nothing else does |
| [Search logs](/docs/logs/search.md) | Use the search interface to find specific log entries |
| Filter by level | Filter by INFO, WARN, ERROR, etc. |
| [Link session replay](/docs/logs/link-session-replay.md) | Connect logs to users and session replays by passing posthogDistinctId and sessionId |
| [Link logs to a person](/docs/logs/link-person.md) | Surface every log emitted on behalf of a user on their PostHog person profile |
| [Logging best practices](/docs/logs/best-practices.md) | Learn what to log, how to structure logs, and patterns that make logs useful in production |
[Troubleshoot common issues](/docs/logs/troubleshooting.md)
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be betterreferences/java.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Java Logs installation - Docs
Copy page
# Java Logs installation - Docs
1. 1
## Install OpenTelemetry packages
Required
Add the following dependencies to your `pom.xml`:
XML
PostHog AI
```xml
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-api</artifactId>
<version>1.32.0</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk</artifactId>
<version>1.32.0</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
<version>1.32.0</version>
</dependency>
```
2. 2
## Get your project token
Required
You'll need your PostHog project token to authenticate log requests. This is the same key you use for capturing events and exceptions with the PostHog SDK.
> **Important:** Use your **project token** which starts with `phc_`. Do **not** use a personal API key (which starts with `phx_`).
You can find your project token in [Project Settings](https://app.posthog.com/settings/project).
3. 3
## Configure the SDK
Required
Set up the OpenTelemetry SDK to send logs to PostHog.
Java
PostHog AI
```java
import io.opentelemetry.api.logs.GlobalLoggerProvider;
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
import io.opentelemetry.sdk.logs.export.BatchLogRecordProcessor;
import io.opentelemetry.exporter.otlp.logs.OtlpHttpLogRecordExporter;
SdkLoggerProvider loggerProvider = SdkLoggerProvider.builder()
.addLogRecordProcessor(
BatchLogRecordProcessor.builder(
OtlpHttpLogRecordExporter.builder()
.setEndpoint("https://us.i.posthog.com/i/v1/logs")
.addHeader("Authorization", "Bearer <ph_project_token>")
.build()
).build()
)
.build();
GlobalLoggerProvider.set(loggerProvider);
```
Alternatively, you can pass the API key as a query parameter:
Java
PostHog AI
```java
OtlpHttpLogRecordExporter.builder()
.setEndpoint("https://us.i.posthog.com/i/v1/logs?token=<ph_project_token>")
.build()
```
4. 4
## Use OpenTelemetry logging
Required
Now you can start logging with OpenTelemetry:
Java
PostHog AI
```java
import io.opentelemetry.api.logs.Logger;
Logger logger = GlobalLoggerProvider.get().get("my-app");
logger.logRecordBuilder()
.setBody("User action")
.setAttributes(Attributes.of(
AttributeKey.stringKey("userId"), "123",
AttributeKey.stringKey("action"), "login"
))
.emit();
```
5. 5
## Test your setup
Recommended
Once everything is configured, test that logs are flowing into PostHog:
1. Send a test log from your application
2. Check the PostHog Logs interface for your log entries
3. Verify the logs appear in your project
[View your logs in PostHog](https://app.posthog.com/logs)
7. ## Next steps
Checkpoint
*What you can do with your logs*
| Action | Description |
| --- | --- |
| [Why you need logs](/docs/logs/basics.md) | What logs show you that nothing else does |
| [Search logs](/docs/logs/search.md) | Use the search interface to find specific log entries |
| Filter by level | Filter by INFO, WARN, ERROR, etc. |
| [Link session replay](/docs/logs/link-session-replay.md) | Connect logs to users and session replays by passing posthogDistinctId and sessionId |
| [Link logs to a person](/docs/logs/link-person.md) | Surface every log emitted on behalf of a user on their PostHog person profile |
| [Logging best practices](/docs/logs/best-practices.md) | Learn what to log, how to structure logs, and patterns that make logs useful in production |
[Troubleshoot common issues](/docs/logs/troubleshooting.md)
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be betterreferences/link-session-replay.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Link session replay - Docs
Copy page
# Link session replay - Docs
Connecting your backend logs to frontend session replays provides complete visibility into the user journey, helping you understand the full context around issues in your application.
## Why link to session replay?
By including session IDs and user identity in your logs, you can:
- **See the full user journey**: Navigate from a log entry directly to the session replay to see what the user was doing
- **Debug issues faster**: Quickly find and watch the exact session where an error or issue occurred
- **Correlate logs with user actions**: Match backend log events with actual user experience
- **View related errors**: See Error Tracking issues that occurred during the same session directly in the log details
## Prerequisites
- A [logging client installed](/docs/logs/installation.md) on your backend
- The [PostHog JavaScript SDK](/docs/libraries/js.md) on your web frontend, or the [React Native SDK](/docs/libraries/react-native.md) in your mobile app
- [Session replay enabled](/docs/session-replay/installation.md) if you want to link to replays (you can still pass `posthogDistinctId` without session replay to link logs to a user profile)
> **Logs captured client-side:** When you call `posthog.captureLog` / `posthog.logger.*` directly from the JavaScript web SDK or React Native SDK, the current `distinct_id` and `session_id` are attached to every log record automatically. You only need the manual setup below when your backend emits the logs.
## Implementation
To link logs to session replays, you need to pass the session ID and user identity from your frontend to your backend, then include them as log attributes.
### Frontend: Get the session ID
In your frontend code, retrieve the current session ID and send it with your API requests:
PostHog AI
### JavaScript
```javascript
import posthog from 'posthog-js'
// Get the current session ID
const sessionId = posthog.get_session_id()
// Send it with your API request
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: userInput,
sessionId: sessionId // Include session ID
})
})
```
### "React
```jsx
import { posthog } from './posthog'
// Get the current session ID
const sessionId = posthog.get_session_id()
// Send it with your API request
const response = await fetch('https://api.example.com/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: userInput,
sessionId, // Include session ID
}),
})
```
### Backend: Include session ID and user identity in logs
Once you have the session ID, include it along with the user's identity using the `sessionId` and `posthogDistinctId` attributes. These examples assume you've already [set up a logging client](/docs/logs/installation.md) for your language.
PostHog AI
### JavaScript
```javascript
import { logs } from '@opentelemetry/api-logs'
const logger = logs.getLogger('my-app')
app.post('/api/chat', async (req, res) => {
const { message, sessionId } = req.body
const userId = req.userId // ... get your user ID
logger.emit({
severityText: 'info',
body: 'Chat request received',
attributes: {
posthogDistinctId: userId, // Links to PostHog user
sessionId: sessionId, // Links to session replay
endpoint: '/api/chat',
},
})
// ... handle the request
res.json({ success: true })
})
```
### Python
```python
import logging
logger = logging.getLogger(__name__)
@app.route('/api/chat', methods=['POST'])
def chat():
data = request.json
message = data['message']
session_id = data.get('sessionId')
user_id = current_user.id
logger.info(
"Chat request received",
extra={
"posthogDistinctId": user_id, # Links to PostHog user
"sessionId": session_id, # Links to session replay
"endpoint": "/api/chat",
}
)
# ... handle the request
return jsonify({"success": True})
```
> **Note:** If you don't include `posthogDistinctId`, logs won't be linked to a user. If you don't include `sessionId`, logs won't be linked to a session replay. You can use either or both independently.
## Viewing linked replays
Once you've set up session linking, you can navigate from logs to their corresponding session replays:
**From the logs list:**
1. Hover over a log entry in the [logs view](https://app.posthog.com/logs)
2. Click the **View recording** button in the floating action menu to open the session replay at the log timestamp
**From log details:**
1. Click on a log entry to open **log details**
2. Click the **View recording** button to open the session replay
The recording button only appears for log entries that have an associated session ID.
## View related errors
When you click on a log entry that has a session ID, you can view related errors in the **Related errors** tab. This tab shows Error Tracking issues that occurred within the same session (within ±6 hours of the log timestamp).
This helps you debug issues by showing errors that happened around the same time as your log entry, giving you a more complete picture of what went wrong.
If no session ID is found in the log entry, the tab displays a message prompting you to link your logs to sessions.
## See also
- [Link logs to a person](/docs/logs/link-person.md): same `posthogDistinctId` attribute, surfaced on the person profile's Logs tab.
- [Session replay installation](/docs/session-replay/installation.md)
- [Logs installation](/docs/logs/installation.md)
- [Search logs](/docs/logs/search.md)
- [Error Tracking](/docs/error-tracking.md)
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be betterreferences/mcp.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Use Logs over PostHog MCP - Docs
Copy page
# Use Logs over PostHog MCP - Docs
The [PostHog MCP server](/docs/model-context-protocol.md) gives coding agents direct access to your logs. Ask your agent to search, filter, and analyze log data without leaving your editor, then fix the bug in the same session.
This works in any MCP client – Cursor, Codex, Claude Code, Windsurf, VS Code, and others.
## Before you start
1. **Set up Logs** – [send them to PostHog with an OpenTelemetry client](/docs/logs/installation.md) so there's something to read.
2. **Connect the MCP server** – [install it in your AI tool](#set-up) and give it access to your project.
## What you can do here
Through MCP tools, your agent can:
- **Query and count logs** – filter by severity, service, time range, and free text, and pull counts or sparklines to see how a problem is trending.
- **Discover what's available** – list log attributes and their values, and list the services sending logs with their volume and error rate, so it can build a targeted query instead of guessing.
- **Break counts down by facet** – group matching logs by any attribute to see where a spike is concentrated.
- **Mine and diff patterns** – group similar log lines into templates, and compare two time ranges to see which patterns are new or spiking.
- **Manage alerts** – create, read, update, and delete [log alerts](/docs/logs/alerts.md), attach or remove Slack, webhook, and Teams destinations, read an alert's event history, and simulate a threshold against historical logs before committing to it.
Saved views, sampling rules, and metric rules aren't exposed over MCP – manage those in the [web app](/docs/logs/surfaces/web-app.md) or with the [Logs API](/docs/logs/surfaces/api.md).
For the full list of tools and how to scope a session to a subset, see the [MCP tools reference](/docs/model-context-protocol/tools.md).
## Example prompts
| Goal | Ask your agent |
| --- | --- |
| Triage | Show me all error logs from the last hour |
| Narrow by service | What services are logging errors? Search for error logs from the payments service |
| Follow a request | Find logs related to trace ID abc123 |
| Explore the schema | What log attributes are available? Show me the values for service.name |
| Spot what changed | Compare log patterns from today against yesterday and show me what's new |
| Cut noise | Show me warning and error logs from the last 24 hours, excluding debug noise |
Because your agent has both your codebase and your logs in one session, it can connect a log line to the exact code path that emitted it and draft the fix.
A useful order of operations: list attributes and their values to learn the schema, count matching logs to check the result set is a sane size, then query. Log records carry trace and span IDs, so your agent can follow a single request across services.
## Set up
Connect the PostHog MCP server to your AI tool of choice, then grant it access to your project. The [MCP overview](/docs/model-context-protocol.md) covers install and authentication for each client.
To browse logs visually, mine patterns interactively, or watch the session replay behind a log line, use the [web app](/docs/logs/surfaces/web-app.md).
## Related
- Browse and pivot through logs in the [web app](/docs/logs/surfaces/web-app.md).
- Script the same operations with the [Logs API](/docs/logs/surfaces/api.md).
- Learn what your agent is creating in [Set up log alerts](/docs/logs/alerts.md).
- See how templates are built in [Log patterns](/docs/logs/patterns.md).
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be better
references/nextjs.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Next.js Logs installation - Docs
Copy page
# Next.js Logs installation - Docs
1. 1
## Install OpenTelemetry packages
Required
Terminal
PostHog AI
```bash
npm install @opentelemetry/sdk-logs @opentelemetry/exporter-logs-otlp-http @opentelemetry/api-logs @opentelemetry/resources
```
2. 2
## Get your project token
Required
You'll need your PostHog project token to authenticate log requests. This is the same key you use for capturing events and exceptions with the PostHog SDK.
> **Important:** Use your **project token** which starts with `phc_`. Do **not** use a personal API key (which starts with `phx_`).
You can find your project token in [Project Settings](https://app.posthog.com/settings/project).
3. 3
## Enable instrumentation in Next.js
Required
> **Note:** This step is only needed on Next.js 13.2–14.x. For Next.js 15 and later, `instrumentation.ts` is enabled by default and the `experimental.instrumentationHook` option is deprecated — remove it from your config if it's set.
On Next.js 14 and earlier, add the following to your `next.config.js` (or `next.config.mjs`) to enable the instrumentation hook:
JavaScript
PostHog AI
```javascript
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
instrumentationHook: true,
},
}
module.exports = nextConfig
```
4. 4
## Create the instrumentation file
Required
Create an `instrumentation.ts` (or `instrumentation.js`) file in the root of your project (or inside `src/` if you use that folder).
typescript
PostHog AI
```typescript
import { BatchLogRecordProcessor, LoggerProvider } from '@opentelemetry/sdk-logs'
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http'
import { logs } from '@opentelemetry/api-logs'
import { resourceFromAttributes } from '@opentelemetry/resources'
// Create LoggerProvider outside register() so it can be exported and flushed in route handlers
export const loggerProvider = new LoggerProvider({
resource: resourceFromAttributes({ 'service.name': 'my-nextjs-app' }),
processors: [
new BatchLogRecordProcessor(
new OTLPLogExporter({
url: 'https://us.i.posthog.com/i/v1/logs',
headers: {
Authorization: 'Bearer <ph_project_token>',
'Content-Type': 'application/json',
},
})
),
],
})
export function register() {
if (process.env.NEXT_RUNTIME === 'nodejs') {
logs.setGlobalLoggerProvider(loggerProvider)
}
}
```
> **Note:** The `loggerProvider` is created outside of `register()` so it can be exported and used to flush logs in route handlers. This pattern is necessary because Route Handlers complete execution before batched logs have a chance to be sent to the collector. By exporting the provider, we can manually flush logs at the end of each request.
> **Important:** The `Content-Type: application/json` header is required.
Alternatively, you can pass the API key as a query parameter:
typescript
PostHog AI
```typescript
new OTLPLogExporter({
url: 'https://us.i.posthog.com/i/v1/logs?token=<ph_project_token>',
headers: {
'Content-Type': 'application/json',
},
})
```
5. 5
## Use OpenTelemetry logging
Required
Now you can use OpenTelemetry logging in your server-side code (API routes, Server Components, etc.):
typescript
PostHog AI
```typescript
import { SeverityNumber } from '@opentelemetry/api-logs'
import { after } from 'next/server'
import { loggerProvider } from '@/instrumentation'
const logger = loggerProvider.getLogger('my-nextjs-app')
export async function GET() {
logger.emit({
body: 'API request received',
severityNumber: SeverityNumber.INFO,
attributes: {
endpoint: '/api/example',
method: 'GET',
},
})
// Ensure logs are flushed before the serverless function freezes
after(async () => {
await loggerProvider.forceFlush()
})
return Response.json({ success: true })
}
```
> **Important:** Without calling `forceFlush()`, your logs may not be sent. Route Handlers complete execution before the OpenTelemetry batch processor has a chance to send logs to the collector. The `after()` function from `next/server` runs code after the response is sent, ensuring logs are flushed before the serverless function freezes.
> **Note:** `after()` is stable in Next.js 15.1+ (available as `unstable_after` in 15.0). On Next.js 14 and earlier, it doesn't exist — flush before returning instead:
>
> typescript
>
> PostHog AI
>
> ```typescript
> await loggerProvider.forceFlush()
> return Response.json({ success: true })
> ```
6. 6
## Test your setup
Recommended
Once everything is configured, test that logs are flowing into PostHog:
1. Send a test log from your application
2. Check the PostHog Logs interface for your log entries
3. Verify the logs appear in your project
[View your logs in PostHog](https://app.posthog.com/logs)
8. ## Next steps
Checkpoint
*What you can do with your logs*
| Action | Description |
| --- | --- |
| [Why you need logs](/docs/logs/basics.md) | What logs show you that nothing else does |
| [Search logs](/docs/logs/search.md) | Use the search interface to find specific log entries |
| Filter by level | Filter by INFO, WARN, ERROR, etc. |
| [Link session replay](/docs/logs/link-session-replay.md) | Connect logs to users and session replays by passing posthogDistinctId and sessionId |
| [Link logs to a person](/docs/logs/link-person.md) | Surface every log emitted on behalf of a user on their PostHog person profile |
| [Logging best practices](/docs/logs/best-practices.md) | Learn what to log, how to structure logs, and patterns that make logs useful in production |
[Troubleshoot common issues](/docs/logs/troubleshooting.md)
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be betterreferences/nodejs.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Node.js Logs installation - Docs
Copy page
# Node.js Logs installation - Docs
1. 1
## Install OpenTelemetry packages
Required
Terminal
PostHog AI
```bash
npm install @opentelemetry/sdk-node @opentelemetry/exporter-logs-otlp-http @opentelemetry/api-logs @opentelemetry/resources @opentelemetry/sdk-logs
```
2. 2
## Get your project token
Required
You'll need your PostHog project token to authenticate log requests. This is the same key you use for capturing events and exceptions with the PostHog SDK.
> **Important:** Use your **project token** which starts with `phc_`. Do **not** use a personal API key (which starts with `phx_`).
You can find your project token in [Project Settings](https://app.posthog.com/settings).
3. 3
## Configure the SDK
Required
Set up the OpenTelemetry SDK to send logs to PostHog.
JavaScript
PostHog AI
```javascript
import { NodeSDK } from '@opentelemetry/sdk-node';
import { OTLPLogExporter } from '@opentelemetry/exporter-logs-otlp-http';
import { BatchLogRecordProcessor } from '@opentelemetry/sdk-logs';
import { resourceFromAttributes } from '@opentelemetry/resources';
const sdk = new NodeSDK({
resource: resourceFromAttributes({
'service.name': 'my-node-service',
}),
logRecordProcessor: new BatchLogRecordProcessor(
new OTLPLogExporter({
url: 'https://us.i.posthog.com/i/v1/logs',
headers: {
'Authorization': 'Bearer <ph_project_token>'
}
})
)
});
sdk.start();
```
Alternatively, you can pass the API key as a query parameter:
JavaScript
PostHog AI
```javascript
const sdk = new NodeSDK({
logRecordProcessor: new BatchLogRecordProcessor(
new OTLPLogExporter({
url: 'https://us.i.posthog.com/i/v1/logs?token=<ph_project_token>'
})
)
});
```
4. 4
## Use OpenTelemetry logging
Required
Now you can start logging with OpenTelemetry:
JavaScript
PostHog AI
```javascript
import { logs } from '@opentelemetry/api-logs';
const logger = logs.getLogger('my-app');
// Log with different levels and attributes
logger.emit({ severityText: 'trace', body: 'log data', attributes: {'my_attribute': 'stringValue'} });
logger.emit({ severityText: 'warn', body: 'log data', attributes: {'warning_count': 3} });
logger.emit({ severityText: 'error', body: 'log data', attributes: {'json_attribute': [1,2,3]} });
```
5. 5
## Test your setup
Recommended
Once everything is configured, test that logs are flowing into PostHog:
1. Send a test log from your application
2. Check the PostHog Logs interface for your log entries
3. Verify the logs appear in your project
[View your logs in PostHog](https://app.posthog.com/logs)
7. ## Next steps
Checkpoint
*What you can do with your logs*
| Action | Description |
| --- | --- |
| [Why you need logs](/docs/logs/basics.md) | What logs show you that nothing else does |
| [Search logs](/docs/logs/search.md) | Use the search interface to find specific log entries |
| Filter by level | Filter by INFO, WARN, ERROR, etc. |
| [Link session replay](/docs/logs/link-session-replay.md) | Connect logs to users and session replays by passing posthogDistinctId and sessionId |
| [Link logs to a person](/docs/logs/link-person.md) | Surface every log emitted on behalf of a user on their PostHog person profile |
| [Logging best practices](/docs/logs/best-practices.md) | Learn what to log, how to structure logs, and patterns that make logs useful in production |
[Troubleshoot common issues](/docs/logs/troubleshooting.md)
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be betterreferences/other.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Other languages Logs installation - Docs
Copy page
# Other languages Logs installation - Docs
PostHog Logs works with any OpenTelemetry-compatible client. Check the [OpenTelemetry documentation](https://opentelemetry.io/docs/) for your specific language or framework.
1. 1
## Install OpenTelemetry packages
Required
The key requirements are:
- Use OTLP (OpenTelemetry Protocol) for log export over HTTP
- Send logs to your Logs endpoint (see configuration step below)
- Include your project token in the Authorization header or as a `?token=` query parameter
Find the OpenTelemetry SDK for your language in the [official registry](https://opentelemetry.io/ecosystem/registry/).
2. 2
## Get your project token
Required
You'll need your PostHog project token to authenticate log requests. This is the same key you use for capturing events and exceptions with the PostHog SDK.
> **Important:** Use your **project token** which starts with `phc_`. Do **not** use a personal API key (which starts with `phx_`).
You can find your project token in [Project Settings](https://app.posthog.com/settings).
3. 3
## Configure the SDK
Required
Configure your OpenTelemetry SDK to send logs to PostHog.
**Endpoint:**
PostHog AI
```
https://us.i.posthog.com/i/v1/logs
```
**Authentication:** Include your project token either as an `Authorization` header:
PostHog AI
```
Authorization: Bearer <ph_project_token>
```
Or as a query parameter on the endpoint:
PostHog AI
```
https://us.i.posthog.com/i/v1/logs?token=<ph_project_token>
```
4. 4
## Test your setup
Recommended
Once everything is configured, test that logs are flowing into PostHog:
1. Send a test log from your application
2. Check the PostHog Logs interface for your log entries
3. Verify the logs appear in your project
[View your logs in PostHog](https://app.posthog.com/logs)
6. ## Next steps
Checkpoint
*What you can do with your logs*
| Action | Description |
| --- | --- |
| [Why you need logs](/docs/logs/basics.md) | What logs show you that nothing else does |
| [Search logs](/docs/logs/search.md) | Use the search interface to find specific log entries |
| Filter by level | Filter by INFO, WARN, ERROR, etc. |
| [Link session replay](/docs/logs/link-session-replay.md) | Connect logs to users and session replays by passing posthogDistinctId and sessionId |
| [Link logs to a person](/docs/logs/link-person.md) | Surface every log emitted on behalf of a user on their PostHog person profile |
| [Logging best practices](/docs/logs/best-practices.md) | Learn what to log, how to structure logs, and patterns that make logs useful in production |
[Troubleshoot common issues](/docs/logs/troubleshooting.md)
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be betterreferences/python.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Python Logs installation - Docs
Copy page
# Python Logs installation - Docs
1. 1
## Install OpenTelemetry packages
Required
Terminal
PostHog AI
```bash
pip install opentelemetry-api opentelemetry-sdk opentelemetry-exporter-otlp
```
2. 2
## Get your project token
Required
You'll need your PostHog project token to authenticate log requests. This is the same key you use for capturing events and exceptions with the PostHog SDK.
> **Important:** Use your **project token** which starts with `phc_`. Do **not** use a personal API key (which starts with `phx_`).
You can find your project token in [Project Settings](https://app.posthog.com/settings).
3. 3
## Configure the SDK
Required
Set up the OpenTelemetry SDK to send logs to PostHog.
> **Note:** The logs API is still experimental in `opentelemetry-python`, so it's only exposed under the private `_logs` import path (e.g. `opentelemetry._logs`). Use these imports rather than `opentelemetry.logs`, which doesn't exist yet.
Python
PostHog AI
```python
import logging
from opentelemetry._logs import set_logger_provider
from opentelemetry.sdk._logs import LoggerProvider, LoggingHandler
from opentelemetry.sdk._logs.export import BatchLogRecordProcessor
from opentelemetry.exporter.otlp.proto.http._log_exporter import OTLPLogExporter
# Configure the logger provider
logger_provider = LoggerProvider()
set_logger_provider(logger_provider)
# Create OTLP exporter with API key in header
otlp_exporter = OTLPLogExporter(
endpoint="https://us.i.posthog.com/i/v1/logs",
headers={"Authorization": "Bearer <ph_project_token>"}
)
# Add processor
logger_provider.add_log_record_processor(
BatchLogRecordProcessor(otlp_exporter)
)
# Attach the OpenTelemetry handler to the root logger
logging.getLogger().addHandler(LoggingHandler(logger_provider=logger_provider))
```
Alternatively, you can pass the API key as a query parameter:
Python
PostHog AI
```python
otlp_exporter = OTLPLogExporter(
endpoint="https://us.i.posthog.com/i/v1/logs?token=<ph_project_token>"
)
```
4. 4
## Use OpenTelemetry logging
Required
With the handler attached in the previous step, you can start logging with standard Python logging and the records flow to PostHog:
Python
PostHog AI
```python
import logging
logging.basicConfig(level=logging.INFO)
# Use standard Python logging
logger = logging.getLogger("my-app")
logger.info("User action", extra={"userId": "123", "action": "login"})
logger.warning("Deprecated API used", extra={"endpoint": "/old-api"})
logger.error("Database connection failed", extra={"error": "Connection timeout"})
```
5. 5
## Test your setup
Recommended
Once everything is configured, test that logs are flowing into PostHog:
1. Send a test log from your application
2. Check the PostHog Logs interface for your log entries
3. Verify the logs appear in your project
[View your logs in PostHog](https://app.posthog.com/logs)
7. ## Next steps
Checkpoint
*What you can do with your logs*
| Action | Description |
| --- | --- |
| [Why you need logs](/docs/logs/basics.md) | What logs show you that nothing else does |
| [Search logs](/docs/logs/search.md) | Use the search interface to find specific log entries |
| Filter by level | Filter by INFO, WARN, ERROR, etc. |
| [Link session replay](/docs/logs/link-session-replay.md) | Connect logs to users and session replays by passing posthogDistinctId and sessionId |
| [Link logs to a person](/docs/logs/link-person.md) | Surface every log emitted on behalf of a user on their PostHog person profile |
| [Logging best practices](/docs/logs/best-practices.md) | Learn what to log, how to structure logs, and patterns that make logs useful in production |
[Troubleshoot common issues](/docs/logs/troubleshooting.md)
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be betterreferences/react-native.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# React Native Logs installation - Docs
Copy page
# React Native Logs installation - Docs
PostHog's React Native SDK has built-in support for capturing structured logs. Unlike other languages where you wire OpenTelemetry directly, the SDK handles the OTLP encoding, batching, persistence, and lifecycle for you. You just call `posthog.captureLog(...)` or `posthog.logger.{trace,debug,info,warn,error,fatal}(...)`.
> **JavaScript layer only.** Logs are captured from the JavaScript side of your app. Native logs from your iOS or Android code (e.g. `os_log`, `Log.d`) are not collected.
> **Minimum version:** `posthog-react-native@4.44.0` or later. Run `npx expo install posthog-react-native` (Expo) or your package manager's equivalent to update.
1. 1
## Install posthog-react-native
Required
If you haven't already, install and initialize `posthog-react-native` using the steps below. For full details, see the [React Native SDK guide](/docs/libraries/react-native.md).
Our React Native enables you to integrate PostHog with your React Native project. For React Native projects built with Expo, there are no mobile native dependencies outside of supported Expo packages.
To install, add the `posthog-react-native` package to your project as well as the required peer dependencies.
#### Expo apps
Terminal
PostHog AI
```bash
npx expo install posthog-react-native expo-file-system expo-application expo-device expo-localization
```
#### React Native apps
Terminal
PostHog AI
```bash
yarn add posthog-react-native @react-native-async-storage/async-storage react-native-device-info react-native-localize
# or
npm i -s posthog-react-native @react-native-async-storage/async-storage react-native-device-info react-native-localize
```
#### React Native Web and macOS
If you're using [React Native Web](https://github.com/necolas/react-native-web) or [React Native macOS](https://github.com/microsoft/react-native-macos), do not use the [expo-file-system](https://github.com/expo/expo/tree/master/packages/expo-file-system) package since the Web and macOS targets aren't supported, use the [@react-native-async-storage/async-storage](https://github.com/react-native-async-storage/async-storage) package instead.
### Configuration
#### With the PosthogProvider
The recommended way to set up PostHog for React Native is to use the `PostHogProvider`. This utilizes the Context API to pass the PostHog client around, and enables [autocapture](/docs/product-analytics/autocapture.md).
To set up `PostHogProvider`, add it to your `App.js` or `App.ts` file:
App.js
PostHog AI
```jsx
// App.(js|ts)
import { usePostHog, PostHogProvider } from 'posthog-react-native'
...
export function MyApp() {
return (
<PostHogProvider apiKey="<ph_project_token>" options={{
// usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'
host: 'https://us.i.posthog.com',
}}>
<MyComponent />
</PostHogProvider>
)
}
```
Then you can access PostHog using the `usePostHog()` hook:
React Native
PostHog AI
```jsx
const MyComponent = () => {
const posthog = usePostHog()
useEffect(() => {
posthog.capture("event_name")
}, [posthog])
}
```
#### Without the PosthogProvider
If you prefer not to use the provider, you can initialize PostHog in its own file and import the instance from there:
posthog.ts
PostHog AI
```jsx
import PostHog from 'posthog-react-native'
export const posthog = new PostHog('<ph_project_token>', {
// usually 'https://us.i.posthog.com' or 'https://eu.i.posthog.com'
host: 'https://us.i.posthog.com'
})
```
Then you can access PostHog by importing your instance:
React Native
PostHog AI
```jsx
import { posthog } from './posthog'
export function MyApp1() {
useEffect(() => {
posthog.capture('event_name')
}, [])
return <View>Your app code</View>
}
```
You can even use this instance with the PostHogProvider:
React Native
PostHog AI
```jsx
import { posthog } from './posthog'
export function MyApp() {
return <PostHogProvider client={posthog}>{/* Your app code */}</PostHogProvider>
}
```
2. 2
## Configure logs in your PostHog options
Required
Add a `logs` block to your PostHog initialization. All fields are optional; defaults are tuned for mobile (cellular bandwidth, battery, OS lifecycle).
React Native
PostHog AI
```jsx
import PostHog from 'posthog-react-native'
const posthog = new PostHog('<ph_project_token>', {
host: 'https://us.i.posthog.com',
logs: {
serviceName: 'my-app', // OTLP service.name – shown in the Logs UI
environment: 'production', // OTLP deployment.environment
serviceVersion: '1.2.3', // OTLP service.version
},
})
```
3. 3
## Capture logs
Required
Use `posthog.logger` for the per-level convenience API, or `posthog.captureLog` for full control over level, attributes, and trace context.
React Native
PostHog AI
```jsx
// Per-level convenience methods
posthog.logger.info('checkout completed', { order_id: 'ord_789', amount_cents: 4999 })
posthog.logger.warn('payment retry', { attempt: 2 })
posthog.logger.error('payment failed', { code: 'E001' })
// Lower-level API for custom severity / trace context
posthog.captureLog({
body: 'checkout failed',
level: 'error',
attributes: { order_id: 'ord_789', step: 'auth' },
trace_id: '4bf92f3577b34da6a3ce929d0e0e4736', // optional W3C trace context
span_id: '00f067aa0ba902b7',
})
```
Records are buffered, batched, persisted to disk, and flushed automatically – every 10 seconds, on AppState change (foreground ↔ background), on buffer fill, or on `posthog.shutdown()`. For an immediate drain, call `await posthog.flushLogs()`.
Each record is automatically tagged with the user's distinct ID, session ID, current screen, app foreground/background state, and active feature flags at the moment of capture.
4. 4
## Test your setup
Recommended
1. Capture a test log from your app:
React Native
PostHog AI
```jsx
posthog.logger.info('hello from RN')
await posthog.flushLogs()
```
2. Open the [PostHog Logs UI](https://app.posthog.com/logs).
3. Filter by `service.name = 'my-app'` (or whatever value you set above).
You should see your record arrive within a few seconds.
[View your logs in PostHog](https://app.posthog.com/logs)
5. 5
## Tune buffering, rate cap, and filtering
Optional
The `logs` config has knobs for high-volume apps:
React Native
PostHog AI
```jsx
const posthog = new PostHog('<ph_project_token>', {
logs: {
serviceName: 'my-app',
flushIntervalMs: 5000, // default 10000ms
maxBufferSize: 200, // default 100
rateCap: { maxLogs: 5000, windowMs: 60000 }, // default 500/10s
beforeSend: (record) =>
record.body.includes('secret') ? null : record, // redact or drop
},
})
```
Full configuration reference:
| Field | Default | What it does |
| --- | --- | --- |
| serviceName | 'unknown_service' | OTLP service.name resource attribute |
| serviceVersion | undefined | OTLP service.version resource attribute |
| environment | undefined | OTLP deployment.environment resource attribute |
| resourceAttributes | {} | Extra OTLP resource attributes |
| flushIntervalMs | 10000 | Periodic flush interval in ms |
| maxBufferSize | 100 | Max records held in memory before eviction |
| maxBatchRecordsPerPost | 50 | Max records per outbound POST (halved on 413) |
| rateCap.maxLogs | 500 | Max records per windowMs window |
| rateCap.windowMs | 10000 | Rate-cap window length in ms |
| beforeSend | undefined | Pre-send filter (return null to drop) |
Defaults are tuned for cellular-aware mobile apps (~50 logs/sec ceiling, ~16KB max queue file). Raise `rateCap.maxLogs` and `maxBufferSize` for high-volume scenarios.
6. 6
## Filtering with beforeSend
Optional
The `beforeSend` hook runs synchronously before the rate cap, so dropped records don't consume the per-interval budget. Use it for redaction, sampling, or filtering by level:
React Native
PostHog AI
```jsx
const posthog = new PostHog('<ph_project_token>', {
host: 'https://us.i.posthog.com',
logs: {
serviceName: 'my-app',
beforeSend: (record) => {
// Drop debug logs in production
if (record.level === 'debug') return null
// Redact secrets in the body
return {
...record,
body: record.body.replace(/api_key=\S+/g, 'api_key=[REDACTED]'),
}
},
},
})
```
You can also pass an array of functions to form a chain (evaluated left-to-right). A `null` return from any link short-circuits and drops the record. A throwing filter never crashes your app: the error is logged and the record is dropped (fail-closed).
8. ## Next steps
Checkpoint
*What you can do with your logs*
| Action | Description |
| --- | --- |
| [Why you need logs](/docs/logs/basics.md) | What logs show you that nothing else does |
| [Search logs](/docs/logs/search.md) | Use the search interface to find specific log entries |
| Filter by level | Filter by INFO, WARN, ERROR, etc. |
| [Link session replay](/docs/logs/link-session-replay.md) | Connect logs to users and session replays by passing posthogDistinctId and sessionId |
| [Link logs to a person](/docs/logs/link-person.md) | Surface every log emitted on behalf of a user on their PostHog person profile |
| [Logging best practices](/docs/logs/best-practices.md) | Learn what to log, how to structure logs, and patterns that make logs useful in production |
[Troubleshoot common issues](/docs/logs/troubleshooting.md)
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be betterreferences/search.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Search logs - Docs
Copy page
# Search logs - Docs
There are two ways to filter logs on the [logs page](https://app.posthog.com/logs): the **facet rail** on the left sidebar and the **filter bar** at the top.
## Facet rail
The facet rail is a sidebar on the left side of the logs viewer. It shows curated filters you can click to narrow results without typing in the filter bar.
Two facets always appear:
- **Level** – filter by log severity (`trace`, `debug`, `info`, `warn`, `error`, `fatal`)
- **Service** – filter by `service.name`
Additional facets appear automatically when your logs contain common OpenTelemetry resource attributes:
| Facet | Resource attribute | Group |
| --- | --- | --- |
| Environment | deployment.environment.name | Standard |
| Namespace | k8s.namespace.name | Kubernetes |
| Deployment | k8s.deployment.name | Kubernetes |
| Pod | k8s.pod.name | Kubernetes |
| Node | k8s.node.name | Kubernetes |
| Host | host.name | Infrastructure |
These facets are presence-gated — they only show up when your logs actually contain the corresponding resource attribute. For example, the Kubernetes facets won't appear if your services don't emit Kubernetes metadata.
## Filter bar
The filter bar at the top of the logs page lets you build precise filters. Pick a field, choose an operator, and enter a value. Add as many filters as you need — they're combined with AND.
There are four kinds of fields you can filter on in the filter bar:
- **Logs** – top-level log properties: `severity_level`, `trace_id`, and `span_id`
- **Message** – full-text search over the log body
- **Resource attributes** – describe where the log came from, like `service.name`, `host.name`, or `k8s.container.name`
- **Attributes** – custom key-value context attached to individual log events, like `user_id`, `endpoint`, or `status_code`
## Filter on resource attributes and attributes
Resource attributes identify the source of a log (the service, host, or container that emitted it). Attributes describe a specific log event. Both come from your OpenTelemetry instrumentation — the richer your [structured logging](/docs/logs/best-practices.md), the more you can filter on.
To filter:
1. Click the filter bar and pick a field. Resource attributes and attributes are grouped separately in the picker.
2. Choose an operator (equals, contains, is set, greater than, etc.).
3. Enter a value.
For example, filter `service.name` equals `checkout-api` to scope to one service, then add `status_code` equals `500` to narrow to failed requests.
## Filter by severity, trace ID, and span ID
The **Logs** group in the filter picker exposes three top-level fields. All three only support equals and not-equals operators.
- **severity\_level** – filter by log severity using a dropdown. Available values: `trace`, `debug`, `info`, `warn`, `error`, `fatal`.
- **trace\_id** – filter logs by their OpenTelemetry trace correlation ID. Accepts hex or base64 format trace IDs
- **span\_id** – filter logs by their OpenTelemetry span ID. Accepts hex or base64, same as `trace_id`.
For example, copy a `trace_id` from a trace URL and paste it into the filter to see every log emitted during that trace.
## Full-text search on Message
To search log bodies, pick the **Message** field from the filter bar. Message supports three operators, each with a negated variant for exclusion:
| Operator | Behavior |
| --- | --- |
| equals / doesn't equal | Exact match. Case-sensitive. |
| contains / doesn't contain | Substring match. Case-insensitive. The default. |
| matches regex / doesn't match regex | RE2 regex. Case-insensitive. |
### Examples
- **Contains** `failed to connect` – matches any log containing that substring, regardless of case.
- **Equals** `Health check OK` – matches only logs whose body is exactly that string.
- **Matches regex** `timeout|refused|reset` – matches logs mentioning any of those words (useful when you'd otherwise add multiple contains filters).
- **Doesn't contain** `healthcheck` – exclude noisy healthcheck lines while keeping everything else.
## Filter from the facet rail
The facet rail is a sidebar on the left of the logs page that displays available log fields grouped by category. Each facet group shows the available values for that field along with their counts, helping you explore your log data at a glance.
Clicking a facet value cycles through three states:
1. **Unchecked** → **Included** – shows only logs matching that value.
2. **Included** → **Excluded** – hides logs matching that value.
3. **Excluded** → **Unchecked** – clears the filter.
The **Level** facet and resource-attribute facets (like Environment and Namespace) support this tri-state cycle. You can include one severity level while excluding another in the same query — for example, include `error` to focus on errors while excluding `debug` to remove noise.
### Search facets
When the facet rail contains many fields, use the search input at the top to find the facet you need. Type a field name or group name and the rail filters to show only matching facets. Empty groups are hidden automatically.
The facet search:
- Matches field titles and group names (case-insensitive)
- Persists in the URL so you can bookmark or share your filtered view
- Clears when you remove the search text
## Pivot from patterns to matching logs
The Patterns view mines your logs into recurring message templates. When you expand a pattern, click **View matching logs** to pivot to the Logs view filtered to lines matching that pattern.
The pivot applies a message filter based on the pattern's template:
- **Regex filter** – when the pattern has a validated regex, it lands as a `matches regex` message filter. The regex is compiled from the template's structure and validated against the pattern's own examples before it ships.
- **Contains filter** – when the regex can't be validated (e.g. examples diverge from the template), the pivot falls back to a `contains` filter using the pattern's longest literal text.
The applied filter appears in the filter bar as a visible, removable chip — it behaves like any filter you'd add manually. Your existing date range, service, and severity selections are preserved so the pivot stays inside your investigation context.
When the pattern's sample is unambiguous (a single service or severity), the pivot also scopes by those values to narrow the scan.
## Tips
- **Stack filters to narrow down.** Every filter you add is ANDed together — combine a `service.name` filter with a Message contains to scope full-text search to one service.
- **Start with contains, then tighten.** Contains is case-insensitive and forgiving. Switch to equals only when you need an exact match, or regex when you want OR-style matching in a single filter.
- **Use regex for alternatives.** Instead of adding three contains filters, use one regex like `(timeout|refused|reset)`.
- **Structured logs make filtering more powerful.** Key-value context like `user_id`, `endpoint`, and `status_code` becomes an attribute you can filter on directly. See our [logging best practices](/docs/logs/best-practices.md) for patterns that make logs easier to query.
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be better
references/start-here.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Getting started with Logs - Docs
Copy page
# Getting started with Logs - Docs
## Use your logging client
PostHog Logs works with any OpenTelemetry client. No PostHog-specific packages required. Use the OTel SDKs you already have, point them at PostHog's HTTP endpoint, and drop in your project token.
On the frontend, our [JavaScript web SDK](/docs/logs/installation/javascript.md), [React Native SDK](/docs/logs/installation/react-native.md), [iOS SDK](/docs/logs/installation/ios.md), and [Android SDK](/docs/logs/installation/android.md) include first-class logging support.
Follow the guides below to set up your logging client:
- [Node.js](/docs/logs/installation/nodejs.md)
- [Python](/docs/logs/installation/python.md)
- [Go](/docs/logs/installation/go.md)
- [Java](/docs/logs/installation/java.md)
- [.NET](/docs/logs/installation/dotnet.md)
- [Rust](/docs/logs/installation/rust.md)
- [Next.js](/docs/logs/installation/nextjs.md)
- [JavaScript web](/docs/logs/installation/javascript.md)
- [React Native](/docs/logs/installation/react-native.md)
- [iOS](/docs/logs/installation/ios.md)
- [Android](/docs/logs/installation/android.md)
- [Flutter](/docs/logs/installation/flutter.md)
- [Ruby on Rails](/docs/logs/installation/ruby-on-rails.md)
- [Datadog](/docs/logs/installation/datadog.md)
- [Other languages](/docs/logs/installation/other.md)
[Configure logging client](/docs/logs/installation.md)
## Send context-rich logs
PostHog ingests logs in the same pattern as OTel's structured logging model: resource attributes, log attributes, and trace context.
Enrich your logs with granular detail and business context for `INFO`, `DEBUG`, `WARN`, and `ERROR` log levels.
Python
PostHog AI
```python
import logging
# Configure logging to use OpenTelemetry
logging.basicConfig(level=logging.INFO)
logging.getLogger().addHandler(LoggingHandler())
# Use standard Python logging
logger = logging.getLogger("my-app")
logger.info("User action", extra={"userId": "123", "action": "login"})
logger.warning("Deprecated API used", extra={"endpoint": "/old-api"})
logger.error("Database connection failed", extra={"error": "Connection timeout"})
```
[Learn best practices](/docs/logs/best-practices.md)
## Search and analyze your logs
Once your logs are flowing into PostHog, you can:
- **Search through logs** using full-text searches, multiple search tokens, and negative filters
- **Filter by time ranges** to find specific events
- **Filter on attributes** for specific resources or events
- **Correlate logs with events** from your PostHog analytics

[Learn how to search logs](/docs/logs/search.md)
## Analyze log patterns
The Patterns view automatically mines your logs to find recurring message templates. Use it to spot noisy log lines consuming your log budget, find new error shapes by their template structure, and see which patterns dominate your log volume.
Expand any pattern and click **View matching logs** to pivot to the Logs view filtered to lines matching that template. The filter lands in the filter bar as a visible, removable chip, and your date range, service, and severity selections carry over.
[Explore log patterns](/docs/logs/patterns.md)
## Set up alerts
Get notified when your logs match specific conditions. Create alerts to:
- **Monitor error spikes** — Alert when error log counts exceed a threshold
- **Track specific services** — Watch for issues in critical services
- **Filter by attributes** — Set up granular alerts based on log attributes
Configure alerting rules in your project settings to stay on top of issues as they happen.
[Configure log alerts](/docs/logs/alerts.md)
## Use MCP and AI to debug
Connect the PostHog MCP server and your AI agent can query logs directly. Use Cursor, Claude Code, or any MCP-compatible tool.
Your coding agent pulls the relevant logs it needs to debug and build faster without switching workflows.
Try asking your agent for these:
- `Show me error logs from the API service in the last hour`
- `What services are logging errors right now?`
- `Compare log patterns from today against yesterday and show me what's new`
In the web app, you can also open a single log record and have [PostHog AI explain it](/docs/logs/explain-logs-ai.md) – what it means, what probably caused it, and what to do next.
[Explore logs with AI](/docs/logs/surfaces/mcp.md)
## Integrate your product data
With PostHog, your logs live alongside your [Product Analytics](/docs/product-analytics.md), [Session Replays](/docs/session-replay.md), [Error Tracking](/docs/error-tracking.md), and [Dashboards](/docs/product-analytics/dashboards.md), so you can go from a log line to a user's session to the flag variant they were on without switching tools.
### Session Replay
Log events in PostHog can be connected to the session and user who triggered them. Jump from a log line to a session replay in one click.

### Product Analytics
Turn log patterns into trends, funnels, and retention insights. Know which logged errors actually hurt user retention vs. which are just noise.

### Error Tracking
Logs with `$exception` events become issues you can assign, resolve, and alert on. No separate error tracking tool needed.

### Dashboards
Add a Recent logs [widget](/docs/product-analytics/dashboards.md#adding-widgets) to any dashboard to monitor log entries alongside your other metrics and insights. Filter by severity level and service, and click a row to jump to that log on the Logs page.
## Use for free
PostHog's Logs is built to be cost-effective by default, with a generous free tier and transparent usage-based pricing. Since we don't charge per seat, more than 90% of companies use PostHog for free.
## TL;DR 💸
- No credit card required to start
- First 10 GB of ingested logs per month are free
- Above 10 GB we have usage-based pricing at $0.25/GB with discounts
- All logs are retained 14 days by default, and we also offer 30-day (and soon 90-day) retention options for an additional storage charge – see [pricing](/pricing.md) for more details
- Set billing limits to avoid surprise charges
- See our [pricing page](/docs/logs/pricing.md) for more up-to-date details
---
That's it! You're ready to start integrating.
[Install logs](/docs/logs/installation.md)
1/8
[**Use your logging client** ***Required***](#quest-item-use-your-logging-client)[**Send context-rich logs** ***Required***](#quest-item-send-context-rich-logs)[**Search and analyze your logs** ***Required***](#quest-item-search-and-analyze-your-logs)[**Analyze log patterns** ***Recommended***](#quest-item-analyze-log-patterns)[**Set up alerts** ***Recommended***](#quest-item-set-up-alerts)[**Use MCP and AI to debug** ***Recommended***](#quest-item-use-mcp-and-ai-to-debug)[**Integrate your product data** ***Recommended***](#quest-item-integrate-your-product-data)[**Use for free** ***Free 10 GB/mo***](#quest-item-use-for-free)
**Use your logging client**
***Required***
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be betterreferences/troubleshooting.md
> AI agents: this is one page from PostHog's docs. Full index of Markdown docs for LLMs: https://posthog.com/llms.txt
# Logs troubleshooting - Docs
Copy page
# Logs troubleshooting - Docs
This page covers troubleshooting for Logs. For setup, see the [installation guide](/docs/logs/installation.md).
## Authentication errors
**Problem**: Getting 401 Unauthorized errors when sending logs.
**Solutions**:
- Verify you're using the correct project token from [Project Settings](https://app.posthog.com/settings/project)
- Check the Authorization header format: `Bearer <ph_project_token>`
- If using query parameter, verify the format: `?token=<ph_project_token>`
- Ensure your project token hasn't been rotated or revoked
## Connection issues
**Problem**: Cannot connect to the PostHog Logs endpoint.
**Solutions**:
- Verify the endpoint URL: `https://us.i.posthog.com/i/v1/logs`
- Check that your application can make outbound HTTPS requests
- Ensure firewall rules allow outbound connections to PostHog
- For self-hosted instances, verify the endpoint is correct for your deployment
## Logs not appearing in PostHog
**Problem**: Logs are being sent but don't appear in the PostHog interface.
**Solutions**:
- Verify your project token is correct and associated with the right project
- Check that logs are being sent in the correct OTLP format
- Ensure your project has access to the Logs feature in PostHog
- Check the network tab in your browser/application to verify requests are succeeding (200 status)
## Early startup logs are missing
**Problem**: Console output from the first moments of a page load – React hydration warnings, errors thrown during bootstrap – never reaches Logs, while console calls made later arrive fine.
**Solutions**:
- Upgrade to posthog-js **1.422.0** or later, which buffers `console.*` calls made while the logs script is still loading and backfills them once it is ready.
- Enable capture with `logs: { captureConsoleLogs: true }` in `posthog.init()` rather than only with the **Capture console logs** project setting. With the project setting alone, a visitor's first page load does not start capturing until remote config responds; the `init()` option starts at `posthog.init()` on every load.
- Check that the calls are among the levels captured: `console.log`, `console.info`, `console.debug`, `console.warn`, and `console.error`.
- If more than `logs.maxBufferSize` console calls (100 by default) happen before the script loads, only the earliest are kept.
## Performance issues
**Problem**: High memory usage or slow log processing.
**Solutions**:
- Adjust the batch size in your OpenTelemetry configuration
- Use BatchLogRecordProcessor instead of SimpleLogRecordProcessor for better performance
- Consider filtering logs on the client side to reduce volume
- Check for network latency between your application and PostHog
## Log format problems
**Problem**: Logs are being received but not parsed correctly.
**Solutions**:
- Ensure you're using the standard [OTLP log format](https://opentelemetry.io/docs/specs/otel/logs/data-model/)
- Verify log levels are set correctly (INFO, WARN, ERROR, etc.)
- Check that log attributes are properly structured
- Use the OpenTelemetry logging APIs instead of raw log libraries
## Logs not appearing on a person's profile
**Problem**: Logs are searchable in the **Logs** view, but the person profile's **Logs** tab is empty (or missing logs you expected to see).
**Solutions**:
- Confirm each log record carries the attribute `posthogDistinctId` (camelCase, lowercase `p`) — see [Link logs to a person](/docs/logs/link-person.md). `distinct_id`, `posthog_distinct_id`, and `user_id` are **not** equivalent unless you've explicitly configured one as the [custom attribute key](/docs/logs/link-person.md#customizing-the-attribute-key).
- The value of the attribute must equal one of the person's `distinct_id`s exactly — partial or prefixed matches are not picked up.
- If your team has customized the attribute key (via the `logs_config` endpoint), the person profile's Logs tab shows a hint above the chart indicating which key is being used. Make sure your pipeline emits logs under that exact key.
- Date range: the person Logs tab respects the same date range picker as the main Logs view. Expand the range if the logs are older than the default window.
## Project token authentication issues
**Problem**: Confused about which key to use or how to authenticate.
**Solutions**:
- Use your **project token** (the same one you use for capturing events)
- Find it in [Project Settings](https://app.posthog.com/settings/project)
- You can authenticate in two ways:
- **Header**: `Authorization: Bearer <ph_project_token>`
- **Query param**: `?token=<ph_project_token>`
- Do not use your personal API key or other authentication methods
## Self-hosted endpoint issues
**Problem**: Logs not working with self-hosted PostHog.
**Solutions**:
- Use your self-hosted instance URL instead of `https://us.i.posthog.com`
- Verify the logs endpoint is enabled on your self-hosted instance
- Check that the endpoint path is correct: `/logs`
- Ensure your PostHog version supports the logs feature
## Still having issues?
If you're still experiencing problems:
1. Verify your OpenTelemetry client configuration matches the examples in the [installation guide](/docs/logs/installation.md)
2. Test with a simple log message first before sending complex logs
3. Check the network requests to see the actual HTTP status codes and error messages
4. Contact PostHog support with your specific error messages and configuration details
### Still have questions?
Ask PostHog AI
### Was this page useful?
HelpfulCould be betterSKILL.md
---
name: instrument-logs
description: >-
Add PostHog log capture to track application logs. Use after implementing
features or reviewing PRs to ensure meaningful log events are captured with
structured properties. Also handles initial OTLP exporter setup if not yet
configured.
metadata:
author: PostHog
---
# Add PostHog log capture
Use this skill to add PostHog log capture for new or changed code. Use it after implementing features or reviewing PRs to ensure meaningful log events are captured with structured properties. If PostHog log export is not yet configured, this skill also covers initial OTLP exporter setup. Supports any platform or language.
Supported platforms: Next.js, Node.js, Python, Go, Java, Datadog, Android, React Native, iOS, and any language via OpenTelemetry.
## Instructions
Follow these steps IN ORDER:
STEP 1: Analyze the codebase and detect the platform.
- Detect the language, framework, and existing logging setup.
- Look for dependency files and project files (package.json, Podfile, Package.swift, requirements.txt, go.mod, pom.xml, etc.).
- Look for log libraries (winston, pino, logging module, logrus, log4j, serilog, os_log, Logger, etc.).
- Look for lockfiles (pnpm-lock.yaml, package-lock.json, yarn.lock, bun.lockb, go.sum, Podfile.lock, Package.resolved, etc.) to determine the package manager.
- Check for existing PostHog log export setup. If the OTLP exporter is already configured, skip to STEP 5 to add log capture for new code.
STEP 2: Research log capture. (Skip if PostHog log export is already configured.)
2.1. Find the reference file below that matches the detected platform — it is the source of truth for OTLP exporter configuration and integration with existing logging. Read it now.
2.2. If no reference matches, use the "Other Languages" reference as a fallback — it covers the generic OpenTelemetry approach.
STEP 3: Install dependencies. (Skip if PostHog log export is already configured.)
- Install the OpenTelemetry SDK and OTLP exporter packages for the detected platform.
- Do not manually edit dependency files — use the package manager's install command.
STEP 4: Configure the OTLP exporter. (Skip if PostHog log export is already configured.)
- PostHog logs use the OpenTelemetry protocol. Set up an OTLP exporter pointed at PostHog's ingest endpoint.
- For SDK-native log support such as Android, React Native, and iOS, follow the platform reference instead of adding a separate OTLP exporter.
- Follow the platform-specific reference for the exact configuration.
STEP 5: Integrate with existing logging.
- Add the PostHog log exporter alongside existing logging. Don't replace existing log handlers or outputs.
- Do not alter the fundamental architecture of existing files. Make additions minimal and targeted.
- You must read a file immediately before attempting to write it.
STEP 6: Add structured properties.
- Ensure logs include structured key-value properties for filtering and search in PostHog.
- Prefer structured log formats with key-value properties over plain text messages.
STEP 7: Set up environment variables.
- Check if the project already has PostHog environment variables configured (e.g. in `.env`, `.env.local`, or framework-specific env files). If valid values already exist, skip this step.
- If the PostHog project token is missing, use the PostHog MCP server's `projects-get` tool to retrieve the project's `api_token`. If multiple projects are returned, ask the user which project to use. If the MCP server is not connected or not authenticated, ask the user for their PostHog project token instead.
- For the PostHog host URL: check the `projects-get` MCP response for a `region` field — `US` maps to `https://us.i.posthog.com`, `EU` maps to `https://eu.i.posthog.com`. If the region is not available from the MCP response or from existing project configuration, ask the user: "Are you on PostHog US Cloud or EU Cloud?" Do not assume US Cloud.
- For the OpenTelemetry endpoint, use `https://us.i.posthog.com/v1` (US) or `https://eu.i.posthog.com/v1` (EU), matching the region determined above.
- Write these values to the appropriate env file using the framework's naming convention.
- Reference these environment variables in code instead of hardcoding them.
## Reference files
- `references/nextjs.md` - Next.js logs installation - docs
- `references/nodejs.md` - Node.js logs installation - docs
- `references/python.md` - Python logs installation - docs
- `references/go.md` - Go logs installation - docs
- `references/java.md` - Java logs installation - docs
- `references/datadog.md` - Datadog logs installation - docs
- `references/android.md` - Android logs installation - docs
- `references/react-native.md` - React native logs installation - docs
- `references/ios.md` - Ios logs installation - docs
- `references/flutter.md` - Flutter logs installation - docs
- `references/other.md` - Other languages logs installation - docs
- `references/start-here.md` - Getting started with logs - docs
- `references/search.md` - Search logs - docs
- `references/best-practices.md` - Logging best practices - docs
- `references/troubleshooting.md` - Logs troubleshooting - docs
- `references/link-session-replay.md` - Link session replay - docs
- `references/mcp.md` - Use logs over PostHog mcp - docs
- `references/COMMANDMENTS.md` - Framework-specific rules the integration must follow
Each platform reference contains specific OTLP configuration, SDK setup, and integration patterns. Find the one matching the user's stack.
## Key principles
- **Environment variables**: Always use environment variables for PostHog keys and OpenTelemetry endpoints. Never hardcode them.
- **Minimal changes**: Add log export alongside existing logging. Don't replace or restructure existing logging code.
- **OpenTelemetry**: PostHog logs use the OpenTelemetry protocol. Configure an OTLP exporter pointed at PostHog's ingest endpoint unless the platform SDK provides native log capture.
- **SDK-native logs**: For Android, React Native, and iOS, use the SDK logger/capture APIs from the platform reference instead of adding a separate OTLP exporter.
- **Structured logging**: Prefer structured log formats with key-value properties over plain text messages.