Skip to content
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,4 @@ jobs:
- name: Lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.10.1
version: v2.12.2
10 changes: 6 additions & 4 deletions .golangci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
---
version: "2"
linters:
enable: # list taken from https://golangci-lint.run/usage/linters/ - last updated 2026-01-09 for v2.8.0
enable: # list taken from https://golangci-lint.run/usage/linters/ - last updated 2026-05-11 for v2.12.2
# enabled by default, but list them here to be explicit
- errcheck
- govet
Expand All @@ -16,6 +16,7 @@ linters:
- bidichk # checks for dangerous unicode character sequences
- bodyclose # checks whether HTTP response body is closed successfully
- canonicalheader # checks for canonical names in HTTP headers
- clickhouselint # detects common mistakes with the ClickHouse native Go driver API
- containedctx # detects struct contained context.Context field
#- contextcheck # checks for inherited context.Context
- copyloopvar # detects places where loop variables are copied
Expand Down Expand Up @@ -45,15 +46,16 @@ linters:
- gochecknoinits # checks that no init functions are present in Go code
- gochecksumtype # checks exhaustiveness on Go "sum types"
#- gocognit # Computes and checks the cognitive complexity of functions
- goconst # finds repeated strings that could be replaced by a constant
#- goconst # finds repeated strings that could be replaced by a constant
- gocritic # provides diagnostics that check for bugs, performance and style issues
#- gocyclo # Computes and checks the cyclomatic complexity of functions
- godoclint # Checks golang docs best practices (godoc)
#- godot # Check if comments end in a period
#- godox # Tool for detection of FIXME, TODO and other comment keywords
#- goheader # Checks is file header matches to pattern
- gomoddirectives # manages the use of 'replace', 'retract', and 'excludes' directives in go.mod
- gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations
#- gomodguard # allow and block lists linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations
- gomodguard_v2 # Allow and blocklist linter for direct Go module dependencies
- goprintffuncname # checks that printf-like functions are named with f at the end
- gosec # inspects source code for security problems
- gosmopolitan # Report certain i18n/l10n anti-patterns in your Go codebase.
Expand Down Expand Up @@ -171,4 +173,4 @@ formatters:
- gci
- gofmt
- gofumpt
- goimports
- goimports
12 changes: 11 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.5.0] - 2026-05-11

### Added

- `workflows`: Added `client.Jobs.QueryLogs` and `client.Jobs.QuerySpans` to query logs and trace spans for a job, plus a telemetry query example.
- `workflows`: Added `ConfigureConsoleLogging` to enable console log output that composes with the Tilebox OpenTelemetry log exporter.
- `workflows`: Added `WithSpan` and `WithSpanResult` helpers to start spans from the current task execution context without manually passing a tracer.

### Changed

- `workflows`: Correlate context-aware task logs with traces by adding `trace_id`, `span_id`, and `task_id` attributes and recording log messages as span events.
- `examples`: Updated workflow and dataset examples to use context-aware `slog` methods.

## [0.4.0] - 2026-03-06

Expand Down Expand Up @@ -81,7 +90,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added support for Tilebox Observability, including logging and tracing helpers.
- Added examples for using the library.

[Unreleased]: https://github.com/tilebox/tilebox-go/compare/v0.4.0...HEAD
[Unreleased]: https://github.com/tilebox/tilebox-go/compare/v0.5.0...HEAD
[0.5.0]: https://github.com/tilebox/tilebox-go/compare/v0.4.0...v0.5.0
[0.4.0]: https://github.com/tilebox/tilebox-go/compare/v0.3.2...v0.4.0
[0.3.2]: https://github.com/tilebox/tilebox-go/compare/v0.3.1...v0.3.2
[0.3.1]: https://github.com/tilebox/tilebox-go/compare/v0.3.0...v0.3.1
Expand Down
25 changes: 14 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ For examples on how to use the library, see the [examples](examples) directory.

### Writing a Task

Here we define a simple task that prints "Hello World!" to the console:
Here we define a simple task that logs "Hello World!":

```go
package helloworld
Expand All @@ -65,8 +65,8 @@ type HelloTask struct {
}

// The Execute method isn't needed to submit a task but is required to run a task.
func (t *HelloTask) Execute(context.Context) error {
slog.Info("Hello World!", "Name", t.Name)
func (t *HelloTask) Execute(ctx context.Context) error {
slog.InfoContext(ctx, "Hello World!", slog.String("Name", t.Name))
return nil
}

Expand Down Expand Up @@ -96,6 +96,7 @@ type HelloTask struct {

func main() {
ctx := context.Background()
workflows.ConfigureConsoleLogging(slog.LevelInfo)
client := workflows.NewClient()

job, err := client.Jobs.Submit(ctx, "hello-world",
Expand All @@ -106,14 +107,16 @@ func main() {
},
)
if err != nil {
slog.Error("Failed to submit job", slog.Any("error", err))
slog.ErrorContext(ctx, "Failed to submit job", slog.Any("error", err))
return
}

slog.Info("Job submitted", slog.String("job_id", job.ID.String()))
slog.InfoContext(ctx, "Job submitted", slog.String("job_id", job.ID.String()))
}
```

`workflows.NewClient()` configures Tilebox OpenTelemetry export when an API key is available. `workflows.ConfigureConsoleLogging` adds console output without replacing the Tilebox exporter, so logs are written to both destinations when both are configured.

### Running a Worker

Here we create a TaskRunner and run a worker that is capable of executing `HelloTask` tasks:
Expand All @@ -133,32 +136,32 @@ type HelloTask struct {
}

// The Execute method is required to run a task.
func (t *HelloTask) Execute(context.Context) error {
slog.Info("Hello World!", "Name", t.Name)
func (t *HelloTask) Execute(ctx context.Context) error {
slog.InfoContext(ctx, "Hello World!", slog.String("Name", t.Name))
return nil
}

func main() {
ctx := context.Background()
workflows.ConfigureConsoleLogging(slog.LevelInfo)
client := workflows.NewClient()

runner, err := client.NewTaskRunner(ctx)
if err != nil {
slog.Error("failed to create task runner", slog.Any("error", err))
slog.ErrorContext(ctx, "failed to create task runner", slog.Any("error", err))
return
}

err = runner.RegisterTasks(&HelloTask{})
if err != nil {
slog.Error("failed to register tasks", slog.Any("error", err))
slog.ErrorContext(ctx, "failed to register tasks", slog.Any("error", err))
return
}

runner.RunForever(ctx)
}
```


## License

Distributed under the MIT License (`The MIT License`).
2 changes: 1 addition & 1 deletion datasets/v1/datapoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ func validateDatapoints(datapoints any) error {
if slice.Kind() != reflect.Slice {
return fmt.Errorf("datapoints must be a pointer to a slice, got %v", reflect.TypeOf(datapoints))
}
if slice.Type().Elem().Kind() != reflect.Ptr {
if slice.Type().Elem().Kind() != reflect.Pointer {
return fmt.Errorf("datapoints must be a pointer to a slice of proto.Message, got %v", reflect.TypeOf(datapoints))
}
messageType := reflect.TypeFor[proto.Message]()
Expand Down
15 changes: 9 additions & 6 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,24 @@ Head over to [Tilebox Console](https://console.tilebox.com/account/api-keys) if
Each example can be run using the following command:

```bash
go run ./<example-folder>
go run ./examples/<example-folder>
```

For example, to run the `workflows/helloworld/submitter` example:
x
For example, to run the `workflows/helloworld` example:

```bash
go run ./workflows/helloworld/submitter
go run ./examples/workflows/helloworld
```

Workflow examples submit a job first, then start a task runner in the same process.

## Workflows examples

- [Hello world](workflows/helloworld): How to submit a task and run it.
- [MapReduce](workflows/mapreduce): How to fan out tasks and submit dependent reduce tasks.
- [Progress](workflows/progress): How to report workflow progress.
- [Protobuf tasks](workflows/protobuf-task): How to use Protobuf tasks.
- [Axiom Observability](workflows/axiom): How to set up tracing and logging for workflows using [Axiom](https://axiom.co/) observability platform.
- [OpenTelemetry Observability](workflows/opentelemetry): How to set up tracing and logging for workflows using [OpenTelemetry](https://opentelemetry.io/).
- [Observability](workflows/observability): How to query Tilebox workflow telemetry and export traces/logs to custom Axiom or OpenTelemetry backends.

## Datasets examples

Expand Down
4 changes: 2 additions & 2 deletions examples/datasets/create/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ func main() {
},
)
if err != nil {
slog.Error("Failed to create dataset", slog.Any("error", err))
slog.ErrorContext(ctx, "Failed to create dataset", slog.Any("error", err))
return
}
slog.InfoContext(ctx, "Created dataset", slog.String("dataset_id", dataset.ID.String()))
Expand All @@ -44,7 +44,7 @@ func main() {
},
)
if err != nil {
slog.Error("Failed to update dataset", slog.Any("error", err))
slog.ErrorContext(ctx, "Failed to update dataset", slog.Any("error", err))
return
}

Expand Down
12 changes: 6 additions & 6 deletions examples/datasets/ingest/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ func main() {
// Select a dataset
dataset, err := client.Datasets.Get(ctx, "open_data.copernicus.sentinel2_msi")
if err != nil {
slog.Error("Failed to get dataset", slog.Any("error", err))
slog.ErrorContext(ctx, "Failed to get dataset", slog.Any("error", err))
return
}

// Create a collection
collection, err := client.Collections.Create(ctx, dataset.ID, "My First Collection")
if err != nil {
slog.Error("Failed to create collection", slog.Any("error", err))
slog.ErrorContext(ctx, "Failed to create collection", slog.Any("error", err))
return
}

Expand All @@ -47,16 +47,16 @@ func main() {
// Ingest datapoints
ingestResponse, err := client.Datapoints.Ingest(ctx, collection.ID, &datapoints, false)
if err != nil {
slog.Error("Failed to ingest datapoints", slog.Any("error", err))
slog.ErrorContext(ctx, "Failed to ingest datapoints", slog.Any("error", err))
return
}
slog.Info("Ingested datapoints", slog.Int64("created", ingestResponse.NumCreated))
slog.InfoContext(ctx, "Ingested datapoints", slog.Int64("created", ingestResponse.NumCreated))

// Delete datapoints again
numDeleted, err := client.Datapoints.DeleteIDs(ctx, collection.ID, ingestResponse.DatapointIDs)
if err != nil {
slog.Error("Failed to delete datapoints", slog.Any("error", err))
slog.ErrorContext(ctx, "Failed to delete datapoints", slog.Any("error", err))
return
}
slog.Info("Deleted datapoints", slog.Int64("deleted", numDeleted))
slog.InfoContext(ctx, "Deleted datapoints", slog.Int64("deleted", numDeleted))
}
10 changes: 5 additions & 5 deletions examples/datasets/query/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ func main() {
// Select a dataset
dataset, err := client.Datasets.Get(ctx, "open_data.copernicus.sentinel2_msi")
if err != nil {
slog.Error("Failed to get dataset", slog.Any("error", err))
slog.ErrorContext(ctx, "Failed to get dataset", slog.Any("error", err))
return
}

// Select a collection
collection, err := client.Collections.Get(ctx, dataset.ID, "S2A_S2MSI1C")
if err != nil {
slog.Error("Failed to get collection", slog.Any("error", err))
slog.ErrorContext(ctx, "Failed to get collection", slog.Any("error", err))
return
}

Expand All @@ -52,13 +52,13 @@ func main() {
datasets.WithSpatialExtent(area),
)
if err != nil {
slog.Error("Failed to query datapoints", slog.Any("error", err))
slog.ErrorContext(ctx, "Failed to query datapoints", slog.Any("error", err))
return
}

slog.Info("Found datapoints over Colorado in March 2025", slog.Int("count", len(foundDatapoints)))
slog.InfoContext(ctx, "Found datapoints over Colorado in March 2025", slog.Int("count", len(foundDatapoints)))
if len(foundDatapoints) > 0 {
slog.Info("First datapoint over Colorado",
slog.InfoContext(ctx, "First datapoint over Colorado",
slog.String("id", foundDatapoints[0].GetId().AsUUID().String()),
slog.Time("event time", foundDatapoints[0].GetTime().AsTime()),
slog.Time("ingestion time", foundDatapoints[0].GetIngestionTime().AsTime()),
Expand Down
55 changes: 0 additions & 55 deletions examples/workflows/axiom/submitter/main.go

This file was deleted.

25 changes: 0 additions & 25 deletions examples/workflows/axiom/task.go

This file was deleted.

11 changes: 11 additions & 0 deletions examples/workflows/helloworld/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Hello World Workflow

Minimal workflow example that submits and runs a single `HelloTask`.

- [main.go](main.go) contains the task definition, submits a job, and then starts a task runner to execute it.

Run the example:

```bash
go run ./examples/workflows/helloworld
```
Loading