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

Add fine grained error handling #1

Merged
merged 6 commits into from
Oct 9, 2023
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
15 changes: 1 addition & 14 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,16 @@ on:

jobs:
build:
name: Build and Test ghe-visualizer
name: Build and Test logwhale
runs-on: ubuntu-latest
env:
GOPROXY: https://goproxy.githubapp.com/mod,https://proxy.golang.org/,direct
GOPRIVATE: ''
GONOPROXY: ''
GONOSUMDB: github.com/github/*

steps:
- name: Checkout repository
uses: actions/checkout@v4

# From https://github.com/github/goproxy/blob/main/doc/user.md#set-up:
- name: Configure Go private module access
run: |
echo "machine goproxy.githubapp.com login nobody password ${{ secrets.GOPROXY_TOKEN }}" >> $HOME/.netrc

- uses: actions/[email protected]
with:
go-version-file: 'go.mod'

# - name: Build packages
# run: make build

- name: Run tests
run: make test
80 changes: 80 additions & 0 deletions errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package logwhale

import (
"fmt"
"strings"
)

// ErrorState defines a set of concrete error states that can be encountered by the LogManager.
type ErrorState int

// String() returns a string representation of the ErrorState.
func (es ErrorState) String() string {
switch es {
case ErrorStateUnknown:
return "Unknown"
case ErrorStateEndOfStream:
return "End of stream"
case ErrorStateFileRemoved:
return "File removed"
case ErrorStateCancelled:
return "Operation cancelled"
case ErrorStateFSWatcher:
return "FS watch process error"
case ErrorStateFilePath:
return "File path error"
case ErrorStateFileIO:
return "File IO error"
case ErrorStateInternal:
return "Internal exception"
default:
return "Unknown"
}
}

const (
ErrorStateUnknown ErrorState = iota
ErrorStateCancelled
ErrorStateFSWatcher
ErrorStateFilePath
ErrorStateFileRemoved
ErrorStateFileIO
ErrorStateEndOfStream
ErrorStateInternal
)

type LogWhaleError struct {
State ErrorState
Msg string
Cause error
}

func NewLogWhaleError(state ErrorState, msg string, cause error) *LogWhaleError {
return &LogWhaleError{
State: state,
Msg: msg,
Cause: cause,
}
}

// Error satisfies the error interface.
func (e *LogWhaleError) Error() string {
es := strings.Builder{}
es.WriteString(fmt.Sprintf("state: %s", e.State))

if len(e.Msg) != 0 {
es.WriteString(fmt.Sprintf(" msg: %s", e.Msg))
}

if e.Cause != nil {
es.WriteString(fmt.Sprintf(" cause: %s", e.Cause))

}
return es.String()
}

// Unwrap satisfies the Wrapper interface. It allows the
// LogWhaleError to work with and errors.As.
func (e *LogWhaleError) Unwrap() error {
return e.Cause
}
2 changes: 1 addition & 1 deletion internal/version/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v0.2.0-prerelease
v0.3.0-prerelease
35 changes: 20 additions & 15 deletions logfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,17 @@ type logFile struct {
}

// newLogFile configures the logFile struct
func newLogFile(fp string) (*logFile, error) {
func newLogFile(fp string, bs int) (*logFile, error) {
fi, _ := os.Stat(fp)
if fi != nil && fi.IsDir() {
return nil, fmt.Errorf("provided filepath (%s) is a directory, must be a file", fp)
return nil, NewLogWhaleError(ErrorStateFilePath, fmt.Sprintf("filepath (%s) is a directory, must be a file", fp), nil)
}

lf := logFile{
filePath: fp,
basepath: path.Dir(fp),
created: true,
dataChan: make(chan []byte, logBufferLen),
dataChan: make(chan []byte, bs),
errorChan: make(chan error, 1),
lastWriteEvent: make(chan time.Time, 1),
stateEvents: make(chan stateEventOp),
Expand Down Expand Up @@ -87,15 +87,15 @@ func (lf *logFile) dataProcessor(ctx context.Context, ewCancelChan <-chan error,
if err != nil {
if !os.IsNotExist(err) {
stopChan <- lf.filePath
lf.errorChan <- fmt.Errorf("unhandlable error encountered with path (%s): %w", lf.filePath, err)
lf.errorChan <- NewLogWhaleError(ErrorStateInternal, fmt.Sprintf("unhandlable error encountered with path (%s)", lf.filePath), err)
return
}
lf.created = false
}

if fi != nil && fi.IsDir() {
stopChan <- lf.filePath
lf.errorChan <- fmt.Errorf("provided filepath (%s) is a directory, must be a file", lf.filePath)
lf.errorChan <- NewLogWhaleError(ErrorStateFilePath, fmt.Sprintf("filepath (%s) is a directory, must be a file", lf.filePath), nil)
return
}

Expand All @@ -105,21 +105,26 @@ func (lf *logFile) dataProcessor(ctx context.Context, ewCancelChan <-chan error,
case <-ctx.Done():
return
case so := <-lf.stateEvents:
if so == stateEventCreated {
switch so {
case stateEventCreated:
lf.created = true
break
case stateEventRemoved:
lf.created = false
continue creationLoop
default:
stopChan <- lf.filePath
lf.errorChan <- NewLogWhaleError(ErrorStateInternal, fmt.Sprintf("unexpected state event while waiting for file creation: %s", so), nil)
return
}
stopChan <- lf.filePath
lf.errorChan <- fmt.Errorf("unexpected state event: %s", so)
return
}
}

// Open the file once created
of, err := os.Open(lf.filePath)
if err != nil {
stopChan <- lf.filePath
lf.errorChan <- fmt.Errorf("unable to open file: %w", err)
lf.errorChan <- NewLogWhaleError(ErrorStateFileIO, fmt.Sprintf("unable to open file: %s", lf.filePath), err)
return
}
defer of.Close()
Expand Down Expand Up @@ -160,7 +165,7 @@ func (lf *logFile) dataProcessor(ctx context.Context, ewCancelChan <-chan error,
lf.lastRead = time.Now()
} else {
stopChan <- lf.filePath
lf.errorChan <- fmt.Errorf("error reading file: %w", readErr)
lf.errorChan <- NewLogWhaleError(ErrorStateFileIO, fmt.Sprintf("error reading file: %s", lf.filePath), readErr)
return
}

Expand All @@ -175,18 +180,18 @@ func (lf *logFile) dataProcessor(ctx context.Context, ewCancelChan <-chan error,
continue
case err := <-ewCancelChan:
stopChan <- lf.filePath
lf.errorChan <- fmt.Errorf("cancellation requested: %w", err)
lf.errorChan <- NewLogWhaleError(ErrorStateCancelled, fmt.Sprintf("operation cancelled"), err)
return
case so := <-lf.stateEvents:
if so == stateEventRemoved {
lf.errorChan <- fmt.Errorf("file removed, waiting for creation")
lf.errorChan <- NewLogWhaleError(ErrorStateFileRemoved, fmt.Sprintf("file removed, waiting for creation: %s", lf.filePath), nil)
lf.created = false
of.Close()
continue creationLoop
}
stopChan <- lf.filePath
lf.errorChan <- fmt.Errorf("unexpected state event: %s", so)
return
lf.errorChan <- NewLogWhaleError(ErrorStateInternal, fmt.Sprintf("unexpected state event waiting for writing to resume: %s", so), nil)
continue
}
}
}
Expand Down
Loading