Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions go.work.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2366,6 +2366,7 @@ golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
golang.org/x/tools v0.37.0 h1:DVSRzp7FwePZW356yEAChSdNcQo6Nsp+fex1SUW09lE=
golang.org/x/tools v0.37.0/go.mod h1:MBN5QPQtLMHVdvsbtarmTNukZDdgwdwlO5qGacAzF0w=
golang.org/x/tools/go/expect v0.1.0-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY=
golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM=
Expand Down
6 changes: 4 additions & 2 deletions receiver/toggltrackreceiver/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ The following settings are required:

The following settings can be optionally configured:

- `interval` (default = 1m): Specifies the time interval between polls to fetch time entries from the Toggl API.
- `collection_interval` (default = 1m): Specifies the time interval between polls to fetch time entries from the Toggl API.
- `lookback` (default = 720h): Specifies the time range to look back when fetching time entries.

### Example configurations

Expand All @@ -30,7 +31,8 @@ Using connection string for authentication:
```yaml
toggltrack:
api_token: ${TOGGL_API_TOKEN}
interval: 30m
collection_interval: 30m
lookback: 720h # 30 days
```

## Limitations
Expand Down
29 changes: 19 additions & 10 deletions receiver/toggltrackreceiver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,36 @@ package toggltrackreceiver
import (
"fmt"
"time"

"go.opentelemetry.io/collector/scraper/scraperhelper"
)

const (
MinCollectionInterval = 1 * time.Minute
MinLookback = 1 * time.Hour
)

type Config struct {
Interval string `mapstructure:"interval"`
Lookback string `mapstructure:"lookback"`
APIToken string `mapstructure:"api_token"`
scraperhelper.ControllerConfig `mapstructure:",squash"`
Lookback string `mapstructure:"lookback"`
APIToken string `mapstructure:"api_token"`
}

func (cfg *Config) Validate() error {
interval, _ := time.ParseDuration(cfg.Interval)
if interval.Minutes() < 1 {
return fmt.Errorf("when defined, the interval has to be set to at least 1 minute (1m)")
if cfg.CollectionInterval < MinCollectionInterval {
return fmt.Errorf("collection_interval must be at least %s", MinCollectionInterval)
}

lookback, _ := time.ParseDuration(cfg.Lookback)
if lookback.Hours() < 1 {
return fmt.Errorf("when defined, the lookback has to be set to at least 1 hour (1h)")
lookback, err := time.ParseDuration(cfg.Lookback)
if err != nil {
return fmt.Errorf("invalid lookback duration: %w", err)
}
if lookback < MinLookback {
return fmt.Errorf("lookback must be at least %s", MinLookback)
}

if cfg.APIToken == "" {
return fmt.Errorf("api_token must is required")
return fmt.Errorf("api_token is required")
}

return nil
Expand Down
57 changes: 42 additions & 15 deletions receiver/toggltrackreceiver/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,69 @@ package toggltrackreceiver

import (
"context"
"fmt"
"time"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/receiver"
"go.opentelemetry.io/collector/scraper"
"go.opentelemetry.io/collector/scraper/scraperhelper"

"github.com/zmoog/collector/receiver/toggltrackreceiver/internal/metadata"
)

var (
typeStr = component.MustNewType("toggltrack")
)

const (
defaultInterval = 1 * time.Minute
defaultLookback = 24 * 30 * time.Hour // 30 days
DefaultCollectionInterval = 1 * time.Minute
DefaultLookback = 24 * 30 * time.Hour // 30 days
)

func createDefaultConfig() component.Config {
return Config{
Interval: defaultInterval.String(),
Lookback: defaultLookback.String(),
cfg := scraperhelper.NewDefaultControllerConfig()
cfg.CollectionInterval = DefaultCollectionInterval
return &Config{
ControllerConfig: cfg,
Lookback: DefaultLookback.String(),
}
}

// createScraperFactory creates a scraper.Factory for toggltrack logs
func createScraperFactory(cfg *Config, settings receiver.Settings) scraper.Factory {
return scraper.NewFactory(
metadata.Type,
func() component.Config { return cfg },
scraper.WithLogs(func(ctx context.Context, scraperSettings scraper.Settings, scraperCfg component.Config) (scraper.Logs, error) {
cfg, ok := scraperCfg.(*Config)
if !ok {
return nil, fmt.Errorf("invalid config type")
}
togglTrackScraper := newScraper(cfg, settings)
return scraper.NewLogs(
togglTrackScraper.scrape,
scraper.WithStart(togglTrackScraper.start),
)
}, component.StabilityLevelAlpha),
)
}

func createLogsReceiver(ctx context.Context, settings receiver.Settings, baseCfg component.Config, consumer consumer.Logs) (receiver.Logs, error) {
logger := settings.Logger
config := baseCfg.(Config)
scraper := NewScraper(config.APIToken, logger)

rcvr := togglTrackReceiver{
logger: logger,
consumer: consumer,
config: &config,
scraper: scraper,
cfg, ok := baseCfg.(*Config)
if !ok {
return nil, fmt.Errorf("invalid config type")
}

return &rcvr, nil
scraperFactory := createScraperFactory(cfg, settings)

return scraperhelper.NewLogsController(
&cfg.ControllerConfig,
settings,
consumer,
scraperhelper.AddFactoryWithConfig(scraperFactory, cfg),
)
}

func NewFactory() receiver.Factory {
Expand Down
3 changes: 3 additions & 0 deletions receiver/toggltrackreceiver/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ require (
go.opentelemetry.io/collector/pdata v1.44.0
go.opentelemetry.io/collector/receiver v1.44.0
go.opentelemetry.io/collector/receiver/receivertest v0.138.0
go.opentelemetry.io/collector/scraper v0.138.0
go.opentelemetry.io/collector/scraper/scraperhelper v0.138.0
go.uber.org/goleak v1.3.0
go.uber.org/zap v1.27.0
)
Expand Down Expand Up @@ -42,6 +44,7 @@ require (
go.opentelemetry.io/collector/internal/telemetry v0.138.0 // indirect
go.opentelemetry.io/collector/pdata/pprofile v0.138.0 // indirect
go.opentelemetry.io/collector/pipeline v1.44.0 // indirect
go.opentelemetry.io/collector/receiver/receiverhelper v0.138.0 // indirect
go.opentelemetry.io/collector/receiver/xreceiver v0.138.0 // indirect
go.opentelemetry.io/contrib/bridges/otelzap v0.13.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
Expand Down
6 changes: 6 additions & 0 deletions receiver/toggltrackreceiver/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -89,10 +89,16 @@ go.opentelemetry.io/collector/pipeline v1.44.0 h1:EFdFBg3Wm2BlMtQbUeork5a4KFpS6h
go.opentelemetry.io/collector/pipeline v1.44.0/go.mod h1:xUrAqiebzYbrgxyoXSkk6/Y3oi5Sy3im2iCA51LwUAI=
go.opentelemetry.io/collector/receiver v1.44.0 h1:oPgHg7u+aqplnVTLyC3FapTsAE7BiGdTtDceE1BuTJg=
go.opentelemetry.io/collector/receiver v1.44.0/go.mod h1:NzkrGOIoWigOG54eF92ZGfJ8oSWhqGHTT0ZCGaH5NMc=
go.opentelemetry.io/collector/receiver/receiverhelper v0.138.0 h1:aEgyMilBJ2FoWQ+U4m28lzjmTP2UteDAIO96jRsPHmM=
go.opentelemetry.io/collector/receiver/receiverhelper v0.138.0/go.mod h1:WxMvaPgL9MWrIKjDiZ/SmopEXAX+sO9CD/SfXI9J63A=
go.opentelemetry.io/collector/receiver/receivertest v0.138.0 h1:K6kZ/epuAjjCCr1UMzNFyx1rynFSc+ifMXt5C/hWcXI=
go.opentelemetry.io/collector/receiver/receivertest v0.138.0/go.mod h1:p3cGSplwwp71r7R6u0e8N0rP/mmPsFjJ4WFV2Bhv7os=
go.opentelemetry.io/collector/receiver/xreceiver v0.138.0 h1:wspJazZc4htPBT08JpUI6gq+qeUUxSOhxXwWGn+QnlM=
go.opentelemetry.io/collector/receiver/xreceiver v0.138.0/go.mod h1:+S/AsbEs1geUt3B+HAhdSjd+3hPkjtmcSBltKwpCBik=
go.opentelemetry.io/collector/scraper v0.138.0 h1:O9P97PwG5tS14T3H1kvQvnu/hisr3x7CvXNE6xHyiPI=
go.opentelemetry.io/collector/scraper v0.138.0/go.mod h1:9xp6yYvAeH8KGn8cJAtcRZ7IRN1r0k40yS5LfhHPzPg=
go.opentelemetry.io/collector/scraper/scraperhelper v0.138.0 h1:11ch82JoBo6ZlAaw+eS1hqqWcBUgt8eYDVgkI6CG4eE=
go.opentelemetry.io/collector/scraper/scraperhelper v0.138.0/go.mod h1:pMheZcc1qK6fXUYlHIj+Ik8fL1v2mL3n9CUmH9NVzaA=
go.opentelemetry.io/contrib/bridges/otelzap v0.13.0 h1:aBKdhLVieqvwWe9A79UHI/0vgp2t/s2euY8X59pGRlw=
go.opentelemetry.io/contrib/bridges/otelzap v0.13.0/go.mod h1:SYqtxLQE7iINgh6WFuVi2AI70148B8EI35DSk0Wr8m4=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
Expand Down
96 changes: 42 additions & 54 deletions receiver/toggltrackreceiver/receiver.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,73 +5,61 @@ import (
"time"

"go.opentelemetry.io/collector/component"
"go.opentelemetry.io/collector/consumer"
"go.opentelemetry.io/collector/pdata/plog"
"go.opentelemetry.io/collector/receiver"
"go.uber.org/zap"
)

type togglTrackReceiver struct {
cancel context.CancelFunc
logger *zap.Logger
consumer consumer.Logs
config *Config
// togglTrackScraper is the struct that contains the TogglTrack scraper.
type togglTrackScraper struct {
cfg *Config
settings component.TelemetrySettings
scraper *Scraper
marshaler *timeEntryMarshaler
}

func (t *togglTrackReceiver) Start(ctx context.Context, host component.Host) error {
t.logger.Info("Starting toggltrack receiver")

_ctx, cancel := context.WithCancel(ctx)
t.cancel = cancel

interval, _ := time.ParseDuration(t.config.Interval)
lookback, _ := time.ParseDuration(t.config.Lookback)
go func() {
ticker := time.NewTicker(interval)
defer ticker.Stop()

for {
select {
case <-_ctx.Done():
return
case <-ticker.C:
// Do something
t.logger.Info("Doing something")

entries, err := t.scraper.Scrape(time.Now(), lookback)
if err != nil {
t.logger.Error("Error scraping toggltrack", zap.Error(err))
continue
}
// newScraper creates a new TogglTrack scraper.
func newScraper(cfg *Config, settings receiver.Settings) *togglTrackScraper {
return &togglTrackScraper{
cfg: cfg,
settings: settings.TelemetrySettings,
scraper: NewScraper(cfg.APIToken, settings.Logger),
marshaler: &timeEntryMarshaler{},
}
}

t.logger.Info("Scraped toggltrack entries", zap.Int("count", len(entries)))
// start initializes the TogglTrack scraper.
func (s *togglTrackScraper) start(_ context.Context, host component.Host) error {
s.settings.Logger.Info("Starting toggltrack scraper")
return nil
}

if len(entries) == 0 {
t.logger.Info("No new entries to process")
continue
}
// scrape is the main function that scrapes the data from the TogglTrack API.
func (s *togglTrackScraper) scrape(ctx context.Context) (plog.Logs, error) {
lookback, err := time.ParseDuration(s.cfg.Lookback)
if err != nil {
s.settings.Logger.Error("Error parsing lookback duration", zap.Error(err))
return plog.NewLogs(), err
}

logs, err := t.marshaler.UnmarshalLogs(entries)
if err != nil {
t.logger.Error("Error marshaling toggltrack entries", zap.Error(err))
continue
}
entries, err := s.scraper.Scrape(time.Now(), lookback)
if err != nil {
s.settings.Logger.Error("Error scraping toggltrack", zap.Error(err))
return plog.NewLogs(), err
}

if err := t.consumer.ConsumeLogs(_ctx, logs); err != nil {
t.logger.Error("Error consuming toggltrack logs", zap.Error(err))
}
}
}
}()
s.settings.Logger.Info("Scraped toggltrack entries", zap.Int("count", len(entries)))

return nil
}
if len(entries) == 0 {
s.settings.Logger.Info("No new entries to process")
return plog.NewLogs(), nil
}

func (t *togglTrackReceiver) Shutdown(ctx context.Context) error {
t.logger.Info("Shutting down toggltrack receiver")
if t.cancel != nil {
t.cancel()
logs, err := s.marshaler.UnmarshalLogs(entries)
if err != nil {
s.settings.Logger.Error("Error marshaling toggltrack entries", zap.Error(err))
return plog.NewLogs(), err
}

return nil
return logs, nil
}
2 changes: 2 additions & 0 deletions tools/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ require (
github.com/awslabs/amazon-ecr-credential-helper/ecr-login v0.9.1 // indirect
github.com/blang/semver v3.5.1+incompatible // indirect
github.com/chrismellard/docker-credential-acr-env v0.0.0-20230304212654-82a0ddb27589 // indirect
github.com/containerd/errdefs v1.0.0 // indirect
github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/stargz-snapshotter/estargz v0.16.3 // indirect
github.com/cyberphone/json-canonicalization v0.0.0-20241213102144-19d51d7fe467 // indirect
github.com/digitorus/pkcs7 v0.0.0-20230818184609-3a137a874352 // indirect
Expand Down
15 changes: 7 additions & 8 deletions tools/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8=
Expand All @@ -141,13 +145,11 @@ github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi
github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v28.0.4+incompatible h1:pBJSJeNd9QeIWPjRcV91RVJihd/TXB77q1ef64XEu4A=
github.com/docker/cli v28.0.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/cli v28.2.2+incompatible h1:qzx5BNUDFqlvyq4AHzdNB7gSyVTmU4cgsyN9SdInc1A=
github.com/docker/cli v28.2.2+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I=
github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker v28.2.2+incompatible h1:CjwRSksz8Yo4+RmQ339Dp/D2tGO5JxwYeqtMOEe0LDw=
github.com/docker/docker v28.2.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
Expand Down Expand Up @@ -238,8 +240,6 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-containerregistry v0.20.4-0.20250225234217-098045d5e61f h1:q+kbH7LI4wK3gNCxyvy2rFldJqAAB+Gch79/xj9/+GU=
github.com/google/go-containerregistry v0.20.4-0.20250225234217-098045d5e61f/go.mod h1:UnXV0UkKqoHbzwn49vfozmwMcLMS8XLLsVKVuhv3cGc=
github.com/google/go-containerregistry v0.20.6 h1:cvWX87UxxLgaH76b4hIvya6Dzz9qHB31qAwjAohdSTU=
github.com/google/go-containerregistry v0.20.6/go.mod h1:T0x8MuoAoKX/873bkeSfLD2FAkwCDf9/HZgsFJ02E2Y=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand Down Expand Up @@ -540,8 +540,7 @@ go.opentelemetry.io/contrib/bridges/otelzap v0.12.0 h1:FGre0nZh5BSw7G73VpT3xs38H
go.opentelemetry.io/contrib/bridges/otelzap v0.12.0/go.mod h1:X2PYPViI2wTPIMIOBjG17KNybTzsrATnvPJ02kkz7LM=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0 h1:rgMkmiGfix9vFJDcDi1PK8WEQP4FLQwLDfhp5ZLpFeE=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.59.0/go.mod h1:ijPqXp5P6IRRByFVVg9DY8P5HkxkHE5ARIa+86aXPf4=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
Expand Down