README.md

# Checkend Elixir SDK

Elixir SDK for [Checkend](https://checkend.com) error monitoring. Async by default with Plug integration.

## Features

- **Async by default** - Non-blocking error sending via GenServer worker
- **Plug integration** - Easy integration with Phoenix and Plug apps
- **Automatic context** - Request, user, and custom context tracking
- **Sensitive data filtering** - Automatic scrubbing of passwords, tokens, etc.
- **Testing utilities** - Capture errors in tests without sending

## Installation

Add `checkend` to your list of dependencies in `mix.exs`:

```elixir
def deps do
  [
    {:checkend, "~> 1.0"}
  ]
end
```

## Quick Start

```elixir
# Configure the SDK (e.g., in application.ex or config.exs)
Checkend.configure(api_key: "your-api-key")

# Report an error
try do
  do_something()
rescue
  e -> Checkend.notify(e, __STACKTRACE__)
end
```

## Configuration

```elixir
Checkend.configure(
  api_key: "your-api-key",              # Required
  endpoint: "https://app.checkend.com",  # Optional: Custom endpoint
  environment: "production",             # Optional: Auto-detected
  enabled: true,                         # Optional: Enable/disable
  async_send: true,                      # Optional: Async sending (default: true)
  timeout: 15_000,                       # Optional: HTTP timeout in ms
  filter_keys: ["custom_secret"],        # Optional: Additional keys to filter
  ignored_exceptions: [MyError],         # Optional: Exceptions to ignore
  debug: false                           # Optional: Enable debug logging
)
```

### Environment Variables

```bash
CHECKEND_API_KEY=your-api-key
CHECKEND_ENDPOINT=https://your-server.com
CHECKEND_ENVIRONMENT=production
CHECKEND_DEBUG=true
```

## Manual Error Reporting

```elixir
# Basic error reporting
try do
  risky_operation()
rescue
  e -> Checkend.notify(e, __STACKTRACE__)
end

# With additional context
try do
  process_order(order_id)
rescue
  e ->
    Checkend.notify(e, __STACKTRACE__,
      context: %{order_id: order_id},
      user: %{id: user.id, email: user.email},
      tags: ["orders", "critical"],
      fingerprint: "order-processing-error"
    )
end

# Synchronous sending (blocks until sent)
{:ok, response} = Checkend.notify_sync(e, __STACKTRACE__)
IO.puts("Notice ID: #{response.id}")
```

## Context & User Tracking

```elixir
# Set context for all errors in this process
Checkend.set_context(%{
  order_id: 12345,
  feature_flag: "new-checkout"
})

# Set user information
Checkend.set_user(%{
  id: user.id,
  email: user.email,
  name: user.name
})

# Set request information
Checkend.set_request(%{
  url: conn.request_path,
  method: conn.method
})

# Clear all context (call at end of request)
Checkend.clear()
```

## Plug Integration

### With Phoenix

Add to your endpoint:

```elixir
# lib/my_app_web/endpoint.ex
plug Checkend.Plugs.ErrorHandler
```

### With Plug.ErrorHandler

```elixir
defmodule MyApp.Router do
  use Plug.Router
  use Plug.ErrorHandler

  # ... your routes ...

  defp handle_errors(conn, %{kind: kind, reason: reason, stack: stack}) do
    Checkend.Plugs.ErrorHandler.handle_error(conn, kind, reason, stack)
    send_resp(conn, 500, "Internal Server Error")
  end
end
```

## Testing

Use the `Testing` module to capture errors without sending them:

```elixir
defmodule MyTest do
  use ExUnit.Case

  setup do
    Checkend.Testing.setup()
    Checkend.configure(api_key: "test-key", enabled: true)

    on_exit(fn ->
      Checkend.reset()
    end)

    :ok
  end

  test "error reporting" do
    try do
      raise "Test error"
    rescue
      e -> Checkend.notify(e, __STACKTRACE__)
    end

    assert Checkend.Testing.has_notices?()
    assert Checkend.Testing.notice_count() == 1

    notice = Checkend.Testing.last_notice()
    assert notice.error_class == "RuntimeError"
  end
end
```

## Filtering Sensitive Data

By default, these keys are filtered: `password`, `secret`, `token`, `api_key`, `authorization`, `credit_card`, `cvv`, `ssn`, etc.

Add custom keys:

```elixir
Checkend.configure(
  api_key: "your-api-key",
  filter_keys: ["custom_secret", "internal_token"]
)
```

Filtered values appear as `[FILTERED]` in the dashboard.

## Ignoring Exceptions

```elixir
Checkend.configure(
  api_key: "your-api-key",
  ignored_exceptions: [
    Ecto.NoResultsError,
    Phoenix.Router.NoRouteError,
    ~r/.*NotFound.*/
  ]
)
```

## Before Notify Callbacks

```elixir
Checkend.configure(
  api_key: "your-api-key",
  before_notify: [
    fn notice ->
      # Add extra context
      %{notice | context: Map.put(notice.context, "server", node())}
    end,
    fn notice ->
      # Skip certain errors
      if String.contains?(notice.message, "ignore-me") do
        false
      else
        true
      end
    end
  ]
)
```

## Graceful Shutdown

The SDK automatically flushes pending notices on application shutdown. For manual control:

```elixir
# Wait for pending notices to send
Checkend.flush()

# Stop the worker
Checkend.stop()
```

## Requirements

- Elixir 1.14+
- OTP 25+

## Optional Dependencies

- `jason` - For JSON encoding (recommended)
- `plug` - For Plug integration

## Development

```bash
# Install dependencies
mix deps.get

# Run tests
mix test

# Format code
mix format
```

## License

MIT License - see [LICENSE](LICENSE) for details.

---

[Checkend](https://checkend.com) - Simple, powerful error monitoring for your applications.

Project sponsored by [Furvur](https://furvur.com).