Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[exporter/datasetexporter] Improved handling of "observed timestamp" and body / message fields, add config option for exporting scope attributes #23826

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
ec8a980
DSET-3998 - export Logs resource info based on export_resource_in…
zdaratom-s1 Jun 7, 2023
b7b7c0e
DSET-3998 - simplify
zdaratom-s1 Jun 7, 2023
322c5b5
DSET-3998 - improve docs
zdaratom-s1 Jun 8, 2023
1e4eaa6
Fix log exporter to set AddEvents Event timestamp (ts) field to event
tomaz-s1 Jun 8, 2023
4ddc9c4
Merge pull request #1 from zdaratom/DSET-3998
zdaratom-s1 Jun 9, 2023
cef4e67
Merge branch 'datasetexporter-latest' of github.com:scalyr/openteleme…
tomaz-s1 Jun 9, 2023
21e9041
Add additional assertions.
tomaz-s1 Jun 9, 2023
e03e0e2
Remove dummy debug logs.
tomaz-s1 Jun 9, 2023
b9c138f
Merge pull request #2 from scalyr/event_no_timestamp_fix_use_occurenc…
tomaz-s1 Jun 9, 2023
a3d5e0d
Create export-logs-resource-info-based-configuration
zdaratom-s1 Jun 13, 2023
65e9b0c
address PR notes - fix changelog gen
zdaratom-s1 Jun 13, 2023
d6cc185
fix docs typo
zdaratom-s1 Jun 13, 2023
9f987ac
fix changelog file suffix
zdaratom-s1 Jun 13, 2023
52e8651
Remove "OtelExporter - Log -" suffix from the event "message" field.
tomaz-s1 Jun 20, 2023
1b05c78
Implement severity field handling and make sure we currently map OTel
tomaz-s1 Jun 20, 2023
4cd8258
Remove flags and flags.is_sampled field since it provides no additional
tomaz-s1 Jun 21, 2023
26aae4f
Merge branch 'open-telemetry:main' into datasetexporter-latest
zdaratom-s1 Jun 21, 2023
b5674e7
Merge branch 'open-telemetry:main' into datasetexporter-latest
zdaratom-s1 Jun 22, 2023
a80ac48
Update metadata and indicate plugin is distributed as part of otel
tomaz-s1 Jun 22, 2023
af8f099
Move default severity to be a module level constant instead of function
tomaz-s1 Jun 22, 2023
1e17c4a
Refactor / split otelSeverityToDataSetSeverity to two functions.
tomaz-s1 Jun 22, 2023
79120e7
Also add test cases for invalid values.
tomaz-s1 Jun 22, 2023
f20f199
Make it a module level constant.
tomaz-s1 Jun 22, 2023
206c8eb
Make dataset log levels constants for easier readability and maintaince
tomaz-s1 Jun 22, 2023
21c07be
Re-generate readme.
tomaz-s1 Jun 23, 2023
211db7e
Per PR review, add "map" suffix to the function names.
tomaz-s1 Jun 23, 2023
656cc5f
For readability, split different test scenarios in different test
tomaz-s1 Jun 23, 2023
401041d
Add changelog entry file.
tomaz-s1 Jun 23, 2023
292122c
Merge pull request #3 from scalyr/dataset_log_exporter_improvements
tomaz-s1 Jun 26, 2023
adf3b7d
Merge branch 'main' of https://github.com/open-telemetry/opentelemetr…
tomaz-s1 Jun 26, 2023
fc14849
Merge branch 'datasetexporter-latest' into dataset_log_exporter_impro…
tomaz-s1 Jun 26, 2023
ae0730f
Use consistent name for "observed time" field and also make sure it's
tomaz-s1 Jun 27, 2023
11a425f
Update body / message field handling - we don't want to utilize
tomaz-s1 Jun 27, 2023
b578baf
Put functionality to decompose message / body field of a complex type
tomaz-s1 Jun 27, 2023
ec0f58b
Fix lint.
tomaz-s1 Jun 27, 2023
d284d14
Add missing code comment.
tomaz-s1 Jun 27, 2023
0bbaa50
Use a constant.
tomaz-s1 Jun 27, 2023
64a5d88
Allow user to control if scope info and attributes are exported with
tomaz-s1 Jun 27, 2023
6ca746d
Add changelog file.
tomaz-s1 Jun 27, 2023
4c2c1bd
Remove debugging change, make sure we don't set scope.name attribute if
tomaz-s1 Jun 27, 2023
893e44a
For consistency name the field "sca:observedTime" instead (we already
tomaz-s1 Jun 28, 2023
9d0a7cd
Merge branch 'main' of https://github.com/open-telemetry/opentelemetr…
tomaz-s1 Jun 28, 2023
96e4652
Merge branch 'datasetexporter-latest' into improve_field_name_and_val…
tomaz-s1 Jun 28, 2023
539dd0a
Add missing PR references to changelog entries for the dataset exporter.
tomaz-s1 Jun 29, 2023
c63b565
Merge branch 'main' into improve_field_name_and_value_serialization_u…
tomaz-s1 Jul 7, 2023
c7e2038
Update exporter readme with new config options.
tomaz-s1 Jul 7, 2023
69f9cd1
Merge branch 'main' into improve_field_name_and_value_serialization_u…
tomaz-s1 Jul 7, 2023
a7ed4fd
Merge branch 'main' into improve_field_name_and_value_serialization_u…
tomaz-s1 Jul 19, 2023
588c4e5
Merge branch 'main' into improve_field_name_and_value_serialization_u…
tomaz-s1 Jul 19, 2023
b0dc06f
Update exporter/datasetexporter/README.md
tomaz-s1 Jul 19, 2023
18f429d
Merge branch 'main' into improve_field_name_and_value_serialization_u…
tomaz-s1 Jul 19, 2023
8df03dc
Update exporter/datasetexporter/config.go
tomaz-s1 Jul 20, 2023
116aa6e
Merge branch 'main' into improve_field_name_and_value_serialization_u…
tomaz-s1 Jul 20, 2023
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Use this changelog template to create an entry for release notes.
# If your change doesn't affect end users, such as a test fix or a tooling change,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.

# One of 'breaking', 'deprecation', 'new_component', 'enhancement', 'bug_fix'
change_type: enhancement

# The name of the component, or a single word describing the area of concern, (e.g. filelogreceiver)
component: exporter/datasetexporter

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: "Rename 'observed_timestamp' field on the DataSet event to 'sca:observedTimestamp' and ensure the value is nanoseconds since epoch, update serializing and handling of body / message field to ensure it's consistent with other DataSet integrations and allow user to disable exporting scope information with each event by setting 'export_scope_info_on_event' logs config option to false."

# Mandatory: One or more tracking issues related to the change. You can use the PR number here if no issue exists.
issues: [20660, 23826]

# (Optional) One or more lines of additional information to render under the primary note.
# These lines will be padded with 2 spaces and then inserted directly into the document.
# Use pipe (|) for multiline entries.
subtext:
2 changes: 2 additions & 0 deletions exporter/datasetexporter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ If you do not want to specify `api_key` in the file, you can use the [builtin fu
- `retry_max_elapsed_time` (default = 300s): Is the maximum amount of time spent trying to send a buffer.
- `logs`:
- `export_resource_info_on_event` (default = false): Include resource info to DataSet Event while exporting Logs. This is especially useful when reducing DataSet billable log volume.
- `export_scope_info_on_event` (default = false): Include LogRecord scope information (if available) on the DataSet event.
- `decompose_complex_message_field` (default = true): Set this to false to disable decomposing complex body / message field types (e.g. a map) into separate fields.
- `retry_on_failure`: See [retry_on_failure](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md)
- `sending_queue`: See [sending_queue](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md)
- `timeout`: See [timeout](https://github.com/open-telemetry/opentelemetry-collector/blob/main/exporter/exporterhelper/README.md)
Expand Down
16 changes: 15 additions & 1 deletion exporter/datasetexporter/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,32 @@ func newDefaultTracesSettings() TracesSettings {
}

const logsExportResourceInfoDefault = false
const logsExportScopeInfoDefault = true
const logsDecomposeComplexMessageFieldDefault = false

type LogsSettings struct {
// ExportResourceInfo is optional flag to signal that the resource info is being exported to DataSet while exporting Logs.
// This is especially useful when reducing DataSet billable log volume.
// Default value: false.
ExportResourceInfo bool `mapstructure:"export_resource_info_on_event"`

// ExportScopeInfo is an optional flag that signals if scope info should be exported (when available) with each event. If scope
// information is not utilized, it makes sense to disable exporting it since it will result in increased billable log volume.
ExportScopeInfo bool `mapstructure:"export_scope_info_on_event"`

// DecomposeComplexMessageField is an optional flag to signal that message / body of complex types (e.g. a map) should be
// decomposed / deconstructed into multiple fields. This is usually done outside of the main DataSet integration on the
// client side (e.g. as part of the attribute processor or similar) or on the server side (DataSet server side JSON parser
// for message field) and that's why this functionality is disabled by default.
DecomposeComplexMessageField bool `mapstructure:"decompose_complex_message_field"`
}

// newDefaultLogsSettings returns the default settings for LogsSettings.
func newDefaultLogsSettings() LogsSettings {
return LogsSettings{
ExportResourceInfo: logsExportResourceInfoDefault,
ExportResourceInfo: logsExportResourceInfoDefault,
ExportScopeInfo: logsExportScopeInfoDefault,
DecomposeComplexMessageField: logsDecomposeComplexMessageFieldDefault,
}
}

Expand Down
19 changes: 18 additions & 1 deletion exporter/datasetexporter/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ func TestConfigUseDefaults(t *testing.T) {
assert.Equal(t, "secret", string(config.APIKey))
assert.Equal(t, bufferMaxLifetime, config.MaxLifetime)
assert.Equal(t, logsExportResourceInfoDefault, config.LogsSettings.ExportResourceInfo)
assert.Equal(t, logsExportScopeInfoDefault, config.LogsSettings.ExportScopeInfo)
assert.Equal(t, logsDecomposeComplexMessageFieldDefault, config.LogsSettings.DecomposeComplexMessageField)
}

func TestConfigValidate(t *testing.T) {
Expand Down Expand Up @@ -111,7 +113,7 @@ func TestConfigString(t *testing.T) {
}

assert.Equal(t,
"DatasetURL: https://example.com; BufferSettings: {MaxLifetime:123ns GroupBy:[field1 field2] RetryInitialInterval:0s RetryMaxInterval:0s RetryMaxElapsedTime:0s}; TracesSettings: {}; RetrySettings: {Enabled:true InitialInterval:5s RandomizationFactor:0.5 Multiplier:1.5 MaxInterval:30s MaxElapsedTime:5m0s}; QueueSettings: {Enabled:true NumConsumers:10 QueueSize:1000 StorageID:<nil>}; TimeoutSettings: {Timeout:5s}; LogsSettings: {ExportResourceInfo:false}",
"DatasetURL: https://example.com; BufferSettings: {MaxLifetime:123ns GroupBy:[field1 field2] RetryInitialInterval:0s RetryMaxInterval:0s RetryMaxElapsedTime:0s}; TracesSettings: {}; RetrySettings: {Enabled:true InitialInterval:5s RandomizationFactor:0.5 Multiplier:1.5 MaxInterval:30s MaxElapsedTime:5m0s}; QueueSettings: {Enabled:true NumConsumers:10 QueueSize:1000 StorageID:<nil>}; TimeoutSettings: {Timeout:5s}; LogsSettings: {ExportResourceInfo:false ExportScopeInfo:false DecomposeComplexMessageField:false}",
config.String(),
)
}
Expand All @@ -130,3 +132,18 @@ func TestConfigUseProvidedExportResourceInfoValue(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, true, config.LogsSettings.ExportResourceInfo)
}

func TestConfigUseProvidedExportScopeInfoValue(t *testing.T) {
f := NewFactory()
config := f.CreateDefaultConfig().(*Config)
configMap := confmap.NewFromStringMap(map[string]interface{}{
"dataset_url": "https://example.com",
"api_key": "secret",
"logs": map[string]any{
"export_scope_info_on_event": false,
},
})
err := config.Unmarshal(configMap)
assert.Nil(t, err)
assert.Equal(t, false, config.LogsSettings.ExportScopeInfo)
}
8 changes: 6 additions & 2 deletions exporter/datasetexporter/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func TestCreateDefaultConfig(t *testing.T) {
assert.Equal(t, &Config{
BufferSettings: newDefaultBufferSettings(),
TracesSettings: newDefaultTracesSettings(),
LogsSettings: newDefaultLogsSettings(),
RetrySettings: exporterhelper.NewDefaultRetrySettings(),
QueueSettings: exporterhelper.NewDefaultQueueSettings(),
TimeoutSettings: exporterhelper.NewDefaultTimeoutSettings(),
Expand Down Expand Up @@ -88,7 +89,9 @@ func TestLoadConfig(t *testing.T) {
},
TracesSettings: TracesSettings{},
LogsSettings: LogsSettings{
ExportResourceInfo: true,
ExportResourceInfo: true,
ExportScopeInfo: true,
DecomposeComplexMessageField: true,
},
RetrySettings: exporterhelper.RetrySettings{
Enabled: true,
Expand Down Expand Up @@ -136,7 +139,7 @@ func createExporterTests() []CreateTest {
{
name: "broken",
config: &Config{},
expectedError: fmt.Errorf("cannot get DataSetExpoter: cannot convert config: DatasetURL: ; BufferSettings: {MaxLifetime:0s GroupBy:[] RetryInitialInterval:0s RetryMaxInterval:0s RetryMaxElapsedTime:0s}; TracesSettings: {}; RetrySettings: {Enabled:false InitialInterval:0s RandomizationFactor:0 Multiplier:0 MaxInterval:0s MaxElapsedTime:0s}; QueueSettings: {Enabled:false NumConsumers:0 QueueSize:0 StorageID:<nil>}; TimeoutSettings: {Timeout:0s}; LogsSettings: {ExportResourceInfo:false}; config is not valid: api_key is required"),
expectedError: fmt.Errorf("cannot get DataSetExpoter: cannot convert config: DatasetURL: ; BufferSettings: {MaxLifetime:0s GroupBy:[] RetryInitialInterval:0s RetryMaxInterval:0s RetryMaxElapsedTime:0s}; TracesSettings: {}; RetrySettings: {Enabled:false InitialInterval:0s RandomizationFactor:0 Multiplier:0 MaxInterval:0s MaxElapsedTime:0s}; QueueSettings: {Enabled:false NumConsumers:0 QueueSize:0 StorageID:<nil>}; TimeoutSettings: {Timeout:0s}; LogsSettings: {ExportResourceInfo:false ExportScopeInfo:false DecomposeComplexMessageField:false}; config is not valid: api_key is required"),
},
{
name: "valid",
Expand All @@ -150,6 +153,7 @@ func createExporterTests() []CreateTest {
RetryMaxInterval: time.Minute,
RetryMaxElapsedTime: time.Hour,
},
LogsSettings: newDefaultLogsSettings(),
TracesSettings: TracesSettings{},
RetrySettings: exporterhelper.NewDefaultRetrySettings(),
QueueSettings: exporterhelper.NewDefaultQueueSettings(),
Expand Down
53 changes: 29 additions & 24 deletions exporter/datasetexporter/logs_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,12 @@ import (
"go.opentelemetry.io/collector/pdata/plog"
)

// We define it here so we can easily mock it inside tests
var now = time.Now

// Prefix which is added to all the special / internal DataSet fields
const specialDataSetFieldNamePrefix string = "sca:"

// If a LogRecord doesn't contain severity or we can't map it to a valid DataSet severity, we use
// this value (3 - INFO) instead
const defaultDataSetSeverityLevel int = dataSetLogLevelInfo
Expand Down Expand Up @@ -54,28 +58,21 @@ func createLogsExporter(ctx context.Context, set exporter.CreateSettings, config
)
}

func buildBody(attrs map[string]interface{}, value pcommon.Value) string {
func buildBody(settings LogsSettings, attrs map[string]interface{}, value pcommon.Value) string {
// The message / body is stored as part of the "message" field on the DataSet event.
message := value.AsString()
attrs["body.type"] = value.Type().String()
switch value.Type() {
case pcommon.ValueTypeEmpty:
attrs["body.empty"] = value.AsString()
case pcommon.ValueTypeStr:
attrs["body.str"] = value.Str()
case pcommon.ValueTypeBool:
attrs["body.bool"] = value.Bool()
case pcommon.ValueTypeDouble:
attrs["body.double"] = value.Double()
case pcommon.ValueTypeInt:
attrs["body.int"] = value.Int()
case pcommon.ValueTypeMap:

// Additionally, we support de-composing complex message value (e.g. map / dictionary) into
// multiple event attributes.
//
// This functionality is behind a config option / feature flag and not enabled by default
// since no other existing DataSet integrations handle it in this manner (aka for out of
// the box consistency reasons).
// If user wants to achieve something like that, they usually handle that on the client
// (e.g. attribute processor or similar) or on the server (DataSet server side JSON parser
// for the message field).
if settings.DecomposeComplexMessageField && value.Type() == pcommon.ValueTypeMap {
updateWithPrefixedValues(attrs, "body.map.", ".", value.Map().AsRaw(), 0)
case pcommon.ValueTypeBytes:
attrs["body.bytes"] = value.AsString()
case pcommon.ValueTypeSlice:
attrs["body.slice"] = value.AsRaw()
default:
attrs["body.unknown"] = value.AsString()
}

return message
Expand Down Expand Up @@ -183,14 +180,17 @@ func buildEventFromLog(
}

if body := log.Body().AsString(); body != "" {
attrs["message"] = buildBody(attrs, log.Body())
attrs["message"] = buildBody(settings, attrs, log.Body())
}

if dropped := log.DroppedAttributesCount(); dropped > 0 {
attrs["dropped_attributes_count"] = dropped
}

if !observedTs.Equal(time.Unix(0, 0)) {
attrs["observed.timestamp"] = observedTs.String()
attrs[specialDataSetFieldNamePrefix+"observedTime"] = strconv.FormatInt(observedTs.UnixNano(), 10)
}

if span := log.SpanID().String(); span != "" {
attrs["span_id"] = span
}
Expand All @@ -215,8 +215,13 @@ func buildEventFromLog(
if settings.ExportResourceInfo {
updateWithPrefixedValues(attrs, "resource.attributes.", ".", resource.Attributes().AsRaw(), 0)
}
attrs["scope.name"] = scope.Name()
updateWithPrefixedValues(attrs, "scope.attributes.", ".", scope.Attributes().AsRaw(), 0)

if settings.ExportScopeInfo {
if scope.Name() != "" {
attrs["scope.name"] = scope.Name()
}
updateWithPrefixedValues(attrs, "scope.attributes.", ".", scope.Attributes().AsRaw(), 0)
}

event.Attrs = attrs
event.Log = "LL"
Expand Down
Loading