Skip to content

Commit

Permalink
Merge branch 'main' into cds-1320
Browse files Browse the repository at this point in the history
  • Loading branch information
daidokoro authored Sep 20, 2024
2 parents 614dcad + 576d322 commit a5f43c0
Show file tree
Hide file tree
Showing 21 changed files with 353 additions and 255 deletions.
27 changes: 27 additions & 0 deletions .chloggen/ottl_time_func_locale_support.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

# 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: pkg/ottl

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Added support for locale in the Time converter

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

# (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:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: [user]
27 changes: 27 additions & 0 deletions .chloggen/redaction-fix-logs.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Use this changelog template to create an entry for release notes.

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

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

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Fix panic when using the redaction processor in a logs pipeline

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

# (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:

# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: []
28 changes: 28 additions & 0 deletions .chloggen/splunkhec-fix-invalid-data-access.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Use this changelog template to create an entry for release notes.

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

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

# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
note: Fix incorrect claim that the exporter doesn't mutate data when batching is enabled.

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

# (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: |
The bug lead to runtime panics when the exporter was used with the batcher enabled in a fanout scenario.
# If your change doesn't affect end users or the exported elements of any package,
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
# Optional: The change log or logs in which this entry should be included.
# e.g. '[user]' or '[user, api]'
# Include 'user' if the change is relevant to end users.
# Include 'api' if there is a change to a library API.
# Default: '[user]'
change_logs: []
3 changes: 2 additions & 1 deletion exporter/splunkhecexporter/batchperscope.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ type perScopeBatcher struct {
next consumer.Logs
}

// Capabilities returns capabilities of the next consumer because perScopeBatcher doesn't mutate data itself.
func (rb *perScopeBatcher) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: false}
return rb.next.Capabilities()
}

func (rb *perScopeBatcher) ConsumeLogs(ctx context.Context, logs plog.Logs) error {
Expand Down
32 changes: 32 additions & 0 deletions exporter/splunkhecexporter/factory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/require"
"go.opentelemetry.io/collector/component/componenttest"
"go.opentelemetry.io/collector/config/confighttp"
"go.opentelemetry.io/collector/exporter/exporterbatcher"
"go.opentelemetry.io/collector/exporter/exportertest"
)

Expand Down Expand Up @@ -88,3 +89,34 @@ func TestFactory_CreateMetricsExporter(t *testing.T) {
assert.NoError(t, err)
assert.NotNil(t, te)
}

func TestFactory_EnabledBatchingMakesExporterMutable(t *testing.T) {
config := &Config{
Token: "testToken",
ClientConfig: confighttp.ClientConfig{
Endpoint: "https://example.com:8000",
},
}

me, err := createMetricsExporter(context.Background(), exportertest.NewNopSettings(), config)
require.NoError(t, err)
assert.False(t, me.Capabilities().MutatesData)
te, err := createTracesExporter(context.Background(), exportertest.NewNopSettings(), config)
require.NoError(t, err)
assert.False(t, te.Capabilities().MutatesData)
le, err := createLogsExporter(context.Background(), exportertest.NewNopSettings(), config)
require.NoError(t, err)
assert.False(t, le.Capabilities().MutatesData)

config.BatcherConfig = exporterbatcher.NewDefaultConfig()

me, err = createMetricsExporter(context.Background(), exportertest.NewNopSettings(), config)
require.NoError(t, err)
assert.True(t, me.Capabilities().MutatesData)
te, err = createTracesExporter(context.Background(), exportertest.NewNopSettings(), config)
require.NoError(t, err)
assert.True(t, te.Capabilities().MutatesData)
le, err = createLogsExporter(context.Background(), exportertest.NewNopSettings(), config)
require.NoError(t, err)
assert.True(t, le.Capabilities().MutatesData)
}
53 changes: 38 additions & 15 deletions internal/coreinternal/timeutils/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package timeutils // import "github.com/open-telemetry/opentelemetry-collector-contrib/internal/coreinternal/timeutils"

import (
"errors"
"fmt"
"regexp"
"strings"
Expand All @@ -28,27 +29,15 @@ func ParseStrptime(layout string, value any, location *time.Location) (time.Time
return ParseGotime(goLayout, value, location)
}

// ParseLocalizedStrptime is like ParseStrptime, but instead of parsing a formatted time in
// English, it parses a value in foreign language, and returns the [time.Time] it represents.
// The language argument must be a well-formed BCP 47 language tag (e.g.: "en", "en-US"), and
// a known CLDR locale.
// ParseLocalizedStrptime is like ParseLocalizedGotime, but instead of using the native Go time layout,
// it uses the ctime-like format.
func ParseLocalizedStrptime(layout string, value any, location *time.Location, language string) (time.Time, error) {
goLayout, err := strptime.ToNative(layout)
if err != nil {
return time.Time{}, err
}

stringValue, err := convertParsingValue(value)
if err != nil {
return time.Time{}, err
}

translatedVal, err := lunes.Translate(goLayout, stringValue, language)
if err != nil {
return time.Time{}, err
}

return ParseGotime(goLayout, translatedVal, location)
return ParseLocalizedGotime(goLayout, value, location, language)
}

func GetLocation(location *string, layout *string) (*time.Location, error) {
Expand All @@ -69,6 +58,24 @@ func GetLocation(location *string, layout *string) (*time.Location, error) {
return time.Local, nil
}

// ParseLocalizedGotime is like ParseGotime, but instead of parsing a formatted time in
// English, it parses a value in foreign language, and returns the [time.Time] it represents.
// The language argument must be a well-formed BCP 47 language tag (e.g.: "en", "en-US"), and
// a known CLDR locale.
func ParseLocalizedGotime(layout string, value any, location *time.Location, language string) (time.Time, error) {
stringValue, err := convertParsingValue(value)
if err != nil {
return time.Time{}, err
}

translatedVal, err := lunes.Translate(layout, stringValue, language)
if err != nil {
return time.Time{}, err
}

return ParseGotime(layout, translatedVal, location)
}

func ParseGotime(layout string, value any, location *time.Location) (time.Time, error) {
timeValue, err := parseGotime(layout, value, location)
if err != nil {
Expand Down Expand Up @@ -155,5 +162,21 @@ func ValidateGotime(layout string) error {
return nil
}

// ValidateLocale checks the given locale and returns an error if the language tag
// is not supported by the localized parser functions.
func ValidateLocale(locale string) error {
_, err := lunes.NewDefaultLocale(locale)
if err == nil {
return nil
}

var e *lunes.ErrUnsupportedLocale
if errors.As(err, &e) {
return fmt.Errorf("unsupported locale '%s', value must be a supported BCP 47 language tag", locale)
}

return fmt.Errorf("invalid locale '%s': %w", locale, err)
}

// Allows tests to override with deterministic value
var Now = time.Now
70 changes: 70 additions & 0 deletions internal/coreinternal/timeutils/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,73 @@ func TestParseLocalizedStrptimeInvalidType(t *testing.T) {
require.Error(t, err)
require.ErrorContains(t, err, "cannot be parsed as a time")
}

func TestParseLocalizedGotime(t *testing.T) {
tests := []struct {
name string
format string
value any
language string
expected time.Time
location *time.Location
}{
{
name: "Foreign language",
format: "January 02 Monday, 2006, 03:04:05 pm",
value: "Febrero 25 jueves, 1993, 02:03:04 p.m.",
expected: time.Date(1993, 2, 25, 14, 3, 4, 0, time.Local),
location: time.Local,
language: "es-ES",
},
{
name: "Foreign language with location",
format: "Monday Jan _2 2006",
value: "mercoledì set 4 2024",
expected: time.Date(2024, 9, 4, 0, 0, 0, 0, time.UTC),
location: time.UTC,
language: "it-IT",
},
{
name: "String value",
format: "January 02 Monday, 2006, 03:04:05 PM",
value: "March 12 Friday, 2004, 02:03:04 AM",
expected: time.Date(2004, 3, 12, 2, 3, 4, 0, time.Local),
location: time.Local,
language: "en",
},
{
name: "Bytes value",
format: "Jan 02 Mon, 06, 03:04:05 PM",
value: []byte("Jun 10 Fri, 04, 02:03:04 AM"),
expected: time.Date(2004, 6, 10, 2, 3, 4, 0, time.Local),
location: time.Local,
language: "en-US",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := ParseLocalizedGotime(tt.format, tt.value, tt.location, tt.language)
require.NoError(t, err)
assert.Equal(t, tt.expected.UnixNano(), result.UnixNano())
})
}
}

func TestParseLocalizedGotimeInvalidType(t *testing.T) {
value := time.Now().UnixNano()
_, err := ParseLocalizedStrptime("Mon", value, time.Local, "en")
require.Error(t, err)
require.ErrorContains(t, err, "cannot be parsed as a time")
}

func TestValidateLocale(t *testing.T) {
require.NoError(t, ValidateLocale("es"))
require.NoError(t, ValidateLocale("en-US"))
require.NoError(t, ValidateLocale("ca-ES-valencia"))
}

func TestValidateLocaleUnsupported(t *testing.T) {
err := ValidateLocale("foo-bar")
require.ErrorContains(t, err, "unsupported locale 'foo-bar'")
}
12 changes: 6 additions & 6 deletions pkg/batchperresourceattr/batchperresourceattr.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ func NewMultiBatchPerResourceTraces(attrKeys []string, next consumer.Traces) con
}
}

// Capabilities implements the consumer interface.
// Capabilities returns the capabilities of the next consumer because batchTraces doesn't mutate data itself.
func (bt *batchTraces) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: false}
return bt.next.Capabilities()
}

func (bt *batchTraces) ConsumeTraces(ctx context.Context, td ptrace.Traces) error {
Expand Down Expand Up @@ -98,9 +98,9 @@ func NewMultiBatchPerResourceMetrics(attrKeys []string, next consumer.Metrics) c
}
}

// Capabilities implements the consumer interface.
// Capabilities returns the capabilities of the next consumer because batchMetrics doesn't mutate data itself.
func (bt *batchMetrics) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: false}
return bt.next.Capabilities()
}

func (bt *batchMetrics) ConsumeMetrics(ctx context.Context, td pmetric.Metrics) error {
Expand Down Expand Up @@ -159,9 +159,9 @@ func NewMultiBatchPerResourceLogs(attrKeys []string, next consumer.Logs) consume
}
}

// Capabilities implements the consumer interface.
// Capabilities returns the capabilities of the next consumer because batchLogs doesn't mutate data itself.
func (bt *batchLogs) Capabilities() consumer.Capabilities {
return consumer.Capabilities{MutatesData: false}
return bt.next.Capabilities()
}

func (bt *batchLogs) ConsumeLogs(ctx context.Context, td plog.Logs) error {
Expand Down
15 changes: 13 additions & 2 deletions pkg/ottl/ottlfuncs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1455,11 +1455,11 @@ Examples:

### Time

`Time(target, format, Optional[location])`
`Time(target, format, Optional[location], Optional[locale])`

The `Time` Converter takes a string representation of a time and converts it to a Golang `time.Time`.

`target` is a string. `format` is a string, `location` is an optional string.
`target` is a string. `format` is a string, `location` is an optional string, `locale` is an optional string.

If either `target` or `format` are nil, an error is returned. The parser used is the parser at [internal/coreinternal/parser](https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/internal/coreinternal/timeutils). If the `target` and `format` do not follow the parsing rules used by this parser, an error is returned.

Expand Down Expand Up @@ -1519,6 +1519,17 @@ Examples:
- `Time("2012-11-01T22:08:41+0000 EST", "%Y-%m-%dT%H:%M:%S%z %Z")`
- `Time("2023-05-26 12:34:56", "%Y-%m-%d %H:%M:%S", "America/New_York")`

`locale` specifies the input language of the `target` value. It is used to interpret timestamp values written in a specific language,
ensuring that the function can correctly parse the localized month names, day names, and periods of the day based on the provided language.

The value must be a well-formed BCP 47 language tag, and a known [CLDR](https://cldr.unicode.org) v45 locale.
If not supplied, English (`en`) is used.

Examples:

- `Time("mercoledì set 4 2024", "%A %h %e %Y", "", "it")`
- `Time("Febrero 25 lunes, 2002, 02:03:04 p.m.", "%B %d %A, %Y, %r", "America/New_York", "es-ES")`

### TraceID

`TraceID(bytes)`
Expand Down
Loading

0 comments on commit a5f43c0

Please sign in to comment.