# Error Handling and Logging

In our SDK, we divide all errors into two primary categories:

1. **Server Errors** – returned directly from the LicenseSpring backend.
2. **Internal SDK Errors** – generated within the SDK itself.

#### Error Type

We define a unified error type for all internal and server errors:

```go
type Error struct {
	Status     int                    `json:"status"`
	Code       string                 `json:"code"`
	Message    string                 `json:"message"`
	Details    map[string]interface{} `json:"details,omitempty"`
	Inner      error                  `json:"-"`
	StackTrace string                 `json:"-"`
}
```

#### Server-Originated Errors

These errors are returned directly from the LicenseSpring API. We do not wrap them multiple times. Instead, we handle them as follows:

* At the **lowest point** where the API response is received, we wrap the error and add a stack trace using `debug.Stack()`.

```go
// resp is the server's response
if resp.Error != nil {			
     err := core_errors.Error{
			Status: resp.Error.Status,
			Code: resp.Error.Code,
			Message: resp.Error.Message,
			Inner: resp.Error,
			StackTrace: string(debug.Stack())
		}
```

* These errors are returned **as-is** up the call chain.
* Logging occurs only once, at the `LicenseHandler` level.

#### Internal SDK Errors

For errors caused by logic inside the SDK, we have predefined constructor functions. These are always called at the **lowest layer where the error originates**, and a stack trace is attached.

Example:

```go
if !ld.IsFloatingorFloatingCloud() {
	return ld, errors.LicenseNotFloating(string(debug.Stack()))
}
```

All such error constructors follow the format:

```go
func LicenseNotFloating(stack ...string) *Error {
	e := &Error{
		Status:  http.StatusBadRequest,
		Code:    "license_not_floating",
		Message: "the license is not a floating license",
	}
	if len(stack) > 0 {
		e.StackTrace = stack[0]
	}
	return e
}
```

#### LicenseHandler Layer: Logging and Safe Return

{% stepper %}
{% step %}

### Log the complete error

At the topmost layer (usually inside `LicenseHandler`), log the complete error including stack trace.

Example:

```go
ld, err := lh.LicenseManager.ReleaseFloatingLicense(ctx)
if err != nil {
	logger.Error(fmt.Errorf("floating license release failed: %w", err))
	return ld, errors.SanitizeError(err)
}
```

{% endstep %}

{% step %}

### Return a sanitized error

Return a safe version of the error (excluding stack and internal details) to avoid leaking internals.

The `SanitizeError` function:

```go
func SanitizeError(err *errors.Error) map[string]interface{} {
	return map[string]interface{}{
		"status":  err.Status,
		"code":    err.Code,
		"message": err.Message,
	}
}
```

{% endstep %}
{% endstepper %}

#### Example: Full Error Flow for Floating License Release

```go
func (ls *LicenseManager) ReleaseFloatingLicense(ctx context.Context) (*types.LicenseData, error) {
	if err := ls.validate(); err != nil {
		// Validation errors are returned immediately, no need to wrap.
		return nil, err
	}
	ld := ls.LicenseData

	isActive, isEnabled, isExpired := ls.CheckLicenseStatus()

	// This is the lowest level where the internal SDK error occurs.
	// We attach a stack trace using debug.Stack() and create a 
	// custom *Error object.
	if !isEnabled {
		return ld, errors.LicenseNotEnabled(string(debug.Stack()))
	}
	if !isActive {
		return ld, errors.LicenseNotActive(string(debug.Stack()))
	}
	if isExpired {
		return ld, errors.LicenseExpired(string(debug.Stack()))
	}

	if !ld.IsFloatingorFloatingCloud() {
		return ld, errors.LicenseNotFloating(string(debug.Stack()))
	}

	config := ls.LicenseClient.LicenseClientConfiguration
	authData := ld.AuthenticationData

	request := core_request.LicenseRequest{
		HardwareId: config.HardwareId,
		Product:    config.ProductCode,
		Auth:       authData,
	}

	// This error comes from the server.
	// The LicenseClient layer has already captured the stack trace.
	// We return it as-is so that the outer layer (LicenseHandler)
	// can log and sanitize it.
	err := ls.LicenseClient.FloatingRelease(ctx, request)
	if err != nil {
		return ld, err
	}

	ld.RevokeFloatingLicense()

	logger.Info("Floating License released successfully")
	return ld, nil
}
```

```go
func (lh *LicenseHandler) ReleaseFloatingLicense(ctx context.Context) (*types.LicenseData, error) {
	ld, err := lh.LicenseManager.ReleaseFloatingLicense(ctx)
	if err != nil {
		// We do all logging at the outermost layer. 
		logger.Error(fmt.Errorf("license floating release failed: %w", err))

		// We return a sanitized version of the error to avoid leaking internals.
		return ld, errors.SanitizeError(err)
	}

	s := lh.Storage
	err = s.SaveLicense(ld)
	if err != nil {
		logger.Error(fmt.Errorf("license floating release failed: %w", err))
		return nil, errors.SanitizeError(err)
	}

	return ld, nil
}
```
