Skip to content

Commit 0c5e3fc

Browse files
authored
[exporter/opensearch] Add bodymap mode support for logs to opensearch exporter (#42206)
<!--Ex. Fixing a bug - Describe the bug and how this fixes the issue. Ex. Adding a feature - Explain what this achieves.--> #### Description Add support for bodymap for opensearch exporter <!-- Issue number (e.g. #1234) or full URL to issue, if applicable. --> #### Link to tracking issue #41654 <!--Describe what testing was performed and which tests were added.--> #### Testing Add unit tests for the bodymap mode <!--Describe the documentation added.--> #### Documentation <!--Please delete paragraphs that you did not use before submitting.-->
1 parent 4ad57bd commit 0c5e3fc

File tree

11 files changed

+773
-11
lines changed

11 files changed

+773
-11
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Use this changelog template to create an entry for release notes.
2+
3+
# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
4+
change_type: enhancement
5+
6+
# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
7+
component: opensearchexporter
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Add support for bodymap mapping mode
11+
12+
# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
13+
issues: [41654]
14+
15+
# (Optional) One or more lines of additional information to render under the primary note.
16+
# These lines will be padded with 2 spaces and then inserted directly into the document.
17+
# Use pipe (|) for multiline entries.
18+
subtext: The bodymap mapping mode supports only logs and uses the body of a log record as the exact content of the OpenSearch document, without any transformation.
19+
20+
# If your change doesn't affect end users or the exported elements of any package,
21+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
22+
# Optional: The change log or logs in which this entry should be included.
23+
# e.g. '[user]' or '[user, api]'
24+
# Include 'user' if the change is relevant to end users.
25+
# Include 'api' if there is a change to a library API.
26+
# Default: '[user]'
27+
change_logs: [user]

exporter/opensearchexporter/README.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,70 @@ This configuration will create:
9393

9494
If `service.name` is missing, the fallback value is used (e.g., `otel-logs-default-service-2024.06.07`).
9595

96+
### OpenSearch document mapping
97+
98+
99+
The mapping mode can be controlled via the scope attribute `opensearch.mapping.mode`.
100+
101+
The OpenSearch exporter supports several document schemas and preprocessing behaviors, which may be configured through the following settings:
102+
103+
- `mapping`:
104+
- `mode` (default=`ss4o`): Configures the field mappings. Supported modes are:
105+
- `ss4o`: Exports logs in the [Simple Schema for Observability](https://opensearch.org/docs/latest/observing-your-data/ss4o/) standard.
106+
- `ecs`: Maps fields defined in the OpenTelemetry Semantic Conventions to the [Elastic Common Schema](https://www.elastic.co/guide/en/ecs/current/index.html)
107+
- `flatten_attributes`: Uses the ECS mapping but flattens all resource and log attributes in the record to the top-level.
108+
- `bodymap`: uses the "body" of a log record as the exact content of the OpenSearch document, without any transformation. This mapping mode is intended for use cases where the client wishes to have complete control over the OpenSearch document structure.
109+
- `timestamp_field`: (optional) Field to store the timestamp in. If not set, uses the default `@timestamp`.
110+
- `unix_timestamp`: (optional) Whether to store the timestamp in epoch milliseconds.
111+
- `dedup`: (optional) removes fields from the document, that have duplicate keys. The filtering only keeps the last value for a key.
112+
- `dedot`: (optional) convert dotted keys into nested JSON objects.
113+
114+
#### SS4O mapping mode
115+
116+
The default [`Simple Schema for Observability`](https://docs.opensearch.org/latest/observing-your-data/ss4o/) mapping mode.
117+
118+
In `ss4o` mapping mode, the OpenSearch exporter stores documents using the SS4O schema, which is designed for observability data in OpenSearch. Documents use standardized field names and structure to facilitate integration with OpenSearch dashboards and tools.
119+
120+
| Signal | Supported |
121+
| --------- | ------------------ |
122+
| Logs | :white_check_mark: |
123+
| Traces | :white_check_mark: |
124+
125+
#### ECS mapping mode
126+
127+
> [!WARNING]
128+
> The ECS mapping mode is currently undergoing changes, and its behaviour is unstable.
129+
130+
In `ecs` mapping mode, the OpenSearch exporter maps fields from [OpenTelemetry Semantic Conventions](https://opentelemetry.io/docs/reference/specification/trace/semantic_conventions/) to [Elastic Common Schema (ECS)](https://www.elastic.co/guide/en/ecs/current/index.html) where possible. This mode may be used for compatibility with dashboards and tools that expect ECS.
131+
132+
| Signal | `ecs` |
133+
| --------- | ------------------ |
134+
| Logs | :white_check_mark: |
135+
| Traces | :no_entry_sign: |
136+
137+
#### Flatten attributes mapping mode
138+
139+
> [!WARNING]
140+
> The Flatten attributes mapping mode is currently undergoing changes, and its behaviour is unstable.
141+
142+
In `flatten_attributes` mapping mode, the OpenSearch exporter uses the ECS mapping but flattens all resource and log attributes in the record to the top-level of the document.
143+
144+
| Signal | `flatten_attributes` |
145+
| --------- | -------------------- |
146+
| Logs | :white_check_mark: |
147+
| Traces | :no_entry_sign: |
148+
149+
#### Bodymap mapping mode
150+
151+
In `bodymap` mapping mode, the OpenSearch exporter supports only logs and uses the "body" of a log record as the exact content of the OpenSearch document, without any transformation. This mapping mode is intended for use cases where the client wishes to have complete control over the OpenSearch document structure.
152+
153+
The bodymap mapping mode only supports log records where the body is of type Map. If the log body is not a Map, encoding will fail with an error. This ensures that only structured map data can be used as the document content in bodymap mode.
154+
155+
| Signal | `bodymap` |
156+
| --------- | ------------------- |
157+
| Logs | :white_check_mark: |
158+
| Traces | :no_entry_sign: |
159+
96160
### HTTP Connection Options
97161

98162
OpenSearch export supports standard [HTTP client settings](https://github.com/open-telemetry/opentelemetry-collector/tree/main/config/confighttp#client-configuration).

exporter/opensearchexporter/config.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,11 @@ type MappingsSettings struct {
8585
//
8686
// flatten_attributes: uses the ECS mapping but flattens all resource and
8787
// log attributes in the record to the top-level.
88+
//
89+
// bodymap: supports only logs and uses the "body" of a log record as the exact content
90+
// of the OpenSearch document, without any transformation.
91+
// This mapping mode is intended for use cases where the client wishes to have complete control over the
92+
// OpenSearch document structure.
8893
Mode string `mapstructure:"mode"`
8994

9095
// Additional field mappings.
@@ -112,6 +117,7 @@ const (
112117
MappingSS4O MappingMode = iota
113118
MappingECS
114119
MappingFlattenAttributes
120+
MappingBodyMap
115121
)
116122

117123
func (m MappingMode) String() string {
@@ -122,6 +128,8 @@ func (m MappingMode) String() string {
122128
return "ecs"
123129
case MappingFlattenAttributes:
124130
return "flatten_attributes"
131+
case MappingBodyMap:
132+
return "bodymap"
125133
default:
126134
return "ss4o"
127135
}
@@ -133,6 +141,7 @@ var mappingModes = func() map[string]MappingMode {
133141
MappingECS,
134142
MappingSS4O,
135143
MappingFlattenAttributes,
144+
MappingBodyMap,
136145
} {
137146
table[strings.ToLower(m.String())] = m
138147
}

exporter/opensearchexporter/encoder.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,21 @@ package opensearchexporter // import "github.com/open-telemetry/opentelemetry-co
66
import (
77
"bytes"
88
"encoding/json"
9+
"errors"
10+
"fmt"
911
"time"
1012

1113
"go.opentelemetry.io/collector/pdata/pcommon"
1214
"go.opentelemetry.io/collector/pdata/plog"
1315
"go.opentelemetry.io/collector/pdata/ptrace"
1416

1517
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/opensearchexporter/internal/objmodel"
18+
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/opensearchexporter/internal/pool"
19+
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/opensearchexporter/internal/serializer"
1620
)
1721

22+
var errInvalidTypeForBodyMapMode = errors.New("invalid log record body type for 'bodymap' mapping mode")
23+
1824
type mappingModel interface {
1925
encodeLog(resource pcommon.Resource,
2026
scope pcommon.InstrumentationScope,
@@ -26,6 +32,39 @@ type mappingModel interface {
2632
record ptrace.Span) ([]byte, error)
2733
}
2834

35+
type bodyMapMappingModel struct {
36+
bufferPool *pool.BufferPool
37+
}
38+
39+
func (*bodyMapMappingModel) encodeTrace(
40+
_ pcommon.Resource,
41+
_ pcommon.InstrumentationScope,
42+
_ string,
43+
_ ptrace.Span,
44+
) ([]byte, error) {
45+
return nil, fmt.Errorf("mapping mode '%s' does not support encoding traces", MappingBodyMap.String())
46+
}
47+
48+
func (m *bodyMapMappingModel) encodeLog(
49+
_ pcommon.Resource,
50+
_ pcommon.InstrumentationScope,
51+
_ string,
52+
record plog.LogRecord,
53+
) ([]byte, error) {
54+
body := record.Body()
55+
if body.Type() != pcommon.ValueTypeMap {
56+
return nil, fmt.Errorf("%w: %q", errInvalidTypeForBodyMapMode, body.Type().String())
57+
}
58+
pooledBuf := m.bufferPool.NewPooledBuffer()
59+
defer pooledBuf.Recycle()
60+
61+
serializer.Map(body.Map(), pooledBuf.Buffer)
62+
// Copy bytes to avoid holding reference to pooled buffer
63+
result := make([]byte, pooledBuf.Buffer.Len())
64+
copy(result, pooledBuf.Buffer.Bytes())
65+
return result, nil
66+
}
67+
2968
// encodeModel supports multiple encoding OpenTelemetry signals to multiple schemas.
3069
type encodeModel struct {
3170
dedup bool

0 commit comments

Comments
 (0)