Logs
Structured logging protocol with severity levels, trace context, and batched envelope delivery.
Logs allow Sentry to ingest structured log data from SDKs. SDKs send log envelopes containing structured log payloads with severity levels, trace context, and arbitrary attributes. Sentry uses this data to provide searchable, correlated log views alongside errors and traces.
There are two wire protocols: the log envelope with the Sentry Log protocol (preferred), and the otel_log envelope with the OpenTelemetry Log protocol. All SDKs MUST send logs via the log envelope and Sentry Log protocol. The otel_log format is documented in Appendix: otel_log Format for completeness.
Related specs:
- Envelopes — transport format
- Attributes — attribute type system
- Tracing without Performance — required for trace context
- Trace Origin — origin attribute format
Logs support parameterized message templates for structured logging. Instead of interpolating values into the message body directly, SDKs send the template and parameters separately. This enables Sentry to group similar log messages and perform efficient searching.
Template: "User %s has logged in!"
Parameter 0: "John"
Body: "User John has logged in!"
The SDK sends the interpolated body, the sentry.message.template, and sentry.message.parameter.X attributes.
Logs use a severity model with six levels (lowest to highest): trace, debug, info, warn, error, fatal. These map exactly to OpenTelemetry's Severity text field.
Each level maps to a numeric severity range following the OpenTelemetry Severity Number specification:
| SeverityNumber range | Range name | Meaning |
|---|---|---|
| 1-4 | Trace | A fine-grained debugging event. Typically disabled in default configurations. |
| 5-8 | Debug | A debugging event. |
| 9-12 | Info | An informational event. Indicates that an event happened. |
| 13-16 | Warn | A warning event. Not an error but is likely more important than an informational event. |
| 17-20 | Error | An error event. Something went wrong. |
| 21-24 | Fatal | A fatal error such as application or system crash. |
SDKs SHOULD set the lowest severity number for a given severity level (e.g., warn uses severity number 13).
Logs are buffered before sending. SDKs collect logs into a buffer and flush them as a batch in a single log envelope item, rather than sending each log individually. This reduces network overhead and aligns with Relay, which is optimized for up to 100 logs per envelope.
Log processing MUST follow this order:
- Capture log via Public APIs (e.g.
Sentry.logger.info) or via SDK integrations. - Check if logging is enabled via
enableLogs/enable_logs— if not, skip remaining steps. - Pass the log to Hub/Scope via a generic method (e.g.,
captureLog). - Pass the log to the Client via a generic method (e.g.,
captureLog). - Process captured log (attach attributes per Default Attributes).
- Run
beforeSendLog/before_send_logto filter or modify the log. - Add log to buffer/batch processor per Buffering.
- At flush time, send array of logs to Sentry via
logenvelope, applying rate limiting per Data Category and Rate Limiting.
Note
An SDK SHOULD implement Tracing without Performance before adding support for logs. This is required to ensure logs are associated with traces and that the correct trace context is sent to Sentry.
SDKs MUST attach the following attributes to every log:
| Attribute | Since | Description |
|---|---|---|
sentry.environment | 1.0.0 | The environment set in the SDK, if defined. |
sentry.release | 1.0.0 | The release set in the SDK, if defined. |
sentry.sdk.name | 1.0.0 | The name of the SDK that sent the log. |
sentry.sdk.version | 1.0.0 | The version of the SDK that sent the log. |
sentry.replay_id | 1.8.0 | The replay ID of the active replay when the log was collected. MUST NOT be set if no replay is active. |
For parameterized logs, SDKs MUST also attach:
| Attribute | Since | Description |
|---|---|---|
sentry.message.template | 1.0.0 | The parameterized template string. |
sentry.message.parameter.X | 1.0.0 | Parameters to the template. X is either the positional index (0, 1, etc.) or the parameter name (item_id, user_id, etc.). |
If there are no sentry.message.parameter.X attributes, SDKs MUST NOT attach sentry.message.template (since 1.7.0). This reduces duplicate content and simplifies PII processing.
SDKs SHOULD minimize default attributes. Logs are charged per byte size. New default attributes SHOULD only be added after significant user feedback and discussion with the SDK and ingest teams.
If a log is generated by an SDK integration, the SDK SHOULD set the sentry.origin attribute per the Trace Origin documentation.
Since 1.9.0, logs follow these rules:
- User calls Sentry's Logging API directly: SDKs MUST NOT send
sentry.origin. This intentionally deviates from the Trace Origin spec to minimize log size. - Captured from a logging library: Use
auto.log.<name>format (e.g.,"auto.log.serilog"). - Auto-emitted from other instrumented systems: Use
auto.<category>.<integration-name>format (e.g.,"auto.db.prisma").
By default, Relay parses the user agent attached to an incoming log envelope to extract browser and os information. SDKs MAY attach these attributes if they do not forward a user agent:
| Attribute | Description |
|---|---|
browser.name | Display name of the browser. Maps to name in Browser Context. |
browser.version | Version string of the browser. Maps to version in Browser Context. |
Backend SDKs (Node.js, Python, PHP, etc.) SHOULD attach:
| Attribute | Description |
|---|---|
server.address | The server address. Equivalent to server_name on errors and transactions. |
Mobile, desktop, and native SDKs (Android, Apple, Electron, etc.) SHOULD attach:
| Attribute | Description |
|---|---|
os.name | OS name. Maps to name in OS Context. |
os.version | OS version. Maps to version in OS Context. |
device.brand | Device brand. Maps to brand in Device Context. |
device.model | Device model. Maps to model in Device Context. |
device.family | Device family. Maps to family in Device Context. |
SDKs MUST buffer logs before sending. A simple initial strategy is flushing when the buffer exceeds 100 items or 5 seconds have passed. The buffer SHOULD forward logs to the transport in the scenarios outlined in the telemetry buffer data forwarding scenarios.
SDKs MUST NOT send more than 100 logs per envelope (since 1.15.0). Relay is optimized for this limit.
SDKs MUST enforce a hard limit of 1000 queued log events to prevent out-of-memory issues (since 1.12.0). Logs added beyond this limit are dropped. SDKs MAY use a lower limit but MUST NOT exceed 1000.
SDKs MUST NOT release logging capabilities to users without a buffering implementation.
Logs use the log_item data category for rate limiting in Relay. Both log and otel_log envelopes are covered by this data category. SDKs MUST implement this data category. Rate limiting applies as usual with no special behavior for logs.
SDKs MUST track client outcomes for this data category to report how often logs are dropped.
SDKs SHOULD provide integrations that capture logs from platform logging libraries (e.g., JavaScript's console, Python's logging, Java's Log4j) when enableLogs/enable_logs is set to true.
SDKs MAY introduce additional options beyond enableLogs/enable_logs to gate integration-specific functionality (e.g., controlling log appenders added via external config).
Logs emitted via the public API are opt-out. The enableLogs/enable_logs option MUST act as a general kill switch for all logs sent to Sentry. When set to false, no logs MUST be sent, regardless of whether they originate from the public API, auto-emitting integrations, or third-party bindings.
However, logs that are automatically emitted by integrations or libraries without an explicit user call follow different rules because they can generate unexpected volume and cost.
Integrations that auto-emit logs - An SDK integration that automatically captures logs (e.g., forwarding console.log calls, capturing framework-level diagnostic output) MUST be opt-in. The integration MUST NOT emit any logs unless the user explicitly enables it. The opt-in mechanism MAY be a dedicated integration-level option or requiring the user to explicitly add the integration.
Third-party log bindings - An SDK feature that binds to an external library's logging API and mirrors those logs as Sentry logs in the background (e.g., syncing from Python's logging module or a framework's built-in logger) MUST also be opt-in. Users MUST explicitly enable the binding before any logs are forwarded to Sentry.
These rules exist because auto-emitted logs can cause a high volume of telemetry that directly impacts a user's quota. Unlike errors, where users have prior intuition about volume, silently emitting logs on their behalf could lead to unexpected costs. Requiring opt-in ensures users remain in control of their log volume.
Logs SHOULD be associated with traces. If a log is recorded during an active span, SDKs SHOULD set the span_id property to the active span's ID.
Logs SHOULD be associated with replays. If a log is recorded during an active replay, SDKs SHOULD set the sentry.replay_id attribute to the active replay's ID.
Some runtimes (notably Cloudflare Workers) freeze timer APIs (Date.now(), performance.now()) during synchronous execution, causing multiple logs to share identical timestamps. SDKs that target runtimes where timestamps may be frozen or lack sub-millisecond precision MUST attach a sentry.timestamp.sequence integer attribute to every log. SDKs that only target runtimes with reliable sub-millisecond timestamps MAY omit it.
When sent, the sequence integer MUST:
- Start at
0when the SDK initializes. - Increment by
1for each log that is captured. - Reset to
0when:- The SDK is re-initialized.
- The current log's integer millisecond differs from the previous log's integer millisecond (i.e.,
floor(timestamp_seconds * 1000)changes).
The sequence provides deterministic ordering within a single SDK instance. It does not guarantee ordering across independent processes or workers, which have separate counters. The reset behavior ensures the sequence only increments for consecutive logs that share the same timestamp.
If debug is set to true in SDK init, calls to the Sentry logger API SHOULD also print to the console with the appropriate log level. This aids debugging of logging setups.
SDKs MUST report count (log_item) and size in bytes (log_byte) of discarded log messages. An approximation of log size is sufficient (e.g., by counting as if serialized).
The log envelope item payload is a JSON object that MUST include an items array of log entries, allowing multiple logs per envelope item. The payload MAY also include top-level version and ingest_settings fields as specified in the next section.
An envelope MUST contain at most one log envelope item — all log entries for a flush are batched into a single item's items array. Logs from different traces MAY be mixed into the same log item, but if they are, the envelope MUST NOT include a DSC (dynamic sampling context) header.
Item Headers:
| Field | Type | Required | Description |
|---|---|---|---|
type | String | REQUIRED | MUST be "log". |
item_count | Integer | REQUIRED | The number of log entries in the items array. MUST match the actual count. |
content_type | String | REQUIRED | MUST be "application/vnd.sentry.items.log+json". |
{
"type": "log",
"item_count": 5,
"content_type": "application/vnd.sentry.items.log+json"
}
{
"version": 2,
"ingest_settings": {
"infer_ip": "auto",
"infer_useragent": "auto"
},
"items": [{..log..}, {..log..}, {..log..}, {..log..}, {..log..}]
}
The log envelope item ingest_settings property is an optional JSON object that contains ingestion settings for Relay, for example, to infer certain data from the incoming request.
| Field | Type | Required | Description |
|---|---|---|---|
infer_ip | String | OPTIONAL | The setting to infer the IP address from the incoming request. One of auto or never (default). |
infer_useragent | String | OPTIONAL | The setting to infer the user agent from the incoming request. One of auto or never (default). |
SDKs like Browser or React Native MAY set these to "auto" to instruct Relay to infer the IP address or user agent from the incoming request and put it onto the log as an attribute.
For ingestion settings to take effect, the SDK MUST also set the version property to 2 (number) or higher. If version is omitted, Relay treats the payload as version 1 and doesn't apply ingest_settings.
Each log in the items array is a JSON object:
{
"timestamp": 1544719860.0,
"trace_id": "5b8efff798038103d269b633813fc60c",
"span_id": "b0e6f15b45c36b12",
"level": "info",
"body": "User John has logged in!",
"severity_number": 9,
"attributes": {
"sentry.message.template": {
"value": "User %s has logged in!",
"type": "string"
},
"sentry.message.parameter.0": {
"value": "John",
"type": "string"
}
}
}
| Field | Type | Required | Since | Description |
|---|---|---|---|---|
timestamp | Number | REQUIRED | 1.0.0 | Timestamp of the log in seconds since the Unix epoch. |
trace_id | String | REQUIRED | 1.0.0 | Trace ID as 16 random bytes encoded as a hex string (32 characters). MUST be grabbed from the current propagation context. |
level | String | REQUIRED | 1.0.0 | Severity level. One of trace, debug, info, warn, error, fatal. |
body | String | REQUIRED | 1.0.0 | The log body/message. |
span_id | String | OPTIONAL | 1.11.0 | Span ID of the active span when the log was collected, as 8 random bytes encoded as a hex string (16 characters). |
severity_number | Integer | OPTIONAL | 1.0.0 | Severity number per Severity Levels. Inferred from level unless explicitly set. |
attributes | Object | OPTIONAL | 1.0.0 | Key-value pairs of arbitrary data. Values MUST declare their type. See Attributes. |
SDKs MUST expose the following configuration options:
| Option | Type | Default | Since | Description |
|---|---|---|---|---|
enableLogs / enable_logs | Boolean | true | 1.0.0 | Controls whether log envelopes are generated and sent. If false, the SDK MUST NOT send logs. |
beforeSendLog / before_send_log | Function | — | 1.0.0 | OPTIONAL callback receiving a log object. Returns a modified log or null to drop it. |
The default for enableLogs/enable_logs was changed from false to true in version 2.0.0. The previous default required users to explicitly opt in before they could use the logging primitives. By defaulting to true, adding a Sentry.logger.* statement is itself the opt-in. Users can start logging without additional configuration. This aligns the behavior with enableMetrics/enable_metrics, which also defaults to true.
While logs functionality is in an experimental state, SDKs SHOULD put these options in an experimental namespace to avoid breaking changes:
Sentry.init({
// stable
enableLogs: true,
// experimental
_experiments: { enableLogs: true },
});
SDKs MUST expose logging methods in a logger module or namespace, with one method per severity level:
Sentry.logger.traceSentry.logger.debugSentry.logger.infoSentry.logger.warnSentry.logger.errorSentry.logger.fatal
These methods accept a string template and parameters for structured logging. They MAY also accept arbitrary attributes.
The Hub/Scope and Client MUST exclusively provide a generic logging method (e.g., captureLog). They MUST NOT replicate the static Sentry.logger.X API methods to avoid increasing our API surface. Users utilizing multiple Hub/Scope or Clients will need to rely on the generic method for logging.
The Sentry.logger.X methods are fire-and-forget (no return value). SDKs MAY run these methods on a separate thread or queue them for later processing, including evaluating the string template and running internal hooks like beforeSendLog.
SDKs MUST ensure logs are sent in the order they are received.
Note
Putting everything on a background thread risks losing logs that occur directly before a crash. Mobile SDKs will need a more robust mechanism. The individual SDK will need to determine the best approach for their platform.
These examples are illustrative, not normative. SDK authors SHOULD ensure the API best fits their platform.
// Browser - tagged template literals
Sentry.logger.info(
Sentry.logger.fmt`Adding item ${itemId} for user ${userId}`,
);
// Server-side - printf-like syntax
Sentry.logger.info("Adding item %s for user %s", [itemId, userId], {
extra: 123,
});
A complete log envelope with six log entries at different severity levels:
{ "sdk": { "name": "sentry.javascript.browser", "version": "9.15.0" } }
{
"type": "log",
"item_count": 6,
"content_type": "application/vnd.sentry.items.log+json"
}
{
"items": [
{
"timestamp": 1746456149.0191,
"level": "trace",
"body": "Modal animation performance: openTime=120ms, closeTime=85ms, fps=60",
"trace_id": "624f66e93a04469f9992c7e9f1485056",
"severity_number": 1,
"attributes": {
"modalId": { "value": "product_details", "type": "string" },
"animationType": { "value": "slide-up", "type": "string" },
"devicePixelRatio": { "value": 2, "type": "integer" },
"browser": { "value": "Chrome", "type": "string" },
"memoryUsage": { "value": "45MB", "type": "string" },
"sentry.sdk.name": {
"value": "sentry.javascript.browser",
"type": "string"
},
"sentry.sdk.version": { "value": "9.15.0", "type": "string" },
"sentry.message.template": {
"value": "Modal animation performance: openTime=%sms, closeTime=%sms, fps=%s",
"type": "string"
},
"sentry.message.parameter.0": { "value": 120, "type": "integer" },
"sentry.message.parameter.1": { "value": 85, "type": "integer" },
"sentry.message.parameter.2": { "value": 60, "type": "integer" },
"sentry.timestamp.sequence": { "value": 0, "type": "integer" }
}
},
{
"timestamp": 1746456149.0191998,
"level": "debug",
"body": "Component ProductCard rendered 3 times",
"trace_id": "624f66e93a04469f9992c7e9f1485056",
"severity_number": 5,
"attributes": {
"props": { "value": "{\"productId\":\"prod_123\"}", "type": "string" },
"renderTime": { "value": 45, "type": "integer" },
"sentry.sdk.name": {
"value": "sentry.javascript.browser",
"type": "string"
},
"sentry.sdk.version": { "value": "9.15.0", "type": "string" },
"sentry.message.template": {
"value": "Component %s rendered %s times",
"type": "string"
},
"sentry.message.parameter.0": {
"value": "ProductCard",
"type": "string"
},
"sentry.message.parameter.1": { "value": 3, "type": "integer" },
"sentry.timestamp.sequence": { "value": 1, "type": "integer" }
}
},
{
"timestamp": 1746456149.0191998,
"level": "info",
"body": "Form checkout_form submitted successfully with 8 fields",
"trace_id": "624f66e93a04469f9992c7e9f1485056",
"severity_number": 9,
"attributes": {
"step": { "value": "payment_details", "type": "string" },
"timeSpent": { "value": 120, "type": "integer" },
"sentry.sdk.name": {
"value": "sentry.javascript.browser",
"type": "string"
},
"sentry.sdk.version": { "value": "9.15.0", "type": "string" },
"sentry.message.template": {
"value": "Form %s submitted successfully with %s fields",
"type": "string"
},
"sentry.message.parameter.0": {
"value": "checkout_form",
"type": "string"
},
"sentry.message.parameter.1": { "value": 8, "type": "integer" },
"sentry.timestamp.sequence": { "value": 2, "type": "integer" }
}
},
{
"timestamp": 1746456149.0192997,
"level": "warn",
"body": "API endpoint /api/products response time 2500ms exceeds threshold of 1000ms",
"trace_id": "624f66e93a04469f9992c7e9f1485056",
"severity_number": 13,
"attributes": {
"userAgent": { "value": "Mozilla/5.0...", "type": "string" },
"connectionType": { "value": "4g", "type": "string" },
"sentry.sdk.name": {
"value": "sentry.javascript.browser",
"type": "string"
},
"sentry.sdk.version": { "value": "9.15.0", "type": "string" },
"sentry.message.template": {
"value": "API endpoint %s response time %sms exceeds threshold of %sms",
"type": "string"
},
"sentry.message.parameter.0": {
"value": "/api/products",
"type": "string"
},
"sentry.message.parameter.1": { "value": 2500, "type": "integer" },
"sentry.message.parameter.2": { "value": 1000, "type": "integer" },
"sentry.timestamp.sequence": { "value": 3, "type": "integer" }
}
},
{
"timestamp": 1746456149.0192997,
"level": "error",
"body": "Failed to load images for products prod_123, prod_456",
"trace_id": "624f66e93a04469f9992c7e9f1485056",
"severity_number": 17,
"attributes": {
"error": { "value": "NetworkError", "type": "string" },
"statusCode": { "value": 404, "type": "integer" },
"retryAttempts": { "value": 2, "type": "integer" },
"browser": { "value": "Chrome", "type": "string" },
"version": { "value": "120.0", "type": "string" },
"sentry.sdk.name": {
"value": "sentry.javascript.browser",
"type": "string"
},
"sentry.sdk.version": { "value": "9.15.0", "type": "string" },
"sentry.message.template": {
"value": "Failed to load images for products %s",
"type": "string"
},
"sentry.message.parameter.0": {
"value": "prod_123, prod_456",
"type": "string"
},
"sentry.timestamp.sequence": { "value": 4, "type": "integer" }
}
},
{
"timestamp": 1746456149.0192997,
"level": "fatal",
"body": "State corruption detected in ShoppingCart component during UPDATE_CART action",
"trace_id": "624f66e93a04469f9992c7e9f1485056",
"severity_number": 21,
"attributes": {
"error": { "value": "ReduxStateError", "type": "string" },
"previousState": { "value": "valid", "type": "string" },
"currentState": { "value": "invalid", "type": "string" },
"sentry.sdk.name": {
"value": "sentry.javascript.browser",
"type": "string"
},
"sentry.sdk.version": { "value": "9.15.0", "type": "string" },
"sentry.message.template": {
"value": "State corruption detected in %s component during %s action",
"type": "string"
},
"sentry.message.parameter.0": {
"value": "ShoppingCart",
"type": "string"
},
"sentry.message.parameter.1": {
"value": "UPDATE_CART",
"type": "string"
},
"sentry.timestamp.sequence": { "value": 5, "type": "integer" }
}
}
]
}
| Version | Date | Summary |
|---|---|---|
2.0.0 | 2026-04-09 | Default enableLogs to true, add auto-emitted logs opt-in requirements for integrations |
1.17.0 | 2026-03-05 | Added `ingest_settings` and `version` to log envelope item |
1.16.0 | 2026-03-04 | Add sentry.timestamp.sequence attribute for deterministic log ordering |
1.15.0 | 2026-02-03 | Clarified 100 logs per envelope hard limit, SDKs MAY use lower buffer limit |
1.14.0 | 2025-12-16 | User attributes now optional, guarded by sendDefaultPii |
1.13.0 | 2025-11-18 | Added client reports requirement (log_item count, log_byte size) |
1.12.0 | 2025-11-10 | Added buffer hard limit of 1000 queued log events |
1.11.0 | 2025-10-30 | Added span_id as top-level payload field, replaced sentry.trace.parent_span_id attribute |
1.10.0 | 2025-10-22 | Hub/Scope MUST offer same logger methods as static API, Client SHOULD offer generic captureLog |
1.9.0 | 2025-10-09 | Consolidated sentry.origin rules — no origin for manual calls, auto.log.* for libraries, auto.*.* for instrumentation |
1.8.0 | 2025-09-10 | Added sentry.replay_id as default attribute and replay association behavior |
1.7.0 | 2025-08-28 | Added message template constraint — MUST NOT send sentry.message.template without parameters |
1.6.0 | 2025-06-10 | User attributes no longer gated behind sendDefaultPii |
1.5.0 | 2025-05-27 | Added platform-specific default attributes for browser, backend, mobile/desktop/native |
1.4.0 | 2025-05-08 | Added Tracing without Performance as prerequisite for logs |
1.3.0 | 2025-05-07 | Added structured log processing pipeline, removed logsSampleRate init option |
1.2.0 | 2025-05-01 | Refined buffering requirements (100 items or 5 seconds flush) |
1.1.0 | 2025-04-24 | Documented log envelope item structure (items array wrapper) |
1.0.0 | 2025-04-22 | Initial spec — log protocol, severity model, logger API, init options, otel_log format |
Our documentation is open source and available on GitHub. Your contributions are welcome, whether fixing a typo (drat!) or suggesting an update ("yeah, this would be better").