Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Step Logging and changes to StepError type to errors #18

Merged
merged 5 commits into from
Nov 17, 2024
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
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@
go.work

# macOS junk
.DS_Store
.DS_Store

*.log
114 changes: 66 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ The idea behind `gosteps` is to define set of functions as chain-of-steps and ex

### Installation

```bash
```bash
go get github.com/TanmoySG/go-steps
```

Expand All @@ -38,47 +38,47 @@ type Step struct {
StepOpts StepOpts `json:"stepConfig"`
Branches *Branches `json:"branches"`
StepArgs map[string]interface{} `json:"stepArgs"`
StepResult *StepResult `json:"stepResult"`
}

// example step
step := gosteps.Step{
Name: "add",
Function: Add,
StepArgs: map[string]interface{}{"n1": 5},
StepOpts: gosteps.StepOpts{},
Branches: &gosteps.Branches{},
}
```

| Field | Description |
|------------|------------------------------------------------------------------------------------------------------------|
| Name | Name of step |
| Function | The function to execute |
| StepOpts | Options/Configurations of the step |
| Branches | Branches are a sequentially executable collection of steps. |
| StepArgs | Any additional arguments need to pass to the step. |
| StepResult | The results - statues of step, message returned by the step function and, errors are defined in StepResult |
| Field | Description |
|----------|-----------------------------------------------------------------------------------|
| Name | Name of step |
| Function | The function to execute |
| StepOpts | Options/Configurations of the step |
| Branches | Branches are a sequentially executable collection of steps. |
| StepArgs | Any additional arguments/variables needed to be passed to the step for execution. |

**StepOpts**

The `StepOpts` type contains the configurations/options for the step execution.

```go
type StepOpts struct {
ErrorsToRetry []StepError `json:"errorsToRetry"`
RetryAllErrors bool `json:"retryAllErrors"`
MaxRunAttempts int `json:"maxAttempts"`
RetrySleep time.Duration `json:"retrySleep"`
}

// example step
step := gosteps.Step{
Name: "add",
Function: Add,
StepArgs: map[string]interface{}{"n1": 5},
StepOpts: gosteps.StepOpts{},
Branches: &gosteps.Branches{},
ErrorsToRetry []error `json:"errorsToRetry"`
ErrorPatternsToRetry []regexp.Regexp `json:"errorPatternsToRetry"`
RetryAllErrors bool `json:"retryAllErrors"`
MaxRunAttempts int `json:"maxAttempts"`
RetrySleep time.Duration `json:"retrySleep"`
}
```

| Field | Description |
|----------------|----------------------------------------------------------------------------------------------------------------------|
| ErrorsToRetry | a set of `StepError`s for which a step should be retried. |
| RetryAllErrors | A boolean type flag which specifies if a step needs to retry for any error, irrespective of those in `ErrorsToRetry` |
| MaxRunAttempts | Max attempts are the number of times the step is ran/executed (first run + retries). If not set, it'll run once. |
| RetrySleep | Sleep duration (type time.Duration) between each re-attempts |
| Field | Description |
|----------------------|----------------------------------------------------------------------------------------------------------------------|
| ErrorsToRetry | a set of errors for which a step should be retried. |
| ErrorPatternsToRetry | a set of `StepErrorPattern`s for which a step should be retried, if error matches pattern |
| RetryAllErrors | A boolean type flag which specifies if a step needs to retry for any error, irrespective of those in `ErrorsToRetry` |
| MaxRunAttempts | Max attempts are the number of times the step is ran/executed (first run + retries). If not set, it'll run once. |
| RetrySleep | Sleep duration (type time.Duration) between each re-attempts |

**Function**

Expand Down Expand Up @@ -264,7 +264,7 @@ type StepResult struct {
StepData GoStepsCtxData `json:"stepData"`
StepState StepState `json:"stepState"`
StepMessage *string `json:"stepMessage"`
StepError *StepError `json:"stepError,omitempty"`
StepError error `json:"stepError,omitempty"`
}
```

Expand Down Expand Up @@ -298,20 +298,7 @@ gosteps.MarkStateComplete().WithData(map[string]interface{}{"key": value})
gosteps.MarkStateComplete().WithMessage("message")

// add error to the step result of type StepError
gosteps.MarkStateError().WithError(stepError1)

// wrap any non-GoStep StepErrors and add to the step result
// it wraps any error passed to it as StepError
gosteps.MarkStateError().WithWrappedError(fmt.Errorf("error"))
```

To define a GoStep error, use the `StepError` type.

```go
stepError1 := gosteps.StepError{
StepErrorNameOrId: "error1",
StepErrorMessage: "error message",
}
gosteps.MarkStateError().WithError(errors.New("error message"))
```

### Conditional Branching
Expand Down Expand Up @@ -355,19 +342,50 @@ The retry mechanism runs for `StepOpts.MaxRunAttempts`, that included the initia

```go
gosteps.StepOpts{
ErrorsToRetry: []gosteps.StepError{
stepError1,
},
ErrorsToRetry: []error{errors.New("error")},
ErrorPatternsToRetry: []regexp.Regexp{*regexp.MustCompile("err*")},
RetryAllErrors: false,
MaxRunAttempts: 5,
RetrySleep: 5 * time.Second,
}
```

### Logging

GoSteps uses the `[zerolog`](<https://github.com/rs/zerolog>) package to enable logging within GoSteps. Initialize the logger using the `gosteps.NewGoStepsLogger` method, passing the output type and options.

```go
// output : of type io.Writer, example os.Stdout, for more options refer to zerolog documentation: https://github.com/rs/zerolog?tab=readme-ov-file#multiple-log-output
output := zerolog.MultiLevelWriter(os.Stdout, runLogFile)

// opts : of type *LoggerOpts, if nil, default options are used to enable step level logging, set StepLoggingEnabled to true
opts := LoggerOpts{
StepLoggingEnabled : true,
}

logger := gosteps.NewGoStepsLogger(output, opts)
```

The logger can be passed to the `GoStepsCtx.Use()` method to enable logging.

```go
ctx := gosteps.NewGoStepsContext()
ctx.Use(logger)
```

If `StepLoggingEnabled` is set to `true` then the step run is also logged with the step name, state, message, and error. In cases where logs are required within the step function, the logger can be accessed using the `ctx.Log()` method.

```go
func(c gosteps.GoStepsCtx) gosteps.StepResult {
c.Log("this is a message", gosteps.InfoLevel)
return gosteps.MarkStateComplete()
}
```

### Example

Sample code can be found in the [example](./example/) directory.

### Help!
### Help

If you want to help fix the above constraint or other bugs/issues, feel free to raise an Issue or Pull Request with the changes. It'd be an immense help!
46 changes: 42 additions & 4 deletions example/multistep-example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,31 @@ package main

import (
"fmt"
"os"
"regexp"
"time"

gosteps "github.com/TanmoySG/go-steps"
"github.com/rs/zerolog"
)

func main() {

count := 0

runLogFile, _ := os.OpenFile(
"myapp.log",
os.O_APPEND|os.O_CREATE|os.O_WRONLY,
0664,
)
output := zerolog.MultiLevelWriter(os.Stdout, runLogFile)

logger := gosteps.NewGoStepsLogger(output, &gosteps.LoggerOpts{StepLoggingEnabled: true})

ctx := gosteps.NewGoStepsContext()

ctx.Use(logger)

multipleDivide := gosteps.Step{
Name: "multipleDivide",
Function: func(c gosteps.GoStepsCtx) gosteps.StepResult {
Expand All @@ -21,7 +38,6 @@ func main() {
Branches: &gosteps.Branches{
Resolver: func(ctx gosteps.GoStepsCtx) gosteps.BranchName {
nx := ctx.GetData("result").(int)

if nx%2 == 0 {
return gosteps.BranchName("divide")
}
Expand All @@ -35,6 +51,7 @@ func main() {
Name: "step3.divide",
Function: func(c gosteps.GoStepsCtx) gosteps.StepResult {
res := c.GetData("result").(int) / 2
c.Log("this is a message", gosteps.LogLevel(zerolog.ErrorLevel))
return gosteps.MarkStateComplete().WithData(map[string]interface{}{
"result": res,
})
Expand Down Expand Up @@ -64,6 +81,7 @@ func main() {
{
Name: "add",
Function: func(c gosteps.GoStepsCtx) gosteps.StepResult {
c.Log("this is a message")

res := c.GetData("n1").(int) + c.GetData("n2").(int)
return gosteps.MarkStateComplete().WithData(map[string]interface{}{
Expand All @@ -78,6 +96,17 @@ func main() {
{
Name: "subtract",
Function: func(c gosteps.GoStepsCtx) gosteps.StepResult {
if count < 2 {
count++
time.Sleep(2 * time.Second)
return gosteps.MarkStatePending()
}

if count < 4 {
count++
return gosteps.MarkStateError().WithError(fmt.Errorf("errpr"))
}

res := c.GetData("n1").(int) - c.GetData("result").(int)
return gosteps.MarkStateComplete().WithData(map[string]interface{}{
"result": res,
Expand All @@ -86,6 +115,15 @@ func main() {
StepArgs: map[string]interface{}{
"n1": 5,
},
StepOpts: gosteps.StepOpts{
MaxRunAttempts: 5,
ErrorPatternsToRetry: []regexp.Regexp{
*regexp.MustCompile("err*"),
},
ErrorsToRetry: []error{
fmt.Errorf("errpr"),
},
},
},
multipleDivide,
{
Expand All @@ -101,12 +139,12 @@ func main() {
{
Name: "print",
Function: func(c gosteps.GoStepsCtx) gosteps.StepResult {
fmt.Println("result", c.GetData("result"))
c.Log(fmt.Sprintf("result %v", c.GetData("result")))
return gosteps.MarkStateComplete()
},
},
}

stepChain := gosteps.NewStepChain(steps)
stepChain.Execute(ctx)
stepsProcessor := gosteps.NewStepsProcessor(steps)
stepsProcessor.Execute(ctx)
}
8 changes: 7 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@ module github.com/TanmoySG/go-steps

go 1.18

require github.com/stretchr/testify v1.9.0
require (
github.com/rs/zerolog v1.33.0
github.com/stretchr/testify v1.9.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.27.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
18 changes: 18 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8=
github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Loading