Backstage Integration for Elastic
| Version | 0.1.0
|
| Subscription level What's this? |
Basic |
| Developed by What's this? |
Elastic |
| Ingestion method(s) | File |
| Minimum Kibana version(s) | 9.1.0 8.19.0 |
To use pre-release integrations, go to the Integrations page in Kibana, scroll down, and toggle on the Display beta integrations option.
The Backstage integration for Elastic collects audit log events emitted by Backstage's built-in auditor service, giving you visibility into user actions, plugin activity, and catalog operations across your Backstage instance. Events are parsed and normalized into the Elastic Common Schema (ECS) so you can search, alert on, and visualize user and plugin activity across your developer platform.
This integration is compatible with Backstage backends built on @backstage/backend-plugin-api version 1.2.0 or later, the release that introduced the built-in auditor core service (implemented by default via @backstage/backend-defaults). No additional Backstage plugin is required — every Backstage backend created with createBackend() emits audit events through this service automatically.
Backstage's auditor service formats each audit event as a single JSON log line (message format <plugin>.<eventId>) through the root Winston logger, along with fields such as the acting user, HTTP request details, the originating plugin, event status, and severity level. This integration's filestream input reads those JSON lines from a log file — for example, captured container or pod output, or a file the Backstage backend writes to directly — and the ingest pipeline parses and maps them to ECS.
The Backstage integration collects audit log events of the following types, depending on which plugins emit auditor events in your Backstage instance:
- Catalog operations: entity fetch and catalog query events, including the requested fields and applied filters.
- User and plugin activity: the acting user or service, the plugin and event name, HTTP method and URL, and whether the operation succeeded, failed, or is still in progress.
- Severity-classified events: each event carries a Backstage-assigned severity level (
low,medium, orhigh), mapped toevent.severity.
This integration primarily targets Backstage audit logs — emitted by the auditor service as NDJSON (one JSON object per line) — which are fully normalized to ECS and backstage.* fields.
Backstage application logs — valid JSON lines where isAuditEvent is either absent or is not set to true — are also retained. The original line is preserved in event.original. Common fields such as messages, service names, log levels, timestamps, tracing IDs, URLs, user agents, HTTP methods, and HTTP response values are mapped to ECS. Other parsed fields remain under backstage.log.* as keywords. Audit-specific fields are not mapped for these records.
Deeper mapping of these logs will be driven by customer requests and observed usage.
Integrating Backstage audit logs with the Elastic Stack lets you:
- Monitor user activity: track who accessed or modified catalog entities, and from where.
- Investigate plugin behavior: correlate events by
event.provider(the Backstage plugin) andevent.code(the audit event ID) to debug or audit plugin-specific actions. - Support compliance and security monitoring: maintain a searchable, ECS-normalized record of administrative and catalog actions performed through Backstage.
To use this integration, you'll need the following vendor prerequisites:
- A Backstage backend built with
createBackend()from@backstage/backend-defaults(version1.2.0or later), so theauditorservice is available and emitting events automatically. - The audit log output written to a location the Elastic Agent can read as a file — for example, captured container/pod logs mounted into the Elastic Agent's filesystem, or a file the Backstage backend logs to directly.
You'll also need the following Elastic prerequisites:
- Elastic Stack (Elasticsearch and Kibana) version
8.19.0or later. - An active Elastic Agent installed and enrolled in Fleet, with filesystem access to the Backstage audit log file(s).
Elastic Agent must be installed on a host or container that has access to the Backstage audit log file(s). For more details, check the Elastic Agent installation instructions. You can install only one Elastic Agent per host.
Elastic Agent is required to collect the log file(s) and ship the data to Elastic, where the events are then processed by this integration's ingest pipeline.
- In Kibana, navigate to Management → Integrations.
- Search for Backstage and select the integration.
- Click Add Backstage.
- Configure Paths to point to the location of the Backstage audit log file(s) — for example, a mounted volume or captured container log path. The default is
/var/log/*.log. - (Optional) Configure Exclude files to skip rotated or compressed log files. The default already excludes
.gzfiles. - (Optional) Configure Parsers if your Backstage audit log lines are pretty-printed across multiple lines rather than one JSON object per line. See the field's description for
ndjsonandmultilineexamples. - Assign the integration to an Elastic Agent policy and click Save and continue.
After the configuration is complete, follow these steps to verify data is flowing correctly from Backstage to the Elastic Stack:
- Trigger an audit event in Backstage — for example, browse the Software Catalog to trigger a
catalog.entity-fetchevent. - In Kibana, navigate to Analytics → Discover.
- Select the
logs-*data view. - In the search bar, enter the filter:
data_stream.dataset: "backstage.logs". - Verify that events appear with a non-null
event.actionfield, and thatevent.providerreflects the Backstage plugin that generated the event (for example,catalog).
For help with Elastic ingest tools, check Common problems.
- No events collected: Confirm the Backstage backend is emitting audit events (not every plugin calls the
auditorservice for every operation) and that the configured Paths match the actual log file location. - Multi-line JSON not parsed: If your Backstage deployment pretty-prints JSON log lines across multiple lines, enable the
multilineparser under Parsers. Otherwise each partial line fails JSON parsing. - Application logs have limited mapping: For this release and the short term future, application logs have limited mappings. This is by design to limit the scope of our initial release which focuses on audit logs (see Log scope). In the meantime, you can create some custom processors to mitigate this, the original log line is kept in
event.original, and other parsed fields land underbackstage.log.*as keywords. Should this not be sufficient, please contact us so we can review your requests and improve the mappings of backstage application logs. - Permission denied reading the log file: Elastic Agent needs read access to the Backstage audit log file(s). If your environment writes them with restrictive ownership/permissions, deploy Elastic Agent as
root(or a user with equivalent read access) on that host or container. This is a deployment-time decision, not a setting configurable through this integration.
For more information on architectures that can be used for scaling this integration, check the Ingest Architectures documentation.
The following inputs are used by this integration:
These inputs can be used with this integration:
filestream
For more details about the Filestream input settings, check the Filebeat documentation.
To collect logs via Filestream, select Collect logs via Filestream and configure the following parameters:
- Filestream paths: The full path to the related log file.
The logs data stream provides application logs, with a focus on audit events from Backstage's auditor service, including catalog operations, user activity, and other plugin-specific audit events. General Backstage application logs where isAuditEvent is not true are also retained with common fields mapped to ECS, the original line in event.original, and other parsed fields under backstage.log.* as keywords; see Log scope.
Exported fields
| Field | Description | Type |
|---|---|---|
| @timestamp | Date/time when the event originated. This is the date/time extracted from the event, typically representing when the event was generated by the source. If the event source has no original timestamp, this value is typically populated by the first time the event was received by the pipeline. Required field for all events. | date |
| backstage.action_type | The Backstage catalog action type for the event (e.g. "get", "ancestry", "create", "execution", "stream"). | keyword |
| backstage.ancestry.entityRef | The entity reference for one node in a Backstage catalog ancestry chain. | keyword |
| backstage.ancestry.parentEntityRefs | The parent entity references for one node in a Backstage catalog ancestry chain. | keyword |
| backstage.entity.ref | The Backstage catalog entity reference the audit event operated on. | keyword |
| backstage.entity.refs | The Backstage catalog entity references returned by a batch entity lookup. | keyword |
| backstage.entity.root_ref | The root entity reference for a Backstage catalog ancestry lookup. | keyword |
| backstage.is_audit_event | Indicates that the record was emitted by Backstage's auditor service. | boolean |
| backstage.log | Backstage application fields without an explicit ECS or backstage mapping. Common fields such as message, service, level, timestamps, tracing, HTTP requests, URLs, and user agents are promoted to their ECS fields. | object |
| backstage.meta | Serialized dump of any Backstage plugin metadata that did not match a known, explicitly-mapped field. Intended to be empty for all currently-known event shapes; exists only as a forward-compatible safety net for future Backstage plugin metadata. Well-known keys are promoted to explicit backstage.\* fields instead of living here. |
text |
| backstage.query.fields | The entity fields requested by the catalog query. | keyword |
| backstage.query.filter | The raw catalog query filter condition(s), as Backstage's key=value comma-separated filter syntax (e.g. "kind=Resource,spec.type=github-repository"). Stored verbatim rather than parsed into structured sub-fields; there is no confirmed customer need yet for querying/aggregating on individual filter keys. Revisit with structured backstage.query.filter.\* sub-fields if that need materializes. |
text |
| backstage.query.type | The type of catalog query that was issued (e.g. "all"). | keyword |
| backstage.severity_level | The severity level of the audit log registered by Backstage. | keyword |
| backstage.task.created_by | The Backstage identity that created the scaffolder task. | keyword |
| backstage.task.id | The identifier of the Backstage scaffolder task. | keyword |
| backstage.task.parameters | The scaffolder task's input parameters, serialized as text. Shape varies per Backstage software template and is not mapped to structured fields. | text |
| backstage.template.ref | The Backstage scaffolder template reference associated with the task. | keyword |
| backstage.total_items | The total number of items available for a paginated Backstage catalog query. | long |
| cloud.image.id | Image ID for the cloud instance. | keyword |
| data_stream.dataset | The field can contain anything that makes sense to signify the source of the data. Examples include nginx.access, prometheus, endpoint etc. For data streams that otherwise fit, but that do not have dataset set we use the value "generic" for the dataset value. event.dataset should have the same value as data_stream.dataset. Beyond the Elasticsearch data stream naming criteria noted above, the dataset value has additional restrictions: * Must not contain - * No longer than 100 characters |
constant_keyword |
| data_stream.namespace | A user defined namespace. Namespaces are useful to allow grouping of data. Many users already organize their indices this way, and the data stream naming scheme now provides this best practice as a default. Many users will populate this field with default. If no value is used, it falls back to default. Beyond the Elasticsearch index naming criteria noted above, namespace value has the additional restrictions: * Must not contain - * No longer than 100 characters |
constant_keyword |
| data_stream.type | An overarching type for the data stream. Currently allowed values are "logs" and "metrics". We expect to also add "traces" and "synthetics" in the near future. | constant_keyword |
| ecs.version | ECS version this event conforms to. ecs.version is a required field and must exist in all events. When querying across multiple indices -- which may conform to slightly different ECS versions -- this field lets integrations adjust to the schema version of the events. |
keyword |
| error.stack_trace | The stack trace of this error in plain text. | wildcard |
| error.stack_trace.text | Multi-field of error.stack_trace. |
match_only_text |
| error.type | The type of the error, for example the class name of the exception. | keyword |
| event.action | The action captured by the event. This describes the information in the event. It is more specific than event.category. Examples are group-add, process-started, file-created. The value is normally defined by the implementer. |
keyword |
| event.category | This is one of four ECS Categorization Fields, and indicates the second level in the ECS category hierarchy. event.category represents the "big buckets" of ECS categories. For example, filtering on event.category:process yields all events relating to process activity. This field is closely related to event.type, which is used as a subcategory. This field is an array. This will allow proper categorization of some events that fall in multiple categories. |
keyword |
| event.code | Identification code for this event, if one exists. Some event sources use event codes to identify messages unambiguously, regardless of message language or wording adjustments over time. An example of this is the Windows Event ID. | keyword |
| event.dataset | Name of the dataset. If an event source publishes more than one type of log or events (e.g. access log, error log), the dataset is used to specify which one the event comes from. It's recommended but not required to start the dataset name with the module name, followed by a dot, then the dataset name. | constant_keyword |
| event.kind | This is one of four ECS Categorization Fields, and indicates the highest level in the ECS category hierarchy. event.kind gives high-level information about what type of information the event contains, without being specific to the contents of the event. For example, values of this field distinguish alert events from metric events. The value of this field can be used to inform how these kinds of events should be handled. They may warrant different retention, different access control, it may also help understand whether the data is coming in at a regular interval or not. |
keyword |
| event.module | Name of the module this data is coming from. If your monitoring agent supports the concept of modules or plugins to process events of a given source (e.g. Apache logs), event.module should contain the name of this module. |
constant_keyword |
| event.outcome | This is one of four ECS Categorization Fields, and indicates the lowest level in the ECS category hierarchy. event.outcome simply denotes whether the event represents a success or a failure from the perspective of the entity that produced the event. Note that when a single transaction is described in multiple events, each event may populate different values of event.outcome, according to their perspective. Also note that in the case of a compound event (a single event that contains multiple logical events), this field should be populated with the value that best captures the overall success or failure from the perspective of the event producer. Further note that not all events will have an associated outcome. For example, this field is generally not populated for metric events, events with event.type:info, or any events for which an outcome does not make logical sense. |
keyword |
| event.provider | Source of the event. Event transports such as Syslog or the Windows Event Log typically mention the source of an event. It can be the name of the software that generated the event (e.g. Sysmon, httpd), or of a subsystem of the operating system (kernel, Microsoft-Windows-Security-Auditing). | keyword |
| event.severity | The numeric severity of the event according to your event source. What the different severity values mean can be different between sources and use cases. It's up to the implementer to make sure severities are consistent across events from the same source. The Syslog severity belongs in log.syslog.severity.code. event.severity is meant to represent the severity according to the event source (e.g. firewall, IDS). If the event source does not publish its own severity, you may optionally copy the log.syslog.severity.code to event.severity. |
long |
| event.type | This is one of four ECS Categorization Fields, and indicates the third level in the ECS category hierarchy. event.type represents a categorization "sub-bucket" that, when used along with the event.category field values, enables filtering events down to a level appropriate for single visualization. This field is an array. This will allow proper categorization of some events that fall in multiple event types. |
keyword |
| host.containerized | If the host is a container. | boolean |
| host.os.build | OS build information. | keyword |
| host.os.codename | OS codename, if any. | keyword |
| http.request.method | HTTP request method. The value should retain its casing from the original event. For example, GET, get, and GeT are all considered valid values for this field. |
keyword |
| http.response.body.bytes | Size in bytes of the response body. | long |
| http.response.status_code | HTTP response status code. | long |
| http.version | HTTP version. | keyword |
| input.type | Input type. | keyword |
| log.file.device_id | Device ID of the log file this event came from. | keyword |
| log.file.inode | Inode number of the log file. | keyword |
| log.file.path | Path to the log file. | keyword |
| log.level | Original log level of the log event. If the source of the event provides a log level or textual severity, this is the one that goes in log.level. If your source doesn't specify one, you may put your event transport's severity here (e.g. Syslog severity). Some examples are warn, err, i, informational. |
keyword |
| log.offset | Log offset. | long |
| related.hosts | All hostnames or other host identifiers seen on your event. Example identifiers include FQDNs, domain names, workstation names, or aliases. | keyword |
| related.ip | All of the IPs seen on your event. | ip |
| related.user | All the user names or other user identifiers seen on the event. | keyword |
| service.name | Name of the service data is collected from. The name of the service is normally user given. This allows for distributed services that run on multiple hosts to correlate the related instances based on the name. In the case of Elasticsearch the service.name could contain the cluster name. For Beats the service.name is by default a copy of the service.type field if no name is specified. |
keyword |
| source.as.number | Unique number allocated to the autonomous system. The autonomous system number (ASN) uniquely identifies each network on the Internet. | long |
| source.as.organization.name | Organization name. | keyword |
| source.as.organization.name.text | Multi-field of source.as.organization.name. |
match_only_text |
| source.geo.city_name | City name. | keyword |
| source.geo.continent_name | Name of the continent. | keyword |
| source.geo.country_iso_code | Country ISO code. | keyword |
| source.geo.country_name | Country name. | keyword |
| source.geo.location | Longitude and latitude. | geo_point |
| source.geo.region_iso_code | Region ISO code. | keyword |
| source.geo.region_name | Region name. | keyword |
| source.ip | IP address of the source (IPv4 or IPv6). | ip |
| span.id | Unique identifier of the span within the scope of its trace. A span represents an operation within a transaction, such as a request to another service, or a database query. | keyword |
| trace.id | Unique identifier of the trace. A trace groups multiple events like transactions that belong together. For example, a user request handled by multiple inter-connected services. | keyword |
| url.original | Unmodified original url as seen in the event source. Note that in network monitoring, the observed URL may be a full URL, whereas in access logs, the URL is often just represented as a path. This field is meant to represent the URL as it was observed, complete or not. | wildcard |
| url.original.text | Multi-field of url.original. |
match_only_text |
| url.path | Path of the request, such as "/search". | wildcard |
| url.query | The field contains the entire query string, excluding the leading ? character, such as "q=elasticsearch". If a URL contains no ?, there is no query field. If there is a ? but no query, the query field exists with an empty string. The exists query can be used to differentiate between the two cases. |
keyword |
| user.id | Unique identifier of the user. | keyword |
| user_agent.device.name | Name of the device. | keyword |
| user_agent.name | Name of the user agent. | keyword |
| user_agent.original | Unparsed user_agent string. | keyword |
| user_agent.original.text | Multi-field of user_agent.original. |
match_only_text |
| user_agent.os.full | Operating system name, including the version or code name. | keyword |
| user_agent.os.full.text | Multi-field of user_agent.os.full. |
match_only_text |
| user_agent.os.name | Operating system name, without the version. | keyword |
| user_agent.os.name.text | Multi-field of user_agent.os.name. |
match_only_text |
| user_agent.os.version | Operating system version as a raw string. | keyword |
| user_agent.version | Version of the user agent. | keyword |
Example
{
"@timestamp": "2026-05-22T20:29:50.000Z",
"agent": {
"ephemeral_id": "bde3f0df-296e-44db-bf0e-f5bddfadfc51",
"id": "e5432c8d-4353-4cdb-870e-ea98d7455d6a",
"name": "elastic-agent-87091",
"type": "filebeat",
"version": "9.3.3"
},
"data_stream": {
"dataset": "backstage.logs",
"namespace": "64864",
"type": "logs"
},
"ecs": {
"version": "9.4.0"
},
"elastic_agent": {
"id": "e5432c8d-4353-4cdb-870e-ea98d7455d6a",
"snapshot": false,
"version": "9.3.3"
},
"event": {
"agent_id_status": "verified",
"dataset": "backstage.logs",
"ingested": "2026-07-23T14:25:20Z",
"kind": "event",
"module": "backstage",
"original": "{\"level\":\"info\",\"message\":\"Listening on port 7007\",\"service\":\"backstage\",\"timestamp\":\"2026-05-22T20:29:50.000Z\"}"
},
"input": {
"type": "filestream"
},
"log": {
"file": {
"device_id": "44",
"inode": "47",
"path": "/tmp/service_logs/test-audit-events.log"
},
"level": "info",
"offset": 5910
},
"message": "Listening on port 7007",
"service": {
"name": "backstage"
},
"tags": [
"forwarded",
"backstage-logs",
"preserve_original_event"
]
}
For more information about Backstage's auditor service, refer to:
Changelog
| Version | Details | Minimum Kibana version |
|---|---|---|
| 0.1.0 | Enhancement (View pull request) Initial draft of the package |
9.1.0 8.19.0 |