Skip to content

Commit 0cb610d

Browse files
authored
[exporter/coralogix] Add profile support for the Coralogix exporter (#38012)
#### Description Add support for profiles to the Coralogix exporter. #### Link to tracking issue Fixes #38011 #### Testing Added new unit tests. Signed-off-by: Israel Blancas <[email protected]>
1 parent 4c094a7 commit 0cb610d

File tree

10 files changed

+343
-15
lines changed

10 files changed

+343
-15
lines changed
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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: coralogixexporter
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 profiles to the Coralogix exporter.
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: [38011]
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+
This change adds support for profiles to the Coralogix exporter.
20+
It allows users to export profiles to Coralogix.
21+
22+
# If your change doesn't affect end users or the exported elements of any package,
23+
# you should instead start your pull request title with [chore] or use the "Skip Changelog" label.
24+
# Optional: The change log or logs in which this entry should be included.
25+
# e.g. '[user]' or '[user, api]'
26+
# Include 'user' if the change is relevant to end users.
27+
# Include 'api' if there is a change to a library API.
28+
# Default: '[user]'
29+
change_logs: []

exporter/coralogixexporter/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33
<!-- status autogenerated section -->
44
| Status | |
55
| ------------- |-----------|
6-
| Stability | [beta]: traces, metrics, logs |
6+
| Stability | [alpha]: profiles |
7+
| | [beta]: traces, metrics, logs |
78
| Distributions | [contrib] |
89
| Issues | [![Open issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aopen%20label%3Aexporter%2Fcoralogix%20&label=open&color=orange&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aopen+is%3Aissue+label%3Aexporter%2Fcoralogix) [![Closed issues](https://img.shields.io/github/issues-search/open-telemetry/opentelemetry-collector-contrib?query=is%3Aissue%20is%3Aclosed%20label%3Aexporter%2Fcoralogix%20&label=closed&color=blue&logo=opentelemetry)](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues?q=is%3Aclosed+is%3Aissue+label%3Aexporter%2Fcoralogix) |
910
| [Code Owners](https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/CONTRIBUTING.md#becoming-a-code-owner) | [@povilasv](https://www.github.com/povilasv), [@iblancasa](https://www.github.com/iblancasa), [@douglascamata](https://www.github.com/douglascamata) |
1011

12+
[alpha]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#alpha
1113
[beta]: https://github.com/open-telemetry/opentelemetry-collector/blob/main/docs/component-stability.md#beta
1214
[contrib]: https://github.com/open-telemetry/opentelemetry-collector-releases/tree/main/distributions/otelcol-contrib
1315
<!-- end autogenerated section -->

exporter/coralogixexporter/config.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package coralogixexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/coralogixexporter"
55

66
import (
7+
"errors"
78
"fmt"
89

910
"go.opentelemetry.io/collector/config/configgrpc"
@@ -43,6 +44,9 @@ type Config struct {
4344
// The Coralogix logs ingress endpoint
4445
Logs configgrpc.ClientConfig `mapstructure:"logs"`
4546

47+
// The Coralogix profiles ingress endpoint
48+
Profiles configgrpc.ClientConfig `mapstructure:"profiles"`
49+
4650
// Your Coralogix private key (sensitive) for authentication
4751
PrivateKey configopaque.String `mapstructure:"private_key"`
4852

@@ -69,14 +73,15 @@ func (c *Config) Validate() error {
6973
if isEmpty(c.Domain) &&
7074
isEmpty(c.Traces.Endpoint) &&
7175
isEmpty(c.Metrics.Endpoint) &&
72-
isEmpty(c.Logs.Endpoint) {
73-
return fmt.Errorf("`domain` or `traces.endpoint` or `metrics.endpoint` or `logs.endpoint` not specified, please fix the configuration")
76+
isEmpty(c.Logs.Endpoint) &&
77+
isEmpty(c.Profiles.Endpoint) {
78+
return errors.New("`domain` or `traces.endpoint` or `metrics.endpoint` or `logs.endpoint` or `profiles.endpoint` not specified, please fix the configuration")
7479
}
7580
if c.PrivateKey == "" {
76-
return fmt.Errorf("`private_key` not specified, please fix the configuration")
81+
return errors.New("`private_key` not specified, please fix the configuration")
7782
}
7883
if c.AppName == "" {
79-
return fmt.Errorf("`application_name` not specified, please fix the configuration")
84+
return errors.New("`application_name` not specified, please fix the configuration")
8085
}
8186

8287
// check if headers exists

exporter/coralogixexporter/factory.go

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,21 @@ import (
1515
"go.opentelemetry.io/collector/consumer"
1616
"go.opentelemetry.io/collector/exporter"
1717
"go.opentelemetry.io/collector/exporter/exporterhelper"
18+
"go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper"
19+
"go.opentelemetry.io/collector/exporter/xexporter"
1820

1921
"github.com/open-telemetry/opentelemetry-collector-contrib/exporter/coralogixexporter/internal/metadata"
2022
)
2123

2224
// NewFactory by Coralogix
2325
func NewFactory() exporter.Factory {
24-
return exporter.NewFactory(
26+
return xexporter.NewFactory(
2527
metadata.Type,
2628
createDefaultConfig,
27-
exporter.WithTraces(createTraceExporter, metadata.TracesStability),
28-
exporter.WithMetrics(createMetricsExporter, metadata.MetricsStability),
29-
exporter.WithLogs(createLogsExporter, metadata.LogsStability),
29+
xexporter.WithTraces(createTraceExporter, metadata.TracesStability),
30+
xexporter.WithMetrics(createMetricsExporter, metadata.MetricsStability),
31+
xexporter.WithLogs(createLogsExporter, metadata.LogsStability),
32+
xexporter.WithProfiles(createProfilesExporter, metadata.ProfilesStability),
3033
)
3134
}
3235

@@ -130,3 +133,27 @@ func createLogsExporter(
130133
exporterhelper.WithShutdown(oce.shutdown),
131134
)
132135
}
136+
137+
func createProfilesExporter(
138+
ctx context.Context,
139+
set exporter.Settings,
140+
cfg component.Config,
141+
) (xexporter.Profiles, error) {
142+
oce, err := newProfilesExporter(cfg, set)
143+
if err != nil {
144+
return nil, err
145+
}
146+
oCfg := cfg.(*Config)
147+
return xexporterhelper.NewProfilesExporter(
148+
ctx,
149+
set,
150+
cfg,
151+
oce.pushProfiles,
152+
exporterhelper.WithCapabilities(consumer.Capabilities{MutatesData: true}),
153+
exporterhelper.WithTimeout(oCfg.TimeoutSettings),
154+
exporterhelper.WithRetry(oCfg.BackOffConfig),
155+
exporterhelper.WithQueue(oCfg.QueueSettings),
156+
exporterhelper.WithStart(oce.start),
157+
exporterhelper.WithShutdown(oce.shutdown),
158+
)
159+
}

exporter/coralogixexporter/go.mod

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,13 @@ require (
1717
go.opentelemetry.io/collector/consumer v1.26.1-0.20250219144032-c2af75d88e89
1818
go.opentelemetry.io/collector/consumer/consumererror v0.120.1-0.20250219144032-c2af75d88e89
1919
go.opentelemetry.io/collector/exporter v0.120.1-0.20250219144032-c2af75d88e89
20+
go.opentelemetry.io/collector/exporter/exporterhelper/xexporterhelper v0.120.1-0.20250219144032-c2af75d88e89
2021
go.opentelemetry.io/collector/exporter/exportertest v0.120.1-0.20250219144032-c2af75d88e89
22+
go.opentelemetry.io/collector/exporter/xexporter v0.120.1-0.20250219144032-c2af75d88e89
2123
go.opentelemetry.io/collector/pdata v1.26.1-0.20250219144032-c2af75d88e89
24+
go.opentelemetry.io/collector/pdata/pprofile v0.120.1-0.20250219144032-c2af75d88e89
2225
go.uber.org/goleak v1.3.0
26+
go.uber.org/zap v1.27.0
2327
google.golang.org/genproto/googleapis/rpc v0.0.0-20250115164207-1a7da9e5054f
2428
google.golang.org/grpc v1.70.0
2529
)
@@ -50,15 +54,15 @@ require (
5054
go.opentelemetry.io/collector/client v1.26.1-0.20250219144032-c2af75d88e89 // indirect
5155
go.opentelemetry.io/collector/config/configauth v0.120.1-0.20250219144032-c2af75d88e89 // indirect
5256
go.opentelemetry.io/collector/config/confignet v1.26.1-0.20250219144032-c2af75d88e89 // indirect
57+
go.opentelemetry.io/collector/consumer/consumererror/xconsumererror v0.120.1-0.20250219144032-c2af75d88e89 // indirect
5358
go.opentelemetry.io/collector/consumer/consumertest v0.120.1-0.20250219144032-c2af75d88e89 // indirect
5459
go.opentelemetry.io/collector/consumer/xconsumer v0.120.1-0.20250219144032-c2af75d88e89 // indirect
55-
go.opentelemetry.io/collector/exporter/xexporter v0.120.1-0.20250219144032-c2af75d88e89 // indirect
5660
go.opentelemetry.io/collector/extension v0.120.1-0.20250219144032-c2af75d88e89 // indirect
5761
go.opentelemetry.io/collector/extension/auth v0.120.1-0.20250219144032-c2af75d88e89 // indirect
5862
go.opentelemetry.io/collector/extension/xextension v0.120.1-0.20250219144032-c2af75d88e89 // indirect
5963
go.opentelemetry.io/collector/featuregate v1.26.1-0.20250219144032-c2af75d88e89 // indirect
60-
go.opentelemetry.io/collector/pdata/pprofile v0.120.1-0.20250219144032-c2af75d88e89 // indirect
6164
go.opentelemetry.io/collector/pipeline v0.120.1-0.20250219144032-c2af75d88e89 // indirect
65+
go.opentelemetry.io/collector/pipeline/xpipeline v0.120.1-0.20250219144032-c2af75d88e89 // indirect
6266
go.opentelemetry.io/collector/receiver v0.120.1-0.20250219144032-c2af75d88e89 // indirect
6367
go.opentelemetry.io/collector/receiver/receivertest v0.120.1-0.20250219144032-c2af75d88e89 // indirect
6468
go.opentelemetry.io/collector/receiver/xreceiver v0.120.1-0.20250219144032-c2af75d88e89 // indirect
@@ -69,7 +73,6 @@ require (
6973
go.opentelemetry.io/otel/sdk/metric v1.34.0 // indirect
7074
go.opentelemetry.io/otel/trace v1.34.0 // indirect
7175
go.uber.org/multierr v1.11.0 // indirect
72-
go.uber.org/zap v1.27.0 // indirect
7376
golang.org/x/net v0.34.0 // indirect
7477
golang.org/x/sys v0.29.0 // indirect
7578
golang.org/x/text v0.21.0 // indirect

exporter/coralogixexporter/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.

exporter/coralogixexporter/internal/metadata/generated_status.go

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

exporter/coralogixexporter/metadata.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ type: coralogix
33
status:
44
class: exporter
55
stability:
6+
alpha: [profiles]
67
beta: [traces, metrics, logs]
78
distributions: [contrib]
89
codeowners:
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package coralogixexporter // import "github.com/open-telemetry/opentelemetry-collector-contrib/exporter/coralogixexporter"
5+
6+
import (
7+
"context"
8+
"errors"
9+
"fmt"
10+
"runtime"
11+
12+
"go.opentelemetry.io/collector/component"
13+
"go.opentelemetry.io/collector/config/configgrpc"
14+
"go.opentelemetry.io/collector/config/configopaque"
15+
"go.opentelemetry.io/collector/exporter"
16+
"go.opentelemetry.io/collector/pdata/pprofile"
17+
"go.opentelemetry.io/collector/pdata/pprofile/pprofileotlp"
18+
"go.uber.org/zap"
19+
"google.golang.org/grpc"
20+
"google.golang.org/grpc/metadata"
21+
)
22+
23+
func newProfilesExporter(cfg component.Config, set exporter.Settings) (*profilesExporter, error) {
24+
oCfg := cfg.(*Config)
25+
26+
if isEmpty(oCfg.Domain) && isEmpty(oCfg.Profiles.Endpoint) {
27+
return nil, errors.New("coralogix exporter config requires `domain` or `profiles.endpoint` configuration")
28+
}
29+
30+
userAgent := fmt.Sprintf("%s/%s (%s/%s)",
31+
set.BuildInfo.Description, set.BuildInfo.Version, runtime.GOOS, runtime.GOARCH)
32+
33+
return &profilesExporter{
34+
config: oCfg,
35+
settings: set.TelemetrySettings,
36+
userAgent: userAgent,
37+
}, nil
38+
}
39+
40+
type profilesExporter struct {
41+
// Input configuration.
42+
config *Config
43+
44+
profilesExporter pprofileotlp.GRPCClient
45+
clientConn *grpc.ClientConn
46+
callOptions []grpc.CallOption
47+
48+
settings component.TelemetrySettings
49+
50+
// Default user-agent header.
51+
userAgent string
52+
}
53+
54+
func (e *profilesExporter) start(ctx context.Context, host component.Host) (err error) {
55+
switch {
56+
case !isEmpty(e.config.Profiles.Endpoint):
57+
if e.clientConn, err = e.config.Profiles.ToClientConn(ctx, host, e.settings, configgrpc.WithGrpcDialOption(grpc.WithUserAgent(e.userAgent))); err != nil {
58+
return err
59+
}
60+
case !isEmpty(e.config.Domain):
61+
if e.clientConn, err = e.config.getDomainGrpcSettings().ToClientConn(ctx, host, e.settings, configgrpc.WithGrpcDialOption(grpc.WithUserAgent(e.userAgent))); err != nil {
62+
return err
63+
}
64+
}
65+
66+
e.profilesExporter = pprofileotlp.NewGRPCClient(e.clientConn)
67+
if e.config.Profiles.Headers == nil {
68+
e.config.Profiles.Headers = make(map[string]configopaque.String)
69+
}
70+
e.config.Profiles.Headers["Authorization"] = configopaque.String("Bearer " + string(e.config.PrivateKey))
71+
72+
e.callOptions = []grpc.CallOption{
73+
grpc.WaitForReady(e.config.Profiles.WaitForReady),
74+
}
75+
76+
return
77+
}
78+
79+
func (e *profilesExporter) pushProfiles(ctx context.Context, md pprofile.Profiles) error {
80+
rss := md.ResourceProfiles()
81+
for i := 0; i < rss.Len(); i++ {
82+
resourceProfile := rss.At(i)
83+
appName, subsystem := e.config.getMetadataFromResource(resourceProfile.Resource())
84+
resourceProfile.Resource().Attributes().PutStr(cxAppNameAttrName, appName)
85+
resourceProfile.Resource().Attributes().PutStr(cxSubsystemNameAttrName, subsystem)
86+
}
87+
88+
resp, err := e.profilesExporter.Export(e.enhanceContext(ctx), pprofileotlp.NewExportRequestFromProfiles(md), e.callOptions...)
89+
if err != nil {
90+
return processError(err)
91+
}
92+
93+
partialSuccess := resp.PartialSuccess()
94+
if !(partialSuccess.ErrorMessage() == "" && partialSuccess.RejectedProfiles() == 0) {
95+
e.settings.Logger.Error("Partial success response from Coralogix",
96+
zap.String("message", partialSuccess.ErrorMessage()),
97+
zap.Int64("rejected_profiles", partialSuccess.RejectedProfiles()),
98+
)
99+
}
100+
return nil
101+
}
102+
103+
func (e *profilesExporter) shutdown(context.Context) error {
104+
if e.clientConn == nil {
105+
return nil
106+
}
107+
return e.clientConn.Close()
108+
}
109+
110+
func (e *profilesExporter) enhanceContext(ctx context.Context) context.Context {
111+
md := metadata.New(nil)
112+
for k, v := range e.config.Profiles.Headers {
113+
md.Set(k, string(v))
114+
}
115+
return metadata.NewOutgoingContext(ctx, md)
116+
}

0 commit comments

Comments
 (0)