Skip to content

Commit 3c11054

Browse files
authored
feat: add support for exporting logs via wasitellog.Exporter (#51)
Signed-off-by: Joonas Bergius <joonas@cosmonic.com>
1 parent ae49804 commit 3c11054

File tree

11 files changed

+881
-15
lines changed

11 files changed

+881
-15
lines changed

x/wasitel/go.mod

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ module go.wasmcloud.dev/component/x/wasitel
33
go 1.23.2
44

55
require (
6-
go.opentelemetry.io/otel v1.31.0
7-
go.opentelemetry.io/otel/sdk v1.31.0
8-
go.opentelemetry.io/otel/trace v1.31.0
6+
go.opentelemetry.io/otel v1.32.0
7+
go.opentelemetry.io/otel/log v0.8.0
8+
go.opentelemetry.io/otel/sdk v1.32.0
9+
go.opentelemetry.io/otel/sdk/log v0.8.0
10+
go.opentelemetry.io/otel/trace v1.32.0
911
go.wasmcloud.dev/component v0.0.5
1012
)
1113

@@ -14,8 +16,8 @@ require (
1416
github.com/go-logr/stdr v1.2.2 // indirect
1517
github.com/google/uuid v1.6.0 // indirect
1618
go.bytecodealliance.org v0.4.0 // indirect
17-
go.opentelemetry.io/otel/metric v1.31.0 // indirect
18-
golang.org/x/sys v0.26.0 // indirect
19+
go.opentelemetry.io/otel/metric v1.32.0 // indirect
20+
golang.org/x/sys v0.27.0 // indirect
1921
)
2022

2123
replace go.wasmcloud.dev/component => ../../

x/wasitel/go.sum

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,19 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
1515
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
1616
go.bytecodealliance.org v0.4.0 h1:SRwgZIcXR54AmbJg9Y3AMgDlZlvD8dffteBYW+nCD3k=
1717
go.bytecodealliance.org v0.4.0/go.mod h1:hkdjfgQ/bFZYUucnm9cn0Q8/SHO3iT0rzskYlkV4Jy0=
18-
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
19-
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
20-
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
21-
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
22-
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
23-
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
24-
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
25-
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
26-
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
27-
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
18+
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
19+
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
20+
go.opentelemetry.io/otel/log v0.8.0 h1:egZ8vV5atrUWUbnSsHn6vB8R21G2wrKqNiDt3iWertk=
21+
go.opentelemetry.io/otel/log v0.8.0/go.mod h1:M9qvDdUTRCopJcGRKg57+JSQ9LgLBrwwfC32epk5NX8=
22+
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
23+
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
24+
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
25+
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
26+
go.opentelemetry.io/otel/sdk/log v0.8.0 h1:zg7GUYXqxk1jnGF/dTdLPrK06xJdrXgqgFLnI4Crxvs=
27+
go.opentelemetry.io/otel/sdk/log v0.8.0/go.mod h1:50iXr0UVwQrYS45KbruFrEt4LvAdCaWWgIrsN3ZQggo=
28+
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
29+
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
30+
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
31+
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
2832
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
2933
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

x/wasitel/wasitellog/client.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package wasitellog
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
"net/http"
9+
"net/url"
10+
11+
"go.wasmcloud.dev/component/net/wasihttp"
12+
"go.wasmcloud.dev/component/x/wasitel/wasitellog/internal/types"
13+
)
14+
15+
type client struct {
16+
config config
17+
httpClient *http.Client
18+
}
19+
20+
func newClient(opts ...Option) *client {
21+
cfg := newConfig(opts...)
22+
23+
wasiTransport := &wasihttp.Transport{}
24+
httpClient := &http.Client{Transport: wasiTransport}
25+
26+
return &client{
27+
config: cfg,
28+
httpClient: httpClient,
29+
}
30+
}
31+
32+
func (c *client) UploadLogs(ctx context.Context, logs []*types.ResourceLogs) error {
33+
if len(logs) == 0 {
34+
return nil
35+
}
36+
37+
export := &types.ExportLogsServiceRequest{
38+
ResourceLogs: logs,
39+
}
40+
41+
body, err := json.Marshal(export)
42+
if err != nil {
43+
return fmt.Errorf("failed to serialize export request to JSON: %w", err)
44+
}
45+
46+
u := c.getUrl()
47+
req, err := http.NewRequest(http.MethodPost, u.String(), bytes.NewBuffer(body))
48+
if err != nil {
49+
return fmt.Errorf("failed to create request to %q: %w", u.String(), err)
50+
}
51+
req.Header.Set("Content-Type", "application/json")
52+
53+
_, err = c.httpClient.Do(req)
54+
if err != nil {
55+
return fmt.Errorf("failed to request %q: %w", u.String(), err)
56+
}
57+
58+
return nil
59+
}
60+
61+
func (c *client) getUrl() url.URL {
62+
scheme := "http"
63+
if !c.config.Insecure {
64+
scheme = "https"
65+
}
66+
u := url.URL{
67+
Scheme: scheme,
68+
Host: c.config.Endpoint,
69+
Path: c.config.Path,
70+
}
71+
return u
72+
}

x/wasitel/wasitellog/config.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package wasitellog
2+
3+
import (
4+
"fmt"
5+
"net/url"
6+
)
7+
8+
const (
9+
// DefaultPort is the default HTTP port of the collector.
10+
DefaultPort uint16 = 4318
11+
// DefaultHost is the host address the client will attempt
12+
// connect to if no collector address is provided.
13+
DefaultHost string = "localhost"
14+
// DefaultPath is a default URL path for endpoint that receives logs
15+
DefaultPath string = "/v1/logs"
16+
)
17+
18+
type config struct {
19+
Endpoint string
20+
Insecure bool
21+
Path string
22+
}
23+
24+
func newConfig(opts ...Option) config {
25+
cfg := config{
26+
Insecure: true,
27+
Endpoint: fmt.Sprintf("%s:%d", DefaultHost, DefaultPort),
28+
Path: DefaultPath,
29+
}
30+
for _, opt := range opts {
31+
cfg = opt.apply(cfg)
32+
}
33+
return cfg
34+
}
35+
36+
type Option interface {
37+
apply(config) config
38+
}
39+
40+
func newWrappedOption(fn func(config) config) Option {
41+
return &wrappedOption{fn: fn}
42+
}
43+
44+
type wrappedOption struct {
45+
fn func(config) config
46+
}
47+
48+
func (o *wrappedOption) apply(cfg config) config {
49+
return o.fn(cfg)
50+
}
51+
52+
func WithEndpoint(endpoint string) Option {
53+
return newWrappedOption(func(cfg config) config {
54+
cfg.Endpoint = endpoint
55+
return cfg
56+
})
57+
}
58+
59+
func WithEndpointURL(eu string) Option {
60+
return newWrappedOption(func(cfg config) config {
61+
u, err := url.Parse(eu)
62+
if err != nil {
63+
return cfg
64+
}
65+
66+
cfg.Endpoint = u.Host
67+
cfg.Path = u.Path
68+
if u.Scheme != "https" {
69+
cfg.Insecure = true
70+
}
71+
72+
return cfg
73+
})
74+
}

x/wasitel/wasitellog/exporter.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package wasitellog
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"sync"
7+
8+
logsdk "go.opentelemetry.io/otel/sdk/log"
9+
"go.wasmcloud.dev/component/x/wasitel/wasitellog/internal/convert"
10+
)
11+
12+
func New(opts ...Option) (*Exporter, error) {
13+
client := newClient(opts...)
14+
return &Exporter{
15+
client: client,
16+
}, nil
17+
}
18+
19+
var _ logsdk.Exporter = (*Exporter)(nil)
20+
21+
type Exporter struct {
22+
client *client
23+
stopped bool
24+
stoppedMu sync.RWMutex
25+
}
26+
27+
func (e *Exporter) Export(ctx context.Context, logs []logsdk.Record) error {
28+
err := ctx.Err()
29+
if err != nil {
30+
return err
31+
}
32+
33+
// Check whether the exporter has been told to Shutdown
34+
e.stoppedMu.RLock()
35+
stopped := e.stopped
36+
e.stoppedMu.RUnlock()
37+
if stopped {
38+
return nil
39+
}
40+
41+
// Check whether there's anything to export
42+
converted := convert.ResourceLogs(logs)
43+
if len(converted) == 0 {
44+
return nil
45+
}
46+
47+
err = e.client.UploadLogs(ctx, converted)
48+
if err != nil {
49+
return fmt.Errorf("failed to export spans: %w", err)
50+
}
51+
return nil
52+
}
53+
54+
func (e *Exporter) Shutdown(ctx context.Context) error {
55+
e.stoppedMu.Lock()
56+
e.stopped = true
57+
e.stoppedMu.Unlock()
58+
59+
return nil
60+
}
61+
62+
func (e *Exporter) ForceFlush(ctx context.Context) error {
63+
return nil
64+
}

0 commit comments

Comments
 (0)