Splunk
Overview
GreptimeDB implements a subset of the Splunk HTTP Event Collector (HEC) protocol, so shippers that already speak HEC — such as Vector and the OpenTelemetry Collector — can write to GreptimeDB by changing the endpoint URL and the token.
Two ingestion endpoints are available:
/services/collector/eventtakes structured JSON events./services/collector/rawtakes plain text and stores it verbatim.
Splunk's index maps to a GreptimeDB table. host, source, sourcetype and the keys under fields become tag columns, which GreptimeDB adds to the table's primary key.
Indexer acknowledgment (/services/collector/ack) is not implemented. The channel parameter is accepted and ignored.
Fluent Bit's native splunk output cannot be used here. It hardcodes the request path to /services/collector/event and exposes no path setting, so it cannot reach GreptimeDB's /v1/splunk mount without a reverse proxy. To send data from Fluent Bit, use its HTTP output instead.
HTTP API
The base path is /v1/splunk; clients append the collector path themselves. Point your shipper at http://${db_host}:${db_http_port}/v1/splunk, for example http://localhost:4000/v1/splunk.
| Endpoint | Method | Purpose |
|---|---|---|
/v1/splunk/services/collector/event | POST | Ingest JSON events. Aliases: /services/collector, /services/collector/event/1.0 |
/v1/splunk/services/collector/raw | POST | Ingest plain text. Alias: /services/collector/raw/1.0 |
/v1/splunk/services/collector/health | GET | Health probe. Alias: /services/collector/health/1.0 |
Request bodies may be gzip-compressed (Content-Encoding: gzip).
Authentication
Authentication only applies when the server runs with a user provider. Without one, requests are accepted unauthenticated.
HEC clients send Authorization: Splunk <token>. GreptimeDB reads the token as username:password:
Authorization: Splunk greptime_user:greptime_pwd
Authorization: Basic <base64> is also accepted.
Most Splunk clients default to a single opaque token with no colon in it. GreptimeDB rejects such a token with HEC code 4. Configure the client's token as <username>:<password>.
The health endpoint is public and never requires a token.
Ingest JSON events
POST /v1/splunk/services/collector/event
The body is one or more HEC event objects. They may be concatenated directly, separated by JSON whitespace such as a newline or a space, or wrapped in a top-level JSON array. Any other separator, a comma or a semicolon for example, is a parse error and fails the request with HEC code 6.
Field mapping
| HEC field | Maps to |
|---|---|
time | The greptime_timestamp time index |
index | The table name |
host, source, sourcetype | Tag columns |
keys under fields | Tag columns, one per key |
event and any remaining keys | Data columns |
time is epoch seconds and may be fractional. Values at or above 1e12 are read as milliseconds instead. If time is absent or null, the server's ingest time is used.
A time that is present but unparsable is an error, not a fallback — the request fails with HEC code 6. The same applies to ?time= on the raw endpoint.
Table routing
The table is resolved in this order:
- The event's
index, coerced into a valid table name. Characters outside[A-Za-z0-9_:\-.@#]become_, and an underscore is prepended when the first character is not valid. For examplemy index/2026becomesmy_index_2026. - The
tablequery parameter. - The default table
splunk_logs.
Query parameters
| Parameter | Description |
|---|---|
db | Target database. Defaults to public. |
table | Fallback table name, used when the event carries no index. |
pipeline_name | Pipeline to run. Defaults to the built-in greptime_identity. |
version | Pipeline version. Defaults to the latest version. |
Only these parameters are honored on this endpoint. Other log-ingestion parameters such as msg_field, custom_time_index, and ignore_errors have no effect here.
Example
curl -X POST "http://localhost:4000/v1/splunk/services/collector/event" \
-H "Authorization: Splunk greptime_user:greptime_pwd" \
-H "Content-Type: application/json" \
-d '{"time":1785312000,"host":"web-01","source":"/var/log/nginx/access.log","sourcetype":"nginx:access","index":"splunk_events","fields":{"env":"prod","region":"us-west"},"event":{"status":200,"path":"/api/v1/health","latency_ms":12}}
{"time":1785312001,"host":"web-02","source":"/var/log/nginx/access.log","sourcetype":"nginx:access","index":"splunk_events","fields":{"env":"prod","region":"eu-central"},"event":{"status":503,"path":"/api/v1/orders","latency_ms":841}}'
A successful write returns:
{"code":0,"text":"Success"}
The table splunk_events is created on the first write:
DESC TABLE splunk_events;
+--------------------+---------------------+------+------+---------+---------------+
| Column | Type | Key | Null | Default | Semantic Type |
+--------------------+---------------------+------+------+---------+---------------+
| greptime_timestamp | TimestampNanosecond | PRI | NO | | TIMESTAMP |
| env | String | PRI | YES | | TAG |
| event.latency_ms | Int64 | | YES | | FIELD |
| event.path | String | | YES | | FIELD |
| event.status | Int64 | | YES | | FIELD |
| host | String | PRI | YES | | TAG |
| region | String | PRI | YES | | TAG |
| source | String | PRI | YES | | TAG |
| sourcetype | String | PRI | YES | | TAG |
+--------------------+---------------------+------+------+---------+---------------+
The default pipeline flattens the nested event object, which is why the data columns are named event.status, event.path, and event.latency_ms.
SELECT * FROM splunk_events ORDER BY greptime_timestamp;
+---------------------+------+------------------+----------------+--------------+--------+------------+---------------------------+--------------+
| greptime_timestamp | env | event.latency_ms | event.path | event.status | host | region | source | sourcetype |
+---------------------+------+------------------+----------------+--------------+--------+------------+---------------------------+--------------+
| 2026-07-29 08:00:00 | prod | 12 | /api/v1/health | 200 | web-01 | us-west | /var/log/nginx/access.log | nginx:access |
| 2026-07-29 08:00:01 | prod | 841 | /api/v1/orders | 503 | web-02 | eu-central | /var/log/nginx/access.log | nginx:access |
+---------------------+------+------------------+----------------+--------------+--------+------------+---------------------------+--------------+
New keys appearing under fields on a later write are added as tag columns and joined to the primary key automatically.
Ingest raw text
POST /v1/splunk/services/collector/raw
The body is stored verbatim in a message column. GreptimeDB does not parse its content — use a custom pipeline reading message if you need structure.
By default the entire body is one event, so multi-line payloads such as stack traces stay intact. To split the body into several events, pass linebreaker with the delimiter you want, percent-encoded — ?linebreaker=%0A splits on newlines. Whitespace-only segments are dropped.
Metadata is request-level on this endpoint: it comes from query parameters and applies to every event in the request.
Query parameters
| Parameter | Description |
|---|---|
host, source, sourcetype | Tag columns applied to every event in the request. |
index | Table name, sanitized the same way as the index field on /event. |
time | Epoch timestamp applied to every event. Defaults to ingest time. |
table | Fallback table name when index is absent. |
linebreaker | Percent-encoded literal delimiter used to split the body. Without it, the whole body is one event. |
pipeline_name | Pipeline to run. Defaults to greptime_identity. |
version | Pipeline version. |
db | Target database. Defaults to public. |
channel | Accepted and ignored. Also accepted as the x-splunk-request-channel header. |
Example
printf 'line one\nline two\nline three\n' | curl -X POST \
"http://localhost:4000/v1/splunk/services/collector/raw?index=splunk_raw&host=web-01&source=app.log&sourcetype=applog&linebreaker=%0A" \
-H "Authorization: Splunk greptime_user:greptime_pwd" \
--data-binary @-
This writes three rows. Dropping linebreaker from the same request writes one row containing all three lines.
SHOW CREATE TABLE splunk_raw;
CREATE TABLE IF NOT EXISTS `splunk_raw` (
`greptime_timestamp` TIMESTAMP(9) NOT NULL,
`host` STRING NULL,
`message` STRING NULL,
`source` STRING NULL,
`sourcetype` STRING NULL,
TIME INDEX (`greptime_timestamp`),
PRIMARY KEY (`host`, `source`, `sourcetype`)
)
ENGINE=mito
WITH(
'comment' = 'Created on insertion',
append_mode = 'true'
)
Using a pipeline
Both endpoints run every event through a pipeline. The default is the built-in greptime_identity, which maps each input key to a column of the same name.
Override it with the pipeline_name query parameter or the x-greptime-pipeline-name header. When both are present, the query parameter wins. Use version to pin a specific pipeline version.
Promoting host, source, sourcetype, and the fields keys to tag columns only happens on the default greptime_identity path. A custom pipeline owns its output schema, so declare the columns you want indexed in the pipeline itself.
Response codes
The response body is {"text": ..., "code": ...}, matching HEC. Clients branch on code.
| Code | Meaning |
|---|---|
0 | Success |
2 | Token is required |
4 | Invalid token |
5 | No data |
6 | Invalid data format, including an unparsable time or version |
7 | Incorrect index, meaning the resolved table name is invalid |
8 | Internal server error |
12 | Event field is required |
13 | Event field cannot be blank |
17 | Returned by the health endpoint to report a healthy collector |
Validation rejects the whole request. If any event in a batch has a missing or blank event, or a time that is present but unparsable, none of the events in that batch are written — the endpoint does not skip bad events and keep the rest. Optional metadata such as host or source may be absent without any effect.
Vector
Vector's splunk_hec_logs sink writes to GreptimeDB with no other changes:
[sinks.greptime]
type = "splunk_hec_logs"
inputs = ["my_source"]
endpoint = "http://localhost:4000/v1/splunk"
default_token = "greptime_user:greptime_pwd"
index = "vector_logs"
compression = "gzip"
encoding.codec = "json"
endpoint: the/v1/splunkbase path, without a collector path. Vector appends that itself.default_token: must be<username>:<password>, not an opaque token.index: the target table.encoding.codec: required by this sink.endpoint_target:eventby default, which uses/services/collector/event. Set it torawfor the raw endpoint.
To use the raw endpoint instead:
[sinks.greptime]
type = "splunk_hec_logs"
inputs = ["my_source"]
endpoint = "http://localhost:4000/v1/splunk"
default_token = "greptime_user:greptime_pwd"
index = "vector_raw_logs"
endpoint_target = "raw"
encoding.codec = "text"
batch.max_events = 1
Vector concatenates batched events with no separator, so on the raw endpoint a whole batch arrives as one event. Set batch.max_events = 1 as above, or stay on the default event target.
linebreaker would split such a batch server-side, but this particular sink cannot send it: it has no option for arbitrary query parameters, and appending one to endpoint does not work because Vector joins endpoint and the collector path as plain strings — http://localhost:4000/v1/splunk?linebreaker=%0A becomes /v1/splunk?linebreaker=%0A/services/collector/raw?index=..., which no longer matches the route. HEC clients that can set query parameters use linebreaker normally.
OpenTelemetry Collector
The splunk_hec exporter is configured the same way:
exporters:
splunk_hec:
endpoint: "http://localhost:4000/v1/splunk/services/collector"
token: "greptime_user:greptime_pwd"
index: "otel_logs"
tls:
insecure: true
service:
pipelines:
logs:
receivers: [otlp]
exporters: [splunk_hec]
Unlike Vector, this exporter expects the full collector path in endpoint.