Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
bc65cad
feat: add opensearch plugin
Oliver-ke Apr 27, 2026
f8832da
chore: fix test run
Oliver-ke May 9, 2026
96274dc
test(opensearch): assert time-bound injects after source clause
Oliver-ke May 9, 2026
b890fec
test(opensearch): cover stats/fields/top/no-pipe time-bound shapes
Oliver-ke May 9, 2026
888bf57
test(opensearch): cover multi-source, 'search' keyword, ws around =
Oliver-ke May 9, 2026
41d5253
fix(opensearch): inject time-bound after source clause
Oliver-ke May 9, 2026
496c296
test(opensearch): complete manuel e2e checks, add cors fix to test do…
Oliver-ke May 10, 2026
3b8e8cf
fix(opensearch): trim OpenSearchPPLError.message to error.reason
Oliver-ke May 10, 2026
c0ea43f
refactor(opensearch): drop unreachable queryError prop from log editor
Oliver-ke May 10, 2026
dc912a9
fix(opensearch): reject empty index/timestampField/messageField in CUE
Oliver-ke May 10, 2026
ece7082
feat(opensearch): validate empty Query in log query Go SDK
Oliver-ke May 10, 2026
46d55e0
docs(opensearch): explain parseTimestamp's seconds-vs-ms threshold
Oliver-ke May 10, 2026
49dd80f
Merge branch 'main' of github.com:Oliver-ke/plugins into feature/open…
Oliver-ke May 10, 2026
09f5607
Merge branch 'main' into feature/opensearch-plugin
Oliver-ke May 23, 2026
0d25e92
Merge branch 'perses:main' into feature/opensearch-plugin
Oliver-ke May 30, 2026
2221e12
[FEATURE] register opensearch plugin in workspace and align deps
Oliver-ke May 30, 2026
f4f9700
Merge branch 'feature/opensearch-plugin' of github.com:Oliver-ke/plug…
Oliver-ke May 30, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions opensearch/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Plugin Module: opensearch

Datasource plugin for [OpenSearch](https://opensearch.org/) in Perses. Supports log queries using
[PPL (Piped Processing Language)](https://opensearch.org/docs/latest/search-plugins/sql/ppl/index/).

This plugin is intended to display logs alongside the traces surfaced by the Tempo plugin, so a user can
pivot from a trace to the related logs in OpenSearch.

### How to install

This plugin requires react and react-dom 18.

```bash
npm install react@18 react-dom@18
npm install @perses-dev/opensearch-plugin
```

## Development

```bash
npm install
npm run dev # start the dev server
npm run build # build the plugin
npm test # run unit tests
```

## Trace to logs

This plugin is intended to display logs alongside traces produced by the Tempo or
Jaeger plugins. Use a dashboard variable (e.g. `$traceId`) to share the selected
trace between the trace panel and the OpenSearch logs panel.

PPL example:

```
source=otel-logs-* | where traceId='$traceId'
```

A complete example dashboard is in `docs/examples/trace-to-logs.json`. See
`docs/examples/README.md` for field-name conventions and how to swap Tempo for Jaeger.

## Field overrides

OpenSearch is schema-flexible; field names vary by exporter. The plugin tries
common names by default (`@timestamp`/`timestamp`/`time` for the timestamp;
`message`/`log`/`body` for the message). If your index uses different names,
set `timestampField` and `messageField` on the query spec.
13 changes: 13 additions & 0 deletions opensearch/cue.mod/module.cue
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module: "github.com/perses/plugins/opensearch@v0"
language: {
version: "v0.15.1"
}
source: {
kind: "git"
}
deps: {
"github.com/perses/shared/cue@v0": {
v: "v0.53.1"
default: true
}
}
30 changes: 30 additions & 0 deletions opensearch/docs/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# OpenSearch examples

## trace-to-logs.json

Demonstrates the trace→logs pivot:

1. The `traceId` dashboard variable holds the currently selected trace.
2. The Tempo panel renders the trace via `TempoTraceQuery` and `TracingGanttChart`.
3. The OpenSearch logs panel runs a PPL query filtered by `traceId='$traceId'`.

When the user picks a trace in the gantt chart (or sets the variable elsewhere),
the logs panel re-runs and shows logs for that trace.

### Field-name conventions

OpenSearch indexes vary in how they name fields. Common defaults:

| Source | timestamp | message | trace id |
| ---------------------------- | ------------ | --------- | ---------- |
| OpenTelemetry / Data Prepper | `@timestamp` | `body` | `traceId` |
| AWS / X-Ray exporter | `@timestamp` | `message` | `traceId` |
| Legacy / custom | `time` | `message` | `trace_id` |

If your index uses different names, set `timestampField` and/or `messageField` on
the `OpenSearchLogQuery` spec, and adjust the `where` clause to your trace_id field.

### Swap Tempo for Jaeger

Replace the Tempo panel's `plugin.kind` and `datasource.kind` with `JaegerTraceQuery`
and `JaegerDatasource`. The OpenSearch panel does not change.
42 changes: 42 additions & 0 deletions opensearch/docs/examples/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
services:
opensearch:
image: opensearchproject/opensearch:2
container_name: opensearch
environment:
- discovery.type=single-node
- DISABLE_SECURITY_PLUGIN=true
- OPENSEARCH_INITIAL_ADMIN_PASSWORD=Perses-Test-1!
- OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m
- bootstrap.memory_lock=true
- http.cors.enabled=true
- http.cors.allow-origin=*
- http.cors.allow-methods=GET,POST,PUT,DELETE,OPTIONS
- http.cors.allow-headers=X-Requested-With,Content-Type,Content-Length,Authorization
- http.cors.allow-credentials=true
ulimits:
memlock:
soft: -1
hard: -1
nofile:
soft: 65536
hard: 65536
ports:
- "9200:9200"
- "9600:9600"
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://localhost:9200/_cluster/health || exit 1"]
interval: 5s
timeout: 5s
retries: 20

dashboards:
image: opensearchproject/opensearch-dashboards:2
container_name: opensearch-dashboards
environment:
- OPENSEARCH_HOSTS=["http://opensearch:9200"]
- DISABLE_SECURITY_DASHBOARDS_PLUGIN=true
ports:
- "5601:5601"
depends_on:
opensearch:
condition: service_healthy
84 changes: 84 additions & 0 deletions opensearch/docs/examples/trace-to-logs.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
{
"kind": "Dashboard",
"metadata": {
"name": "trace-to-logs-example",
"project": "examples"
},
"spec": {
"display": {
"name": "Trace to Logs (Tempo + OpenSearch)"
},
"duration": "1h",
"variables": [
{
"kind": "TextVariable",
"spec": {
"name": "traceId",
"value": ""
}
}
],
"panels": {
"trace": {
"kind": "Panel",
"spec": {
"display": { "name": "Trace" },
"plugin": {
"kind": "TracingGanttChart",
"spec": {}
},
"queries": [
{
"kind": "TraceQuery",
"spec": {
"plugin": {
"kind": "TempoTraceQuery",
"spec": {
"query": "$traceId",
"datasource": { "kind": "TempoDatasource", "name": "tempo" }
}
}
}
}
]
}
},
"logs": {
"kind": "Panel",
"spec": {
"display": { "name": "Logs for current trace" },
"plugin": {
"kind": "LogsTable",
"spec": {}
},
"queries": [
{
"kind": "LogQuery",
"spec": {
"plugin": {
"kind": "OpenSearchLogQuery",
"spec": {
"datasource": { "kind": "OpenSearchDatasource", "name": "opensearch" },
"index": "otel-logs-*",
"query": "source=otel-logs-* | where traceId='$traceId'"
}
}
}
}
]
}
}
},
"layouts": [
{
"kind": "Grid",
"spec": {
"items": [
{ "x": 0, "y": 0, "width": 24, "height": 12, "content": { "$ref": "#/spec/panels/trace" } },
{ "x": 0, "y": 12, "width": 24, "height": 12, "content": { "$ref": "#/spec/panels/logs" } }
]
}
}
]
}
}
32 changes: 32 additions & 0 deletions opensearch/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
module github.com/perses/plugins/opensearch

go 1.25.7

require github.com/perses/perses v0.53.1

require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/muhlemmer/gu v0.3.1 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
github.com/perses/common v0.30.2 // indirect
github.com/prometheus/client_golang v1.23.2 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.67.5 // indirect
github.com/prometheus/procfs v0.17.0 // indirect
github.com/zitadel/oidc/v3 v3.45.4 // indirect
github.com/zitadel/schema v1.3.2 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/oauth2 v0.35.0 // indirect
golang.org/x/sys v0.41.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
70 changes: 70 additions & 0 deletions opensearch/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/muhlemmer/gu v0.3.1 h1:7EAqmFrW7n3hETvuAdmFmn4hS8W+z3LgKtrnow+YzNM=
github.com/muhlemmer/gu v0.3.1/go.mod h1:YHtHR+gxM+bKEIIs7Hmi9sPT3ZDUvTN/i88wQpZkrdM=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nexucis/lamenv v0.5.2 h1:tK/u3XGhCq9qIoVNcXsK9LZb8fKopm0A5weqSRvHd7M=
github.com/nexucis/lamenv v0.5.2/go.mod h1:HusJm6ltmmT7FMG8A750mOLuME6SHCsr2iFYxp5fFi0=
github.com/perses/common v0.30.2 h1:RAiVxUpX76lTCb4X7pfcXSvYdXQmZwKi4oDKAEO//u0=
github.com/perses/common v0.30.2/go.mod h1:DFtur1QPah2/ChXbKKhw7djYdwNgz27s5fPKpiK0Xao=
github.com/perses/perses v0.53.1 h1:9VY/6p9QWrZwPSV7qiwTMSOsgcB37Lb1AXKT0ORXc6I=
github.com/perses/perses v0.53.1/go.mod h1:ro8fsgBkHYOdrL/MV+fdP9mflKzYCy/+gcbxiaReI/A=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTUGI4=
github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw=
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/zitadel/oidc/v3 v3.45.4 h1:GKyWaPRVQ8sCu9XgJ3NgNGtG52FzwVJpzXjIUG2+YrI=
github.com/zitadel/oidc/v3 v3.45.4/go.mod h1:XALmFXS9/kSom9B6uWin1yJ2WTI/E4Ti5aXJdewAVEs=
github.com/zitadel/schema v1.3.2 h1:gfJvt7dOMfTmxzhscZ9KkapKo3Nei3B6cAxjav+lyjI=
github.com/zitadel/schema v1.3.2/go.mod h1:IZmdfF9Wu62Zu6tJJTH3UsArevs3Y4smfJIj3L8fzxw=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.35.0 h1:Mv2mzuHuZuY2+bkyWXIHMfhNdJAdwW3FuWeCPYN5GVQ=
golang.org/x/oauth2 v0.35.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
31 changes: 31 additions & 0 deletions opensearch/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// Copyright The Perses Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import type { Config } from '@jest/types';
import shared from '../jest.shared';

const jestConfig: Config.InitialOptions = {
...shared,

moduleNameMapper: {
...(shared.moduleNameMapper ?? {}),
'^react$': '<rootDir>/../node_modules/react',
'^react-dom$': '<rootDir>/../node_modules/react-dom',
'^react/jsx-runtime$': '<rootDir>/../node_modules/react/jsx-runtime',
'^react-dom/(.*)$': '<rootDir>/../node_modules/react-dom/$1',
},

setupFilesAfterEnv: [...(shared.setupFilesAfterEnv ?? []), '<rootDir>/src/setup-tests.ts'],
};

export default jestConfig;
Loading
Loading