Skip to content

Commit 4e4045f

Browse files
committed
[extension/azure_encoding] Implement general Azure Resource Log parsing functionality
1 parent 36a7cf3 commit 4e4045f

22 files changed

+1937
-8
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. receiver/filelog)
7+
component: extension/azure_encoding
8+
9+
# A brief description of the change. Surround your text with quotes ("") if it needs to start with a backtick (`).
10+
note: Implement general Azure Resource Log parsing functionality
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: [41725]
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:
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: []

extension/encoding/azureencodingextension/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ Currently supported following Azure Resource Logs export formats:
142142
143143
Currently only subset of available Azure Resource Logs Categories properly translated using OpenTelemetry SemConv.
144144
145+
[Transformation rules from Azure Resource Logs fields to OpenTelemetry](./internal/unmarshaler/logs/README.md).
146+
145147
Unsupported Categories simply copies attributes from "properties" field of incoming Azure Log Record to OpenTelemetry Log Attributes as a strings.
146148
147149
***time_formats (Optional)***

extension/encoding/azureencodingextension/extension.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,16 @@ var (
2222
)
2323

2424
type azureExtension struct {
25-
config *Config
25+
config *Config
26+
logUnmarshaler plog.Unmarshaler
2627
}
2728

2829
func (*azureExtension) UnmarshalTraces(_ []byte) (ptrace.Traces, error) {
2930
return ptrace.Traces{}, errors.New("not implemented yet")
3031
}
3132

32-
func (*azureExtension) UnmarshalLogs(_ []byte) (plog.Logs, error) {
33-
return plog.Logs{}, errors.New("not implemented yet")
33+
func (ex *azureExtension) UnmarshalLogs(buf []byte) (plog.Logs, error) {
34+
return ex.logUnmarshaler.UnmarshalLogs(buf)
3435
}
3536

3637
func (*azureExtension) UnmarshalMetrics(_ []byte) (pmetric.Metrics, error) {

extension/encoding/azureencodingextension/factory.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111

1212
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/azureencodingextension/internal/metadata"
1313
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/azureencodingextension/internal/unmarshaler"
14+
"github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/azureencodingextension/internal/unmarshaler/logs"
1415
)
1516

1617
func NewFactory() extension.Factory {
@@ -22,11 +23,17 @@ func NewFactory() extension.Factory {
2223
)
2324
}
2425

25-
func createExtension(_ context.Context, _ extension.Settings, cfg component.Config) (extension.Extension, error) {
26+
func createExtension(_ context.Context, settings extension.Settings, cfg component.Config) (extension.Extension, error) {
2627
config := cfg.(*Config)
2728

2829
return &azureExtension{
2930
config: config,
31+
logUnmarshaler: logs.NewAzureResourceLogsUnmarshaler(
32+
settings.BuildInfo,
33+
settings.Logger,
34+
config.Format,
35+
config.Logs,
36+
),
3037
}, nil
3138
}
3239

extension/encoding/azureencodingextension/go.mod

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ module github.com/open-telemetry/opentelemetry-collector-contrib/extension/encod
33
go 1.24.0
44

55
require (
6+
github.com/goccy/go-json v0.10.5
7+
github.com/json-iterator/go v1.1.12
68
github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding v0.140.1
9+
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden v0.140.1
10+
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest v0.140.1
11+
github.com/relvacode/iso8601 v1.7.0
712
github.com/stretchr/testify v1.11.1
813
go.opentelemetry.io/collector/component v1.46.1-0.20251120204106-2e9c82787618
914
go.opentelemetry.io/collector/component/componenttest v0.140.1-0.20251120204106-2e9c82787618
@@ -12,39 +17,46 @@ require (
1217
go.opentelemetry.io/collector/extension v1.46.1-0.20251120204106-2e9c82787618
1318
go.opentelemetry.io/collector/extension/extensiontest v0.140.1-0.20251120204106-2e9c82787618
1419
go.opentelemetry.io/collector/pdata v1.46.1-0.20251120204106-2e9c82787618
20+
go.opentelemetry.io/otel v1.38.0
1521
go.uber.org/goleak v1.3.0
22+
go.uber.org/zap v1.27.0
1623
)
1724

1825
require (
26+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
1927
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
2028
github.com/go-logr/logr v1.4.3 // indirect
2129
github.com/go-logr/stdr v1.2.2 // indirect
2230
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
2331
github.com/gobwas/glob v0.2.3 // indirect
2432
github.com/google/uuid v1.6.0 // indirect
2533
github.com/hashicorp/go-version v1.7.0 // indirect
26-
github.com/json-iterator/go v1.1.12 // indirect
2734
github.com/knadh/koanf/maps v0.1.2 // indirect
2835
github.com/knadh/koanf/providers/confmap v1.0.0 // indirect
2936
github.com/knadh/koanf/v2 v2.3.0 // indirect
3037
github.com/mitchellh/copystructure v1.2.0 // indirect
3138
github.com/mitchellh/reflectwalk v1.0.2 // indirect
3239
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
3340
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
41+
github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil v0.140.1 // indirect
3442
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
3543
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
3644
go.opentelemetry.io/collector/featuregate v1.46.1-0.20251120204106-2e9c82787618 // indirect
3745
go.opentelemetry.io/collector/pdata/pprofile v0.140.1-0.20251120204106-2e9c82787618 // indirect
38-
go.opentelemetry.io/otel v1.38.0 // indirect
3946
go.opentelemetry.io/otel/metric v1.38.0 // indirect
4047
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
4148
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
4249
go.opentelemetry.io/otel/trace v1.38.0 // indirect
4350
go.uber.org/multierr v1.11.0 // indirect
44-
go.uber.org/zap v1.27.0 // indirect
4551
go.yaml.in/yaml/v3 v3.0.4 // indirect
4652
golang.org/x/sys v0.37.0 // indirect
4753
gopkg.in/yaml.v3 v3.0.1 // indirect
4854
)
4955

5056
replace github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding => ../
57+
58+
replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatautil => ../../../pkg/pdatautil
59+
60+
replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/pdatatest => ../../../pkg/pdatatest
61+
62+
replace github.com/open-telemetry/opentelemetry-collector-contrib/pkg/golden => ../../../pkg/golden

extension/encoding/azureencodingextension/go.sum

Lines changed: 6 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

extension/encoding/azureencodingextension/internal/unmarshaler/helpers.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,106 @@
33

44
package unmarshaler // import "github.com/open-telemetry/opentelemetry-collector-contrib/extension/encoding/azureencodingextension/internal/unmarshaler"
55

6+
import (
7+
"encoding/json"
8+
"fmt"
9+
"time"
10+
11+
"github.com/relvacode/iso8601"
12+
"go.opentelemetry.io/collector/pdata/pcommon"
13+
)
14+
615
type RecordsBatchFormat string
716

17+
// Supported wrapper formats of Azure Logs Records batch
818
const (
919
FormatEventHub RecordsBatchFormat = "eventhub"
1020
FormatBlobStorage RecordsBatchFormat = "blobstorage"
1121
)
22+
23+
// JSON Path expressions that matches specific wrapper format
24+
const (
25+
// As exported to Azure Event Hub, e.g. `{"records": [ {...}, {...} ]}`
26+
JSONPathEventHubLogRecords = "$.records[*]"
27+
// As exported to Azure Blob Storage, e.g. `[ {...}, {...} ]`
28+
JSONPathBlobStorageLogRecords = "$[*]"
29+
)
30+
31+
// Commonly used attributes non-SemConv attributes across all telemetry signals
32+
const (
33+
AttributeAzureCategory = "azure.category"
34+
AttributeAzureOperationName = "azure.operation.name"
35+
)
36+
37+
const originalSuffix = ".original"
38+
39+
// AsTimestamp tries to parse a string with timestamp into OpenTelemetry
40+
// using provided list of formats layouts.
41+
// If not formats provided or parsing using them failed - will use an ISO8601 parser.
42+
// If the string cannot be parsed, it will return zero and the error.
43+
func AsTimestamp(s string, formats ...string) (pcommon.Timestamp, error) {
44+
var err error
45+
var t time.Time
46+
47+
// Try parsing with provided formats first
48+
for _, format := range formats {
49+
if t, err = time.Parse(format, s); err == nil {
50+
return pcommon.Timestamp(t.UnixNano()), nil
51+
}
52+
}
53+
54+
// Fallback to ISO 8601 parsing if no format matches
55+
if t, err = iso8601.ParseString(s); err == nil {
56+
return pcommon.Timestamp(t.UnixNano()), nil
57+
}
58+
59+
return 0, err
60+
}
61+
62+
// AttrPutStrIf is a helper function to set a string attribute
63+
// only if the value is not empty
64+
func AttrPutStrIf(attrs pcommon.Map, attrKey, attrValue string) {
65+
if attrValue != "" {
66+
attrs.PutStr(attrKey, attrValue)
67+
}
68+
}
69+
70+
// AttrPutStrPtrIf is a helper function to set a string attribute
71+
// only if the value exists and is not empty
72+
func AttrPutStrPtrIf(attrs pcommon.Map, attrKey string, attrValue *string) {
73+
if attrValue != nil && *attrValue != "" {
74+
attrs.PutStr(attrKey, *attrValue)
75+
}
76+
}
77+
78+
// AttrPutIntNumberIf is a helper function to set an int64 attribute with defined key,
79+
// trying to parse it from json.Number value
80+
// If parsing failed - no attribute will be set
81+
func AttrPutIntNumberIf(attrs pcommon.Map, attrKey string, attrValue json.Number) {
82+
if i, err := attrValue.Int64(); err == nil {
83+
attrs.PutInt(attrKey, i)
84+
}
85+
}
86+
87+
// AttrPutIntNumberPtrIf is a same function as AttrPutIntNumberIf but
88+
// accepts a pointer to json.Number instead of value
89+
func AttrPutIntNumberPtrIf(attrs pcommon.Map, attrKey string, attrValue *json.Number) {
90+
if attrValue != nil {
91+
AttrPutIntNumberIf(attrs, attrKey, *attrValue)
92+
}
93+
}
94+
95+
// attrPutMap is a helper function to set a map attribute with defined key,
96+
// trying to parse it from raw value
97+
// If parsing failed - no attribute will be set
98+
func AttrPutMapIf(attrs pcommon.Map, attrKey string, attrValue any) {
99+
if attrKey == "" || attrValue == nil {
100+
return
101+
}
102+
103+
if err := attrs.PutEmpty(attrKey).FromRaw(attrValue); err != nil {
104+
// Failed to parse - put string representation of the attrValue
105+
attrs.Remove(attrKey)
106+
attrs.PutStr(attrKey+originalSuffix, fmt.Sprintf("%v", attrValue))
107+
}
108+
}

0 commit comments

Comments
 (0)