Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
744c0e8
Traversing logs
LikeTheSalad May 16, 2025
6214e37
Renaming files
LikeTheSalad May 16, 2025
057d1d7
Changing package name
LikeTheSalad May 16, 2025
c575f8e
Reorganizing files
LikeTheSalad May 16, 2025
b6a6649
Reorganizing files
LikeTheSalad May 16, 2025
a0e3547
Adding test case for crash events
LikeTheSalad May 16, 2025
66c8f69
Making mobile enricher internal
LikeTheSalad May 16, 2025
0d4ca93
Moving resource enricher
LikeTheSalad May 16, 2025
5e4bff5
Passing resource config to resource enrichment
LikeTheSalad May 16, 2025
dcbbd09
Enriching log resources
LikeTheSalad May 16, 2025
9afe9b3
Extracting attribute config to an independent
LikeTheSalad May 16, 2025
f18a840
Using resource and attribute configs for logs
LikeTheSalad May 16, 2025
aab7e0e
Testing log resource enrichment
LikeTheSalad May 17, 2025
5a7b010
Adding timestamp.us verification
LikeTheSalad May 19, 2025
e22e428
Extracting getting timestamp.us tool
LikeTheSalad May 19, 2025
0e6247a
Adding timestamp.us to crash events
LikeTheSalad May 19, 2025
2d4e4bb
Moving attributeconfig to elasticattr
LikeTheSalad May 19, 2025
94efe4d
Adding error.id
LikeTheSalad May 19, 2025
ba187a5
Improving event tests
LikeTheSalad May 19, 2025
9075512
Updatig tests
LikeTheSalad May 20, 2025
ac4a852
Validating error.id
LikeTheSalad May 20, 2025
c835f55
Adding java stacktrace testdata
LikeTheSalad May 21, 2025
428ce3a
Adding groupingkey go files
LikeTheSalad May 21, 2025
73bb483
Adding stacktrace without message to testdata
LikeTheSalad May 21, 2025
05a3984
Creating test for curating stacktraces
LikeTheSalad May 21, 2025
251b2e6
Curating stacktraces
LikeTheSalad May 21, 2025
7a4c01e
Validating curating different types of stacktraces
LikeTheSalad May 21, 2025
14e1022
Updating tests
LikeTheSalad May 21, 2025
8e2d593
Creating grouping key for java stacktraces
LikeTheSalad May 22, 2025
19a9460
Updating tests
LikeTheSalad May 22, 2025
f34a28c
Adding grouping_key to crash events
LikeTheSalad May 22, 2025
e8bcd04
Adding error.type crash to crash events
LikeTheSalad May 22, 2025
63b629d
Using camelcase for variable names
LikeTheSalad May 22, 2025
0107628
Updating tests
LikeTheSalad May 22, 2025
e7b8f8a
Using observerdtimestamp when timestamp is not
LikeTheSalad May 22, 2025
4975a42
Adding validation for non-crash events
LikeTheSalad May 23, 2025
2560274
Validating only events are sent to the event
LikeTheSalad May 23, 2025
8596af8
Updating stacktrace test data
LikeTheSalad May 23, 2025
c39bb93
Merge branch 'main' into mobile-crash-support
LikeTheSalad May 23, 2025
afd7b69
Using elasticattr attributes
LikeTheSalad May 23, 2025
b7b3979
Adding license headers
LikeTheSalad May 23, 2025
c8bed90
Update enrichments/logs/internal/mobile/groupingkey.go
LikeTheSalad May 26, 2025
7464c1b
Keeping cause exception name
LikeTheSalad May 26, 2025
0945cfe
Removing stacktrace line numbers
LikeTheSalad May 26, 2025
628010c
Updating groupingkey tests
LikeTheSalad May 26, 2025
4bf601a
Updating event tests
LikeTheSalad May 26, 2025
6aa037b
Replacing sha256 by xxhash
LikeTheSalad May 27, 2025
462b27f
Replace SHA-256 with xxHash and optimize regex patterns in groupingke…
LikeTheSalad May 27, 2025
1126cb0
Update test expectations in event_test.go for xxHash output
LikeTheSalad May 27, 2025
39d9f55
Merge branch 'main' into mobile-crash-support
LikeTheSalad May 29, 2025
9edaab4
Make error grouping key conditional on Java language
LikeTheSalad May 29, 2025
c15e561
Addressing struct pointer lint issue
LikeTheSalad May 29, 2025
5dbfb38
Avoiding creating new map instances on each loop
LikeTheSalad May 29, 2025
ee3a9f2
Merge remote-tracking branch 'origin/main' into mobile-crash-support
LikeTheSalad May 29, 2025
b0db138
Merge remote-tracking branch 'origin/main' into mobile-crash-support
LikeTheSalad Jun 10, 2025
3976044
Move Android stacktrace test data to dedicated subfolder
LikeTheSalad Jun 10, 2025
e28bd47
Adding ios crash test files
LikeTheSalad Jun 10, 2025
e7fbdd8
Add Swift stack trace grouping key function using xxHash for iOS crashes
LikeTheSalad Jun 10, 2025
ff7dff8
Adding testdata curated swift stacktraces
LikeTheSalad Jun 10, 2025
eef5cf4
Removing unicode chars from ios stacktraces
LikeTheSalad Jun 11, 2025
865ad56
removing unicode chars
LikeTheSalad Jun 11, 2025
3b68636
Creating swift stacktrace groupingkey
LikeTheSalad Jun 11, 2025
684ecd0
Add Swift error grouping key support to enrichCrashEvent function
LikeTheSalad Jun 11, 2025
b3b8145
Renaming function
LikeTheSalad Jun 11, 2025
8d90190
Add test case for Swift error grouping key support
LikeTheSalad Jun 11, 2025
6c9f4be
Merge remote-tracking branch 'origin/main' into mobile-crash-support
LikeTheSalad Jun 18, 2025
641a02e
Ensuring swift grouping keys don't contain the
LikeTheSalad Jun 18, 2025
1948e4c
Updating swift curated stacktrace test files
LikeTheSalad Jun 19, 2025
9c63da1
Removing frame and image load addresses from swift
LikeTheSalad Jun 19, 2025
16eb4f2
Merge branch 'main' into mobile-crash-support
LikeTheSalad Jun 20, 2025
032fa99
Update from main and fix conflicts
LikeTheSalad Jul 1, 2025
edd08ab
Moving mobile enrichments to enricher
LikeTheSalad Jul 1, 2025
df8526d
Clean up
LikeTheSalad Jul 1, 2025
89822d0
Merge branch 'mobile-crash-support' of github.com:LikeTheSalad/opente…
LikeTheSalad Jul 1, 2025
6202e29
Moving GetTimestampUs out of elasticattr
LikeTheSalad Jul 1, 2025
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
2 changes: 2 additions & 0 deletions elasticattr/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ const (
SpanType = "span.type"
SpanSubtype = "span.subtype"
EventOutcome = "event.outcome"
EventKind = "event.kind"
SuccessCount = "event.success_count"
ServiceTargetType = "service.target.type"
ServiceTargetName = "service.target.name"
Expand All @@ -55,4 +56,5 @@ const (
ErrorExceptionHandled = "error.exception.handled"
ErrorGroupingKey = "error.grouping_key"
ErrorGroupingName = "error.grouping_name"
ErrorType = "error.type"
)
10 changes: 6 additions & 4 deletions enrichments/enricher.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (e *Enricher) EnrichTraces(pt ptrace.Traces) {
resSpans := pt.ResourceSpans()
for i := 0; i < resSpans.Len(); i++ {
resSpan := resSpans.At(i)
elastic.EnrichResource(resSpan.Resource(), e.Config)
elastic.EnrichResource(resSpan.Resource(), e.Config.Resource)
scopeSpans := resSpan.ScopeSpans()
for j := 0; j < scopeSpans.Len(); j++ {
scopeSpan := scopeSpans.At(j)
Expand All @@ -63,14 +63,16 @@ func (e *Enricher) EnrichLogs(pl plog.Logs) {
resLogs := pl.ResourceLogs()
for i := 0; i < resLogs.Len(); i++ {
resLog := resLogs.At(i)
elastic.EnrichResource(resLog.Resource(), e.Config)
resource := resLog.Resource()
elastic.EnrichResource(resource, e.Config.Resource)
resourceAttrs := resource.Attributes().AsRaw()
scopeLogs := resLog.ScopeLogs()
for j := 0; j < scopeLogs.Len(); j++ {
scopeSpan := scopeLogs.At(j)
elastic.EnrichScope(scopeSpan.Scope(), e.Config)
logRecords := scopeSpan.LogRecords()
for k := 0; k < logRecords.Len(); k++ {
elastic.EnrichLog(logRecords.At(k), e.Config)
elastic.EnrichLog(resourceAttrs, logRecords.At(k), e.Config)
}
}
}
Expand All @@ -83,7 +85,7 @@ func (e *Enricher) EnrichMetrics(pl pmetric.Metrics) {
for i := 0; i < resMetrics.Len(); i++ {
resMetric := resMetrics.At(i)
elastic.EnrichMetric(resMetric, e.Config)
elastic.EnrichResource(resMetric.Resource(), e.Config)
elastic.EnrichResource(resMetric.Resource(), e.Config.Resource)
scopeMetics := resMetric.ScopeMetrics()
for j := 0; j < scopeMetics.Len(); j++ {
scopeMetric := scopeMetics.At(j)
Expand Down
24 changes: 23 additions & 1 deletion enrichments/internal/elastic/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,35 @@ package elastic
import (
"github.com/elastic/opentelemetry-lib/elasticattr"
"github.com/elastic/opentelemetry-lib/enrichments/config"
"github.com/elastic/opentelemetry-lib/enrichments/internal/elastic/mobile"
"go.opentelemetry.io/collector/pdata/plog"
)

func EnrichLog(log plog.LogRecord, cfg config.Config) {
func EnrichLog(resourceAttrs map[string]any, log plog.LogRecord, cfg config.Config) {
if cfg.Log.ProcessorEvent.Enabled {
if _, exists := log.Attributes().Get(elasticattr.ProcessorEvent); !exists {
log.Attributes().PutStr(elasticattr.ProcessorEvent, "log")
}
}
eventName, ok := getEventName(log)
if ok {
ctx := mobile.EventContext{
ResourceAttributes: resourceAttrs,
EventName: eventName,
}
mobile.EnrichLogEvent(ctx, log)
}
}

// getEventName returns the event name from the log record.
// If the event name is not set, it returns an empty string.
func getEventName(logRecord plog.LogRecord) (string, bool) {
if logRecord.EventName() != "" {
return logRecord.EventName(), true
}
attributeValue, ok := logRecord.Attributes().Get("event.name")
if ok {
return attributeValue.AsString(), true
}
return "", false
}
86 changes: 86 additions & 0 deletions enrichments/internal/elastic/mobile/event.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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.

package mobile

import (
"crypto/rand"
"encoding/hex"
"io"

"github.com/elastic/opentelemetry-lib/elasticattr"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
)

// EventContext contains contextual information for log event enrichment
type EventContext struct {
ResourceAttributes map[string]any
EventName string
}

func EnrichLogEvent(ctx EventContext, logRecord plog.LogRecord) {
logRecord.Attributes().PutStr(elasticattr.EventKind, "event")

if ctx.EventName == "device.crash" {
enrichCrashEvent(logRecord, ctx.ResourceAttributes)
}
}

func enrichCrashEvent(logRecord plog.LogRecord, resourceAttrs map[string]any) {
timestamp := logRecord.Timestamp()
if timestamp == 0 {
timestamp = logRecord.ObservedTimestamp()
}
logRecord.Attributes().PutStr(elasticattr.ProcessorEvent, "error")
logRecord.Attributes().PutInt(elasticattr.TimestampUs, getTimestampUs(timestamp))
if id, err := newUniqueID(); err == nil {
logRecord.Attributes().PutStr(elasticattr.ErrorID, id)
}
stacktrace, ok := logRecord.Attributes().Get("exception.stacktrace")
if ok {
language, hasLanguage := resourceAttrs["telemetry.sdk.language"]
if hasLanguage {
switch language {
case "java":
logRecord.Attributes().PutStr(elasticattr.ErrorGroupingKey, CreateJavaStacktraceGroupingKey(stacktrace.AsString()))
case "swift":
if key, err := CreateSwiftStacktraceGroupingKey(stacktrace.AsString()); err == nil {
logRecord.Attributes().PutStr(elasticattr.ErrorGroupingKey, key)
}
}
}
}
logRecord.Attributes().PutStr(elasticattr.ErrorType, "crash")
}

func newUniqueID() (string, error) {
var u [16]byte
if _, err := io.ReadFull(rand.Reader, u[:]); err != nil {
return "", err
}

// convert to string
buf := make([]byte, 32)
hex.Encode(buf, u[:])

return string(buf), nil
}

func getTimestampUs(ts pcommon.Timestamp) int64 {
return int64(ts) / 1000
}
183 changes: 183 additions & 0 deletions enrichments/internal/elastic/mobile/event_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
// Licensed to Elasticsearch B.V. under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Elasticsearch B.V. licenses this file to you 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.

package mobile

import (
"testing"
"time"

"maps"

"github.com/google/go-cmp/cmp"
"github.com/stretchr/testify/assert"
"go.opentelemetry.io/collector/pdata/pcommon"
"go.opentelemetry.io/collector/pdata/plog"
)

func TestEnrichEvents(t *testing.T) {
now := time.Unix(3600, 0)
timestamp := pcommon.NewTimestampFromTime(now)
javaStacktrace := "Exception in thread \"main\" java.lang.RuntimeException: Test exception\n at com.example.GenerateTrace.methodB(GenerateTrace.java:13)\n at com.example.GenerateTrace.methodA(GenerateTrace.java:9)\n at com.example.GenerateTrace.main(GenerateTrace.java:5)"
javaStacktraceHash := "e25c4196dc720d91"

swiftStacktrace := readSwiftStacktraceFile(t, "thread-8-crash.txt")
swiftStacktraceHash := "e737b0da1c8f9d5a"

for _, tc := range []struct {
name string
eventName string
input func() plog.LogRecord
resourceAttrs map[string]any
expectedAttributes map[string]any
}{
{
name: "crash_event_java",
eventName: "device.crash",
resourceAttrs: map[string]any{
"telemetry.sdk.language": "java",
},
input: func() plog.LogRecord {
logRecord := plog.NewLogRecord()
logRecord.SetTimestamp(timestamp)
logRecord.Attributes().PutStr("event.name", "device.crash")
logRecord.Attributes().PutStr("exception.message", "Exception message")
logRecord.Attributes().PutStr("exception.type", "java.lang.RuntimeException")
logRecord.Attributes().PutStr("exception.stacktrace", javaStacktrace)
return logRecord
},
expectedAttributes: map[string]any{
"processor.event": "error",
"timestamp.us": timestamp.AsTime().UnixMicro(),
"error.grouping_key": javaStacktraceHash,
"error.type": "crash",
"event.kind": "event",
},
},
{
name: "crash_event_without_timestamp_java",
eventName: "device.crash",
resourceAttrs: map[string]any{
"telemetry.sdk.language": "java",
},
input: func() plog.LogRecord {
logRecord := plog.NewLogRecord()
logRecord.SetObservedTimestamp(timestamp)
logRecord.Attributes().PutStr("event.name", "device.crash")
logRecord.Attributes().PutStr("exception.message", "Exception message")
logRecord.Attributes().PutStr("exception.type", "java.lang.RuntimeException")
logRecord.Attributes().PutStr("exception.stacktrace", javaStacktrace)
return logRecord
},
expectedAttributes: map[string]any{
"processor.event": "error",
"timestamp.us": timestamp.AsTime().UnixMicro(),
"error.grouping_key": javaStacktraceHash,
"error.type": "crash",
"event.kind": "event",
},
},
{
name: "crash_event_non_java",
eventName: "device.crash",
resourceAttrs: map[string]any{
"telemetry.sdk.language": "go",
},
input: func() plog.LogRecord {
logRecord := plog.NewLogRecord()
logRecord.SetTimestamp(timestamp)
logRecord.Attributes().PutStr("event.name", "device.crash")
logRecord.Attributes().PutStr("exception.message", "Exception message")
logRecord.Attributes().PutStr("exception.type", "go.error")
logRecord.Attributes().PutStr("exception.stacktrace", javaStacktrace)
return logRecord
},
expectedAttributes: map[string]any{
"processor.event": "error",
"timestamp.us": timestamp.AsTime().UnixMicro(),
"error.type": "crash",
"event.kind": "event",
},
},
{
name: "non_crash_event",
eventName: "othername",
resourceAttrs: map[string]any{},
input: func() plog.LogRecord {
logRecord := plog.NewLogRecord()
logRecord.Attributes().PutStr("event.name", "othername")
return logRecord
},
expectedAttributes: map[string]any{
"event.kind": "event",
},
},
{
name: "crash_event_swift",
eventName: "device.crash",
resourceAttrs: map[string]any{
"telemetry.sdk.language": "swift",
},
input: func() plog.LogRecord {
logRecord := plog.NewLogRecord()
logRecord.SetTimestamp(timestamp)
logRecord.Attributes().PutStr("event.name", "device.crash")
logRecord.Attributes().PutStr("exception.type", "SIGTRAP")
logRecord.Attributes().PutStr("exception.stacktrace", swiftStacktrace)
return logRecord
},
expectedAttributes: map[string]any{
"processor.event": "error",
"timestamp.us": timestamp.AsTime().UnixMicro(),
"error.grouping_key": swiftStacktraceHash,
"error.type": "crash",
"event.kind": "event",
},
},
} {
t.Run(tc.name, func(t *testing.T) {
inputLogRecord := tc.input()

maps.Copy(tc.expectedAttributes, inputLogRecord.Attributes().AsRaw())

ctx := EventContext{
ResourceAttributes: tc.resourceAttrs,
EventName: tc.eventName,
}
EnrichLogEvent(ctx, inputLogRecord)

assert.Empty(t, cmp.Diff(inputLogRecord.Attributes().AsRaw(), tc.expectedAttributes, ignoreMapKey("error.id")))
errorId, ok := inputLogRecord.Attributes().Get("error.id")
if ok {
assert.Equal(t, "device.crash", tc.eventName)
assert.Equal(t, 32, len(errorId.AsString()))
} else {
assert.NotEqual(t, "device.crash", tc.eventName)
}
})
}
}

func ignoreMapKey(k string) cmp.Option {
return cmp.FilterPath(func(p cmp.Path) bool {
mapIndex, ok := p.Last().(cmp.MapIndex)
if !ok {
return false
}
return mapIndex.Key().String() == k
}, cmp.Ignore())
}
Loading
Loading